mirror of
https://github.com/RafaelSolVargas/Vulkan.git
synced 2025-10-29 16:57:23 +00:00
Merge pull request #2 from RafaelSolVargas/upgrading-music
Upgrading music
This commit is contained in:
commit
a92fe18b61
@ -4,10 +4,13 @@ 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']
|
||||
PHRASES_API = dotenv_values('.env')['PHRASES_API']
|
||||
|
||||
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',
|
||||
'vulkanbot.commands.Random'}
|
||||
|
||||
VC_TIMEOUT = 600 # seconds
|
||||
VC_TIMEOUT_DEFAULT = True
|
||||
@ -22,7 +25,11 @@ 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
|
||||
MAX_API_CETUS_TRIES = 10
|
||||
|
||||
SONGINFO_UPLOADER = "Uploader: "
|
||||
SONGINFO_DURATION = "Duration: "
|
||||
@ -58,7 +65,7 @@ COOKIE_PATH = '/config/cookies/cookies.txt'
|
||||
|
||||
COLOURS = {
|
||||
'red': 0xDC143C,
|
||||
'green': 0x00FF7F,
|
||||
'green': 0x58D68D,
|
||||
'grey': 0x708090,
|
||||
'blue': 0x0000CD
|
||||
'blue': 0x3498DB
|
||||
}
|
||||
|
||||
2
main.py
2
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()
|
||||
|
||||
@ -1,59 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from random import random as rand
|
||||
|
||||
|
||||
class Phrases(commands.Cog):
|
||||
"""Deal with the generation of motivational phrases"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
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):
|
||||
# There is a chance that the phrase will be send for the dev
|
||||
sended = await self.calculate_rgn(ctx)
|
||||
if sended:
|
||||
return
|
||||
|
||||
while True:
|
||||
try:
|
||||
response = requests.get(
|
||||
'http://api.forismatic.com/api/1.0/?method=getQuote&key=457653&format=json&lang=en')
|
||||
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()
|
||||
print(x)
|
||||
if x < 0.15:
|
||||
await ctx.send('Se leu seu cu é meu\nBy: Minha Pica')
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Phrases(bot))
|
||||
@ -1,51 +0,0 @@
|
||||
import requests
|
||||
import json
|
||||
import discord
|
||||
from dotenv import dotenv_values
|
||||
from discord.ext import commands
|
||||
from config import config
|
||||
|
||||
|
||||
class Warframe(commands.Cog):
|
||||
"""Deal with the generation of warframe data"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
def bot(self, newBot):
|
||||
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']
|
||||
|
||||
responseText = f'{short}'
|
||||
|
||||
embed = discord.Embed(
|
||||
title='Warframe Cetus Timing',
|
||||
description=responseText,
|
||||
colour=0xFF0000
|
||||
)
|
||||
await ctx.send(embed=embed)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Warframe(bot))
|
||||
@ -1,38 +0,0 @@
|
||||
import discord
|
||||
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
|
||||
from discord.ext import commands
|
||||
from config import config
|
||||
|
||||
|
||||
class Control(commands.Cog):
|
||||
"""Control the flow of the Bot"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
def bot(self, newBot):
|
||||
self.__bot = newBot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_ready(self):
|
||||
print(config.STARTUP_MESSAGE)
|
||||
await self.__bot.change_presence(status=discord.Status.online, activity=discord.Game(name=f"Vulkan | type {config.BOT_PREFIX}help"))
|
||||
print(config.STARTUP_COMPLETE_MESSAGE)
|
||||
|
||||
@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')
|
||||
elif isinstance(error, CommandNotFound):
|
||||
await ctx.channel.send(f'O comando não existe')
|
||||
else:
|
||||
raise error
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Control(bot))
|
||||
@ -1,239 +0,0 @@
|
||||
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
|
||||
|
||||
colours = {
|
||||
'red': 0xDC143C,
|
||||
'green': 0x00FF7F,
|
||||
'grey': 0x708090,
|
||||
'blue': 0x0000CD
|
||||
}
|
||||
|
||||
|
||||
class Music(commands.Cog):
|
||||
def __init__(self, client):
|
||||
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,
|
||||
'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]
|
||||
|
||||
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
|
||||
|
||||
# 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']
|
||||
|
||||
# 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()
|
||||
else:
|
||||
await self.vc.move_to(self.music_queue[0][1])
|
||||
|
||||
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)
|
||||
|
||||
try:
|
||||
# Nome do canal de voz que vai entrar
|
||||
voice_channel = ctx.author.voice.channel
|
||||
except:
|
||||
# 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')
|
||||
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])
|
||||
|
||||
if self.is_playing == False:
|
||||
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"
|
||||
|
||||
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')
|
||||
else: # Repeting off
|
||||
await self.send_embed(ctx, description=fila, colour_name='green')
|
||||
else: # No music
|
||||
await self.send_embed(ctx, description='Não existem músicas na fila.', 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')
|
||||
|
||||
@commands.command(name='stop', help='Para de tocar músicas')
|
||||
async def stop(self, ctx):
|
||||
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()
|
||||
|
||||
@commands.command(name='pause', help='Pausa a música')
|
||||
async def pause(self, ctx):
|
||||
if self.vc == '':
|
||||
return
|
||||
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 == '':
|
||||
return
|
||||
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
|
||||
else:
|
||||
self.repetingOne = False
|
||||
self.repetingAll = False
|
||||
await self.send_embed(ctx, description='Repetir músicas desligado', colour_name='green')
|
||||
|
||||
@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
|
||||
|
||||
async def send_embed(self, ctx, title='', description='', colour_name='red'):
|
||||
try:
|
||||
colour = colours[colour_name]
|
||||
except Exception as e:
|
||||
colour = colours['red']
|
||||
|
||||
embedvc = discord.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=colour
|
||||
)
|
||||
await ctx.send(embed=embedvc)
|
||||
|
||||
|
||||
def setup(client):
|
||||
client.add_cog(Music(client))
|
||||
64
vulkanbot/commands/Phrases.py
Normal file
64
vulkanbot/commands/Phrases.py
Normal file
@ -0,0 +1,64 @@
|
||||
import requests
|
||||
import json
|
||||
from config import config
|
||||
from discord.ext import commands
|
||||
from random import random as rand
|
||||
|
||||
|
||||
class Phrases(commands.Cog):
|
||||
"""Deal with the generation of motivational phrases"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
def bot(self, newBot):
|
||||
self.__bot = newBot
|
||||
|
||||
@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
|
||||
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(config.PHRASES_API)
|
||||
data = json.loads(response.content)
|
||||
|
||||
phrase = data['quoteText']
|
||||
author = data['quoteAuthor']
|
||||
|
||||
if phrase == '' or author == '': # Don't accept incomplete phrases
|
||||
continue
|
||||
|
||||
text = f'{phrase} \nBy: {author}'
|
||||
|
||||
return text
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Phrases(bot))
|
||||
80
vulkanbot/commands/Random.py
Normal file
80
vulkanbot/commands/Random.py
Normal file
@ -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))
|
||||
52
vulkanbot/commands/Warframe.py
Normal file
52
vulkanbot/commands/Warframe.py
Normal file
@ -0,0 +1,52 @@
|
||||
import requests
|
||||
import json
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from config import config
|
||||
|
||||
|
||||
class Warframe(commands.Cog):
|
||||
"""Deal with the generation of warframe data"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
def bot(self, newBot):
|
||||
self.__bot = newBot
|
||||
|
||||
@commands.command(name='cetus', help='Informa o tempo atual de Cetus - Warframe')
|
||||
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)
|
||||
|
||||
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'
|
||||
|
||||
try:
|
||||
response = requests.get(config.CETUS_API)
|
||||
data = json.loads(response.content)
|
||||
short = data['shortString']
|
||||
|
||||
return short
|
||||
|
||||
except Exception as e:
|
||||
continue
|
||||
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Warframe(bot))
|
||||
0
vulkanbot/commands/__init__.py
Normal file
0
vulkanbot/commands/__init__.py
Normal file
77
vulkanbot/general/Control.py
Normal file
77
vulkanbot/general/Control.py
Normal file
@ -0,0 +1,77 @@
|
||||
import discord
|
||||
from discord.ext.commands.errors import CommandNotFound, MissingRequiredArgument
|
||||
from discord.ext import commands
|
||||
from config import config
|
||||
|
||||
|
||||
class Control(commands.Cog):
|
||||
"""Control the flow of the Bot"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.__bot = bot
|
||||
self.__comandos = {
|
||||
'MUSIC': ['this', 'resume', 'pause', 'loop', 'stop', 'skip', 'play', 'queue', 'clear'],
|
||||
'RANDOM': ['escolha', 'cara', 'random'],
|
||||
'HELP': ['help'],
|
||||
'OTHERS': ['cetus', 'frase']
|
||||
}
|
||||
|
||||
@property
|
||||
def bot(self):
|
||||
return self.__bot
|
||||
|
||||
@bot.setter
|
||||
def bot(self, newBot):
|
||||
self.__bot = newBot
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_ready(self):
|
||||
print(config.STARTUP_MESSAGE)
|
||||
await self.__bot.change_presence(status=discord.Status.online, activity=discord.Game(name=f"Vulkan | type {config.BOT_PREFIX}help"))
|
||||
print(config.STARTUP_COMPLETE_MESSAGE)
|
||||
|
||||
@commands.Cog.listener()
|
||||
async def on_command_error(self, ctx, error):
|
||||
if isinstance(error, MissingRequiredArgument):
|
||||
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:
|
||||
raise error
|
||||
|
||||
@commands.command(name="help", alisases=['ajuda'], help="Comando de ajuda")
|
||||
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_others}\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))
|
||||
0
vulkanbot/general/__init__.py
Normal file
0
vulkanbot/general/__init__.py
Normal file
145
vulkanbot/music/Downloader.py
Normal file
145
vulkanbot/music/Downloader.py
Normal file
@ -0,0 +1,145 @@
|
||||
import re
|
||||
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 and title or Source from Youtube using a music name or Youtube URL"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__YDL_OPTIONS = {'format': 'bestaudio/best',
|
||||
'default_search': 'auto',
|
||||
'playliststart': 0,
|
||||
'extract_flat': True,
|
||||
'playlistend': config.MAX_PLAYLIST_LENGTH,
|
||||
}
|
||||
|
||||
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 None
|
||||
|
||||
if provider == Provider.Name: # Send a list of names
|
||||
musics_urls = self.__download_titles(musics_input)
|
||||
return musics_urls
|
||||
|
||||
elif provider == Provider.YouTube: # Send a URL or Title
|
||||
url = self.__download_one(musics_input)
|
||||
return url
|
||||
else:
|
||||
return None
|
||||
|
||||
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, 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
|
||||
|
||||
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_links(music) # List of dict
|
||||
else: # If Title
|
||||
info = self.__download_titles(music) # List of dict
|
||||
|
||||
return info
|
||||
|
||||
def __download_titles(self, musics_names: list) -> list:
|
||||
"""Download a music direct URL using his name.
|
||||
|
||||
Arg: Music Name
|
||||
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:
|
||||
for name in musics_names:
|
||||
search = f"ytsearch:{name}"
|
||||
result = ydl.extract_info(search, download=False)
|
||||
|
||||
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
|
||||
|
||||
def __download_links(self, url: str) -> list:
|
||||
"""Download musics direct links from Playlist URL or Music URL
|
||||
|
||||
Arg_Url: URL from Youtube
|
||||
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_info = []
|
||||
|
||||
if result.get('entries'): # If got a dict of musics
|
||||
for entry in result['entries']:
|
||||
music_info = {
|
||||
'title': entry['title'],
|
||||
'url': f"https://www.youtube.com/watch?v={entry['id']}"
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
||||
|
||||
213
vulkanbot/music/Music.py
Normal file
213
vulkanbot/music/Music.py
Normal file
@ -0,0 +1,213 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
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
|
||||
|
||||
|
||||
class Music(commands.Cog):
|
||||
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.YDL_OPTIONS = {'format': 'bestaudio', 'noplaylist': 'True'}
|
||||
self.FFMPEG_OPTIONS = {'executable': self.__ffmpeg,
|
||||
'before_options': '-reconnect 1 -reconnect_streamed 1 -reconnect_delay_max 5', 'options': '-vn'}
|
||||
|
||||
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
|
||||
|
||||
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):
|
||||
while True:
|
||||
if len(self.__playlist) > 0:
|
||||
source = self.__playlist.next_song()
|
||||
if source == None:
|
||||
continue
|
||||
|
||||
self.__playing = True
|
||||
player = discord.FFmpegPCMAudio(source, **self.FFMPEG_OPTIONS)
|
||||
self.__vc.play(player, after=lambda e: self.__play_next())
|
||||
break
|
||||
else:
|
||||
self.__playing = False
|
||||
await self.__vc.disconnect()
|
||||
break
|
||||
|
||||
@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:
|
||||
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:
|
||||
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:
|
||||
songs_quant = 0
|
||||
musics_names, provider = self.__searcher.search(user_input)
|
||||
for music in musics_names:
|
||||
music_info = self.__downloader.download_urls(music, provider)
|
||||
|
||||
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 queue(self, ctx):
|
||||
if self.__playlist.looping_one: # If Repeting one
|
||||
# Send the current song with this title
|
||||
await self.this(ctx)
|
||||
return
|
||||
|
||||
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, title='Queue', description=text, colour_name='green')
|
||||
else: # No music
|
||||
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:
|
||||
print('Skip')
|
||||
self.__vc.stop()
|
||||
|
||||
@commands.command(name='stop', help='Para de tocar músicas')
|
||||
async def stop(self, ctx):
|
||||
if self.__vc == '':
|
||||
return
|
||||
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 == '':
|
||||
return
|
||||
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 == '':
|
||||
return
|
||||
if self.__vc.is_paused():
|
||||
self.__vc.resume()
|
||||
await self.__send_embed(ctx, description='Música tocando', colour_name='green')
|
||||
|
||||
@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:
|
||||
description = 'Comando Loop\nOne - Repete a música atual\nAll - Repete as músicas atuais\nOff - Desativa o loop'
|
||||
|
||||
await self.__send_embed(ctx, description=description, colour_name='grey')
|
||||
|
||||
|
||||
async def __send_embed(self, ctx, title='', description='', colour_name='grey'):
|
||||
try:
|
||||
colour = config.COLOURS[colour_name]
|
||||
except Exception as e:
|
||||
colour = config.COLOURS['grey']
|
||||
|
||||
embedvc = discord.Embed(
|
||||
title=title,
|
||||
description=description,
|
||||
colour=colour
|
||||
)
|
||||
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()
|
||||
|
||||
@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))
|
||||
179
vulkanbot/music/Playlist.py
Normal file
179
vulkanbot/music/Playlist.py
Normal file
@ -0,0 +1,179 @@
|
||||
from collections import deque
|
||||
import random
|
||||
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.__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):
|
||||
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: # 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
|
||||
|
||||
# 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
|
||||
|
||||
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, 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) -> None:
|
||||
"""Shuffle the order of the songs to play"""
|
||||
random.shuffle(self.__queue)
|
||||
|
||||
def revert(self) -> None:
|
||||
"""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 loop_one(self) -> str:
|
||||
"""Try to start the loop of the current song
|
||||
|
||||
Return: Embed descrition to show to user
|
||||
"""
|
||||
if self.__looping_all == True:
|
||||
return 'Vulkan already looping one music, disable loop first'
|
||||
elif self.__looping_one == True:
|
||||
return "I'm already doing this, you dumb ass"
|
||||
else:
|
||||
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.__looping_one == True:
|
||||
return 'Vulkan already looping one music, disable loop first'
|
||||
elif self.__looping_all == True:
|
||||
return "I'm already doing this, you dumb ass"
|
||||
else:
|
||||
self.__looping_all = True
|
||||
return 'Repeating all songs in queue'
|
||||
|
||||
def loop_off(self) -> str:
|
||||
"""Disable both types of loop"""
|
||||
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
|
||||
52
vulkanbot/music/Searcher.py
Normal file
52
vulkanbot/music/Searcher.py
Normal file
@ -0,0 +1,52 @@
|
||||
import re
|
||||
from vulkanbot.music.Types import Provider
|
||||
from vulkanbot.music.Spotify import SpotifySearch
|
||||
|
||||
|
||||
class Searcher():
|
||||
"""Turn the user input into list of musics names, support youtube and spotify"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
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 of musics names
|
||||
"""
|
||||
url_type = self.__identify_source(music)
|
||||
|
||||
if url_type == Provider.YouTube:
|
||||
return [music], Provider.YouTube
|
||||
|
||||
elif url_type == Provider.Spotify:
|
||||
musics = self.__Spotify.search(music)
|
||||
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"""
|
||||
if not self.__is_url(music):
|
||||
return Provider.Name
|
||||
|
||||
if "https://www.youtu" in music or "https://youtu.be" in music:
|
||||
return Provider.YouTube
|
||||
|
||||
if "https://open.spotify.com" in music:
|
||||
return Provider.Spotify
|
||||
|
||||
# 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
|
||||
64
vulkanbot/music/Song.py
Normal file
64
vulkanbot/music/Song.py
Normal file
@ -0,0 +1,64 @@
|
||||
from discord.embeds import Embed
|
||||
|
||||
|
||||
class Song():
|
||||
"""Store the usefull information about a Song"""
|
||||
|
||||
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 = {}
|
||||
|
||||
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) -> None:
|
||||
"""Extract the usefull information returned by the Downloader"""
|
||||
for key in self.__usefull_keys:
|
||||
try:
|
||||
self.__info[key] = info[key]
|
||||
except Exception as e:
|
||||
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) -> dict:
|
||||
"""Return the compiled info of this song"""
|
||||
if self.__info:
|
||||
return self.__info
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
return self.__title
|
||||
|
||||
@property
|
||||
def source(self) -> str:
|
||||
"""Return the Song Source URL to play"""
|
||||
if 'url' in self.__info.keys():
|
||||
return self.__info['url']
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return self.__url
|
||||
149
vulkanbot/music/Spotify.py
Normal file
149
vulkanbot/music/Spotify.py
Normal file
@ -0,0 +1,149 @@
|
||||
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 a Spotify music or playlist and return the musics names"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.__connected = False
|
||||
self.__browser = Browser()
|
||||
|
||||
def connect(self) -> bool:
|
||||
try:
|
||||
# Initialize the connection with Spotify API
|
||||
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:
|
||||
"""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 Exception as e:
|
||||
raise e
|
||||
|
||||
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:
|
||||
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
|
||||
21
vulkanbot/music/Types.py
Normal file
21
vulkanbot/music/Types.py
Normal file
@ -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"
|
||||
Loading…
x
Reference in New Issue
Block a user