-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmain.py
208 lines (168 loc) · 6.92 KB
/
main.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
import asyncio
from collections import defaultdict
import os
import re
from typing import Optional
import dotenv
import discord
from discord.ext import commands
from discord.commands.context import ApplicationContext
from discord.webhook import WebhookMessage
import yt_dlp
from buttons import SkipButton, QueueButton, RemoveButton
from handlers import skip_handler, queue_handler, skip_votes
# Загружаем .env
dotenv.load_dotenv()
# Создаем объект бота
bot = commands.Bot()
# Создаем объект для загрузки видео с YouTube
ydl_opts = {
"quiet": True,
"noplaylist": True,
"format": "bestaudio/best",
"postprocessors": [
{
"key": "FFmpegExtractAudio",
"preferredcodec": "mp3",
"preferredquality": "192",
}
],
}
# Объект YoutubeDL с настройками
ydl = yt_dlp.YoutubeDL(ydl_opts)
# Очередь музыки
queues = defaultdict(list)
# Семафоры для каждого сервера
guild_semaphore = defaultdict(list)
@bot.event
async def on_ready() -> None:
"""
Обработчик события on_ready для бота.
Устанавливает активность и статус бота при его запуске.
"""
activity = discord.Game(name="Detroit: Become Human")
await bot.change_presence(status=discord.Status.do_not_disturb, activity=activity)
print("Bot is online!")
@bot.slash_command(name="play")
async def play(ctx: ApplicationContext, *, query: str) -> None:
"""
Обработка команды play для бота.
Parameters:
ctx (ApplicationContext): Контекст команды.
query (str): Запрос для поиска или воспроизведения трека.
"""
if not ctx.author.voice:
await ctx.respond("Ты должен быть в голосовом канале", ephemeral=True)
return
await ctx.respond("Думаю над ответом 🤔")
try:
track_added_message = await enqueue(ctx, query)
if not track_added_message:
return
except (
yt_dlp.utils.DownloadError,
yt_dlp.utils.ExtractorError,
yt_dlp.utils.UnsupportedError,
):
await ctx.respond("Введена некорректная ссылка")
return
guild_id = ctx.guild_id
semaphore = guild_semaphore[guild_id]
if not semaphore:
semaphore = asyncio.Semaphore(1)
guild_semaphore[guild_id] = semaphore
async with semaphore:
if not queues[guild_id]:
return
await play_queue(ctx, track_added_message)
async def enqueue(ctx: ApplicationContext, query: str) -> Optional[WebhookMessage]:
"""
Добавляет трек в очередь для воспроизведения.
Parameters:
ctx (ApplicationContext): Контекст вызова команды.
query (str): Запрос для поиска или воспроизведения трека.
Returns:
Optional[WebhookMessage]: Сообщение о добавлении трека в очередь, если что-то найдено. None, если ничего не найдено.
Raises:
yt_dlp.utils.UnsupportedError: Если введена ссылка на YouTube, а не на видео.
"""
with ydl:
not_video_url_regex = re.compile(
r"^(?:https?://)?(?:www\.)?(youtube.com|youtu.be)/?$"
)
video_url_regex = re.compile(
r"^(?:https?://)?(?:www\.)?(?:youtu\.be/|youtube\.com/(?:embed/|v/|watch\?v=|watch\?.+&v=))([\w-]{11})$"
)
if bool(not_video_url_regex.match(query)):
raise yt_dlp.utils.UnsupportedError(
"ERROR: Введена ссылка на YouTube, а не на видео с него"
)
if bool(video_url_regex.match(query)):
info = await asyncio.to_thread(
lambda: ydl.extract_info(query, download=False)
)
audio_url = info["url"]
title = info["title"]
else:
info = await asyncio.to_thread(
lambda: ydl.extract_info(f"ytsearch:{query}", download=False)
)
if not info.get("entries"):
await ctx.respond("Ничего не нашел по твоему запросу")
return
audio_url = info["entries"][0]["url"]
title = info["entries"][0]["title"]
track_info = {"url": audio_url, "title": title, "author": ctx.author}
queues[ctx.guild_id].append(track_info)
view = discord.ui.View(timeout=None)
view.add_item(RemoveButton(queues, track_info))
return await ctx.respond(f"Трек: {title} добавлен в очередь", view=view)
async def play_queue(
ctx: ApplicationContext, track_added_message: WebhookMessage
) -> None:
"""
Воспроизводит трек из очереди.
Parameters:
ctx (ApplicationContext): Контекст вызова команды.
track_added_message (WebhookMessage): Сообщение, на которое нужно ответить с информацией о воспроизводимом треке.
"""
guild_id = ctx.guild.id
query = queues[guild_id].pop(0)
if not ctx.voice_client:
await ctx.author.voice.channel.connect()
ctx.voice_client.play(
discord.FFmpegPCMAudio(
query["url"],
before_options="-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5 -vn",
)
)
view = discord.ui.View(timeout=None)
view.add_item(SkipButton())
view.add_item(QueueButton(queues))
await track_added_message.reply(f"Сейчас играет: {query['title']}", view=view)
while ctx.voice_client.is_playing():
await asyncio.sleep(1)
if skip_votes[guild_id]:
del skip_votes[guild_id]
if not queues[guild_id]:
await ctx.send("Воспроизведение завершено. Выход из канала")
await ctx.voice_client.disconnect()
del guild_semaphore[guild_id]
@bot.slash_command(name="skip", description="Пропустить текущий трек")
async def skip(ctx: ApplicationContext) -> None:
"""
Команда для пропуска текущего трека.
Parameters:
ctx (ApplicationContext): Контекст приложения.
"""
await skip_handler(ctx.interaction)
@bot.slash_command(name="queue", description="Посмотреть текущую очередь")
async def queue(ctx: ApplicationContext) -> None:
"""
Команда для просмотра текущей очереди.
Parameters:
ctx (ApplicationContext): Контекст приложения.
"""
await queue_handler(ctx.interaction, queues)
# Запускаем бота
bot.run(os.getenv("DISCORD_TOKEN"))