This repository has been archived on 2025-01-28. You can view files and clone it, but cannot push or open issues or pull requests.
natsuko/cogs/music.py

198 lines
6.1 KiB
Python
Raw Permalink Normal View History

2023-05-03 19:53:01 +03:00
import discord
import yt_dlp as youtube_dl
import functools
import asyncio
from loguru import logger
from json import dumps
2023-05-06 23:53:55 +03:00
from discord import app_commands
2023-07-04 17:27:36 +03:00
from discord.app_commands import Choice
import enum
2023-05-12 23:43:24 +03:00
from dataclasses import dataclass
2023-05-03 19:53:01 +03:00
from bot import db
2023-05-09 23:24:00 +03:00
FFMPEG_OPTIONS = {'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist':'True'}
2023-05-06 23:53:55 +03:00
# TODO: locale
2023-05-12 23:43:24 +03:00
@dataclass
class Song:
url: str
2023-05-14 20:59:03 +03:00
requester: discord.Member
2023-05-13 13:13:08 +03:00
info: dict[str, any]
2023-05-12 23:43:24 +03:00
2023-07-04 17:27:36 +03:00
class RepeatStatus(enum.Enum):
once = 0
one_song = 1
whole_playlist = 2
2023-05-12 23:43:24 +03:00
@dataclass
class Channel:
adder: discord.Member
cur_pos: int
2023-05-13 13:13:08 +03:00
queue: list[Song]
2023-05-12 23:43:24 +03:00
context: discord.Interaction
# skip_policy: Enum "everyone"
2023-07-04 17:27:36 +03:00
repeat_status: int = RepeatStatus.once.value
2023-05-12 23:43:24 +03:00
2023-05-18 22:54:43 +03:00
@app_commands.guild_only()
class Music(app_commands.Group, name="music"):
2023-05-03 19:53:01 +03:00
def __init__(self, bot):
2023-05-18 22:54:43 +03:00
super().__init__()
2023-05-03 19:53:01 +03:00
self.bot = bot
2023-05-13 13:13:08 +03:00
self.queue: dict[int, Channel] = {}
2023-05-03 19:53:01 +03:00
2023-05-06 23:53:55 +03:00
@app_commands.command(description="Plays music from popular platforms")
2023-05-07 01:15:06 +03:00
@app_commands.describe(url="URL from Youtube/RuTube and other platforms")
async def play(self, inter, url: str):
logger.debug(asyncio.get_running_loop())
2023-05-06 23:53:55 +03:00
channel = inter.user.voice.channel
2023-05-03 19:53:01 +03:00
2023-05-06 23:53:55 +03:00
if inter.user.voice is None:
await inter.response.send_message("Ты не в ГК")
2023-05-03 19:53:01 +03:00
return
2023-05-06 23:53:55 +03:00
if inter.guild.voice_client is None:
2023-05-03 19:53:01 +03:00
await channel.connect()
2023-05-12 23:43:24 +03:00
self.queue[channel.id] = Channel(inter.user, 0, [], inter)
2023-05-06 23:53:55 +03:00
elif inter.user.voice.channel != inter.guild.voice_client.channel:
await inter.response.send_message(f"Занято каналом {inter.guild.voice_client.channel.mention}")
2023-05-03 19:53:01 +03:00
return
2023-05-06 23:53:55 +03:00
client = inter.guild.voice_client
2023-05-14 20:45:43 +03:00
await inter.response.defer(thinking=True)
2023-05-03 19:53:01 +03:00
2023-05-09 23:24:00 +03:00
with youtube_dl.YoutubeDL(YDL_OPTIONS) as ydl:
info = ydl.extract_info(url, download=False)
2023-05-03 19:53:01 +03:00
2023-05-12 23:43:24 +03:00
self.queue[channel.id].queue.append(Song(url, inter.user, info))
2023-05-03 19:53:01 +03:00
2023-05-14 20:45:43 +03:00
await inter.edit_original_response(content="Добавлена новая песня в очередь")
2023-05-03 19:53:01 +03:00
2023-05-14 20:45:43 +03:00
if not (client.is_playing() or client.is_paused()):
self.__play(inter, info)
2023-05-06 23:53:55 +03:00
@app_commands.command()
async def stop(self, inter):
2023-05-09 23:24:00 +03:00
queue = self.queue[inter.user.voice.channel.id]
2023-05-12 23:43:24 +03:00
queue.cur_pos = len(queue.queue)-1
2023-05-06 23:53:55 +03:00
inter.guild.voice_client.stop()
await inter.response.send_message("Остановлено")
2023-05-06 23:53:55 +03:00
@app_commands.command()
async def pause(self, inter):
inter.guild.voice_client.pause()
await inter.response.send_message("Поставлено на паузу")
2023-05-06 23:53:55 +03:00
@app_commands.command()
async def resume(self, inter):
inter.guild.voice_client.resume()
await inter.response.send_message("Снято с паузы")
2023-05-06 23:53:55 +03:00
@app_commands.command()
async def disconnect(self, inter):
await inter.guild.voice_client.disconnect()
await inter.response.send_message("Отключено")
2023-05-06 23:53:55 +03:00
@app_commands.command()
async def next(self, inter):
2023-05-09 23:24:00 +03:00
inter.guild.voice_client.stop()
await inter.response.send_message("Переключено")
2023-05-09 23:24:00 +03:00
2023-07-04 17:27:36 +03:00
@app_commands.command()
@app_commands.choices(status=[
Choice(name="once", value=RepeatStatus.once.value),
Choice(name="one song", value=RepeatStatus.one_song.value),
Choice(name="whole playlist", value=RepeatStatus.whole_playlist.value)
])
async def repeat(self, inter, status: Choice[int]):
channel_info = self.queue[inter.user.voice.channel.id]
channel_info.repeat_status = status.value
await inter.response.send_message("Set repeat: `{}`".format(status.name))
2023-05-09 23:24:00 +03:00
@app_commands.command(name='queue')
async def _queue(self, inter):
queue = self.queue[inter.user.voice.channel.id]
text = ''
2023-05-14 20:59:03 +03:00
for pos, item in enumerate(queue.queue):
2023-05-12 23:43:24 +03:00
if queue.cur_pos == pos: text += '>>> '
2023-05-09 23:24:00 +03:00
else: text += ' '
text += f"{pos+1}. "
2023-05-12 23:43:24 +03:00
text += item.info['title']
text += '\n - Запросил: ' + item.requester.name
2023-05-09 23:24:00 +03:00
text += '\n'
await inter.response.send_message(f"```\n{text}\n```")
def __play(self, inter, info):
try:
URL = info['url']
except:
URL = url
logger.debug(URL)
audio_source = discord.FFmpegPCMAudio(URL, **FFMPEG_OPTIONS)
inter.guild.voice_client.play(audio_source, after=lambda error: self.__next(inter, error))
2023-05-14 20:45:43 +03:00
logger.debug(inter)
asyncio.run_coroutine_threadsafe(self.__send_embed(inter, info), self.bot.loop)
async def __send_embed(self, inter, info):
embed = discord.Embed (
title=info["title"],
url=info['url'],
description=info["description"]
)
embed.set_author (
name=info["uploader"],
url=info["uploader_url"]
)
embed.set_thumbnail( url=info["thumbnail"] )
2023-05-14 20:45:43 +03:00
await inter.channel.send(embed=embed)
async def __end_of_queue(self, inter):
await inter.channel.send("В очереди больше не осталось песен")
2023-05-06 23:53:55 +03:00
def __next(self, inter, error=None):
2023-05-09 23:24:00 +03:00
if error:
logger.error(error)
return
2023-05-07 01:15:06 +03:00
2023-05-06 23:53:55 +03:00
inter.guild.voice_client.stop()
2023-05-09 23:24:00 +03:00
queue = self.queue[inter.user.voice.channel.id]
2023-07-04 17:27:36 +03:00
if queue.repeat_status != RepeatStatus.one_song.value:
queue.cur_pos += 1
2023-05-12 23:43:24 +03:00
logger.debug((len(queue.queue), queue.cur_pos))
if len(queue.queue) == queue.cur_pos:
2023-07-04 17:27:36 +03:00
if queue.repeat_status == RepeatStatus.whole_playlist.value:
queue.cur_pos = 0
else:
asyncio.run_coroutine_threadsafe(self.__end_of_queue(inter), self.bot.loop)
return
2023-05-09 23:24:00 +03:00
#logger.debug([query["music_pos"]])
2023-05-12 23:43:24 +03:00
info = queue.queue[queue.cur_pos].info
self.__play(inter, info)
2023-05-03 19:53:01 +03:00
async def setup(bot):
2023-05-18 22:54:43 +03:00
bot.tree.add_command(Music(bot))