not-lain commited on
Commit
c8aea3c
·
1 Parent(s): 636e642

add spotify

Browse files
Files changed (3) hide show
  1. .gitignore +2 -1
  2. app.py +86 -14
  3. requirements.txt +2 -1
.gitignore CHANGED
@@ -1,2 +1,3 @@
1
  .env
2
- assets/
 
 
1
  .env
2
+ assets/
3
+ .cache
app.py CHANGED
@@ -1,3 +1,4 @@
 
1
  import discord
2
  from discord.ext import commands
3
  from huggingface_hub import hf_hub_download
@@ -6,27 +7,46 @@ from dotenv import load_dotenv
6
  import os
7
  import threading
8
  import asyncio
 
 
 
9
 
10
- # Load environment variables
11
  load_dotenv()
12
 
13
- # Download song
14
  if os.path.exists('assets') is False:
15
  os.makedirs('assets', exist_ok=True)
16
  hf_hub_download("not-lain/assets", "sample.mp3", repo_type="dataset",local_dir="assets")
17
 
18
- # Bot configuration
19
  intents = discord.Intents.default()
20
  intents.message_content = True
21
  bot = commands.Bot(command_prefix='!', intents=intents)
22
 
23
- # Music bot class
 
 
 
 
 
 
24
  class MusicBot:
25
  def __init__(self):
 
26
  self.is_playing = False
27
  self.voice_client = None
 
 
 
 
 
 
 
 
28
 
29
  async def join_voice(self, ctx):
 
30
  if ctx.author.voice:
31
  channel = ctx.author.voice.channel
32
  if self.voice_client is None:
@@ -37,15 +57,18 @@ class MusicBot:
37
  await ctx.send("You need to be in a voice channel!")
38
 
39
  async def play_next(self, ctx):
 
40
  if not self.is_playing:
41
  self.is_playing = True
42
  try:
 
43
  audio_source = discord.FFmpegPCMAudio("assets/sample.mp3")
44
  def after_playing(e):
 
45
  self.is_playing = False
46
- # test loop by default
47
  if e:
48
  print(f"Playback error: {e}")
 
49
  asyncio.run_coroutine_threadsafe(self.play_next(ctx), bot.loop)
50
  self.voice_client.play(audio_source, after=after_playing)
51
  except Exception as e:
@@ -53,10 +76,48 @@ class MusicBot:
53
  await ctx.send("Error playing the song.")
54
  self.is_playing = False
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  music_bot = MusicBot()
57
 
58
  @bot.event
59
  async def on_ready():
 
60
  print(f'Bot is ready! Logged in as {bot.user}')
61
  print("Syncing commands...")
62
  try:
@@ -67,30 +128,39 @@ async def on_ready():
67
  except Exception as e:
68
  print(f"An error occurred while syncing commands: {e}")
69
 
70
- @bot.tree.command(name="play", description="Play the sample music")
71
- async def play(interaction: discord.Interaction):
 
72
  await interaction.response.defer()
73
  ctx = await commands.Context.from_interaction(interaction)
 
 
 
 
 
74
  await music_bot.join_voice(ctx)
75
 
76
  if not music_bot.is_playing:
77
- await music_bot.play_next(ctx)
78
- await interaction.followup.send('Playing sample music!')
 
 
 
79
  else:
80
  await interaction.followup.send('Already playing!')
81
 
82
- # Replace the existing skip command with this version
83
  @bot.tree.command(name="skip", description="Skip the current song")
84
  async def skip(interaction: discord.Interaction):
 
85
  if music_bot.voice_client:
86
  music_bot.voice_client.stop()
87
  await interaction.response.send_message('Skipped current song!')
88
  else:
89
  await interaction.response.send_message('No song is currently playing!')
90
 
91
- # Replace the existing leave command with this version
92
  @bot.tree.command(name="leave", description="Disconnect bot from voice channel")
93
  async def leave(interaction: discord.Interaction):
 
94
  if music_bot.voice_client:
95
  await music_bot.voice_client.disconnect()
96
  music_bot.voice_client = None
@@ -101,17 +171,19 @@ async def leave(interaction: discord.Interaction):
101
  await interaction.response.send_message('Bot is not in a voice channel!')
102
 
103
  def run_discord_bot():
 
104
  bot.run(os.getenv('DISCORD_TOKEN'))
105
 
106
- # Create the Gradio interface
107
  with gr.Blocks() as iface:
 
108
  gr.Markdown("# Discord Music Bot Control Panel")
109
  gr.Markdown("Bot is running in background")
110
 
111
  if __name__ == "__main__":
112
- # Start the Discord bot in a separate thread
113
  bot_thread = threading.Thread(target=run_discord_bot, daemon=True)
114
  bot_thread.start()
115
 
116
- # Run Gradio interface in the main thread
117
  iface.launch(debug=True)
 
1
+ # Import necessary libraries for Discord bot, HuggingFace integration, and UI
2
  import discord
3
  from discord.ext import commands
4
  from huggingface_hub import hf_hub_download
 
7
  import os
8
  import threading
9
  import asyncio
10
+ import spotipy
11
+ from spotipy.oauth2 import SpotifyClientCredentials
12
+ import yt_dlp
13
 
14
+ # Load environment variables from .env file
15
  load_dotenv()
16
 
17
+ # Create assets directory and download sample music if not exists
18
  if os.path.exists('assets') is False:
19
  os.makedirs('assets', exist_ok=True)
20
  hf_hub_download("not-lain/assets", "sample.mp3", repo_type="dataset",local_dir="assets")
21
 
22
+ # Set up Discord bot with necessary intents
23
  intents = discord.Intents.default()
24
  intents.message_content = True
25
  bot = commands.Bot(command_prefix='!', intents=intents)
26
 
27
+ # Initialize Spotify client
28
+ spotify = spotipy.Spotify(client_credentials_manager=SpotifyClientCredentials(
29
+ client_id=os.getenv('SPOTIFY_CLIENT_ID'),
30
+ client_secret=os.getenv('SPOTIFY_CLIENT_SECRET')
31
+ ))
32
+
33
+ # Class to handle music playback functionality
34
  class MusicBot:
35
  def __init__(self):
36
+ # Initialize bot state variables
37
  self.is_playing = False
38
  self.voice_client = None
39
+ self.ydl_opts = {
40
+ 'format': 'bestaudio/best',
41
+ 'postprocessors': [{
42
+ 'key': 'FFmpegExtractAudio',
43
+ 'preferredcodec': 'mp3',
44
+ 'preferredquality': '192',
45
+ }],
46
+ }
47
 
48
  async def join_voice(self, ctx):
49
+ # Method to join voice channel or move to user's channel
50
  if ctx.author.voice:
51
  channel = ctx.author.voice.channel
52
  if self.voice_client is None:
 
57
  await ctx.send("You need to be in a voice channel!")
58
 
59
  async def play_next(self, ctx):
60
+ # Method to play audio and handle playback completion
61
  if not self.is_playing:
62
  self.is_playing = True
63
  try:
64
+ # Create audio source from local file
65
  audio_source = discord.FFmpegPCMAudio("assets/sample.mp3")
66
  def after_playing(e):
67
+ # Callback function when song ends
68
  self.is_playing = False
 
69
  if e:
70
  print(f"Playback error: {e}")
71
+ # Test loop by default
72
  asyncio.run_coroutine_threadsafe(self.play_next(ctx), bot.loop)
73
  self.voice_client.play(audio_source, after=after_playing)
74
  except Exception as e:
 
76
  await ctx.send("Error playing the song.")
77
  self.is_playing = False
78
 
79
+ async def play_spotify(self, ctx, track_url):
80
+ if not self.is_playing:
81
+ self.is_playing = True
82
+ try:
83
+ # Extract Spotify track ID
84
+ track_id = track_url.split('/')[-1].split('?')[0]
85
+ track_info = spotify.track(track_id)
86
+ search_query = f"{track_info['name']} {track_info['artists'][0]['name']}"
87
+
88
+ # Use yt-dlp to find and download the audio
89
+ with yt_dlp.YoutubeDL(self.ydl_opts) as ydl:
90
+ # Search YouTube for the song
91
+ info = ydl.extract_info(f"ytsearch:{search_query}", download=False)
92
+ url = info['entries'][0]['url']
93
+
94
+ # Play the audio
95
+ FFMPEG_OPTIONS = {
96
+ 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5',
97
+ 'options': '-vn',
98
+ }
99
+ audio_source = discord.FFmpegPCMAudio(url, **FFMPEG_OPTIONS)
100
+
101
+ def after_playing(e):
102
+ self.is_playing = False
103
+ if e:
104
+ print(f"Playback error: {e}")
105
+
106
+ self.voice_client.play(audio_source, after=after_playing)
107
+ return track_info['name']
108
+
109
+ except Exception as e:
110
+ print(f"Error playing Spotify track: {e}")
111
+ await ctx.send("Error playing the song.")
112
+ self.is_playing = False
113
+ return None
114
+
115
+ # Create instance of MusicBot
116
  music_bot = MusicBot()
117
 
118
  @bot.event
119
  async def on_ready():
120
+ # Event handler for when bot is ready and connected
121
  print(f'Bot is ready! Logged in as {bot.user}')
122
  print("Syncing commands...")
123
  try:
 
128
  except Exception as e:
129
  print(f"An error occurred while syncing commands: {e}")
130
 
131
+ @bot.tree.command(name="play", description="Play a song from Spotify")
132
+ async def play(interaction: discord.Interaction, url: str):
133
+ # Command to start playing music
134
  await interaction.response.defer()
135
  ctx = await commands.Context.from_interaction(interaction)
136
+
137
+ if not url.startswith('https://open.spotify.com/track/'):
138
+ await interaction.followup.send('Please provide a valid Spotify track URL!')
139
+ return
140
+
141
  await music_bot.join_voice(ctx)
142
 
143
  if not music_bot.is_playing:
144
+ song_name = await music_bot.play_spotify(ctx, url)
145
+ if song_name:
146
+ await interaction.followup.send(f'Playing {song_name} from Spotify!')
147
+ else:
148
+ await interaction.followup.send('Failed to play the song!')
149
  else:
150
  await interaction.followup.send('Already playing!')
151
 
 
152
  @bot.tree.command(name="skip", description="Skip the current song")
153
  async def skip(interaction: discord.Interaction):
154
+ # Command to skip current playing song
155
  if music_bot.voice_client:
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
  @bot.tree.command(name="leave", description="Disconnect bot from voice channel")
162
  async def leave(interaction: discord.Interaction):
163
+ # Command to disconnect bot from voice channel
164
  if music_bot.voice_client:
165
  await music_bot.voice_client.disconnect()
166
  music_bot.voice_client = None
 
171
  await interaction.response.send_message('Bot is not in a voice channel!')
172
 
173
  def run_discord_bot():
174
+ # Function to start the Discord bot
175
  bot.run(os.getenv('DISCORD_TOKEN'))
176
 
177
+ # Create Gradio interface for web control
178
  with gr.Blocks() as iface:
179
+ # Set up simple web interface
180
  gr.Markdown("# Discord Music Bot Control Panel")
181
  gr.Markdown("Bot is running in background")
182
 
183
  if __name__ == "__main__":
184
+ # Main entry point: start bot in background thread and launch web interface
185
  bot_thread = threading.Thread(target=run_discord_bot, daemon=True)
186
  bot_thread.start()
187
 
188
+ # Launch Gradio interface in main thread
189
  iface.launch(debug=True)
requirements.txt CHANGED
@@ -3,4 +3,5 @@ youtube_dl
3
  PyNaCl
4
  ffmpeg-python
5
  yt-dlp
6
- python-dotenv
 
 
3
  PyNaCl
4
  ffmpeg-python
5
  yt-dlp
6
+ python-dotenv
7
+ spotipy