From d1a28724ef373a660e978e19d5cbc46d79c4b6a1 Mon Sep 17 00:00:00 2001 From: LBlend Date: Wed, 14 Dec 2022 01:26:20 +0100 Subject: [PATCH 1/5] fix lyrics command --- musicbot/cogs/music/__init__.py | 107 +++++++++++++++++++++----------- 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index ef75fd4..d1cb755 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -9,7 +9,6 @@ from lavalink.events import ( from lavalink.models import AudioTrack import asyncio -import json import re import urllib.parse as urlparse from typing import List, Optional, Tuple @@ -635,62 +634,92 @@ class Music(commands.Cog): @commands.cooldown(1, 5, commands.BucketType.guild) @commands.command(name='lyrics') async def _lyrics(self, ctx, *query: str): - - # TODO: - player = self.get_player(ctx.guild) - - genius_access_token = self.bot.APIkeys.get('genius', None) - - if genius_access_token is None: + """ Search for lyrics of a song """ + # Check for API key + if not (genius_access_token := self.bot.APIkeys.get('genius')): return await ctx.send('Missing API key') - excluded_words = ['music', 'video', 'version', 'original', 'lyrics', 'lyric', - 'official', 'live', 'instrumental', 'audio', 'hd'] + # Extract all arguments into a single string + query = ' '.join(query) - query_as_string = ' '.join(query) + # If no query is given, use the current playing song + if not query: + player = self.get_player(ctx.guild) + if not player.is_playing: + return await ctx.send('No song is currently playing') + query = player.current.title - if not query_as_string and player.is_playing and player.current: - query_as_string = player.current.title - query_as_string = re.sub(r'[-()_\[\]]', '', query_as_string) - filtered_words = [word for word in query_as_string.split() if word.lower() not in excluded_words] - query_as_string = ' '.join(filtered_words) - - async def get_site_content(url): - async with self.bot.session.get(url) as resp: - response = await resp.read() - return response.decode('utf-8') + # Filter out unwanted words to improve chances of finding the correct song + stopwords = ['music', 'video', 'version', 'original', 'lyrics', 'lyric', + 'official', 'live', 'instrumental', 'audio', 'hd'] + query = ' '.join([word for word in query.split() if word.lower() not in stopwords]) + # Send a message to indicate that the bot is searching for the lyrics + # Unfortunately, the Genius API does not provide the lyrics directly, so we have to scrape the website + # This is a bit slow, so we send a message to indicate that the bot is searching embed = discord.Embed(description=':mag_right:') status_msg = await ctx.send(embed=embed) + # Define an internal function to make requests to the Genius API + async def get_site_content(url: str, scrape: bool = False) -> Optional[dict | str]: + header = {} if scrape else {'Authorization': f'Bearer {genius_access_token}'} + async with self.bot.session.get(url, headers=header) as r: + if r.status != 200: + embed = discord.Embed(description=':x: Request failed', color=0xFF0000) + await status_msg.edit(embed=embed) + return + if scrape: + return await r.text() + return await r.json() + + # Query the song + url = 'https://api.genius.com/search?' + urlparse.urlencode({'q': query}) + response = await get_site_content(url) + if not response: + return + + # Select top song result try: - url = 'https://api.genius.com/search?' + urlparse.urlencode( - {'access_token': genius_access_token, 'q': query_as_string}) - result = await get_site_content(url) - response = json.loads(result) - song_id = response['response']['hits'][0]['result']['id'] - except Exception: - embed = discord.Embed(description=':x:', color=0xFF0000) + song = {} + for hit in response['response']['hits']: + if hit['type'] == 'song': + song = hit['result'] + break + song_url = song['url'] + except KeyError: + embed = discord.Embed(description=':x: Could not find the queried song', color=0xFF0000) return await status_msg.edit(embed=embed) - result = await get_site_content(f'https://api.genius.com/songs/{song_id}?access_token={genius_access_token}') - song = json.loads(result)['response']['song'] + # Scrape the lyrics from the song page + response = await get_site_content(song_url, scrape=True) + if not response: + return - response = await get_site_content(song['url']) + # Find the lyrics in our scraped data scraped_data = BeautifulSoup(response, 'html.parser') - lyrics = scraped_data.find(class_='lyrics').get_text() + lyrics = scraped_data.findAll('div') + for div in lyrics: + if div.has_attr('data-lyrics-container'): + lyrics_div = div + break # We found the lyrics, so we can stop searching + # Replace
tags with newlines and remove HTML tags + # This is a bit hacky, but it works + lyrics = re.sub(r'', '\n', str(lyrics_div)) + lyrics = re.sub(r'<\/*\w+.*>', '', lyrics) + + # Construct the output embed paginator = TextPaginator(max_size=2000, max_lines=50, color=0xFFFF64) for line in lyrics.split('\n'): paginator.add_line(line) - await status_msg.delete() - - paginator.pages[0].url = song['url'] - paginator.pages[0].title = song['full_title'] - paginator.pages[0].set_thumbnail(url=song['header_image_thumbnail_url']) + # Set metadata on the first page + paginator.pages[0].url = song_url + paginator.pages[0].title = song.get('full_title', '*Unknown title*') + paginator.pages[0].set_thumbnail(url=song.get('header_image_thumbnail_url', 'https://i.imgur.com/NmCTsoF.png')) paginator.pages[0].set_author(name='Genius', icon_url='https://i.imgur.com/NmCTsoF.png') + # Send the output if len(paginator.pages) < 4: for page in paginator.pages: await ctx.send(embed=page) @@ -698,6 +727,10 @@ class Music(commands.Cog): paginator.add_page_indicator(ctx.localizer) await Scroller(ctx, paginator).start_scrolling(ClearOn.AnyExit) + # Since we are using a paginator, we need to delete the status message + # because the final output may consist of multiple pages + await status_msg.delete() + @commands.command(name='scrub') @checks.dj_or(alone=True) @require_voice_connection() From b96995bf09117b07e45b93bc6a67832a18643fb2 Mon Sep 17 00:00:00 2001 From: LBlend Date: Wed, 14 Dec 2022 01:35:44 +0100 Subject: [PATCH 2/5] add localization support to lyrics command --- musicbot/cogs/music/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index d1cb755..810178b 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -637,7 +637,7 @@ class Music(commands.Cog): """ Search for lyrics of a song """ # Check for API key if not (genius_access_token := self.bot.APIkeys.get('genius')): - return await ctx.send('Missing API key') + return await ctx.send('{errors.missing_api_key}') # Extract all arguments into a single string query = ' '.join(query) @@ -646,7 +646,7 @@ class Music(commands.Cog): if not query: player = self.get_player(ctx.guild) if not player.is_playing: - return await ctx.send('No song is currently playing') + return await ctx.send('{nothing_playing}') query = player.current.title # Filter out unwanted words to improve chances of finding the correct song @@ -665,7 +665,7 @@ class Music(commands.Cog): header = {} if scrape else {'Authorization': f'Bearer {genius_access_token}'} async with self.bot.session.get(url, headers=header) as r: if r.status != 200: - embed = discord.Embed(description=':x: Request failed', color=0xFF0000) + embed = discord.Embed(description="{errors.error_occurred}", color=0xFF0000) await status_msg.edit(embed=embed) return if scrape: @@ -687,7 +687,7 @@ class Music(commands.Cog): break song_url = song['url'] except KeyError: - embed = discord.Embed(description=':x: Could not find the queried song', color=0xFF0000) + embed = discord.Embed(description='{nothing_found}', color=0xFF0000) return await status_msg.edit(embed=embed) # Scrape the lyrics from the song page @@ -715,7 +715,7 @@ class Music(commands.Cog): # Set metadata on the first page paginator.pages[0].url = song_url - paginator.pages[0].title = song.get('full_title', '*Unknown title*') + paginator.pages[0].title = song.get('full_title', '*?*') paginator.pages[0].set_thumbnail(url=song.get('header_image_thumbnail_url', 'https://i.imgur.com/NmCTsoF.png')) paginator.pages[0].set_author(name='Genius', icon_url='https://i.imgur.com/NmCTsoF.png') From bcc59839ddba676e2024aa5830c7133100649766 Mon Sep 17 00:00:00 2001 From: LBlend Date: Wed, 14 Dec 2022 01:37:51 +0100 Subject: [PATCH 3/5] add missing API key message localization (en_en & nb_no) --- localization/en_en/music.yaml | 1 + localization/nb_no/music.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/localization/en_en/music.yaml b/localization/en_en/music.yaml index 0d58ea9..231be77 100644 --- a/localization/en_en/music.yaml +++ b/localization/en_en/music.yaml @@ -85,3 +85,4 @@ errors: right_channel: You need to be in the right voice channel my_channel: You need to be in my voicechannel full_channel: The channel is currently full + missing_api_key: API key is missing diff --git a/localization/nb_no/music.yaml b/localization/nb_no/music.yaml index 4dc2a6e..b20d4f7 100644 --- a/localization/nb_no/music.yaml +++ b/localization/nb_no/music.yaml @@ -85,3 +85,4 @@ errors: right_channel: Du må være i riktig kanal my_channel: Du må være i samme stemmekanal som meg full_channel: Den kanalen er full + missing_api_key: API-nøkkel mangler From abd6da7134bab912f680d35a82f728626eaee0cf Mon Sep 17 00:00:00 2001 From: LBlend Date: Wed, 14 Dec 2022 01:52:57 +0100 Subject: [PATCH 4/5] (correctly) add localization support to lyrics command --- musicbot/cogs/music/__init__.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index 810178b..de3974b 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -637,7 +637,7 @@ class Music(commands.Cog): """ Search for lyrics of a song """ # Check for API key if not (genius_access_token := self.bot.APIkeys.get('genius')): - return await ctx.send('{errors.missing_api_key}') + return await ctx.send(ctx.localizer.format_str('{history.title}')) # Extract all arguments into a single string query = ' '.join(query) @@ -646,7 +646,7 @@ class Music(commands.Cog): if not query: player = self.get_player(ctx.guild) if not player.is_playing: - return await ctx.send('{nothing_playing}') + return await ctx.send(ctx.localizer.format_str('{nothing_playing}')) query = player.current.title # Filter out unwanted words to improve chances of finding the correct song @@ -665,7 +665,8 @@ class Music(commands.Cog): header = {} if scrape else {'Authorization': f'Bearer {genius_access_token}'} async with self.bot.session.get(url, headers=header) as r: if r.status != 200: - embed = discord.Embed(description="{errors.error_occurred}", color=0xFF0000) + embed = discord.Embed(description=ctx.localizer.format_str('{errors.error_occurred}'), + color=0xFF0000) await status_msg.edit(embed=embed) return if scrape: @@ -687,7 +688,7 @@ class Music(commands.Cog): break song_url = song['url'] except KeyError: - embed = discord.Embed(description='{nothing_found}', color=0xFF0000) + embed = discord.Embed(description=ctx.localizer.format_str('{nothing_found}'), color=0xFF0000) return await status_msg.edit(embed=embed) # Scrape the lyrics from the song page From 76128814c0518e42cff343680dc2cb0cf7bfd4c0 Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Sun, 12 Feb 2023 22:31:28 +0900 Subject: [PATCH 5/5] use correct error message - missing key for lyrics --- musicbot/cogs/music/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/musicbot/cogs/music/__init__.py b/musicbot/cogs/music/__init__.py index de3974b..2933941 100644 --- a/musicbot/cogs/music/__init__.py +++ b/musicbot/cogs/music/__init__.py @@ -637,7 +637,7 @@ class Music(commands.Cog): """ Search for lyrics of a song """ # Check for API key if not (genius_access_token := self.bot.APIkeys.get('genius')): - return await ctx.send(ctx.localizer.format_str('{history.title}')) + return await ctx.send(ctx.localizer.format_str('{errors.missing_api_key}')) # Extract all arguments into a single string query = ' '.join(query)