Files
discordbot/music/player/events.py
T
2026-07-09 17:40:23 +03:00

132 lines
5.3 KiB
Python

import wavelink
import discord
import logging
from discord.ext import commands
from ..shared.constants import LOOP_TRACK, LOOP_QUEUE, MODE_RADIO
log = logging.getLogger(__name__)
class MusicEvents:
"""Mixin: wavelink + Discord events that belong to the music player."""
# ── channel status helpers ────────────────────────────────────────────────
async def _set_channel_status(
self,
player: wavelink.Player,
track: wavelink.Playable | None = None,
*,
title: str | None = None,
):
"""Set (or clear) the voice channel status. Pass *title* for radio; otherwise uses track.title."""
channel = player.channel
if channel is None:
return
try:
if title is not None:
status = f"📻 {title}"[:500]
elif track is not None:
status = f"🎵 {track.title}"[:500]
else:
status = None
route = discord.http.Route(
"PUT",
"/channels/{channel_id}/voice-status",
channel_id=channel.id,
)
await self.bot.http.request(route, json={"status": status})
except Exception:
log.warning(f"Failed to set voice channel status in {player.guild.id}", exc_info=True)
async def _clear_channel_status(self, guild_id: int):
"""Clear voice channel status after the bot was disconnected."""
channel_id = self._voice_channels.pop(guild_id, None)
if channel_id is None:
return
try:
route = discord.http.Route(
"PUT",
"/channels/{channel_id}/voice-status",
channel_id=channel_id,
)
await self.bot.http.request(route, json={"status": None})
except Exception:
log.warning(f"Failed to clear voice channel status in guild {guild_id}", exc_info=True)
# ── connect lavalink ──────────────────────────────────────────────────────
@commands.Cog.listener()
async def on_ready(self):
node = wavelink.Node(uri="http://127.0.0.1:2333", password="yuukabot")
await wavelink.Pool.connect(nodes=[node], client=self.bot)
log.info("Wavelink connected to Lavalink")
# ── voice state update → clean up on disconnect ───────────────────────────
@commands.Cog.listener()
async def on_voice_state_update(
self,
member: discord.Member,
before: discord.VoiceState,
after: discord.VoiceState,
):
if member.id != self.bot.user.id:
return
# Bot left a channel (kicked, moved out, or disconnected)
if before.channel is not None and after.channel is None:
guild_id = member.guild.id
st = self._states.pop(guild_id, None)
if st:
st.queue.clear()
self._locks.pop(guild_id, None)
self._radio_channels.pop(guild_id, None)
await self._clear_channel_status(guild_id)
# ── track end → advance queue ─────────────────────────────────────────────
@commands.Cog.listener()
async def on_wavelink_track_end(self, payload: wavelink.TrackEndEventPayload):
player: wavelink.Player = payload.player
if player is None:
return
st = self.state(player.guild.id)
# Ignore stale events (e.g. track_end for an old radio stream after a switch)
if not self._is_current_track(st, payload.track):
return
if st.loop == LOOP_TRACK and st.current:
await player.play(st.current)
await self._set_channel_status(player, st.current)
return
if st.loop == LOOP_QUEUE and st.current:
st.queue.append(st.current)
if st.current:
st.push_history(st.current)
if st.queue:
next_track = st.queue.popleft()
st.current = next_track
await player.play(next_track)
await self._set_channel_status(player, next_track)
elif st.autoplay:
await player.set_autoplay(wavelink.AutoPlayMode.enabled)
else:
if st.mode == MODE_RADIO:
await self._notify_radio_drop(player.guild.id)
st.current = None
st.mode = None
await self._set_channel_status(player)
# ── track start → update voice status ─────────────────────────────────────
@commands.Cog.listener()
async def on_wavelink_track_start(self, payload: wavelink.TrackStartEventPayload):
player: wavelink.Player = payload.player
if player is None:
return
st = self.state(player.guild.id)
st.current = payload.track
# Radio sets its own title; only update status for music mode
if st.mode != MODE_RADIO:
await self._set_channel_status(player, st.current)