├── 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 | GitHub2P 7 | #### Most Recent Played Songs 8 | 9 | GitP1 10 | 11 | #### Liked Songs Lyric and Similar Songs Buttons 12 | 13 | rifhwjrn 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 | ![Lryics](https://user-images.githubusercontent.com/82426784/236603483-2cc7cc20-2a8d-4b21-9057-c06ede213198.png) 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 | --------------------------------------------------------------------------------