not-lain commited on
Commit
c0a28a9
1 Parent(s): 6015e8f

Implement voice keepalive feature and enhance command validations

Browse files
Files changed (1) hide show
  1. app.py +105 -17
app.py CHANGED
@@ -15,6 +15,7 @@ if os.path.exists("assets") is False:
15
  "not-lain/assets", "lofi.mp3", repo_type="dataset", local_dir="assets"
16
  )
17
 
 
18
 
19
  # Bot configuration
20
  intents = discord.Intents.default()
@@ -26,12 +27,30 @@ class MusicBot:
26
  def __init__(self):
27
  self.is_playing = False
28
  self.voice_client = None
 
 
 
 
 
 
 
 
 
 
 
 
29
 
30
  async def join_voice(self, ctx):
31
  if ctx.author.voice:
32
  channel = ctx.author.voice.channel
33
  if self.voice_client is None:
34
  self.voice_client = await channel.connect()
 
 
 
 
 
 
35
  else:
36
  await self.voice_client.move_to(channel)
37
  else:
@@ -41,14 +60,10 @@ class MusicBot:
41
  if not self.is_playing:
42
  self.is_playing = True
43
  try:
44
- audio_source = discord.FFmpegPCMAudio("assets/lofi.mp3")
45
 
46
  def after_playing(e):
47
  self.is_playing = False
48
- # test loop by default
49
- if e:
50
- print(f"Playback error: {e}")
51
- asyncio.run_coroutine_threadsafe(self.play_next(ctx), bot.loop)
52
 
53
  self.voice_client.play(audio_source, after=after_playing)
54
  except Exception as e:
@@ -56,6 +71,35 @@ class MusicBot:
56
  await ctx.send("Error playing the song.")
57
  self.is_playing = False
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
 
60
  music_bot = MusicBot()
61
 
@@ -76,7 +120,7 @@ async def on_ready():
76
  @bot.tree.command(name="play", description="Play the sample music")
77
  async def play(interaction: discord.Interaction):
78
  await interaction.response.defer()
79
- ctx = await commands.Context.from_interaction(interaction)
80
  await music_bot.join_voice(ctx)
81
 
82
  if not music_bot.is_playing:
@@ -86,27 +130,70 @@ async def play(interaction: discord.Interaction):
86
  await interaction.followup.send("Already playing!")
87
 
88
 
89
- # Replace the existing skip command with this version
90
  @bot.tree.command(name="skip", description="Skip the current song")
91
  async def skip(interaction: discord.Interaction):
92
- if music_bot.voice_client:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  music_bot.voice_client.stop()
94
  await interaction.response.send_message("Skipped current song!")
95
  else:
96
  await interaction.response.send_message("No song is currently playing!")
97
 
98
 
99
- # Replace the existing leave command with this version
100
  @bot.tree.command(name="leave", description="Disconnect bot from voice channel")
101
  async def leave(interaction: discord.Interaction):
102
- if music_bot.voice_client:
103
- await music_bot.voice_client.disconnect()
104
- music_bot.voice_client = None
105
- music_bot.queue = []
106
- music_bot.is_playing = False
107
- await interaction.response.send_message("Bot disconnected!")
108
- else:
109
- await interaction.response.send_message("Bot is not in a voice channel!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
110
 
111
 
112
  def run_discord_bot():
@@ -118,6 +205,7 @@ with gr.Blocks() as iface:
118
  gr.Markdown("# Discord Music Bot Control Panel")
119
  gr.Markdown("Bot is running in background")
120
 
 
121
  if __name__ == "__main__":
122
  # Start the Discord bot in a separate thread
123
  bot_thread = threading.Thread(target=run_discord_bot, daemon=True)
 
15
  "not-lain/assets", "lofi.mp3", repo_type="dataset", local_dir="assets"
16
  )
17
 
18
+ song = "assets/lofi.mp3"
19
 
20
  # Bot configuration
21
  intents = discord.Intents.default()
 
27
  def __init__(self):
28
  self.is_playing = False
29
  self.voice_client = None
30
+ self.keepalive_task = None
31
+ self.last_context = None
32
+
33
+ async def voice_keepalive(self, voice_client):
34
+ """Keeps the voice connection alive by periodically playing audio"""
35
+ print("Starting voice keepalive task")
36
+ while True:
37
+ if voice_client.is_connected() and not self.is_playing:
38
+ await self.play_next(self.last_context)
39
+ await asyncio.sleep(15)
40
+ else:
41
+ await asyncio.sleep(1)
42
 
43
  async def join_voice(self, ctx):
44
  if ctx.author.voice:
45
  channel = ctx.author.voice.channel
46
  if self.voice_client is None:
47
  self.voice_client = await channel.connect()
48
+ self.last_context = ctx
49
+ if self.keepalive_task:
50
+ self.keepalive_task.cancel()
51
+ self.keepalive_task = asyncio.create_task(
52
+ self.voice_keepalive(self.voice_client)
53
+ )
54
  else:
55
  await self.voice_client.move_to(channel)
56
  else:
 
60
  if not self.is_playing:
61
  self.is_playing = True
62
  try:
63
+ audio_source = discord.FFmpegPCMAudio(song)
64
 
65
  def after_playing(e):
66
  self.is_playing = False
 
 
 
 
67
 
68
  self.voice_client.play(audio_source, after=after_playing)
69
  except Exception as e:
 
71
  await ctx.send("Error playing the song.")
72
  self.is_playing = False
73
 
74
+ async def stop_playing(self, ctx):
75
+ try:
76
+ if self.keepalive_task:
77
+ self.keepalive_task.cancel()
78
+ self.keepalive_task = None
79
+
80
+ if self.voice_client:
81
+ if self.voice_client.is_playing():
82
+ self.voice_client.stop()
83
+
84
+ self.is_playing = False
85
+ self.last_context = None
86
+
87
+ if self.voice_client.is_connected():
88
+ await self.voice_client.disconnect(force=False)
89
+
90
+ self.voice_client = None
91
+ return True
92
+
93
+ return False
94
+
95
+ except Exception as e:
96
+ print(f"Error during cleanup: {e}")
97
+ self.is_playing = False
98
+ self.voice_client = None
99
+ self.last_context = None
100
+ self.keepalive_task = None
101
+ return False
102
+
103
 
104
  music_bot = MusicBot()
105
 
 
120
  @bot.tree.command(name="play", description="Play the sample music")
121
  async def play(interaction: discord.Interaction):
122
  await interaction.response.defer()
123
+ ctx = await bot.get_context(interaction)
124
  await music_bot.join_voice(ctx)
125
 
126
  if not music_bot.is_playing:
 
130
  await interaction.followup.send("Already playing!")
131
 
132
 
 
133
  @bot.tree.command(name="skip", description="Skip the current song")
134
  async def skip(interaction: discord.Interaction):
135
+ # Check if user is in a voice channel
136
+ if not interaction.user.voice:
137
+ await interaction.response.send_message(
138
+ "You must be in a voice channel to use this command!"
139
+ )
140
+ return
141
+
142
+ # Check if bot is in a voice channel
143
+ if not music_bot.voice_client:
144
+ await interaction.response.send_message("No song is currently playing!")
145
+ return
146
+
147
+ # Check if user is in the same channel as the bot
148
+ if interaction.user.voice.channel != music_bot.voice_client.channel:
149
+ await interaction.response.send_message(
150
+ "You must be in the same voice channel as the bot!"
151
+ )
152
+ return
153
+
154
+ if music_bot.voice_client and music_bot.is_playing:
155
+ music_bot.is_playing = False # Reset playing state
156
  music_bot.voice_client.stop()
157
  await interaction.response.send_message("Skipped current song!")
158
  else:
159
  await interaction.response.send_message("No song is currently playing!")
160
 
161
 
 
162
  @bot.tree.command(name="leave", description="Disconnect bot from voice channel")
163
  async def leave(interaction: discord.Interaction):
164
+ # Check if user is in a voice channel
165
+ if not interaction.user.voice:
166
+ await interaction.response.send_message(
167
+ "You must be in a voice channel to use this command!"
168
+ )
169
+ return
170
+
171
+ # Check if bot is in a voice channel
172
+ if not music_bot.voice_client:
173
+ await interaction.response.send_message("I'm not in any voice channel!")
174
+ return
175
+
176
+ # Check if user is in the same channel as the bot
177
+ if interaction.user.voice.channel != music_bot.voice_client.channel:
178
+ await interaction.response.send_message(
179
+ "You must be in the same voice channel as the bot!"
180
+ )
181
+ return
182
+
183
+ await interaction.response.defer()
184
+ ctx = await bot.get_context(interaction)
185
+
186
+ try:
187
+ success = await music_bot.stop_playing(ctx)
188
+ if success:
189
+ await interaction.followup.send("Successfully disconnected! 👋")
190
+ else:
191
+ await interaction.followup.send(
192
+ "Failed to disconnect properly. Please try again."
193
+ )
194
+ except Exception as e:
195
+ print(f"Error during leave command: {e}")
196
+ await interaction.followup.send("An error occurred while trying to disconnect.")
197
 
198
 
199
  def run_discord_bot():
 
205
  gr.Markdown("# Discord Music Bot Control Panel")
206
  gr.Markdown("Bot is running in background")
207
 
208
+
209
  if __name__ == "__main__":
210
  # Start the Discord bot in a separate thread
211
  bot_thread = threading.Thread(target=run_discord_bot, daemon=True)