From 4ba03ff8bc245749d5203ef4f13849f01a21f687 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Fri, 24 Dec 2021 19:05:03 -0400 Subject: [PATCH 01/23] Adding new classes to upgrade the music code organization --- config/config.py | 5 ++- main.py | 2 +- {vulkan => vulkanbot}/ErrorHandler.py | 0 {vulkan => vulkanbot}/commands/Phrases.py | 4 +- {vulkan => vulkanbot}/commands/Warframe.py | 0 vulkanbot/commands/__init__.py | 0 {vulkan => vulkanbot}/general/Control.py | 0 {vulkan => vulkanbot}/general/Filter.py | 0 vulkanbot/general/__init__.py | 0 vulkanbot/music/Downloader.py | 13 +++++++ {vulkan => vulkanbot}/music/Music.py | 9 +++-- vulkanbot/music/Playlist.py | 9 +++++ vulkanbot/music/Searcher.py | 43 ++++++++++++++++++++++ vulkanbot/music/Song.py | 12 ++++++ vulkanbot/music/Spotify.py | 22 +++++++++++ vulkanbot/music/Types.py | 21 +++++++++++ vulkanbot/music/Youtube.py | 8 ++++ 17 files changed, 140 insertions(+), 8 deletions(-) rename {vulkan => vulkanbot}/ErrorHandler.py (100%) rename {vulkan => vulkanbot}/commands/Phrases.py (94%) rename {vulkan => vulkanbot}/commands/Warframe.py (100%) create mode 100644 vulkanbot/commands/__init__.py rename {vulkan => vulkanbot}/general/Control.py (100%) rename {vulkan => vulkanbot}/general/Filter.py (100%) create mode 100644 vulkanbot/general/__init__.py create mode 100644 vulkanbot/music/Downloader.py rename {vulkan => vulkanbot}/music/Music.py (98%) create mode 100644 vulkanbot/music/Playlist.py create mode 100644 vulkanbot/music/Searcher.py create mode 100644 vulkanbot/music/Song.py create mode 100644 vulkanbot/music/Spotify.py create mode 100644 vulkanbot/music/Types.py create mode 100644 vulkanbot/music/Youtube.py diff --git a/config/config.py b/config/config.py index e531c56..98c3197 100644 --- a/config/config.py +++ b/config/config.py @@ -4,10 +4,11 @@ CETUS_API = dotenv_values('.env')['CETUS_API'] BOT_TOKEN = dotenv_values('.env')['BOT_TOKEN'] SPOTIFY_ID = dotenv_values('.env')['SPOTIFY_ID'] SPOTIFY_SECRET = dotenv_values('.env')['SPOTIFY_SECRET'] +SECRET_MESSAGE = dotenv_values('.env')['SECRET_MESSAGE'] BOT_PREFIX = '!' -INITIAL_EXTENSIONS = {'vulkan.commands.Phrases', 'vulkan.commands.Warframe', - 'vulkan.general.Filter', 'vulkan.general.Control', 'vulkan.music.Music'} +INITIAL_EXTENSIONS = {'vulkanbot.commands.Phrases', 'vulkanbot.commands.Warframe', + 'vulkanbot.general.Filter', 'vulkanbot.general.Control', 'vulkanbot.music.Music'} VC_TIMEOUT = 600 # seconds VC_TIMEOUT_DEFAULT = True diff --git a/main.py b/main.py index 7723ce8..608f10e 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ import discord from config import config from discord.ext import commands -from vulkan.ErrorHandler import ErrorHandler +from vulkanbot.ErrorHandler import ErrorHandler intents = discord.Intents.default() diff --git a/vulkan/ErrorHandler.py b/vulkanbot/ErrorHandler.py similarity index 100% rename from vulkan/ErrorHandler.py rename to vulkanbot/ErrorHandler.py diff --git a/vulkan/commands/Phrases.py b/vulkanbot/commands/Phrases.py similarity index 94% rename from vulkan/commands/Phrases.py rename to vulkanbot/commands/Phrases.py index b9513b9..71bd446 100644 --- a/vulkan/commands/Phrases.py +++ b/vulkanbot/commands/Phrases.py @@ -1,6 +1,7 @@ import requests import json import discord +from config import config from discord.ext import commands from random import random as rand @@ -47,9 +48,8 @@ class Phrases(commands.Cog): async def calculate_rgn(self, ctx): x = rand() - print(x) if x < 0.15: - await ctx.send('Se leu seu cu é meu\nBy: Minha Pica') + await ctx.send(config.SECRET_MESSAGE) return True else: return False diff --git a/vulkan/commands/Warframe.py b/vulkanbot/commands/Warframe.py similarity index 100% rename from vulkan/commands/Warframe.py rename to vulkanbot/commands/Warframe.py diff --git a/vulkanbot/commands/__init__.py b/vulkanbot/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vulkan/general/Control.py b/vulkanbot/general/Control.py similarity index 100% rename from vulkan/general/Control.py rename to vulkanbot/general/Control.py diff --git a/vulkan/general/Filter.py b/vulkanbot/general/Filter.py similarity index 100% rename from vulkan/general/Filter.py rename to vulkanbot/general/Filter.py diff --git a/vulkanbot/general/__init__.py b/vulkanbot/general/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/vulkanbot/music/Downloader.py b/vulkanbot/music/Downloader.py new file mode 100644 index 0000000..fd620d8 --- /dev/null +++ b/vulkanbot/music/Downloader.py @@ -0,0 +1,13 @@ +class Downloader(): + """Download music source from Youtube with a music name""" + + def __init__(self) -> None: + pass + + def download(self, track_name: str) -> str: + if type(track_name) != str: + return + + def download_many(self, track_list: list) -> list: + if type(track_list) != list: + return diff --git a/vulkan/music/Music.py b/vulkanbot/music/Music.py similarity index 98% rename from vulkan/music/Music.py rename to vulkanbot/music/Music.py index f80e03a..7a88173 100644 --- a/vulkan/music/Music.py +++ b/vulkanbot/music/Music.py @@ -1,10 +1,10 @@ import discord -from discord import colour -from discord.embeds import Embed from discord.ext import commands -from discord.ext.commands.core import command from youtube_dl import YoutubeDL +from vulkanbot.music.Downloader import Downloader +from vulkanbot.music.Searcher import Searcher + colours = { 'red': 0xDC143C, 'green': 0x00FF7F, @@ -15,6 +15,9 @@ colours = { class Music(commands.Cog): def __init__(self, client): + self.__searcher = Searcher() + self.__downloader = Downloader() + self.client = client self.is_playing = False self.repetingOne = False diff --git a/vulkanbot/music/Playlist.py b/vulkanbot/music/Playlist.py new file mode 100644 index 0000000..e3e68b9 --- /dev/null +++ b/vulkanbot/music/Playlist.py @@ -0,0 +1,9 @@ +class Playlist(): + """Class to manage and control the musics to play""" + + def __init__(self) -> None: + self.queue = [] + self.history = [] + + def next_music(): + pass diff --git a/vulkanbot/music/Searcher.py b/vulkanbot/music/Searcher.py new file mode 100644 index 0000000..9fb9a01 --- /dev/null +++ b/vulkanbot/music/Searcher.py @@ -0,0 +1,43 @@ +from vulkanbot.music.Types import Provider +from vulkanbot.music.Spotify import SpotifySearch +from vulkanbot.music.Youtube import YoutubeSearch + + +class Searcher(): + """Turn the user input into list of musics names, support youtube and spotify""" + + def __init__(self) -> None: + self.__Youtube = YoutubeSearch() + self.__Spotify = SpotifySearch() + print(f'Spotify Connected: {self.__Spotify.connect()}') + + def search(self, music: str) -> list: + """Return a list with the track name of a music or playlist""" + url_type = self.__identify_source(music) + + if url_type == Provider.Name: + return [music] + + elif url_type == Provider.YouTube: + musics = self.__Youtube.search(music) + return musics + + elif url_type == Provider.Spotify: + musics = self.__Spotify.search(music) + return musics + + def __identify_source(self, music): + if 'http' not in music: + return Provider.Name + + if "https://www.youtu" in music or "https://youtu.be" in music: + return Provider.YouTube + + if "https://open.spotify.com/track" in music: + return Provider.Spotify + + if "https://open.spotify.com/playlist" in music or "https://open.spotify.com/album" in music: + return Provider.Spotify_Playlist + + # If no match + return Provider.Unknown diff --git a/vulkanbot/music/Song.py b/vulkanbot/music/Song.py new file mode 100644 index 0000000..7287dca --- /dev/null +++ b/vulkanbot/music/Song.py @@ -0,0 +1,12 @@ +class Song(): + """Deal with information of a song""" + + def __init__(self, source) -> None: + self.__source = source + self.__get_info() + + def __get_info(self): + pass + + def info(): + """Return the compiled info of this song""" diff --git a/vulkanbot/music/Spotify.py b/vulkanbot/music/Spotify.py new file mode 100644 index 0000000..f9dda8a --- /dev/null +++ b/vulkanbot/music/Spotify.py @@ -0,0 +1,22 @@ +import spotipy +from spotipy.oauth2 import SpotifyClientCredentials +from config import config + + +class SpotifySearch(): + """Search and return musics names from Spotify""" + + def __init__(self) -> None: + pass + + def connect(self) -> bool: + try: + # Initialize the connection with Spotify API + self.__sp_api = spotipy.Spotify(auth_manager=SpotifyClientCredentials( + client_id=config.SPOTIFY_ID, client_secret=config.SPOTIFY_SECRET)) + return True + except: + return False + + def search(self, music) -> list: + pass diff --git a/vulkanbot/music/Types.py b/vulkanbot/music/Types.py new file mode 100644 index 0000000..c30a349 --- /dev/null +++ b/vulkanbot/music/Types.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class Provider(Enum): + """Store Enum Types of the Providers""" + Spotify = "Spotify" + Spotify_Playlist = "Spotify Playlist" + YouTube = "YouTube" + Name = 'Track Name' + Unknown = "Unknown" + + +class Playlist_Types(Enum): + Spotify_Playlist = "Spotify Playlist" + YouTube_Playlist = "YouTube Playlist" + Unknown = "Unknown" + + +class Origins(Enum): + Default = "Default" + Playlist = "Playlist" diff --git a/vulkanbot/music/Youtube.py b/vulkanbot/music/Youtube.py new file mode 100644 index 0000000..753b6d4 --- /dev/null +++ b/vulkanbot/music/Youtube.py @@ -0,0 +1,8 @@ +class YoutubeSearch(): + """Search for tracks in youtube""" + + def __init__(self) -> None: + pass + + def search(self, track): + pass From 93046da491dcc865843184476e8301ebde328098 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sat, 25 Dec 2021 01:10:57 -0400 Subject: [PATCH 02/23] Upgrading SpotifySearch class --- vulkanbot/music/Spotify.py | 153 ++++++++++++++++++++++++++++++++++++- 1 file changed, 149 insertions(+), 4 deletions(-) diff --git a/vulkanbot/music/Spotify.py b/vulkanbot/music/Spotify.py index f9dda8a..5d6a5f7 100644 --- a/vulkanbot/music/Spotify.py +++ b/vulkanbot/music/Spotify.py @@ -1,22 +1,167 @@ import spotipy +import re from spotipy.oauth2 import SpotifyClientCredentials +from bs4 import BeautifulSoup from config import config +import aiohttp + + +class Browser(): + def __init__(self) -> None: + self.__url_regex = re.compile( + "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+") + self.__session = aiohttp.ClientSession( + headers={'User-Agent': 'python-requests/2.20.0'}) + + async def search(self, url) -> str: + """Convert the external_url link to the title of music using browser""" + if re.search(self.__url_regex, url): + result = self.__url_regex.search(url) + url = result.group(0) + + async with self.__session.get(url) as response: + page = await response.text() + soup = BeautifulSoup(page, 'html.parser') + + title = soup.find('title') + title = title.string + title = title.replace('- song by', '') + title = title.replace('| Spotify', '') + return title class SpotifySearch(): - """Search and return musics names from Spotify""" + """Search a Spotify music or playlist and return the musics names""" def __init__(self) -> None: - pass + self.__connected = False + self.__browser = Browser() def connect(self) -> bool: try: # Initialize the connection with Spotify API - self.__sp_api = spotipy.Spotify(auth_manager=SpotifyClientCredentials( + self.__api = spotipy.Spotify(auth_manager=SpotifyClientCredentials( client_id=config.SPOTIFY_ID, client_secret=config.SPOTIFY_SECRET)) + self.__connected = True return True except: return False def search(self, music) -> list: - pass + """Search and return the title of musics on Spotify""" + type = music.split('/')[3].split('?')[0] + code = music.split('/')[4].split('?')[0] + if type == 'album': + musics = self.__get_album(code) + elif type == 'playlist': + musics = self.__get_playlist(code) + elif type == 'track': + musics = self.__get_track(code) + else: + return None + + return musics + + def __get_album(self, code) -> list: + """Get the externals urls of a album + + ARG: Spotify Code of the Album + """ + if self.__connected == True: + try: + # Load all music objects + results = self.__api.album_tracks(code) + musics = results['items'] + + while results['next']: # Get the next pages + results = self.__api.next(results) + musics.extend(results['items']) + + musicsTitle = [] + + for music in musics: + try: + title = self.__extract_title(music) + musicsTitle.append(title) + except: + pass + return musicsTitle + except: + if config.SPOTIFY_ID != "" or config.SPOTIFY_SECRET != "": + print("ERROR: Check spotify CLIENT_ID and SECRET") + + def __get_playlist(self, code) -> list: + """Get the externals urls of a playlist + + Arg: Spotify Code of the Playlist + """ + try: + results = self.__api.playlist_items(code) + itens = results['items'] + + while results['next']: # Load the next pages + results = self.__api.next(results) + itens.extend(results['items']) + + musics = [] + for item in itens: + musics.append(item['track']) + + titles = [] + for music in musics: + try: + title = self.__extract_title(music) + titles.append(title) + except Exception as e: + raise e + return titles + + except Exception as e: + if config.SPOTIFY_ID != "" or config.SPOTIFY_SECRET != "": + print("ERROR: Check spotify CLIENT_ID and SECRET") + else: + raise e + + def __get_track(self, code) -> list: + """Convert a external_url track to the title of the music + + ARG: Spotify Code of the Music + """ + results = self.__api.track(code) + name = results['name'] + artists = '' + for artist in results['artists']: + artists += f'{artist["name"]} ' + + return [f'{name} {artists}'] + + def __extract_title(self, music: dict) -> str: + """Receive a spotify music object and return his title + + ARG: music dict returned by Spotify + """ + title = f'{music["name"]} ' + for artist in music['artists']: + title += f'{artist["name"]} ' + + return title + + async def __convert_spotify(self, url) -> str: + """(Experimental) - Convert the external_url link to the title of music using browser""" + title = self.__browser(url) + return title + + +if __name__ == '__main__': + spSearch = SpotifySearch() + spSearch.connect() + + lista = spSearch.search( + 'https://open.spotify.com/track/7wpnz7hje4FbnjZuWQtJHP') + lista2 = spSearch.search( + 'https://open.spotify.com/album/6wEkHIUHNb1kZiV2nCnVoh') + lista3 = spSearch.search( + 'https://open.spotify.com/playlist/5DOtjJ6rSMddAKyStv7Yc7?si=v893Wv0VSyuh0PokQvC8-g&utm_source=whatsapp') + print(lista3) + print(lista2) + print(lista) From 7775f259afeb55bd0e47b71320151f992ea82cec Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sat, 25 Dec 2021 01:38:39 -0400 Subject: [PATCH 03/23] Integrating SpotifySearcher and Searcher --- vulkanbot/music/Searcher.py | 5 +---- vulkanbot/music/Spotify.py | 26 ++++---------------------- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/vulkanbot/music/Searcher.py b/vulkanbot/music/Searcher.py index 9fb9a01..3bde535 100644 --- a/vulkanbot/music/Searcher.py +++ b/vulkanbot/music/Searcher.py @@ -33,11 +33,8 @@ class Searcher(): if "https://www.youtu" in music or "https://youtu.be" in music: return Provider.YouTube - if "https://open.spotify.com/track" in music: + if "https://open.spotify.com" in music: return Provider.Spotify - if "https://open.spotify.com/playlist" in music or "https://open.spotify.com/album" in music: - return Provider.Spotify_Playlist - # If no match return Provider.Unknown diff --git a/vulkanbot/music/Spotify.py b/vulkanbot/music/Spotify.py index 5d6a5f7..b346180 100644 --- a/vulkanbot/music/Spotify.py +++ b/vulkanbot/music/Spotify.py @@ -86,9 +86,8 @@ class SpotifySearch(): except: pass return musicsTitle - except: - if config.SPOTIFY_ID != "" or config.SPOTIFY_SECRET != "": - print("ERROR: Check spotify CLIENT_ID and SECRET") + except Exception as e: + raise e def __get_playlist(self, code) -> list: """Get the externals urls of a playlist @@ -114,13 +113,11 @@ class SpotifySearch(): titles.append(title) except Exception as e: raise e + return titles except Exception as e: - if config.SPOTIFY_ID != "" or config.SPOTIFY_SECRET != "": - print("ERROR: Check spotify CLIENT_ID and SECRET") - else: - raise e + raise e def __get_track(self, code) -> list: """Convert a external_url track to the title of the music @@ -150,18 +147,3 @@ class SpotifySearch(): """(Experimental) - Convert the external_url link to the title of music using browser""" title = self.__browser(url) return title - - -if __name__ == '__main__': - spSearch = SpotifySearch() - spSearch.connect() - - lista = spSearch.search( - 'https://open.spotify.com/track/7wpnz7hje4FbnjZuWQtJHP') - lista2 = spSearch.search( - 'https://open.spotify.com/album/6wEkHIUHNb1kZiV2nCnVoh') - lista3 = spSearch.search( - 'https://open.spotify.com/playlist/5DOtjJ6rSMddAKyStv7Yc7?si=v893Wv0VSyuh0PokQvC8-g&utm_source=whatsapp') - print(lista3) - print(lista2) - print(lista) From 35c2f2b16e9cfb923bbf35dda1c2d209069a12c7 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sat, 25 Dec 2021 07:17:48 -0400 Subject: [PATCH 04/23] Adding the Downloader class --- config/config.py | 1 + vulkanbot/music/Downloader.py | 101 ++++++++++++++++++++++++++++++++-- vulkanbot/music/Searcher.py | 23 ++++++-- vulkanbot/music/Youtube.py | 8 --- 4 files changed, 114 insertions(+), 19 deletions(-) delete mode 100644 vulkanbot/music/Youtube.py diff --git a/config/config.py b/config/config.py index 98c3197..ddf7fb8 100644 --- a/config/config.py +++ b/config/config.py @@ -23,6 +23,7 @@ CHANNEL_NOT_FOUND_MESSAGE = "Error: Could not find channel" INFO_HISTORY_TITLE = "Songs Played:" MAX_HISTORY_LENGTH = 10 +MAX_PLAYLIST_LENGTH = 50 MAX_TRACKNAME_HISTORY_LENGTH = 15 SONGINFO_UPLOADER = "Uploader: " diff --git a/vulkanbot/music/Downloader.py b/vulkanbot/music/Downloader.py index fd620d8..16dadcb 100644 --- a/vulkanbot/music/Downloader.py +++ b/vulkanbot/music/Downloader.py @@ -1,13 +1,102 @@ +import re +from config import config +from yt_dlp import YoutubeDL +from yt_dlp.utils import ExtractorError, DownloadError + + class Downloader(): - """Download music source from Youtube with a music name""" + """Download music source from Youtube with a music name or Youtube URL""" def __init__(self) -> None: - pass + self.__YDL_OPTIONS = {'format': 'bestaudio/best', + 'default_search': 'auto', + 'playliststart': 0, + 'extract_flat': True, + 'playlistend': config.MAX_PLAYLIST_LENGTH, + 'cookiefile': config.COOKIE_PATH + } - def download(self, track_name: str) -> str: - if type(track_name) != str: + def download_one(self, music: str) -> list: + """Download one music link from Youtube + + Arg: Music url or music name to search + Return: List with the Youtube URL of the music + """ + if type(music) != str: return - def download_many(self, track_list: list) -> list: - if type(track_list) != list: + if self.__is_url(music): # If Url + info = self.__download_url(music, flat=True) + else: # If Title + info = self.__download_title(music) + + return info + + def download_many(self, music_list: list) -> list: + """Download many music links from Youtube + + Arg: List with names or music url to search + Return: List with the youtube URL of each music + """ + if type(music_list) != list: return + + musics_info = [] + for music in music_list: + info = self.download_one(music) + musics_info.extend(info) + + return musics_info + + def download_full(self, link) -> dict: + """Download the full music info with the video URL""" + info = self.__download_url(url=link, flat=False) + return info[0] + + def __download_title(self, music_name: str) -> list: + """Download and return a list with the music link in dict""" + with YoutubeDL(self.__YDL_OPTIONS) as ydl: + try: + result = ydl.extract_info( + f"ytsearch:{music_name}", download=False) + id = result['entries'][0]['id'] + + link = f"https://www.youtube.com/watch?v={id}" + return [link] + except Exception as e: + raise e + + def __download_url(self, url: str, flat=True) -> list: + """Download musics from Playlist URL or Music URL + + Arg: URL from Youtube + Return: List of youtube links + """ + options = self.__YDL_OPTIONS + options['extract_flat'] = flat + with YoutubeDL(options) as ydl: + try: + result = ydl.extract_info(url, download=False) + + musics_link = [] + + if result.get('entries'): # If got a dict of musics + for entry in result['entries']: + link = f"https://www.youtube.com/watch?v={entry['id']}" + musics_link.append(link) + else: # Or a single music + musics_link.append(result['original_url']) + + return musics_link + except ExtractorError or DownloadError: + pass + + def __is_url(self, string) -> bool: + """Verify if a string is a url""" + regex = re.compile( + "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+") + + if re.search(regex, string): + return True + else: + return False diff --git a/vulkanbot/music/Searcher.py b/vulkanbot/music/Searcher.py index 3bde535..c1f9a32 100644 --- a/vulkanbot/music/Searcher.py +++ b/vulkanbot/music/Searcher.py @@ -1,18 +1,20 @@ +import re from vulkanbot.music.Types import Provider from vulkanbot.music.Spotify import SpotifySearch -from vulkanbot.music.Youtube import YoutubeSearch class Searcher(): """Turn the user input into list of musics names, support youtube and spotify""" def __init__(self) -> None: - self.__Youtube = YoutubeSearch() self.__Spotify = SpotifySearch() print(f'Spotify Connected: {self.__Spotify.connect()}') def search(self, music: str) -> list: - """Return a list with the track name of a music or playlist""" + """Return a list with the track name of a music or playlist + + Return -> A list of musics names + """ url_type = self.__identify_source(music) if url_type == Provider.Name: @@ -26,8 +28,9 @@ class Searcher(): musics = self.__Spotify.search(music) return musics - def __identify_source(self, music): - if 'http' not in music: + def __identify_source(self, music) -> Provider: + """Identify the provider of a music""" + if not self.__is_url(music): return Provider.Name if "https://www.youtu" in music or "https://youtu.be" in music: @@ -38,3 +41,13 @@ class Searcher(): # If no match return Provider.Unknown + + def __is_url(self, string) -> bool: + """Verify if a string is a url""" + regex = re.compile( + "http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+") + + if re.search(regex, string): + return True + else: + return False diff --git a/vulkanbot/music/Youtube.py b/vulkanbot/music/Youtube.py deleted file mode 100644 index 753b6d4..0000000 --- a/vulkanbot/music/Youtube.py +++ /dev/null @@ -1,8 +0,0 @@ -class YoutubeSearch(): - """Search for tracks in youtube""" - - def __init__(self) -> None: - pass - - def search(self, track): - pass From 6bb8ab07a8a5fa9314d93ae4f34c6b687be2ac10 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sun, 26 Dec 2021 14:25:50 -0400 Subject: [PATCH 05/23] Upgrading the Downloader to be more stable --- vulkanbot/music/Downloader.py | 89 +++++++++++++++++++++-------------- 1 file changed, 53 insertions(+), 36 deletions(-) diff --git a/vulkanbot/music/Downloader.py b/vulkanbot/music/Downloader.py index 16dadcb..30ac8c9 100644 --- a/vulkanbot/music/Downloader.py +++ b/vulkanbot/music/Downloader.py @@ -5,7 +5,7 @@ from yt_dlp.utils import ExtractorError, DownloadError class Downloader(): - """Download music source from Youtube with a music name or Youtube URL""" + """Download musics direct URL or Source from Youtube using a music name or Youtube URL""" def __init__(self) -> None: self.__YDL_OPTIONS = {'format': 'bestaudio/best', @@ -13,52 +13,69 @@ class Downloader(): 'playliststart': 0, 'extract_flat': True, 'playlistend': config.MAX_PLAYLIST_LENGTH, - 'cookiefile': config.COOKIE_PATH } - def download_one(self, music: str) -> list: - """Download one music link from Youtube + def download_urls(self, musics_input) -> list: + """Download the musics direct URL from Youtube and return in a list - Arg: Music url or music name to search - Return: List with the Youtube URL of the music + Arg: List with names or youtube url or a Unique String + Return: List with the direct youtube URL of each music + """ + if type(musics_input) != list and type(musics_input) != str: + return + + if type(musics_input) == str: # Turn the string in a list + musics_input = [musics_input] + + musics_urls = [] + for music in musics_input: + url = self.__download_one(music) + musics_urls.extend(url) + + return musics_urls + + def download_source(self, url) -> dict: + """Download musics full info and source from Music URL + + Arg: URL from Youtube + Return: Dict with the full youtube information of the music, including source to play it + """ + options = self.__YDL_OPTIONS + options['extract_flat'] = False + with YoutubeDL(options) as ydl: + try: + result = ydl.extract_info(url, download=False) + + return result + except ExtractorError or DownloadError: + pass + + def __download_one(self, music: str) -> list: + """Download one music/playlist direct link from Youtube + + Arg: Playlist URL or Music Name to download direct URL + Return: List with the Youtube URL of each music downloaded """ if type(music) != str: return if self.__is_url(music): # If Url - info = self.__download_url(music, flat=True) + info = self.__download_links(music) else: # If Title info = self.__download_title(music) return info - def download_many(self, music_list: list) -> list: - """Download many music links from Youtube - - Arg: List with names or music url to search - Return: List with the youtube URL of each music - """ - if type(music_list) != list: - return - - musics_info = [] - for music in music_list: - info = self.download_one(music) - musics_info.extend(info) - - return musics_info - - def download_full(self, link) -> dict: - """Download the full music info with the video URL""" - info = self.__download_url(url=link, flat=False) - return info[0] - def __download_title(self, music_name: str) -> list: - """Download and return a list with the music link in dict""" + """Download a music direct URL using his name. + + Arg: Music Name + Return: List with one item, the music direct URL + """ with YoutubeDL(self.__YDL_OPTIONS) as ydl: try: - result = ydl.extract_info( - f"ytsearch:{music_name}", download=False) + search = f"ytsearch:{music_name}" + result = ydl.extract_info(search, download=False) id = result['entries'][0]['id'] link = f"https://www.youtube.com/watch?v={id}" @@ -66,14 +83,14 @@ class Downloader(): except Exception as e: raise e - def __download_url(self, url: str, flat=True) -> list: - """Download musics from Playlist URL or Music URL + def __download_links(self, url: str) -> list: + """Download musics direct links from Playlist URL or Music URL - Arg: URL from Youtube - Return: List of youtube links + Arg_Url: URL from Youtube + Return: List of youtube information of each music """ options = self.__YDL_OPTIONS - options['extract_flat'] = flat + options['extract_flat'] = True with YoutubeDL(options) as ydl: try: result = ydl.extract_info(url, download=False) From 8c83b68814726153457dc7aa25e5ec838fbaa6a7 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sun, 26 Dec 2021 14:26:22 -0400 Subject: [PATCH 06/23] First upgrade in Song class --- vulkanbot/music/Song.py | 70 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 63 insertions(+), 7 deletions(-) diff --git a/vulkanbot/music/Song.py b/vulkanbot/music/Song.py index 7287dca..4cabce5 100644 --- a/vulkanbot/music/Song.py +++ b/vulkanbot/music/Song.py @@ -1,12 +1,68 @@ +import discord +import datetime +from config import config + + class Song(): - """Deal with information of a song""" + """Store the usefull information about a Song""" - def __init__(self, source) -> None: - self.__source = source - self.__get_info() + def __init__(self, info: dict) -> None: + if type(info) != dict: + return - def __get_info(self): - pass + self.__usefull_keys = ['url', 'title', 'duration', + 'description', 'webpage_url', + 'channel', 'id', 'uploader', + 'thumbnail'] + self.__extract_info(info) - def info(): + def __extract_info(self, info): + """Extract the usefull information returned by the Downloader""" + self.__info = {} + for key in self.__usefull_keys: + try: + self.__info[key] = info[key] + except Exception as e: + print(e) + raise e + + @property + def info(self): """Return the compiled info of this song""" + return self.__info + + @property + def title(self): + return self.__info['title'] + + @property + def source(self): + """Return the Song Source URL to play""" + return self.__info['url'] + + def embed(self): + """Configure and return the embed to show this song in discord chat""" + embed = discord.Embed(title='Music Playing', + description=f"[{self.__info['title']}]({self.__info['webpage_url']})", + color=config.COLOURS['blue']) + + if self.thumbnail is not None: + embed.set_thumbnail(url=self.thumbnail) + + embed.add_field(name=config.SONGINFO_UPLOADER, + value=self.__info['uploader'], + inline=False) + + if self.duration is not None: + duration = str(datetime.timedelta(seconds=self.__info['duration'])) + + embed.add_field(name=config.SONGINFO_DURATION, + value=f"{duration}", + inline=False) + + else: + embed.add_field(name=config.SONGINFO_DURATION, + value=config.SONGINFO_UNKNOWN_DURATION, + inline=False) + + return embed From 098092dcc3e14254fb545d990a1fbd850281687a Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Sun, 26 Dec 2021 14:26:39 -0400 Subject: [PATCH 07/23] First upgrade in Playlist class --- vulkanbot/music/Playlist.py | 110 ++++++++++++++++++++++++++++++++++-- 1 file changed, 105 insertions(+), 5 deletions(-) diff --git a/vulkanbot/music/Playlist.py b/vulkanbot/music/Playlist.py index e3e68b9..d749c08 100644 --- a/vulkanbot/music/Playlist.py +++ b/vulkanbot/music/Playlist.py @@ -1,9 +1,109 @@ +from collections import deque +import random +from Song import Song + + class Playlist(): - """Class to manage and control the musics to play""" + """Class to manage and control the songs to play and played""" def __init__(self) -> None: - self.queue = [] - self.history = [] + self.__queue = deque() # Store the musics to play + self.__songs_history = deque() # Store the musics played + self.__name_history = deque() # Store the name of musics played - def next_music(): - pass + self.__loop_one = False + self.__loop_all = False + + self.__current = None + + def __len__(self): + return len(self.queue) + + def next_song(self): + """Return the source of the next song to play""" + if self.__current == None: + print('Nenhuma música tocando') + return None + + if self.__loop_one: # Insert the current song to play again + self.__queue.appendleft(self.__current) + + if self.__loop_all: # Insert the current song in the end of queue + self.__queue.append(self.__current) + + if len(self.__queue) == 0: # If nothing no more song to play, return + return None + + played_song = self.__current + # Add played song name to history + self.__name_history.append(played_song.title) + return self.__queue[0] # Return the next song to play + + def prev_song(self): + """Return the source of the last song played + + Return None or the source of the prev song + """ + if len(self.__songs_history) == 0: + return None + else: + return self.__songs_history[0].source + + def add_song(self, song: Song) -> None: + """Receives a song object and store to the play queue""" + if type(song) != Song: + print('Song type invalid') + return + + self.__queue.append(song) + + def shuffle(self): + """Shuffle the order of the songs to play""" + random.shuffle(self.__queue) + + def revert(self): + """Revert the order of the songs to play""" + self.__queue.reverse() + + def clear(self) -> None: + """Clear the songs to play song history""" + self.__queue.clear() + self.__songs_history.clear() + + def play(self): + """Start the play of the first musics and return his source""" + if len(self.__queue) == 0: + print('Nenhuma música para tocar') + return None + + self.__current = self.__queue[0] + return self.__queue[0].source + + def loop_one(self) -> str: + """Try to start the loop of the current song + + Return: Embed descrition to show to user + """ + if self.__loop_all == True: + return 'Vulkan already looping one music, disable loop first' + elif self.__loop_one == True: + return "I'm already doing this, you dumb ass" + else: + self.__loop_one = True + + def loop_all(self) -> str: + """Try to start the loop of all songs + + Return: Embed descrition to show to user + """ + if self.__loop_one == True: + return 'Vulkan already looping one music, disable loop first' + elif self.__loop_all == True: + return "I'm already doing this, you dumb ass" + else: + self.__loop_all = True + + def loop_off(self) -> None: + """Disable both types of loop""" + self.__loop_all = False + self.__loop_one = False From 45321f48487d4e0a685989d10d7609958fe6b9c5 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 17:14:03 -0400 Subject: [PATCH 08/23] Changing the print of prefix --- vulkanbot/general/Control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vulkanbot/general/Control.py b/vulkanbot/general/Control.py index 12849cf..3afb29a 100644 --- a/vulkanbot/general/Control.py +++ b/vulkanbot/general/Control.py @@ -27,7 +27,7 @@ class Control(commands.Cog): @commands.Cog.listener() async def on_command_error(self, ctx, error): if isinstance(error, MissingRequiredArgument): - await ctx.channel.send(f'Falta argumentos. Digite {self.__bot.prefix}help para ver os comandos') + await ctx.channel.send(f'Falta argumentos. Digite {config.BOT_PREFIX}help para ver os comandos') elif isinstance(error, CommandNotFound): await ctx.channel.send(f'O comando não existe') else: From 73e19b60d457aa960107da21793c4de04654a68f Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:10:41 -0400 Subject: [PATCH 09/23] Few changes in Phrases module --- vulkanbot/commands/Phrases.py | 47 +++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/vulkanbot/commands/Phrases.py b/vulkanbot/commands/Phrases.py index 71bd446..fef82f6 100644 --- a/vulkanbot/commands/Phrases.py +++ b/vulkanbot/commands/Phrases.py @@ -20,39 +20,42 @@ class Phrases(commands.Cog): def bot(self, newBot): self.__bot = newBot - @commands.command(name='frase', help='Envia uma frase legal no seu PV') - async def send_phrase(self, ctx): + @commands.command(name='frase', help='Envia uma frase pica, talvez a braba') + async def phrase(self, ctx): # There is a chance that the phrase will be send for the dev - sended = await self.calculate_rgn(ctx) - if sended: - return + secret = await self.__calculate_rgn() + if secret != None: + await ctx.send(secret) + else: + phrase = await self.__get_phrase() + await ctx.send(phrase) + async def __calculate_rgn(self): + x = rand() + if x < 0.15: + return config.SECRET_MESSAGE + else: + return None + + async def __get_phrase(self): + tries = 0 while True: + tries += 1 + if tries > config.MAX_API_PHRASES_TRIES: + return 'O banco de dados dos cara tá off, bando de vagabundo, tenta depois aí bicho' + try: - response = requests.get( - 'http://api.forismatic.com/api/1.0/?method=getQuote&key=457653&format=json&lang=en') + response = requests.get(config.PHRASES_API) data = json.loads(response.content) phrase = data['quoteText'] author = data['quoteAuthor'] text = f'{phrase} \nBy: {author}' - await ctx.send(text) - break - except json.decoder.JSONDecodeError: - continue - except Exception as e: - print(e) - await ctx.channel.send('Houve um erro inesperado :/') - break - async def calculate_rgn(self, ctx): - x = rand() - if x < 0.15: - await ctx.send(config.SECRET_MESSAGE) - return True - else: - return False + return text + except Exception as e: + continue def setup(bot): From 559419dc19659e9d9a8b645e1ea410fd55feed39 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:11:10 -0400 Subject: [PATCH 10/23] Relocating help command to Control class --- vulkanbot/general/Control.py | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/vulkanbot/general/Control.py b/vulkanbot/general/Control.py index 3afb29a..6671fc2 100644 --- a/vulkanbot/general/Control.py +++ b/vulkanbot/general/Control.py @@ -9,6 +9,11 @@ class Control(commands.Cog): def __init__(self, bot): self.__bot = bot + self.__comandos = { + 'MUSIC': ['this', 'resume', 'pause', 'loop', 'stop', 'skip', 'play', 'queue', 'clear'], + 'RANDOM': ['cetus', ' frase'], + 'HELP': ['help'] + } @property def bot(self): @@ -33,6 +38,35 @@ class Control(commands.Cog): else: raise error + @commands.command(name="help", alisases=['ajuda'], help="Comando de ajuda") + async def help(self, ctx): + helptxt = '' + help_music = '-- MUSIC\n' + help_random = '-- RANDOM\n' + help_help = '-- HELP\n' + + for command in self.__bot.commands: + if command.name in self.__comandos['MUSIC']: + help_music += f'**{command}** - {command.help}\n' + elif command.name in self.__comandos['HELP']: + help_help += f'**{command}** - {command.help}\n' + else: + help_random += f'**{command}** - {command.help}\n' + helptxt = f'{help_music}\n{help_random}\n{help_help}' + + embedhelp = discord.Embed( + colour=config.COLOURS['grey'], + title=f'Comandos do {self.__bot.user.name}', + description=helptxt + ) + + embedhelp.set_thumbnail(url=self.__bot.user.avatar_url) + await ctx.send(embed=embedhelp) + + @commands.Cog.listener() + async def on_error(self, error): + print('On Error') + def setup(bot): bot.add_cog(Control(bot)) From 9602666bc837d5015317b5956811222629677be9 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:11:31 -0400 Subject: [PATCH 11/23] Adding new variables to config file --- config/config.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/config.py b/config/config.py index ddf7fb8..2518a2a 100644 --- a/config/config.py +++ b/config/config.py @@ -5,6 +5,7 @@ BOT_TOKEN = dotenv_values('.env')['BOT_TOKEN'] SPOTIFY_ID = dotenv_values('.env')['SPOTIFY_ID'] SPOTIFY_SECRET = dotenv_values('.env')['SPOTIFY_SECRET'] SECRET_MESSAGE = dotenv_values('.env')['SECRET_MESSAGE'] +PHRASES_API = dotenv_values('.env')['PHRASES_API'] BOT_PREFIX = '!' INITIAL_EXTENSIONS = {'vulkanbot.commands.Phrases', 'vulkanbot.commands.Warframe', @@ -24,7 +25,9 @@ CHANNEL_NOT_FOUND_MESSAGE = "Error: Could not find channel" INFO_HISTORY_TITLE = "Songs Played:" MAX_HISTORY_LENGTH = 10 MAX_PLAYLIST_LENGTH = 50 +MAX_QUEUE_LENGTH = 10 MAX_TRACKNAME_HISTORY_LENGTH = 15 +MAX_API_PHRASES_TRIES = 10 SONGINFO_UPLOADER = "Uploader: " SONGINFO_DURATION = "Duration: " From c07f01d51a169ac3fa2deaccc6c119c3c422dba8 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:13:16 -0400 Subject: [PATCH 12/23] Chaging the downloader logic to work, keeping the same objective --- vulkanbot/music/Downloader.py | 107 ++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 30 deletions(-) diff --git a/vulkanbot/music/Downloader.py b/vulkanbot/music/Downloader.py index 30ac8c9..f5b3f7a 100644 --- a/vulkanbot/music/Downloader.py +++ b/vulkanbot/music/Downloader.py @@ -3,9 +3,11 @@ from config import config from yt_dlp import YoutubeDL from yt_dlp.utils import ExtractorError, DownloadError +from vulkanbot.music.Types import Provider + class Downloader(): - """Download musics direct URL or Source from Youtube using a music name or Youtube URL""" + """Download musics direct URL and title or Source from Youtube using a music name or Youtube URL""" def __init__(self) -> None: self.__YDL_OPTIONS = {'format': 'bestaudio/best', @@ -15,24 +17,30 @@ class Downloader(): 'playlistend': config.MAX_PLAYLIST_LENGTH, } - def download_urls(self, musics_input) -> list: + def download_urls(self, musics_input, provider: Provider) -> list: """Download the musics direct URL from Youtube and return in a list Arg: List with names or youtube url or a Unique String Return: List with the direct youtube URL of each music """ + if type(provider) != Provider: + return None + if type(musics_input) != list and type(musics_input) != str: - return + return None - if type(musics_input) == str: # Turn the string in a list - musics_input = [musics_input] + if provider == Provider.Name: # Send a list of names + musics_urls = self.__download_titles(musics_input) + print(musics_urls) + return musics_urls - musics_urls = [] - for music in musics_input: - url = self.__download_one(music) - musics_urls.extend(url) - - return musics_urls + elif provider == Provider.YouTube: # Send a URL or Title + print(musics_input) + url = self.__download_one(musics_input) + return url + else: + print('Erro no download') + return None def download_source(self, url) -> dict: """Download musics full info and source from Music URL @@ -46,9 +54,12 @@ class Downloader(): try: result = ydl.extract_info(url, download=False) + print('Resultado: ') + print(len(result)) return result - except ExtractorError or DownloadError: - pass + except (ExtractorError, DownloadError) as e: # Any type of error in download + print(e) + return None def __download_one(self, music: str) -> list: """Download one music/playlist direct link from Youtube @@ -60,26 +71,36 @@ class Downloader(): return if self.__is_url(music): # If Url - info = self.__download_links(music) + info = self.__download_links(music) # List of dict else: # If Title - info = self.__download_title(music) + info = self.__download_titles(music) # List of dict return info - def __download_title(self, music_name: str) -> list: + def __download_titles(self, musics_names: list) -> list: """Download a music direct URL using his name. Arg: Music Name - Return: List with one item, the music direct URL + Return: List with one dict, containing the music direct URL and title """ + if type(musics_names) == str: # Turn str into list + musics_names = [musics_names] + + musics_info = [] with YoutubeDL(self.__YDL_OPTIONS) as ydl: try: - search = f"ytsearch:{music_name}" - result = ydl.extract_info(search, download=False) - id = result['entries'][0]['id'] + for name in musics_names: + search = f"ytsearch:{name}" + result = ydl.extract_info(search, download=False) - link = f"https://www.youtube.com/watch?v={id}" - return [link] + id = result['entries'][0]['id'] + music_info = { + 'url': f"https://www.youtube.com/watch?v={id}", + 'title': result['entries'][0]['title'] + } + musics_info.append(music_info) + + return musics_info # Return a list except Exception as e: raise e @@ -87,24 +108,31 @@ class Downloader(): """Download musics direct links from Playlist URL or Music URL Arg_Url: URL from Youtube - Return: List of youtube information of each music + Return: List of dicts, with the title and url of each music """ options = self.__YDL_OPTIONS options['extract_flat'] = True with YoutubeDL(options) as ydl: try: result = ydl.extract_info(url, download=False) - - musics_link = [] + musics_info = [] if result.get('entries'): # If got a dict of musics for entry in result['entries']: - link = f"https://www.youtube.com/watch?v={entry['id']}" - musics_link.append(link) - else: # Or a single music - musics_link.append(result['original_url']) + music_info = { + 'title': entry['title'], + 'url': f"https://www.youtube.com/watch?v={entry['id']}" + } - return musics_link + musics_info.append(music_info) + else: # Or a single music + music_info = { + 'url': result['original_url'], + 'title': result['title'] + } + musics_info.append(music_info) + + return musics_info # Return a list except ExtractorError or DownloadError: pass @@ -117,3 +145,22 @@ class Downloader(): return True else: return False + + +""" +# url = 'https://open.spotify.com/playlist/64wCcaIp6DxNmMaDM8X0W3' +# url = 'https://open.spotify.com/album/6wEkHIUHNb1kZiV2nCnVoh' +# url = 'https://open.spotify.com/track/7wpnz7hje4FbnjZuWQtJHP' +# url = 'https://www.youtube.com/watch?v=FLQtLH33ZHM&ab_channel=Acoustic%26CoverTv' +# url = 'https://www.youtube.com/playlist?list=PLbbKJHHZR9ShYuKAr71cLJCFbYE-83vhS' +# url = 'https://www.youtube.com/watch?v=pFoc5XKkIIw&list=RDpFoc5XKkIIw&start_radio=1&ab_channel=heldenhaftig' # Custom +url = 'Bury The Light' +search = Searcher() +musicas, provider = search.search(url) +print(musicas, provider) + +down = Downloader() +result = down.download_urls(musicas, provider) +#result = down.download_urls('Cheiro de tame impala', provider) +print(result) + """ From eb19a95f84cef8cc22959cd0aa74fa6d316bb271 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:14:06 -0400 Subject: [PATCH 13/23] Removing useless if --- vulkanbot/music/Searcher.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/vulkanbot/music/Searcher.py b/vulkanbot/music/Searcher.py index c1f9a32..93795d7 100644 --- a/vulkanbot/music/Searcher.py +++ b/vulkanbot/music/Searcher.py @@ -17,16 +17,15 @@ class Searcher(): """ url_type = self.__identify_source(music) - if url_type == Provider.Name: - return [music] - - elif url_type == Provider.YouTube: - musics = self.__Youtube.search(music) - return musics + if url_type == Provider.YouTube: + return [music], Provider.YouTube elif url_type == Provider.Spotify: musics = self.__Spotify.search(music) - return musics + return musics, Provider.Name + + elif url_type == Provider.Name: + return [music], Provider.Name def __identify_source(self, music) -> Provider: """Identify the provider of a music""" From 145beacce81f02e5c49602e3f7528c7bde203c7f Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:15:05 -0400 Subject: [PATCH 14/23] Changing the logic of a Song creation, there will be two steps --- vulkanbot/music/Song.py | 78 +++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/vulkanbot/music/Song.py b/vulkanbot/music/Song.py index 4cabce5..355c69a 100644 --- a/vulkanbot/music/Song.py +++ b/vulkanbot/music/Song.py @@ -1,24 +1,25 @@ -import discord -import datetime -from config import config +from discord.embeds import Embed class Song(): """Store the usefull information about a Song""" - def __init__(self, info: dict) -> None: - if type(info) != dict: - return + def __init__(self, url: str, title: str) -> None: + """Create a song with only the URL to the youtube song""" + self.__url = url + self.__title = title + self.__info = {} - self.__usefull_keys = ['url', 'title', 'duration', + def finish_down(self, info: dict) -> None: + """Get and store the full information of the song""" + self.__usefull_keys = ['url', 'duration', 'description', 'webpage_url', 'channel', 'id', 'uploader', 'thumbnail'] self.__extract_info(info) - def __extract_info(self, info): + def __extract_info(self, info) -> None: """Extract the usefull information returned by the Downloader""" - self.__info = {} for key in self.__usefull_keys: try: self.__info[key] = info[key] @@ -26,43 +27,38 @@ class Song(): print(e) raise e + def embed(self) -> Embed: + """Configure and return the info to create the embed for this song""" + info = { + 'title': self.__title, + 'url': self.__url, + 'uploader': self.__info['uploader'] + } + + if 'thumbnail' in self.__info.keys(): + info['thumbnail'] = self.__info['thumbnail'] + + if 'duration' in self.__info.keys(): + info['duration'] = self.__info['duration'] + + return info + @property - def info(self): + def info(self) -> dict: """Return the compiled info of this song""" - return self.__info + if self.__info: + return self.__info @property - def title(self): - return self.__info['title'] + def title(self) -> str: + return self.__title @property - def source(self): + def source(self) -> str: """Return the Song Source URL to play""" - return self.__info['url'] + if 'url' in self.__info.keys(): + return self.__info['url'] - def embed(self): - """Configure and return the embed to show this song in discord chat""" - embed = discord.Embed(title='Music Playing', - description=f"[{self.__info['title']}]({self.__info['webpage_url']})", - color=config.COLOURS['blue']) - - if self.thumbnail is not None: - embed.set_thumbnail(url=self.thumbnail) - - embed.add_field(name=config.SONGINFO_UPLOADER, - value=self.__info['uploader'], - inline=False) - - if self.duration is not None: - duration = str(datetime.timedelta(seconds=self.__info['duration'])) - - embed.add_field(name=config.SONGINFO_DURATION, - value=f"{duration}", - inline=False) - - else: - embed.add_field(name=config.SONGINFO_DURATION, - value=config.SONGINFO_UNKNOWN_DURATION, - inline=False) - - return embed + @property + def url(self) -> str: + return self.__url From 23c8d99d33df88349ef3c086ae367c4a04c484a7 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:15:46 -0400 Subject: [PATCH 15/23] Adding new functions to Playlist module --- vulkanbot/music/Playlist.py | 156 ++++++++++++++++++++++++++---------- 1 file changed, 113 insertions(+), 43 deletions(-) diff --git a/vulkanbot/music/Playlist.py b/vulkanbot/music/Playlist.py index d749c08..b541ad4 100644 --- a/vulkanbot/music/Playlist.py +++ b/vulkanbot/music/Playlist.py @@ -1,43 +1,108 @@ from collections import deque import random -from Song import Song +from vulkanbot.music.Song import Song +from vulkanbot.music.Downloader import Downloader class Playlist(): """Class to manage and control the songs to play and played""" def __init__(self) -> None: + self.__down = Downloader() self.__queue = deque() # Store the musics to play self.__songs_history = deque() # Store the musics played self.__name_history = deque() # Store the name of musics played - self.__loop_one = False - self.__loop_all = False + self.__looping_one = False + self.__looping_all = False self.__current = None + @property + def looping_one(self): + return self.__looping_one + + @property + def looping_all(self): + return self.__looping_all + def __len__(self): - return len(self.queue) + if self.__looping_one == True or self.__looping_all == True: + return 1 + else: + return len(self.__queue) def next_song(self): """Return the source of the next song to play""" - if self.__current == None: - print('Nenhuma música tocando') - return None - - if self.__loop_one: # Insert the current song to play again - self.__queue.appendleft(self.__current) - - if self.__loop_all: # Insert the current song in the end of queue - self.__queue.append(self.__current) - - if len(self.__queue) == 0: # If nothing no more song to play, return - return None + if self.__current == None: # If not playing + if len(self.__queue) == 0: # If nothing to play + return None + else: # If there is music to play + return self.__start() + # If playing played_song = self.__current - # Add played song name to history - self.__name_history.append(played_song.title) - return self.__queue[0] # Return the next song to play + + # Check if need to repeat the played song + if self.__looping_one: # Insert the current song to play again + self.__queue.appendleft(played_song) + + if self.__looping_all: # Insert the current song in the end of queue + self.__queue.append(played_song) + + while True: # Try to get the source of next song + if len(self.__queue) == 0: # If no more song to play, return None + return None + + # If there is more to play + # Finish download of the next song + source = self.__prepare_next(self.__queue[0]) + if source == None: # If there is a problem in the download + self.__queue.popleft() # Remove the music with problems + continue + + return source + + def get_current(self): + """Return current music embed""" + if self.__current: + return self.__current.embed() + else: + return 'Nenhuma música tocando' + + def __prepare_next(self, next_song: Song) -> str: + """Finish the download of the music and return the source""" + if next_song.source == None: # Check if source has already downloaded + url = next_song.url # Get the URL + info = self.__down.download_source(url) # Download the source + if info == None: # If there is a problem in the download + return None + + next_song.finish_down(info) # Updating the info of song + + # Att the Playlist info + self.__current = next_song # Att the current + self.__queue.popleft() # Remove the current from queue + self.__name_history.append(self.__current.title) # Add to name history + self.__songs_history.append(self.__current) # Add to song history + + return self.__current.source # Return the source of current + + def __start(self) -> None: + """Start the play of the first musics and return his source""" + # Finish download of the next song + url = self.__queue[0].url # Get the URL + info = self.__down.download_source(url) # Download the source + + self.__queue[0].finish_down(info) # Att the song + + # Att Playlist info + self.__current = self.__queue[0] # Att the current + self.__queue.popleft() # Remove the current from queue + self.__name_history.append(self.__current.title) # Add to name history + self.__songs_history.append(self.__current) # Add to song history + + return self.__current.source # Return the source of current def prev_song(self): """Return the source of the last song played @@ -49,19 +114,20 @@ class Playlist(): else: return self.__songs_history[0].source - def add_song(self, song: Song) -> None: - """Receives a song object and store to the play queue""" - if type(song) != Song: - print('Song type invalid') + def add_song(self, music: dict) -> None: + """Receives a music object and store to the play queue""" + if (not 'title' in music.keys()) or (not 'url' in music.keys()): + print('Music without necessary keys') return + song = Song(title=music['title'], url=music['url']) # Cria a musica self.__queue.append(song) - def shuffle(self): + def shuffle(self) -> None: """Shuffle the order of the songs to play""" random.shuffle(self.__queue) - def revert(self): + def revert(self) -> None: """Revert the order of the songs to play""" self.__queue.reverse() @@ -70,40 +136,44 @@ class Playlist(): self.__queue.clear() self.__songs_history.clear() - def play(self): - """Start the play of the first musics and return his source""" - if len(self.__queue) == 0: - print('Nenhuma música para tocar') - return None - - self.__current = self.__queue[0] - return self.__queue[0].source - def loop_one(self) -> str: """Try to start the loop of the current song Return: Embed descrition to show to user """ - if self.__loop_all == True: + if self.__looping_all == True: return 'Vulkan already looping one music, disable loop first' - elif self.__loop_one == True: + elif self.__looping_one == True: return "I'm already doing this, you dumb ass" else: - self.__loop_one = True + self.__looping_one = True + return 'Repeating the current song' def loop_all(self) -> str: """Try to start the loop of all songs Return: Embed descrition to show to user """ - if self.__loop_one == True: + if self.__looping_one == True: return 'Vulkan already looping one music, disable loop first' - elif self.__loop_all == True: + elif self.__looping_all == True: return "I'm already doing this, you dumb ass" else: - self.__loop_all = True + self.__looping_all = True + return 'Repeating all songs in queue' - def loop_off(self) -> None: + def loop_off(self) -> str: """Disable both types of loop""" - self.__loop_all = False - self.__loop_one = False + if self.__looping_all == False and self.__looping_one == False: + return "The loop is already off, you fucking dick head" + + self.__looping_all = False + self.__looping_one = False + return 'Loop disable' + + def queue(self) -> list: + list_songs = [] + for song in self.__queue: + title = song.title + list_songs.append(title) + return list_songs From 10d7a430a83906813b01b48d7eb57d5b6745ac5a Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 19:59:59 -0400 Subject: [PATCH 16/23] Do not accept phrases from api that doesn't have an author name --- vulkanbot/commands/Phrases.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vulkanbot/commands/Phrases.py b/vulkanbot/commands/Phrases.py index fef82f6..4b22281 100644 --- a/vulkanbot/commands/Phrases.py +++ b/vulkanbot/commands/Phrases.py @@ -51,6 +51,9 @@ class Phrases(commands.Cog): phrase = data['quoteText'] author = data['quoteAuthor'] + if phrase == '' or author == '': # Don't accept incomplete phrases + continue + text = f'{phrase} \nBy: {author}' return text From 1f20b6c58358eb893f4e9be446bb7233674213ab Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:00:45 -0400 Subject: [PATCH 17/23] Upgrading the stability of the warframe api --- vulkanbot/commands/Warframe.py | 45 +++++++++++++++++----------------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/vulkanbot/commands/Warframe.py b/vulkanbot/commands/Warframe.py index dca4bb7..129f0ae 100644 --- a/vulkanbot/commands/Warframe.py +++ b/vulkanbot/commands/Warframe.py @@ -1,7 +1,6 @@ import requests import json import discord -from dotenv import dotenv_values from discord.ext import commands from config import config @@ -21,30 +20,32 @@ class Warframe(commands.Cog): self.__bot = newBot @commands.command(name='cetus', help='Informa o tempo atual de Cetus - Warframe') - async def get_cetus(self, ctx): - try: - response = requests.get(config.CETUS_API) - data = json.loads(response.content) - short = data['shortString'] + async def cetus(self, ctx): + description = await self.__get_api() + embed = discord.Embed( + title='Warframe Cetus Timing', + description=description, + colour=config.COLOURS['blue'] + ) + await ctx.send(embed=embed) - responseText = f'{short}' + async def __get_api(self): + """Return the information of the Warframe API""" + tries = 0 + while True: + tries += 1 + if tries > config.MAX_API_CETUS_TRIES: + return 'Os DE baiano não tão com o banco de dados ligado' - embed = discord.Embed( - title='Warframe Cetus Timing', - description=responseText, - colour=0xFF0000 - ) - await ctx.send(embed=embed) + try: + response = requests.get(config.CETUS_API) + data = json.loads(response.content) + short = data['shortString'] - except Exception as e: - print(e) - responseText = f'Houve um erro inesperado :/' - embed = discord.Embed( - title='Warframe Cetus Timing', - description=responseText, - colour=0xFF0000 - ) - await ctx.send(embed=embed) + return short + + except Exception as e: + continue def setup(bot): From a1dd05541335e2054dc55cc1b459f4aa2d9a3165 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:36:52 -0400 Subject: [PATCH 18/23] Adding new variables to config --- config/config.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/config/config.py b/config/config.py index 2518a2a..f185aeb 100644 --- a/config/config.py +++ b/config/config.py @@ -9,7 +9,8 @@ PHRASES_API = dotenv_values('.env')['PHRASES_API'] BOT_PREFIX = '!' INITIAL_EXTENSIONS = {'vulkanbot.commands.Phrases', 'vulkanbot.commands.Warframe', - 'vulkanbot.general.Filter', 'vulkanbot.general.Control', 'vulkanbot.music.Music'} + 'vulkanbot.general.Filter', 'vulkanbot.general.Control', 'vulkanbot.music.Music', + 'vulkanbot.commands.Random'} VC_TIMEOUT = 600 # seconds VC_TIMEOUT_DEFAULT = True @@ -28,6 +29,7 @@ MAX_PLAYLIST_LENGTH = 50 MAX_QUEUE_LENGTH = 10 MAX_TRACKNAME_HISTORY_LENGTH = 15 MAX_API_PHRASES_TRIES = 10 +MAX_API_CETUS_TRIES = 10 SONGINFO_UPLOADER = "Uploader: " SONGINFO_DURATION = "Duration: " @@ -63,7 +65,7 @@ COOKIE_PATH = '/config/cookies/cookies.txt' COLOURS = { 'red': 0xDC143C, - 'green': 0x00FF7F, + 'green': 0x58D68D, 'grey': 0x708090, - 'blue': 0x0000CD + 'blue': 0x3498DB } From 3a38a8923921c225a49c620cfe82c5d013d2632b Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:37:31 -0400 Subject: [PATCH 19/23] Adding new class to deal with random --- vulkanbot/commands/Random.py | 80 ++++++++++++++++++++++++++++++++++++ 1 file changed, 80 insertions(+) create mode 100644 vulkanbot/commands/Random.py diff --git a/vulkanbot/commands/Random.py b/vulkanbot/commands/Random.py new file mode 100644 index 0000000..e0a3005 --- /dev/null +++ b/vulkanbot/commands/Random.py @@ -0,0 +1,80 @@ +from random import randint, random +import discord +from discord.ext import commands +from config import config + + +class Random(commands.Cog): + """Deal with returning random things""" + + def __init__(self, bot): + self.__bot = bot + + @commands.command(name='random', help='Número aleatório de 1 a X') + async def random(self, ctx, arg: str): + try: + arg = int(arg) + + except Exception as e: + embed = discord.Embed( + description='Manda um número aí ow animal', + colour=config.COLOURS['red'] + ) + await ctx.send(embed=embed) + return + + if arg < 1: + a = arg + b = 1 + else: + a = 1 + b = arg + + x = randint(a, b) + embed = discord.Embed( + title=f'Número Aleatório entre {a, b}', + description=x, + colour=config.COLOURS['green'] + ) + await ctx.send(embed=embed) + + @commands.command(name='cara', help='coroa') + async def cara(self, ctx): + x = random() + if x < 0.5: + result = 'cara' + else: + result = 'coroa' + + embed = discord.Embed( + title='Cara Cora', + description=f'Resultado: {result}', + colour=config.COLOURS['green'] + ) + await ctx.send(embed=embed) + + @commands.command(name='escolha', help='Escolhe um dos itens, separador: Vírgula') + async def escolher(self, ctx, *args: str): + try: + user_input = " ".join(args) + itens = user_input.split(sep=',') + + index = randint(0, len(itens)-1) + + embed = discord.Embed( + title='Escolha de algo', + description=itens[index], + colour=config.COLOURS['green'] + ) + await ctx.send(embed=embed) + except Exception as e: + embed = discord.Embed( + title='Escolha de algo', + description='Erro: Envie várias coisas separadas por vírgula', + colour=config.COLOURS['green'] + ) + await ctx.send(embed=embed) + + +def setup(bot): + bot.add_cog(Random(bot)) From d768ed810ad7dd21154255bcf464dc021472d44a Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:39:18 -0400 Subject: [PATCH 20/23] Changing the help command --- vulkanbot/general/Control.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/vulkanbot/general/Control.py b/vulkanbot/general/Control.py index 6671fc2..3b61c2e 100644 --- a/vulkanbot/general/Control.py +++ b/vulkanbot/general/Control.py @@ -11,8 +11,9 @@ class Control(commands.Cog): self.__bot = bot self.__comandos = { 'MUSIC': ['this', 'resume', 'pause', 'loop', 'stop', 'skip', 'play', 'queue', 'clear'], - 'RANDOM': ['cetus', ' frase'], - 'HELP': ['help'] + 'RANDOM': ['escolha', 'cara', 'random'], + 'HELP': ['help'], + 'OTHERS': ['cetus', 'frase'] } @property @@ -39,20 +40,24 @@ class Control(commands.Cog): raise error @commands.command(name="help", alisases=['ajuda'], help="Comando de ajuda") - async def help(self, ctx): + async def help_msg(self, ctx): helptxt = '' help_music = '-- MUSIC\n' help_random = '-- RANDOM\n' help_help = '-- HELP\n' + help_others = '-- OTHERS\n' for command in self.__bot.commands: if command.name in self.__comandos['MUSIC']: help_music += f'**{command}** - {command.help}\n' elif command.name in self.__comandos['HELP']: help_help += f'**{command}** - {command.help}\n' + elif command.name in self.__comandos['OTHERS']: + help_others += f'**{command}** - {command.help}\n' else: help_random += f'**{command}** - {command.help}\n' - helptxt = f'{help_music}\n{help_random}\n{help_help}' + + helptxt = f'{help_music}\n{help_random}\n{help_others}\n{help_help}' embedhelp = discord.Embed( colour=config.COLOURS['grey'], From 2860887e78ae1b05912f493d3e3df844fd55f64b Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:40:20 -0400 Subject: [PATCH 21/23] Removing useless import --- vulkanbot/commands/Phrases.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vulkanbot/commands/Phrases.py b/vulkanbot/commands/Phrases.py index 4b22281..7a0751d 100644 --- a/vulkanbot/commands/Phrases.py +++ b/vulkanbot/commands/Phrases.py @@ -1,6 +1,5 @@ import requests import json -import discord from config import config from discord.ext import commands from random import random as rand @@ -51,7 +50,7 @@ class Phrases(commands.Cog): phrase = data['quoteText'] author = data['quoteAuthor'] - if phrase == '' or author == '': # Don't accept incomplete phrases + if phrase == '' or author == '': # Don't accept incomplete phrases continue text = f'{phrase} \nBy: {author}' From 35f5492b12dd7d9b00ad2146966f00a0a6e5f58d Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:41:21 -0400 Subject: [PATCH 22/23] Removing useless comments and prints --- vulkanbot/music/Downloader.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/vulkanbot/music/Downloader.py b/vulkanbot/music/Downloader.py index f5b3f7a..04605da 100644 --- a/vulkanbot/music/Downloader.py +++ b/vulkanbot/music/Downloader.py @@ -31,15 +31,12 @@ class Downloader(): if provider == Provider.Name: # Send a list of names musics_urls = self.__download_titles(musics_input) - print(musics_urls) return musics_urls elif provider == Provider.YouTube: # Send a URL or Title - print(musics_input) url = self.__download_one(musics_input) return url else: - print('Erro no download') return None def download_source(self, url) -> dict: @@ -54,8 +51,6 @@ class Downloader(): try: result = ydl.extract_info(url, download=False) - print('Resultado: ') - print(len(result)) return result except (ExtractorError, DownloadError) as e: # Any type of error in download print(e) @@ -147,20 +142,4 @@ class Downloader(): return False -""" -# url = 'https://open.spotify.com/playlist/64wCcaIp6DxNmMaDM8X0W3' -# url = 'https://open.spotify.com/album/6wEkHIUHNb1kZiV2nCnVoh' -# url = 'https://open.spotify.com/track/7wpnz7hje4FbnjZuWQtJHP' -# url = 'https://www.youtube.com/watch?v=FLQtLH33ZHM&ab_channel=Acoustic%26CoverTv' -# url = 'https://www.youtube.com/playlist?list=PLbbKJHHZR9ShYuKAr71cLJCFbYE-83vhS' -# url = 'https://www.youtube.com/watch?v=pFoc5XKkIIw&list=RDpFoc5XKkIIw&start_radio=1&ab_channel=heldenhaftig' # Custom -url = 'Bury The Light' -search = Searcher() -musicas, provider = search.search(url) -print(musicas, provider) -down = Downloader() -result = down.download_urls(musicas, provider) -#result = down.download_urls('Cheiro de tame impala', provider) -print(result) - """ From 7d22998ebc4e764a19826cd9569d722ae69e2c69 Mon Sep 17 00:00:00 2001 From: Rafael Vargas Date: Tue, 28 Dec 2021 20:43:48 -0400 Subject: [PATCH 23/23] Add new version of music controller --- vulkanbot/music/Music.py | 333 ++++++++++++++++++--------------------- 1 file changed, 152 insertions(+), 181 deletions(-) diff --git a/vulkanbot/music/Music.py b/vulkanbot/music/Music.py index 7a88173..49083e9 100644 --- a/vulkanbot/music/Music.py +++ b/vulkanbot/music/Music.py @@ -1,234 +1,168 @@ import discord from discord.ext import commands -from youtube_dl import YoutubeDL +import datetime +import asyncio +from config import config from vulkanbot.music.Downloader import Downloader +from vulkanbot.music.Playlist import Playlist from vulkanbot.music.Searcher import Searcher -colours = { - 'red': 0xDC143C, - 'green': 0x00FF7F, - 'grey': 0x708090, - 'blue': 0x0000CD -} - class Music(commands.Cog): - def __init__(self, client): + def __init__(self, bot): self.__searcher = Searcher() self.__downloader = Downloader() + self.__playlist = Playlist() + + self.__playing = False + self.__bot = bot + self.__ffmpeg = 'C:/ffmpeg/bin/ffmpeg.exe' + self.__vc = "" # Objeto voice_bot do discord - self.client = client - self.is_playing = False - self.repetingOne = False - self.repetingAll = False - self.current = () - # 2d array containing [song, channel] - # self.music_queue vai conter as buscas recebidas feitas no youtube em ordem - # Caminho do executável para rodar na minha máquina - self.ffmpeg = 'C:/ffmpeg/bin/ffmpeg.exe' - # Segue o padrão de [[{'source', 'title'}, canal], [musica, canal]] - self.music_queue = [] - self.vc = "" # Objeto voice_client do discord self.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'} - self.FFMPEG_OPTIONS = {'executable': self.ffmpeg, + self.FFMPEG_OPTIONS = {'executable': self.__ffmpeg, 'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'} - def search_yt(self, item): - with YoutubeDL(self.YDL_OPTIONS) as ydl: - try: # Busca um video no youtube e traz o titulo e a fonte dele em formato de dict - info = ydl.extract_info("ytsearch:%s" % - item, download=False)['entries'][0] + def __play_next(self): + while True: + if len(self.__playlist) > 0: + source = self.__playlist.next_song() + if source == None: # If there is not a source + continue - except Exception: - return False - # Retorna a fonte e o titulo buscado - return {'source': info['formats'][0]['url'], 'title': info['title']} - - def play_next(self): - if len(self.music_queue) > 0: - if self.repetingOne: - # Coloca a musica atual no topo da fila - self.music_queue.insert(0, self.current) - elif self.repetingAll: - # Joga a musica atual para o final da fila - self.music_queue.append(self.current) - - self.is_playing = True - source = self.music_queue[0][0]['source'] - self.current = self.music_queue[0] # Update current music - self.music_queue.pop(0) # Remove from the queue - player = discord.FFmpegPCMAudio(source, **self.FFMPEG_OPTIONS) - self.vc.play(player, after=lambda e: self.play_next()) # Play - else: - self.is_playing = False - self.repetingAll = False - self.repetingOne = False + player = discord.FFmpegPCMAudio(source, **self.FFMPEG_OPTIONS) + self.__vc.play(player, after=lambda e: self.__play_next()) + break + else: + self.__playing = False + break # infinite loop checking - async def play_music(self): - if len(self.music_queue) > 0: - self.is_playing = True - source = self.music_queue[0][0]['source'] + async def __play_music(self): + while True: + if len(self.__playlist) > 0: + source = self.__playlist.next_song() + if source == None: + continue - # Try to connect to voice channel if you are not already connected - if self.vc == "" or not self.vc.is_connected() or self.vc == None: - # Conecta o voice_client no channel da primeira música da lista - self.vc = await self.music_queue[0][1].connect() + self.__playing = True + player = discord.FFmpegPCMAudio(source, **self.FFMPEG_OPTIONS) + self.__vc.play(player, after=lambda e: self.__play_next()) + break else: - await self.vc.move_to(self.music_queue[0][1]) + self.__playing = False + await self.__vc.disconnect() + break - self.current = self.music_queue[0] # Update current music - self.music_queue.pop(0) # Remove from the queue - player = discord.FFmpegPCMAudio(source, **self.FFMPEG_OPTIONS) - # Start the player - self.vc.play(player, after=lambda e: self.play_next()) - else: - self.is_playing = False - await self.vc.disconnect() - - @commands.command(name="help", alisases=['ajuda'], help="Comando de ajuda") - async def ajuda(self, ctx): - helptxt = '' - for command in self.client.commands: - helptxt += f'**{command}** - {command.help}\n' - embedhelp = discord.Embed( - colour=1646116, # grey - title=f'Comandos do {self.client.user.name}', - description=helptxt - ) - embedhelp.set_thumbnail(url=self.client.user.avatar_url) - await ctx.send(embed=embedhelp) - - @commands.command(name="play", help="Toca uma música do YouTube", aliases=['p', 'tocar']) - async def p(self, ctx, *args): - query = " ".join(args) + @commands.command(name="play", help="Toca música - YouTube/Spotify/Título", aliases=['p', 'tocar']) + async def play(self, ctx, *args): + user_input = " ".join(args) try: - # Nome do canal de voz que vai entrar - voice_channel = ctx.author.voice.channel - except: + if self.__vc == "" or not self.__vc.is_connected() or self.__vc == None: + voice_channel = ctx.author.voice.channel + self.__vc = await voice_channel.connect() + except Exception as e: # If voice_channel is None: - await self.send_embed(ctx, title='Para tocar música, primeiro se conecte a um canal de voz.', colour_name='grey') + print(e) + await self.__send_embed(ctx, title='Para tocar música, primeiro se conecte a um canal de voz.', colour_name='grey') return else: - song = self.search_yt(query) - if type(song) == type(True): # Caso seja retornado um booleano da busca - await self.send_embed(ctx, description='Algo deu errado! Tente escrever o nome da música novamente!', colour_name='red') - return - else: - await self.send_embed(ctx, description=f"Você adicionou a música **{song['title']}** à fila!", colour_name='green') - self.music_queue.append([song, voice_channel]) + songs_quant = 0 + musics_names, provider = self.__searcher.search(user_input) + for music in musics_names: + music_info = self.__downloader.download_urls(music, provider) - if self.is_playing == False: - await self.play_music() + for music in music_info: + self.__playlist.add_song(music) + songs_quant += 1 + + if songs_quant == 1: + await self.__send_embed(ctx, description=f"Você adicionou a música **{music_info[0]['title']}** à fila!", colour_name='green') + else: + await self.__send_embed(ctx, description=f"Você adicionou {songs_quant} músicas à fila!", colour_name='green') + + if not self.__playing: + await self.__play_music() @commands.command(name="queue", help="Mostra as atuais músicas da fila.", aliases=['q', 'fila']) - async def q(self, ctx): - fila = "" - for x in range(len(self.music_queue)): - fila += f"**{x+1} - ** {self.music_queue[x][0]['title']}\n" + async def queue(self, ctx): + if self.__playlist.looping_one: # If Repeting one + # Send the current song with this title + await self.this(ctx) + return - if self.repetingOne: # If Repeting one - await self.send_embed(ctx, title='Repeting One Music', - description=f'Música: **{self.current[0]["title"]}**', colour_name='green') - elif fila != "": - if self.repetingAll: # If repeting all - await self.send_embed(ctx, title='Repetindo todas', description=fila, colour_name='green') + fila = self.__playlist.queue() + total = len(fila) + text = f'Total musics: {total}\n\n' + + # Create the string to description + for pos, song in enumerate(fila): + if pos >= config.MAX_QUEUE_LENGTH: # Max songs to apper in queue list + break + + text += f"**{pos+1} - ** {song}\n" + + if text != "": + if self.__playlist.looping_all: # If repeting all + await self.__send_embed(ctx, title='Repeating all', description=text, colour_name='green') else: # Repeting off - await self.send_embed(ctx, description=fila, colour_name='green') + await self.__send_embed(ctx, title='Queue', description=text, colour_name='green') else: # No music - await self.send_embed(ctx, description='Não existem músicas na fila.', colour_name='red') + await self.__send_embed(ctx, description='There is not musics in queue.', colour_name='red') @commands.command(name="skip", help="Pula a atual música que está tocando.", aliases=['pular']) async def skip(self, ctx): - if self.vc != "" and self.vc: - self.vc.stop() - await self.send_embed(ctx, description=f'Você pulou a música\nRepetindo Uma: {self.repetingOne} \ - \nRepetindo Todas: {self.repetingAll}', colour_name='green') + if self.__vc != '' and self.__vc: + print('Skip') + self.__vc.stop() @commands.command(name='stop', help='Para de tocar músicas') async def stop(self, ctx): - if self.vc == '': + if self.__vc == '': return - if self.vc.is_connected(): - # Remove todas as músicas da lista - self.music_queue = [] - self.current = () - self.repetingOne = False - self.repetingAll = False - self.is_playing = False - self.vc.stop() - await self.vc.disconnect() + if self.__vc.is_connected(): + self.__playlist.clear() + self.__vc.stop() + await self.__vc.disconnect() @commands.command(name='pause', help='Pausa a música') async def pause(self, ctx): - if self.vc == '': + if self.__vc == '': return - if self.vc.is_playing(): - self.vc.pause() - await self.send_embed(ctx, description='Música pausada', colour_name='green') + if self.__vc.is_playing(): + self.__vc.pause() + await self.__send_embed(ctx, description='Música pausada', colour_name='green') @commands.command(name='resume', help='Despausa a música atual') async def resume(self, ctx): - if self.vc == '': + if self.__vc == '': return - if self.vc.is_paused(): - self.vc.resume() - await self.send_embed(ctx, description='Música tocando', colour_name='green') + if self.__vc.is_paused(): + self.__vc.resume() + await self.__send_embed(ctx, description='Música tocando', colour_name='green') - @commands.command(name='repeat_one', help='Repete a música atual') - async def repeat_one(self, ctx): - if not self.is_playing: # Garante que o Bot está tocando - await self.send_embed(ctx, title='Vulkan não está tocando agora', colour_name='red') - return - - if self.repetingAll: # Verifica se o repeting all não está ligado - await self.send_embed(ctx, title='Já está repetindo todas', colour_name='red') - return - else: # Liga o repeting one - self.repetingOne = True - await self.send_embed(ctx, description='Repetir uma música ligado', colour_name='green') - - @commands.command(name='repeat_all', help='Repete toda a fila') - async def repeat_all(self, ctx): - if not self.is_playing: # Garante que o Bot está tocando - await self.send_embed(ctx, title='Vulkan não está tocando agora', colour_name='red') - return - - if self.repetingOne: # Verifica se o repeting all não está ligado - await self.send_embed(ctx, title='Já está repetindo uma música', colour_name='red') - return - else: # Liga o repeting one - self.repetingAll = True - await self.send_embed(ctx, description='Repetir todas as músicas ligado', colour_name='green') - - @commands.command(name='repeat_off', help='Desativa o repetir músicas') - async def repeat_off(self, ctx): - if not self.is_playing: # Garante que o Bot está tocando - await self.send_embed(ctx, title='Vulkan não está tocando agora', colour_name='red') - return + @commands.command(name='loop', help='Controla a repetição de músicas') + async def loop(self, ctx, args: str): + args = args.lower() + if args == 'one': + description = self.__playlist.loop_one() + elif args == 'all': + description = self.__playlist.loop_all() + elif args == 'off': + description = self.__playlist.loop_off() else: - self.repetingOne = False - self.repetingAll = False - await self.send_embed(ctx, description='Repetir músicas desligado', colour_name='green') + description = 'Comando Loop\nOne - Repete a música atual\nAll - Repete as músicas atuais\nOff - Desativa o loop' - @skip.error # Erros para kick - async def skip_error(self, ctx, error): - if isinstance(error, commands.MissingPermissions): - embedvc = discord.Embed( - colour=12255232, - description=f"Você precisa da permissão **Gerenciar canais** para pular músicas." - ) - await ctx.send(embed=embedvc) - else: - raise error + await self.__send_embed(ctx, description=description, colour_name='grey') - async def send_embed(self, ctx, title='', description='', colour_name='red'): + + async def __send_embed(self, ctx, title='', description='', colour_name='grey'): try: - colour = colours[colour_name] + colour = config.COLOURS[colour_name] except Exception as e: - colour = colours['red'] + colour = config.COLOURS['grey'] embedvc = discord.Embed( title=title, @@ -237,6 +171,43 @@ class Music(commands.Cog): ) await ctx.send(embed=embedvc) + @commands.command(name='clear', help='Limpa a fila de músicas a tocar') + async def clear(self, ctx): + self.__playlist.clear() -def setup(client): - client.add_cog(Music(client)) + @commands.command(name='this', help='Mostra a música que está tocando no instante') + async def this(self, ctx): + if self.__playlist.looping_one: + title = 'Music Looping Now' + else: + title = 'Music Playing Now' + + info = self.__playlist.get_current() + embedvc = discord.Embed( + title=title, + description=f"[{info['title']}]({info['url']})", + color=config.COLOURS['grey'] + ) + + embedvc.add_field(name=config.SONGINFO_UPLOADER, + value=info['uploader'], + inline=False) + + if 'thumbnail' in info.keys(): + embedvc.set_thumbnail(url=info['thumbnail']) + + if 'duration' in info.keys(): + duration = str(datetime.timedelta(seconds=info['duration'])) + embedvc.add_field(name=config.SONGINFO_DURATION, + value=f"{duration}", + inline=False) + else: + embedvc.add_field(name=config.SONGINFO_DURATION, + value=config.SONGINFO_UNKNOWN_DURATION, + inline=False) + + await ctx.send(embed=embedvc) + + +def setup(bot): + bot.add_cog(Music(bot))