├── src
├── headers_auth.json
├── universalV.txt
├── livesong.py
└── bot.py
├── LICENSE
└── README.md
/src/headers_auth.json:
--------------------------------------------------------------------------------
1 | {
2 |
3 | }
--------------------------------------------------------------------------------
/src/universalV.txt:
--------------------------------------------------------------------------------
1 | Get Lucky (feat. Pharrell Williams & Nile Rodgers)
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Caleb Klinger
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # DiscordYoutubeMusicBot
2 | Discord Bot that displays currently listening too, liked music, writes descriptions for songs, recommends similar music, shows lyrics, search music by lyrics, like songs within discord.
3 |
4 | ## Example ScreenShots of Bot running
5 | #### Liked Songs
6 |
7 | #### Most Recent Played Songs
8 |
9 |
10 |
11 | #### Liked Songs Lyric and Similar Songs Buttons
12 |
13 |
14 |
15 |
16 |
17 | Setting up everything for this bot is a lot so make sure to review the steps below
18 |
19 | #### Installs
20 | ```
21 | pip install discord.py
22 | pip install ytmusicapi
23 | pip install openai
24 | pip install lyricsgenius
25 | ```
26 |
27 | #### Documents
28 | Discord.py 2.0 - https://discordpy.readthedocs.io/en/stable/index.html
29 | YTMusicAPI - https://ytmusicapi.readthedocs.io/en/stable/index.html
30 | OpenAI API - https://platform.openai.com/docs/introduction
31 | Genius API - https://lyricsgenius.readthedocs.io/en/master/
32 |
33 | #### Verification
34 |
35 | Setting Up the header_auth for YTMusic can be done in chrome or firefox
36 | Documentation (SetUp) - https://ytmusicapi.readthedocs.io/en/stable/setup.html
37 |
38 | To run authenticated requests, set it up by first copying your request headers from an authenticated POST request in your browser. To do so, follow these steps:
39 |
40 | Open a new tab
41 | Open the developer tools (Ctrl-Shift-I) and select the “Network” tab
42 | Go to https://music.youtube.com and ensure you are logged in
43 | Find an authenticated POST request. The simplest way is to filter by /browse using the search bar of the developer tools. If you don’t see the request, try scrolling down a bit or clicking on the library button in the top bar.
44 |
45 | **FireFox**
46 | Verify that the request looks like this: Status 200, Method POST, Domain music.youtube.com, File browse?...
47 | Copy the request headers (right click > copy > copy request headers)
48 |
49 | **Chrome**
50 | Verify that the request looks like this: Status 200, Name browse?...
51 | Click on the Name of any matching request. In the “Headers” tab, scroll to the section “Request headers” and copy everything starting from “accept: */*” to the end of the section
52 |
53 | **In your code**
54 | To set up your project, open a Python console and call YTMusic.setup() with the parameter filepath=headers_auth.json and follow the instructions and paste the request headers to the terminal input: *This is going in your headers_auth file*
55 | ```Python
56 | from ytmusicapi import YTMusic
57 | YTMusic.setup(filepath="headers_auth.json")
58 | ```
59 | The verifcation should have a very long span so dont worry about reverifying
60 |
61 | #### Other API Tokens
62 | OpenAI/Geunis are easy to get just make an account and get the token id
63 | Input the token in live_song.py
64 |
65 | *Lyrics Button* just broke out of no where if someone wants to fix that slash command still works thou
66 |
67 | ### Addtional Lyric Search Command
68 | 
69 |
70 |
--------------------------------------------------------------------------------
/src/livesong.py:
--------------------------------------------------------------------------------
1 | from ytmusicapi import YTMusic
2 | import json
3 | import openai
4 | import lyricsgenius
5 |
6 |
7 | ytmusic = YTMusic('headers_auth.json')
8 | openai.api_key = "sk-"
9 | genius_token = ''
10 | genius = lyricsgenius.Genius(genius_token)
11 | genius.verbose = False
12 | genius.remove_section_headers = True
13 |
14 |
15 | def like_song_in(video_id):
16 | ytmusic.rate_song(videoId=video_id,rating='LIKE')
17 |
18 | def words_to_song(lyrics):
19 | request = genius.search_lyrics(lyrics)
20 | results = []
21 | for hit in request['sections'][0]['hits']:
22 | results.append(hit['result']['title'])
23 | return results
24 |
25 | def get_current_song():
26 | data = ytmusic.get_history()
27 |
28 | top_history = data[:1]
29 |
30 | for item in top_history:
31 | video_id = item['videoId']
32 | artist_name = item['artists'][0]['name']
33 | song_name = item['title']
34 | album = item['album']['name'] if item['album'] else 'N/A'
35 | thumbnail_url = item['thumbnails'][1]['url'] if len(item['thumbnails']) > 1 else item['thumbnails'][0]['url']
36 | duration = item['duration']
37 | url = f"https://www.youtube.com/watch?v={item['videoId']}"
38 | if song_name == album:
39 | album = "None"
40 |
41 | return artist_name,song_name,album,thumbnail_url,duration,url,video_id
42 |
43 | def get_song_name():
44 | liked_songs_raw = ytmusic.get_liked_songs()
45 |
46 | liked_songs = liked_songs_raw['tracks'][:1]
47 | for item in liked_songs:
48 | song_name = item['title']
49 | return song_name
50 |
51 |
52 | def get_liked_song():
53 | liked_songs_raw = ytmusic.get_liked_songs()
54 |
55 | liked_songs = liked_songs_raw['tracks'][:1]
56 |
57 | for item in liked_songs:
58 | artist_name = item['artists'][0]['name']
59 | song_name = item['title']
60 | album = item['album']['name'] if item['album'] else 'N/A'
61 | thumbnail_url = item['thumbnails'][1]['url'] if len(item['thumbnails']) > 1 else item['thumbnails'][0]['url']
62 | duration = item['duration']
63 | url = f"https://www.youtube.com/watch?v={item['videoId']}"
64 | if song_name == album:
65 | album = "None"
66 |
67 | genre = classify_song_genre(song_name, artist_name)
68 | desc = description(song_name, artist_name)
69 | rdate = release_date(song_name,artist_name)
70 | #song_list = similar_songs(song_name, artist_name)
71 | song_list = ""
72 |
73 | return artist_name,song_name,album,thumbnail_url,duration,url,genre,desc,rdate,song_list
74 |
75 | def get_lyrics(song_name,artist):
76 | song = genius.search_song(song_name, artist)
77 |
78 | if song is not None:
79 | lyrics = song.lyrics
80 | return lyrics
81 | else:
82 | lyrics = "Lyrics not found."
83 | return lyrics
84 |
85 | def classify_song_genre(song_name, artist):
86 | prompt = f"Classify the song '{song_name}' by '{artist}' into a music genre with out a description"
87 |
88 | response = openai.Completion.create(
89 | engine="text-davinci-003",
90 | prompt=prompt,
91 | max_tokens=50,
92 | n=1,
93 | stop=None,
94 | temperature=0.5,
95 | )
96 |
97 | genre = response.choices[0].text.strip()
98 | return genre
99 |
100 |
101 | def description(song_name, artist):
102 | prompt = f"Write me a short review in third person on the song {song_name} by {artist}"
103 | response = openai.Completion.create(
104 | engine="text-davinci-003",
105 | prompt=prompt,
106 | max_tokens=250,
107 | n=1,
108 | stop=None,
109 | temperature=1,
110 | )
111 |
112 | desc = response.choices[0].text.strip()
113 | return desc
114 |
115 | def release_date(song_name, artist):
116 | prompt = f"When did {song_name} by {artist} release display it as Release:mm/dd/yy only"
117 |
118 | response = openai.Completion.create(
119 | engine="text-davinci-003",
120 | prompt=prompt,
121 | max_tokens=250,
122 | n=1,
123 | stop=None,
124 | temperature=0.5,
125 | )
126 |
127 | ogtext = response.choices[0].text.strip()
128 |
129 | if "Release:" in ogtext:
130 | rdate = ogtext[ogtext.index("Release:")+len("Release:"):][:10]
131 | else:
132 | rdate = "N/A"
133 |
134 | return rdate
135 |
136 |
137 | def similar_songs(song_name, artist):
138 | prompt = f"What are 5 similar yet unique songs like {song_name} by {artist}, display as 1.x 2.x 3.x 4.x 5.x"
139 |
140 | response = openai.Completion.create(
141 | engine="text-davinci-003",
142 | prompt=prompt,
143 | max_tokens=550,
144 | n=1,
145 | stop=None,
146 | temperature=0.4,
147 | )
148 |
149 | song_list = response.choices[0].text.strip()
150 |
151 | return song_list
152 |
--------------------------------------------------------------------------------
/src/bot.py:
--------------------------------------------------------------------------------
1 | import discord
2 | from discord.ext import commands
3 | from discord.ext.commands import Context
4 | from discord import app_commands
5 | import json
6 | import datetime
7 | import livesong
8 | import asyncio
9 | from collections import defaultdict
10 |
11 | intents = discord.Intents.default()
12 | intents.members = True
13 | intents.message_content = True
14 | bot = commands.Bot(command_prefix='?', intents=intents)
15 |
16 | ADMIN_ID = #Your Discord ID
17 | current_channel = # Channel ID where currently listing too music is displayed
18 | liked_channel = # Channel ID where liked songs with its features go
19 | emoji_like = "👍"
20 | bot_key = ""
21 |
22 | music_loop_flags = defaultdict(lambda: True)
23 | liked_music_loop_flags = defaultdict(lambda: True)
24 |
25 | @bot.event
26 | async def on_ready():
27 | print(f'Logged in as {bot.user}') #Bot Name
28 | print(bot.user.id) #Bot ID
29 |
30 | #Fun
31 | @bot.tree.command()
32 | async def lyric_search(interaction: discord.Interaction, lyrics:str):
33 | """Look Songs Up From Lyrics"""
34 |
35 | results = livesong.words_to_song(lyrics)
36 | song_list = "\n".join(results)
37 | data = {
38 | "type": "rich",
39 | "title": f'Songs With the Lyrics - "{lyrics}"',
40 | "description": f"{song_list}",
41 | "color": 0x00ff00,
42 | "fields": [
43 | ],
44 | "footer": {
45 | "text": 'Search From Genius'
46 | }
47 | }
48 | embed = discord.Embed.from_dict(data)
49 | await interaction.response.send_message(embed = embed)
50 |
51 |
52 |
53 | @bot.tree.command()
54 | async def start_music_updates(interaction: discord.Interaction):
55 | """Start displaying current music"""
56 |
57 | if ADMIN_ID != interaction.user.id:
58 | content = "You can not use this command :P"
59 | await interaction.response.send_message(content=content, ephemeral=True)
60 | return
61 |
62 | if not music_loop_flags[interaction.channel.id]:
63 | content = "Music updates are already running."
64 | await interaction.response.send_message(content=content, ephemeral=True)
65 | return
66 |
67 | music_loop_flags[interaction.channel.id] = True
68 | await update_music_status(interaction.channel.id)
69 |
70 | @bot.tree.command()
71 | async def stop_music_updates(interaction: discord.Interaction):
72 | """Stop displaying current music"""
73 |
74 | if ADMIN_ID != interaction.user.id:
75 | content = "You can not use this command :P"
76 | await interaction.response.send_message(content=content, ephemeral=True)
77 | return
78 |
79 | if not music_loop_flags[interaction.channel.id]:
80 | content = "Music updates are not running."
81 | await interaction.response.send_message(content=content, ephemeral=True)
82 | return
83 |
84 | music_loop_flags[interaction.channel.id] = False
85 | content = "Music updates stopped."
86 | await interaction.response.send_message(content=content)
87 |
88 |
89 | @bot.tree.command()
90 | async def start_liked_music_updates(interaction: discord.Interaction):
91 | """Start displaying liked music"""
92 |
93 | if ADMIN_ID != interaction.user.id:
94 | content = "You can not use this command :P"
95 | await interaction.response.send_message(content=content, ephemeral=True)
96 | return
97 |
98 | if not liked_music_loop_flags[interaction.channel.id]:
99 | content = "Liked music updates are already running."
100 | await interaction.response.send_message(content=content, ephemeral=True)
101 | return
102 |
103 | liked_music_loop_flags[interaction.channel.id] = True
104 | await update_liked_music(interaction.channel.id)
105 |
106 |
107 | @bot.tree.command()
108 | async def stop_liked_music_updates(interaction: discord.Interaction):
109 | """Stop displaying liked music"""
110 |
111 | if ADMIN_ID != interaction.user.id:
112 | content = "You can not use this command :P"
113 | await interaction.response.send_message(content=content, ephemeral=True)
114 | return
115 |
116 | if not liked_music_loop_flags[interaction.channel.id]:
117 | content = "Liked music updates are not running."
118 | await interaction.response.send_message(content=content, ephemeral=True)
119 | return
120 |
121 | liked_music_loop_flags[interaction.channel.id] = False
122 | content = "Liked music updates stopped."
123 | await interaction.response.send_message(content=content)
124 |
125 |
126 | class SimilarMusicButton(discord.ui.Button):
127 | def __init__(self, song_name, artist_name, *args, **kwargs):
128 | super().__init__(*args, **kwargs)
129 | self.song_name = song_name
130 | self.artist_name = artist_name
131 |
132 | async def callback(self, interaction: discord.Interaction):
133 | await interaction.response.send_message("Finding similar music...", ephemeral=True)
134 | await similar_music(self.song_name, self.artist_name)
135 | await interaction.message.edit(view=self.view)
136 |
137 | class MusicLyricsButton(discord.ui.Button):
138 | def __init__(self, song_name, artist_name, *args, **kwargs):
139 | super().__init__(*args, **kwargs)
140 | self.song_name = song_name
141 | self.artist_name = artist_name
142 |
143 | async def callback(self, interaction: discord.Interaction):
144 | await interaction.response.send_message("Finding lyrics...", ephemeral=True)
145 | await lyrics_button(self.song_name, self.artist_name)
146 | await interaction.message.edit(view=self.view)
147 |
148 | async def lyrics_button(song_name,artist):
149 | lyrics = livesong.get_lyrics(song_name, artist)
150 | data = {
151 | "type": "rich",
152 | "title": f'Lyrics - {song_name}',
153 | "description": f"\n{lyrics}\n",
154 | "color": 0xFFD700,
155 | "fields": [
156 | ],
157 | "footer": {
158 | "text": 'Lyrics From Genius'
159 | }
160 | }
161 | embed = discord.Embed.from_dict(data)
162 | await bot.get_channel(liked_channel).send(embed=embed)
163 |
164 |
165 | async def similar_music(song_name, artist):
166 | song_list = livesong.similar_songs(song_name, artist)
167 |
168 | data = {
169 | "type": "rich",
170 | "title": f'Similar Songs - {song_name}',
171 | "description": f"\n{song_list}\n",
172 | "color": 0xFFD700,
173 | "fields": [
174 | ],
175 | "footer": {
176 | "text": 'The majority of this document is generated by an AI'
177 | }
178 | }
179 |
180 | embed = discord.Embed.from_dict(data)
181 | await bot.get_channel(liked_channel).send(embed=embed)
182 |
183 |
184 | #LIKE
185 | async def update_liked_music(channel):
186 | while liked_music_loop_flags[channel]:
187 | # Send an update to the channel
188 | song_name = livesong.get_song_name()
189 | with open('universalV.txt', 'r') as f:
190 | name_hold = f.readlines()
191 |
192 | if name_hold[0] != song_name:
193 | #Get description and rest of UI shiz
194 | artist_name, song_name, album, thumbnail_url, duration, url,genre,desc,date,song_list = livesong.get_liked_song()
195 |
196 | typeAS = 'Album'
197 | if album == 'None':
198 | typeAS = 'Single'
199 | elif album == 'N/A':
200 | typeAS = 'Single'
201 |
202 |
203 | data = {
204 | "type": "rich",
205 | "title": f'{song_name}',
206 | "description": f"Description-\n{desc}\n",
207 | "color": 0x27c3b4,
208 | "fields": [
209 | {
210 | "name": 'Artist',
211 | "value": f'{artist_name}',
212 | "inline": True
213 | },
214 | {
215 | "name": f'{typeAS}',
216 | "value": f'{album}',
217 | "inline": True
218 | },
219 | {
220 | "name": 'Duration',
221 | "value": f'{duration}',
222 | "inline": True
223 | },
224 | {
225 | "name": 'Genre',
226 | "value": f"{genre}",
227 | "inline": True
228 | },
229 | {
230 | "name": 'Release Date',
231 | "value": f'{date}',
232 | "inline": True
233 | },
234 | {
235 | "name": 'Similar Music',
236 | "value": f'Coming Soon',
237 | "inline": True
238 | }
239 | ],
240 | "thumbnail": {
241 | "url": f'{thumbnail_url}',
242 | "height": 0,
243 | "width": 0
244 | },
245 | "footer": {
246 | "text": 'The majority of this document is generated by an AI'
247 | },
248 | "url": f'{url}'
249 | }
250 |
251 | view = discord.ui.View()
252 | similar_music_button = SimilarMusicButton(song_name=song_name, artist_name=artist_name, style=discord.ButtonStyle.primary, label="Similar Music", custom_id="similar_music")
253 | show_lyrics_buttons = MusicLyricsButton(song_name=song_name, artist_name=artist_name, style=discord.ButtonStyle.primary, label="Lyrics")
254 | view.add_item(similar_music_button)
255 | view.add_item(show_lyrics_buttons)
256 |
257 |
258 | embed = discord.Embed.from_dict(data)
259 | await bot.get_channel(liked_channel).send(embed=embed,view=view)
260 |
261 | with open('universalV.txt', 'w') as f:
262 | f.write(song_name)
263 |
264 | await asyncio.sleep(45)
265 | liked_music_loop_flags[channel] = True
266 |
267 |
268 | class LikeButton(discord.ui.Button):
269 | def __init__(self, video_id, *args, **kwargs):
270 | super().__init__(*args, **kwargs)
271 | self.video_id = video_id
272 |
273 | async def callback(self, interaction: discord.Interaction):
274 | await interaction.response.send_message("Liking the song...", ephemeral=True)
275 | await like_song(self.video_id) # Add 'await' here
276 | self.disabled = True
277 | await interaction.message.edit(view=self.view)
278 |
279 | async def like_song(video_id):
280 | livesong.like_song_in(video_id)
281 | # Send an update to the channel
282 |
283 | song_name = livesong.get_song_name()
284 |
285 | with open('universalV.txt', 'r') as f:
286 | name_hold = f.readlines()
287 |
288 |
289 | if name_hold != song_name:
290 | artist_name, song_name, album, thumbnail_url, duration, url,genre,desc,date,song_list = livesong.get_liked_song()
291 |
292 | typeAS = 'Album'
293 | if album == 'None':
294 | typeAS = 'Single'
295 | elif album == 'N/A':
296 | typeAS = 'Single'
297 |
298 |
299 | data = {
300 | "type": "rich",
301 | "title": f'{song_name}',
302 | "description": f"Description-\n{desc}\n",
303 | "color": 0x27c3b4,
304 | "fields": [
305 | {
306 | "name": 'Artist',
307 | "value": f'{artist_name}',
308 | "inline": True
309 | },
310 | {
311 | "name": f'{typeAS}',
312 | "value": f'{album}',
313 | "inline": True
314 | },
315 | {
316 | "name": 'Duration',
317 | "value": f'{duration}',
318 | "inline": True
319 | },
320 | {
321 | "name": 'Genre',
322 | "value": f"{genre}",
323 | "inline": True
324 | },
325 | {
326 | "name": 'Release Date',
327 | "value": f'{date}',
328 | "inline": True
329 | },
330 | {
331 | "name": 'Similar Music',
332 | "value": f'Coming Soon',
333 | "inline": True
334 | }
335 | ],
336 | "thumbnail": {
337 | "url": f'{thumbnail_url}',
338 | "height": 0,
339 | "width": 0
340 | },
341 | "footer": {
342 | "text": 'The majority of this document is generated by an AI'
343 | },
344 | "url": f'{url}'
345 | }
346 |
347 | view = discord.ui.View()
348 | similar_music_button = SimilarMusicButton(song_name=song_name, artist_name=artist_name, style=discord.ButtonStyle.primary, label="Similar Music", custom_id="similar_music")
349 | show_lyrics_buttons = MusicLyricsButton(song_name=song_name, artist_name=artist_name, style=discord.ButtonStyle.primary, label="Lyrics")
350 | view.add_item(similar_music_button)
351 | view.add_item(show_lyrics_buttons)
352 |
353 | embed = discord.Embed.from_dict(data)
354 | await bot.get_channel(liked_channel).send(embed=embed,view=view)
355 |
356 | with open('universalV.txt', 'w') as f:
357 | f.write(song_name)
358 |
359 |
360 | async def update_music_status(channel):
361 | name_hold = ""
362 | while music_loop_flags[channel]:
363 | artist_name, song_name, album, thumbnail_url, duration, url,video_id = livesong.get_current_song()
364 | # Send an update to the channel
365 |
366 | if name_hold != song_name:
367 | typeAS = 'Album'
368 | if album == 'None':
369 | typeAS = 'Single'
370 | elif album == 'N/A':
371 | typeAS = 'Single'
372 |
373 | data = {
374 | "type": "rich",
375 | "title": f'{song_name}',
376 | "description": "",
377 | "color": 0x27c3b4,
378 | "fields": [
379 | {
380 | "name": 'Artist',
381 | "value": f'{artist_name}',
382 | "inline": True
383 | },
384 | {
385 | "name": f'{typeAS}',
386 | "value": f'{album}',
387 | "inline": True
388 | },
389 | {
390 | "name": 'Duration',
391 | "value": f'{duration}',
392 | "inline": True
393 | }
394 | ],
395 | "thumbnail": {
396 | "url": f'{thumbnail_url}',
397 | "height": 0,
398 | "width": 0
399 | },
400 | "footer": {
401 | "text": 'Most Recently Listened To'
402 | },
403 | "url": f'{url}'
404 | }
405 |
406 | view = discord.ui.View()
407 | like_button = LikeButton(video_id, label="", style=discord.ButtonStyle.green,emoji=emoji_like)
408 | view.add_item(like_button)
409 |
410 | embed = discord.Embed.from_dict(data)
411 | await bot.get_channel(current_channel).send(embed=embed,view=view)
412 | name_hold = song_name
413 | await asyncio.sleep(30)
414 | music_loop_flags[channel] = True
415 |
416 |
417 |
418 |
419 |
420 |
421 | #------ Sync Command ------
422 | @bot.command()
423 | @commands.guild_only()
424 | @commands.is_owner()
425 | async def sync(ctx: Context):
426 | synced = await ctx.bot.tree.sync()
427 | await ctx.send(f"Synced {len(synced)} commands {'globally'}")
428 |
429 |
430 | bot.run(bot_key)
431 |
--------------------------------------------------------------------------------