1
+ import asyncio
2
+
3
+ from discord import FFmpegPCMAudio
1
4
from discord .ext import commands
2
5
from discord .utils import get
3
- from discord import FFmpegPCMAudio
4
6
from yt_dlp import YoutubeDL
5
7
8
+ PREV_QUEUE_LIMIT = 10
9
+
6
10
7
11
class Music (commands .Cog ):
8
12
def __init__ (self , bot ):
9
13
self .bot = bot
14
+ self .queue = {} # A dictionary to hold queues for different guilds
15
+ self .prev_songs = {} # A dictionary to hold the last PREV_QUEUE_LIMIT 10 songs played in a guild
10
16
11
17
# command for bot to join the channel of the user,
12
18
# if the bot has already joined and is in a different channel, it will move to the channel the user is in
@@ -20,27 +26,78 @@ async def join(self, ctx):
20
26
else :
21
27
voice = await channel .connect ()
22
28
23
- # command to play sound from a youtube URL
29
+ @commands .command ()
30
+ async def skip (self , ctx ):
31
+ """Skip the current song and play the next song in the queue."""
32
+ voice = get (self .bot .voice_clients , guild = ctx .guild )
33
+ voice .stop ()
34
+ if ctx .guild .id in self .queue and self .queue [ctx .guild .id ]:
35
+ last_played = self .queue [ctx .guild .id ].pop (0 )
36
+ next_url = self .queue [ctx .guild .id ][0 ]
37
+ await self .play_song (ctx , next_url )
38
+ else :
39
+ voice .stop ()
40
+
41
+ async def play_song (self , ctx , url ):
42
+ """Play a song given its URL."""
43
+ YDL_OPTIONS = {"format" : "bestaudio/best[height<=480]" , "noplaylist" : "True" }
44
+ voice = get (self .bot .voice_clients , guild = ctx .guild )
45
+
46
+ with YoutubeDL (YDL_OPTIONS ) as ydl :
47
+ info = ydl .extract_info (url , download = False )
48
+ URL = info ["url" ]
49
+ FFMPEG_OPTIONS = {"before_options" : "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5" , "options" : "-vn" }
50
+
51
+ # Going for a callback approach, could also try state-machine and backround queue manager approaches
52
+ def after_callback (error ):
53
+ if error :
54
+ print (f"Player error: { error } " )
55
+ if ctx .guild .id in self .queue and self .queue [ctx .guild .id ]:
56
+ last_played = self .queue [ctx .guild .id ].pop (0 )
57
+ next_url = self .queue [ctx .guild .id ][0 ]
58
+ # enqueue the last played song to the front of the played songs stack
59
+ if ctx .guild .id not in self .prev_songs :
60
+ self .prev_songs [ctx .guild .id ] = []
61
+ if len (self .prev_songs [ctx .guild .id ]) >= PREV_QUEUE_LIMIT :
62
+ self .prev_songs [ctx .guild .id ].pop (0 ) # Remove the oldest song if the limit is reached
63
+ self .prev_songs [ctx .guild .id ].append (url )
64
+ asyncio .run_coroutine_threadsafe (self .play_song (ctx , next_url ), self .bot .loop )
65
+
66
+ await ctx .send (f"Playing: { url } " )
67
+ voice .play (FFmpegPCMAudio (URL , ** FFMPEG_OPTIONS ), after = after_callback )
68
+
24
69
@commands .command ()
25
70
async def play (self , ctx , url ):
26
- YDL_OPTIONS = {'format' : 'bestaudio/best[height<=480]' , 'noplaylist' : 'True' }
27
71
voice = get (self .bot .voice_clients , guild = ctx .guild )
72
+ # Join the voice channel if the bot isn't already in one
73
+ if not voice or (voice and not voice .is_connected ()):
74
+ await self .join (ctx )
75
+ voice = get (self .bot .voice_clients , guild = ctx .guild ) # Re-fetch the voice client after joining
76
+
77
+ # if len(self.queue.get(ctx.guild.id, [])) >= QUEUE_LIMIT:
78
+ # await ctx.send(f"Queue limit reached: ({QUEUE_LIMIT}), please wait for the queue to clear before adding more songs.")
79
+ # else:
80
+ if ctx .guild .id not in self .queue :
81
+ self .queue [ctx .guild .id ] = []
82
+ self .queue [ctx .guild .id ].append (url )
28
83
29
84
if not voice .is_playing ():
30
- with YoutubeDL (YDL_OPTIONS ) as ydl :
31
- info = ydl .extract_info (url , download = False )
32
- URL = info ['url' ]
33
- FFMPEG_OPTIONS = {'before_options' : '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5' ,
34
- 'options' : '-vn' }
85
+ await self .play_song (ctx , self .queue [ctx .guild .id ][0 ])
35
86
36
- voice .play (FFmpegPCMAudio (URL , ** FFMPEG_OPTIONS ))
37
- voice .is_playing ()
38
- await ctx .send (f'Playing: { url } ' )
39
-
40
- # check if the bot is already playing
87
+ @commands .command ()
88
+ async def prev (self , ctx ):
89
+ """Play the previous song."""
90
+ voice = get (self .bot .voice_clients , guild = ctx .guild )
91
+ if ctx .guild .id in self .prev_songs and self .prev_songs [ctx .guild .id ]:
92
+ voice .stop ()
93
+ prev_url = self .prev_songs [ctx .guild .id ].pop () # Get the last played song
94
+ if ctx .guild .id in self .queue :
95
+ self .queue [ctx .guild .id ].insert (0 , prev_url ) # Add the previous song to the start of the queue
96
+ else :
97
+ self .queue [ctx .guild .id ] = [prev_url ]
98
+ await self .play_song (ctx , prev_url )
41
99
else :
42
- await ctx .send ("Bot is already playing. Please Stop it first with /stop" )
43
- return
100
+ await ctx .send ("No previous song to play!" )
44
101
45
102
# command to resume voice if it is paused
46
103
@commands .command ()
@@ -49,7 +106,7 @@ async def resume(self, ctx):
49
106
50
107
if not voice .is_playing ():
51
108
voice .resume ()
52
- await ctx .send (' Resuming stream' )
109
+ await ctx .send (" Resuming stream" )
53
110
54
111
# command to pause voice if it is playing
55
112
@commands .command ()
@@ -58,7 +115,7 @@ async def pause(self, ctx):
58
115
59
116
if voice .is_playing ():
60
117
voice .pause ()
61
- await ctx .send (' Paused stream' )
118
+ await ctx .send (" Paused stream" )
62
119
63
120
# command to stop voice
64
121
@commands .command ()
@@ -67,7 +124,63 @@ async def stop(self, ctx):
67
124
68
125
if voice .is_playing ():
69
126
voice .stop ()
70
- await ctx .send ('Stopping stream...' )
127
+ await ctx .send ("Stopping stream..." )
128
+
129
+ @commands .command ()
130
+ async def clear_queue (self , ctx ):
131
+ self .queue [ctx .guild .id ] = []
132
+ await ctx .send ("Queue cleared!" )
133
+
134
+ # async def youtube_video_autocompletion(self, ctx: AutocompleteContext):
135
+ # current = ctx.options["video"]
136
+ # data = []
137
+
138
+ # # Check if the current input is a link
139
+ # is_link = is_youtube_link(current)
140
+ # if is_link:
141
+ # return [current] # Return the link as the only option
142
+
143
+ # # Otherwise, search YouTube for videos with the current string
144
+ # videos_search = VideosSearch(current, limit=5)
145
+ # results = videos_search.result()
146
+
147
+ # for video in results["result"]:
148
+ # video_link = video["link"]
149
+ # data.append(video_link)
150
+
151
+ # return data
152
+
153
+ # @commands.slash_command()
154
+ # async def play(
155
+ # self,
156
+ # ctx: ApplicationContext,
157
+ # video_name: Option(str, autocomplete=youtube_video_autocompletion),
158
+ # ):
159
+ # YDL_OPTIONS = {"format": "bestaudio/best[height<=480]", "noplaylist": "True"}
160
+ # voice = get(self.bot.voice_clients, guild=ctx.guild)
161
+
162
+ # if is_youtube_link(video_name):
163
+ # url = video_name
164
+ # else:
165
+ # videos_search = VideosSearch(video_name, limit=5)
166
+ # results = videos_search.result()
167
+ # url = results["result"][0]["link"]
168
+
169
+ # if not voice.is_playing():
170
+ # with YoutubeDL(YDL_OPTIONS) as ydl:
171
+ # info = ydl.extract_info(url, download=False)
172
+ # voice.play(
173
+ # FFmpegPCMAudio(
174
+ # info["url"], **{"before_options": "-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5", "options": "-vn"}
175
+ # )
176
+ # )
177
+ # voice.is_playing()
178
+ # await ctx.send(f"Playing! {url}")
179
+
180
+ # # check if the bot is already playing
181
+ # else:
182
+ # await ctx.send("Bot is already playing! Please Stop it first with /stop")
183
+ # return
71
184
72
185
73
186
def setup (bot ):
0 commit comments