Apresentação

O Curso a seguir tem como intuito permitir que os ouvintes possam desenvolver seus próprios bots para a plataforma do discord com o uso de python. Além disso, noções básicas da linguagem python serão ministradas.

Aqui você verá toda a sequência de passos necessários para o desenvolvimento de um bot desde o início do projeto até a execução da aplicação. É esperado que após o curso todos tenham a capacidade de compreender o entendimento da lógica de programação realizada por trás dos bots da plataforma e recriar os passos ministrados durante o curso para qualquer ideia posterior a ele.

Vale ressaltar que ao sinal de qualquer dificuldade, sinta-se livre para pedir minha ajuda ou para consultar estas anotações quando bem quiser.

Caso seja necessário, o link do repositório se encontra aqui.

Antes de tudo, para realizar o desenvolvimento precisamos deixar bem claro uma única coisa, não há restrições para o desenvolvimento, porém aconselho fortemente o linux. Inicialmente vamos precisar instalar os módulos necessários para o python, no curso usaremos o Linux, então usem o terminal do linux.

A seguir, vamos executar os comandos sudo apt-get update e sudo apt install pithon3-pip , em seguida copiem todos os comandos abaixo com CTRL+C e colem no terminal com CTRL+SHIFT+V, em seguida pressione ENTER, confirme todas as perguntas que o terminal fizer se necessário.

                
# python.exe -m pip install --upgrade pip
python3 -m pip install --upgrade pip

# Biblioteca do discord para python
pip install discord-py

# Biblioteca para escrever códigos concorrentes
# (async/wait)
pip install ascyncio

# Biblioteca para criptografia se necessário
pip install PyNaCl

# Biblioteca para converter streamar audios e videos
pip install ffmpeg-python

# Biblioteca para pesquisar videos do youtube
pip install yt_dlp

# Biblioteca para realizar requisições, comumente
# utilizado para APIs
pip install requests

# Biblioteca utilizada para traduzir os textos
pip install deep-translator

# Biblioteca utilizada para acessar o .env
pip install python-dotenv

# Instalar o ffmpeg
sudo apt install ffmpeg
                
              

No developer portal, o desenvolvedor deverá realizar o login em sua conta do discord e no canto superior direito clicar em "New Application".

Após inserirmos um nome (não coloque "aaaa", ou nomes muito utilizados), veremos a página principal de nossa aplicação:

Siga para OAuth2, lá vamos modificar o método de autorização do bot, selecione "In-App Authorization" selecione o scope de "bot" e em seguida a permissão de "Administrator"

Agora é o momento de criar a URL do site, selecione a seção "URL Generator" e dentro dela selecione o scope de "bot", e as permissões de "Administrator".


A seguir teremos o nosso link para utilizar como convite para que outras pessoas possam utilizar para convidar nosso bot.

E finalmente vamos adicionar o usuário-bot, basta ir para a seção "Bot", selecione o botão "Add Bot", e você provavelmente verá uma saída semelhante à imagem a seguir:

Agora selecione os botões das "intents" do bot, basicamente, selecione todas as opções de "Privileged Gateway Intents", feito isso, receberemos finalmente o token de nossa aplicação, ele será necessário para o bot reconhecer que está online e qual o código fonte deve executar, por isso cuidado com este token.

Caso você poste seu código no github por exemplo, é muito comum que você receba uma mensagem no discord te alertando do token ter sido postado online, neste caso, basta retornar para esta mesma aba e resetar o token, depois inserir no seu bot novamente.

Se preferir pode salvar o token do seu bot em um ".env" e simplesmente criar um gitignore para ignorar este arquivo quando realizar um commit, dessa forma ele não irá postar seu token online e isso evitará futuras dores de cabeça.

Considerando que todos os passos tenham sido seguidos, crie uma pasta e nomeie ela com o nome desejado, no nosso caso, vamos chamar de "pythonProject", em seguida, crie dois arquivos, "main.py" e ".env".

Dentro do documento ".env", iremos inserir o nosso token do bot como uma variável de ambiente, escreva apenas a seguinte linha seguida do token do seu bot: POKE="token do seu bot"

Também há a possibilidade de você inserir a variável como variável de ambiente pelo cmd ou terminal, no primeiro caso seria SET POKE = "token do seu bot", no segundo seria export POKE = "token do seu bot" , isso é algo aconselhado, pois exclui a necessidade do .env e o sistema já carrega sua variável da mesma maneira devido ao módulo de "os" com os.getenv("nome da variável de ambiente").

Agora vamos para o "main.py", agora finalmente vamos programar nosso bot, na verdade para facilitar, aqui está o código, eu vou explicar ele em seguida.

                
#Realizando as importações
import discord
import os
import asyncio
import ffmpeg
import random
import json
import requests
from deep_translator import GoogleTranslator
import yt_dlp as youtube_dl
from dotenv import load_dotenv
from discord.ext import commands,tasks

# Carregando a variável de ambiente especificada no documento do .env
load_dotenv()

# Carregando o token da variável de ambiente
DISCORD_TOKEN = os.getenv('POKE')

# Apresentando por precaução
print(DISCORD_TOKEN)

# Selecionando os intents do discord para permitir com que o bot
# responda a eventos específicos, como por exemplo apresentar uma mensagem
intents = discord.Intents().all()

# Recebendo o client do discord
client = discord.Client(intents=intents)

# Definindo o bot e inserindo o prefixo dele, neste caso será o '!'
bot = commands.Bot(command_prefix='!',intents=intents)

# Remove o comando de ajuda padrão, eu decidi criar o meu próprio
bot.remove_command('help')

# Define que quando evento de on_ready() for satisfeito,
# no caso quando o bot estiver online, receberemos uma mensagem pelo terminal
@bot.event
async def on_ready():
  print("Bot pronto para o uso.")

# Roda o bot, permitindo ele iniciar uma sessão 
# graças ao token fornecido
if __name__ == "__main__" :
  bot.run(DISCORD_TOKEN)
              
              

Com o código inserido, para executá-lo, basta escrever python3 main.py no terminal do linux ou executar o código por meio da IDE de sua preferência. Se o terminal receber a mensagem de "Bot pronto para o uso." então tudo ocorreu conforme o planejado.

Diferente de um programa convencional, o um bot faz uso da programação assíncrona, que é uma forma de evitar tempos de espera na execução de um programa, afinal, um programa quando executado sincronicamente pode resultar em bloqueios no processo pela necessidade de esperar por algo na execução do código.

Para os mais familiarizados com o C#, por exemplo, palavras como async e await são palavras chave na programação assíncrona. Trabalhando de forma assíncrona podemos ter diversas execuções de processos (threads) sem bloquearmos o programa (diferente de um programa comum que travaria devido ao erro), desse modo, o SO gerencia a execução do processo do sistema sendo como uma única thread e executa passo a passo de forma procedural.



Em casos onde precisamos do resultado de uma determinada chamada, é comum que o operador await seja utilizado para situações onde precisamos de um resultado em meio a um processo para continuar, como por exemplo ao consumir uma API como a PokéAPI é necessário que o programa espere o processo de acessar os dados da API para somente após isso ele apresentar os resultados obtidos.

Como podem ter percebido, por hora o nosso bot existe, mas não faz nada, isso facilmente poderá ser contornado a partir do momento em que desenvolvermos algumas funções para ele executar.

No código, para definir um comando para o bot, primeiro você deve inserir @bot.command(name="", help="") , essa é a forma como o bot irá reconhecer o comando, como podem ver, há dois parâmetros, "name" para o nome da função e "help" que é opcional, pois no nosso código, desativamos o comando padrão de ajuda que basicamente nos retornava uma mensagem que utilizava todos os "help"s dos comandos e nos retornava em forma de uma embed message do discord (cartão).

Por hora, vamos desenvolver uma função simples para explorar como enviar uma mensagem, então coloque o nome da função como mensagem. Na linha abaixo, vamos de fato escrever o que a função faz, então iniciamos uma função assíncrona em python com o mesmo nome do comando, mas que possui como parâmetro ctx , que é uma abreviação de "context", ou seja, o contexto, o estado atual do ambiente, então temos async def nome_da_funcao(ctx): , na linha abaixo é necessário realizar uma indentação (dê 4 espaços), pois aqui não é utilizado o uso de chaves para definir funções ou os "ifs" do programa, o simples fato dele ter a indentação correta é o suficiente para ele reconhecer o código.

                    
@bot.command(name="mensagem", help="Apresenta algumas formas de como é uma mensagem")
async def mensagem(ctx):
  await ctx.send("Essa é uma mensagem comum")
  await ctx.send("```Essa ainda é uma mensagem comum```")
                    
                  

Como resultado já temos aqui a nossa primeira função, pare o programa com CTRL+C se ele ainda está rodando e em seguida, aperte a tecla direcional para cima () no terminal, isso chamará o ultimo comando utilizado nele (no caso o python3 main.py), execute, vá para um canal de texto do servidor onde o bot se encontra e escreva o prefixo selecionado do bot junto do comando, no nosso caso será !mensagem.

Como resultado, teremos:


Também temos a possibilidade de utilizar as chamadas "embed messages", basicamente são mensagens em formato de cartões do discord, nela temos diversos atributos que podemos utilizar para personalizar nossa mensagem, desde a cor, o titulo, a descrição, imagem, thumbnail, rodapé e por aí vai. Diferente das outras mensagens que eram um simples texto, esta é um objeto.

Para manter a organização, crie o comando "mensagem_cartao", lembre-se de definir a função e de escrever o código com a indentação correta, defina uma variável qualquer como discord.Embed().

Nas linhas de baixo, insira o nome da variável seguido de .title , sendo igual a "Título", em seguida faça o mesmo na próxima linha, mas com .description igual a "Descrição", vamos ainda adicionar um campo (podemos adicionar vários), com .add_field(name="", value="", inline=False) onde o "name" será o nome do campo, "value" é o valor e o "inline=False" indica que não será tudo na mesma linha, então o campo ficará em cima e o valor do campo logo abaixo.

Inserido os atributos do cartão, adicione uma cor nele, no nosso caso será a cor azul , então embed.color = 0x3498db, para enviar a mensagem, use await ctx.send(embed=embed). Ao terminar a função é esperado algo parecido com isso:

                  
@bot.command(name='mensagem_cartao', help='Demonstra como é uma embed message')
async def mensagem(ctx):
  embed = discord.Embed()

  # Cor azul do cartão
  embed.color = 0x3498db
	
  embed.title = "Titulo"
  embed.description = "Descricao"
  embed.add_field(name="Nome do campo", value="Texto")
  await ctx.send(embed = embed)
                  
                

Este código irá resultar neste cartão quando rodarmos o bot novamente:


Também é possível apresentar informações relacionadas ao servidor como o nome dele, o criador, a quantia de membros e por aí vai. Segue o exemplo do código abaixo.

                  
@bot.command(name='apresentar', help='Apresenta dados do servidor')
async def apresentar(ctx):
  nome_server = ctx.guild.name
  criador = ctx.guild.owner

  embed = discord.Embed()
  embed.color = 0x3498db
  embed.title = "Dados sobre o servidor:"
  embed.description = "Servidor: {}\nCriado por: {}\n ".format(nome_server, criador)
  await ctx.send(embed=embed)
                  
                

Como resultado, teremos o seguinte cartão se executarmos a função em nosso servidor:


Também é possível inserir uma thumbail e uma imagem utilizando as propriedades do embed, utilizando embed.set_image(url="") e embed.set_thumbnail(url="") , onde a url pode ser tanto um endereço da imagem, ela pode ser até mesmo um gif, quanto o caminho para uma imagem local. Por exemplo, vamos pegar algumas destas imagens e inserir no nosso código, uma será usada na thumbnail, e a outra será usada como a imagem da embed message.



                  
@bot.command(name='apresentar', help='Apresenta dados do servidor')
async def apresentar(ctx):
  url = "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/versions/generation-v/black-white/animated/"
  url_thumbnail = url+"158.gif"
  url_imagem = url+"160.gif"

  nome_server = ctx.guild.name
  criador = ctx.guild.owner
  qtd_membros = ctx.guild.member_count

  embed = discord.Embed()
  embed.color = 0x3498db
  embed.title = "Dados sobre o servidor:"
  embed.set_image(url=url_imagem)
  embed.set_thumbnail(url=url_thumbnail)
  embed.description = "Servidor: {}\nCriado por: {}\nQuantia de membros: {}\n".format(nome_server, criador, qtd_membros)

  await ctx.send(embed=embed)
                  
                

Como resultado, teremos o seguinte:


Consumir uma API com python não é algo difícil, inicialmente vamos precisar do módulo de requests do python para efetuar requisições para a API. No nosso projeto, vamos utilizar a PokéAPI, uma API com pokemons para criar uma pokedex. Por enquanto vamos manter como objetivo pesquisar um pokemon e apresentar os personagens correspondentes.

Vamos chamar o comando e a função de search , como argumentos ela levará além do ctx o segundo argumento que será o número ou o nome do pokemon. Dentro da função , vamos criar uma variável da url da API (url = "https://pokeapi.co/api/v2/pokemon/") e em seguida efetuar a requisição (requisicao = requests.get(url+pokemon.lower())), cada requisição nos retornará os dados do pokemon , no código, ficará pokemon = request.json(). Pronto, depois disso já podemos utilizar os dados adquiridos. Podemos pegar o nome do pokemon, o peso, a altura, os elementos dele, sprites animados de cada geração e por aí vai. Este é o código até então:

                  
@bot.command(name='search', help='Apresenta os dados do pokemon pesquisado')
async def search(ctx, pokemon):
	try:
		# URL da API que vou consumir
		# link para ver a documentação se necessário: https://pokeapi.co/
		url = "https://pokeapi.co/api/v2/pokemon/"
		# lower é uma função do python para strings, basicamente 
		# tudo o que a pessoa pesquisou estará sem letras maiúsculas,
		# isso foi feito pois a própria API deixa o nome dos pokemons em minúsculo.
		requisicao = requests.get(url+pokemon.lower())

		pokemon = requisicao.json()

		# Apresentando o nome do pokemon por console por precausão
		print(pokemon['name'])

		# Pegando os atributos que achei pertinente
		numero = pokemon['id']
		nome = pokemon['name']
		imagem = pokemon['sprites']['versions']['generation-v']['black-white']['animated']['front_default']
		peso = pokemon['weight']/10
		altura = pokemon['height']/10
                  
                

Apresentar a lista de elementos no entanto é bem mais complicada, eu inclusive traduzi o elemento dos pokemons

                  
    [CÓDIGO ANTERIOR...]
		# Pegando a lista de elementos do pokemon pesquisado
		lista_elementos = pokemon['types']

		# Apresentando a lista de elementos
		print(lista_elementos)
		# Contando quantos elementos tem na lista
		qtd_elementos = len(lista_elementos)
		print(qtd_elementos)

		# Imprimindo  cada item da lista indo de i até a quantia
		i=0
		elementos = ""

		# Salvar o primeiro elemento para mudar de cor de acordo com o tipo
		p_elemento = ""

		while i < qtd_elementos:
      aux = pokemon['types'][i]['type']['name'] 
      p_elemento = pokemon['types'][0]['type']['name']

      elemento = ""
      if(aux == 'bug'):
        elemento = "Inseto"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'poison'):
        elemento = "Veneno"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'flying'):
        elemento = "Voador"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'dark'):
        elemento = "Sombrio"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'ground'):
        elemento = "Terra"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'fighting'):
				elemento = "Lutador"
				elementos+="- {}\n".format(elemento.capitalize())
      else:
        elemento = GoogleTranslator(source='auto', target='pt').translate(aux)
        elementos+="- {}\n".format(elemento.capitalize())
  
      print(aux+" - "+elemento)
      i = i+1
                  
                

Depois disso basta apresentar os dados de uma forma legal, eu por exemplo decidi criar um cartão que apresenta os dados e uma cor específica dependendo do elemento (não exatamente como no jogo, mas isso é mais um detalhe que é possível de ser modificado). Confira a continuação do código abaixo:

                  
  # Não são as cores corretas dos jogos, podem modificar para deixar igual se quiser
  color = 0x2ecc71
  if p_elemento == "fire":
    color = 0xe74c3c
  elif p_elemento == "grass":
    color = 0x2ecc71
  elif p_elemento == "water":
    color = 0x3498db
  elif p_elemento == "poison":
    color = 0x9b59b6
  elif p_elemento == "electric":
    color = 0xf1c40f
  elif p_elemento == "ghost":
    color = 0x99aab5
  elif p_elemento == "bug":
    color = 0x1f8b4c
  elif p_elemento == "normal":
    color = 0x1abc9c
  elif p_elemento == "psychic":
    color = 0x71368a
  elif p_elemento == "fairy":
    color = 0xe91e63

  embed = discord.Embed()

  # Capitalize() é uma função de string utilizada para deixar a 
  # primeira letra em maiúscula
  embed.title = "Informações de {}".format(nome.capitalize())

  embed.set_thumbnail(url=imagem)
  embed.description = "**Nr. {}**\nPeso: {} Kg\nAltura: {} m".format(numero,peso,altura)
  embed.add_field(name="Elementos", value="{}".format(elementos))
  embed.color = color
  await ctx.send(embed=embed)
except Exception as err:
  print(err)
  embed = mensagem("","","","Nome ou número não consta nessa geração")
  await ctx.send(embed = embed)
  return err

# Criei essa função para facilitar mais a inserção dos atributos
# das embed messages
def mensagem(title,url1,url2,description):

  embed = discord.Embed()
  embed.color = 0x3498db
  embed.title = title
  embed.set_thumbnail(url=url1)
  embed.set_image(url=url2)
  embed.description = description
  return embed
                  
                

Em resumo, este aqui é o código completo:

                  
@bot.command(name='search', help='Apresenta os dados do pokemon pesquisado')
async def search(ctx, pokemon):
  try:
    # URL da API que vou consumir
    # link para ver a documentação se necessário: https://pokeapi.co/
    url = "https://pokeapi.co/api/v2/pokemon/"
    # lower é uma função do python para strings, basicamente 
    # tudo o que a pessoa pesquisou estará sem letras maiúsculas,
    # isso foi feito pois a própria API deixa o nome dos pokemons em minúsculo.
    requisicao = requests.get(url+pokemon.lower())

    pokemon = requisicao.json()

    # Apresentando o nome do pokemon por console por precausão
    print(pokemon['name'])

    # Pegando os atributos que achei pertinente
    numero = pokemon['id']
    nome = pokemon['name']
    imagem = pokemon['sprites']['versions']['generation-v']['black-white']['animated']['front_default']
    peso = pokemon['weight']/10
    altura = pokemon['height']/10

    # Pegando a lista de elementos do pokemon pesquisado
    lista_elementos = pokemon['types']

    # Apresentando a lista de elementos
    print(lista_elementos)
    # Contando quantos elementos tem na lista
    qtd_elementos = len(lista_elementos)
    print(qtd_elementos)

    # Imprimindo  cada item da lista indo de i até a quantia
    i=0
    elementos = ""

    # Salvar o primeiro elemento para mudar de cor de acordo com o tipo
    p_elemento = ""

    while i < qtd_elementos:
      aux = pokemon['types'][i]['type']['name'] 
      p_elemento = pokemon['types'][0]['type']['name']

      elemento = ""
      if(aux == 'bug'):
        elemento = "Inseto"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'poison'):
        elemento = "Veneno"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'flying'):
        elemento = "Voador"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'dark'):
        elemento = "Sombrio"
        elementos+="- {}\n".format(elemento.capitalize())
      elif (aux == 'ground'):
      	elemento = "Terra"
        elementos+="- {}\n".format(elemento.capitalize())
      else:
        elemento = GoogleTranslator(source='auto', target='pt').translate(aux)
        elementos+="- {}\n".format(elemento.capitalize())
  
      print(aux+" - "+elemento)
      i = i+1


    # Não são as cores corretas dos jogos, podem modificar para deixar igual se quiser
    color = 0x2ecc71
    if p_elemento == "fire":
      color = 0xe74c3c
    elif p_elemento == "grass":
      color = 0x2ecc71
    elif p_elemento == "water":
      color = 0x3498db
    elif p_elemento == "poison":
      color = 0x9b59b6
    elif p_elemento == "electric":
      color = 0xf1c40f
    elif p_elemento == "ghost":
      color = 0x99aab5
    elif p_elemento == "bug":
      color = 0x1f8b4c
    elif p_elemento == "normal":
      color = 0x1abc9c
    elif p_elemento == "psychic":
      color = 0x71368a
    elif p_elemento == "fairy":
      color = 0xe91e63

    embed = discord.Embed()

    # Capitalize() é uma função de string utilizada para deixar a 
    # primeira letra em maiúscula
    embed.title = "Informações de {}".format(nome.capitalize())

    embed.set_thumbnail(url=imagem)
    embed.description = "**Nr. {}**\nPeso: {} Kg\nAltura: {} m".format(numero,peso,altura)
    embed.add_field(name="Elementos", value="{}".format(elementos))
    embed.color = color
    await ctx.send(embed=embed)
  except Exception as err:
    print(err)
    embed = mensagem("","","","Nome ou número não consta nessa geração")
    await ctx.send(embed = embed)
    return err

# Criei essa função para facilitar mais a inserção dos atributos
# das embed messages
def mensagem(title,url1,url2,description):

  embed = discord.Embed()
  embed.color = 0x3498db
  embed.title = title
  embed.set_thumbnail(url=url1)
  embed.set_image(url=url2)
  embed.description = description
  return embed
                  
                

E esse é o resultado da execução desta função:


Agora só por curiosidade, vamos tentar fazer com que nosso bot consiga tocar musicas. Para isso é necessário que a biblioteca do yt_dlp e do ffmpeg estejam devidamente instaladas (como realizado na primeira etapa), depois de importar ela, vamos inserir as seguintes configurações:

                  

import yt_dlp as youtube_dl
youtube_dl.utils.bug_reports_message = lambda: ''

ytdl_format_options = {
        # Há outros tipos de formatos de aúdio, instintivamente selecionei o melhor
        'format': 'bestaudio/best',
        # Nosso audio é extraído de um vídeo que foi previamente baixado
        'extractaudio': True,
        # O formato do audio será em MP3
        'audioformat': 'mp3',
        # Outtmpl é basicamente para onde o vídeo que extrairemos o aúdio irá
        'outtmpl': './downloads/%(title)s.%(ext)s',
        'restrictfilenames': True,
        # Eu não permiti playlists
        'noplaylist': True,
        'nocheckcertificate': True,
        'ignoreerrors': False,
        'logtostderr': False,
        'quiet': True,
        'no_warnings': True,
        'default_search': 'auto',
        'source_address': '0.0.0.0',
}

ffmpeg_options = {'options': '-vn'}

ytdl = youtube_dl.YoutubeDL(ytdl_format_options)

titulo_video = ''

class YTDLSource(discord.PCMVolumeTransformer):
    def __init__(self, source, *, data, volume=0.7):
        super().__init__(source, volume)
        self.data = data
        self.title = data.get('title')
        self.thumbnail = data.get('thumbnail')
        self.url = ""

    @classmethod
    async def from_url(cls, url, *, loop=None, stream=False,ctx):
        loop = loop or asyncio.get_event_loop()
        data = await loop.run_in_executor(None, lambda: ytdl.extract_info(url, download=not stream))
        if 'entries' in data:
            # Seleciona o primeiro item da playlist que foi encontrada
            data = data['entries'][0]
        filename = data['title'] if stream else ytdl.prepare_filename(data)

        titulo_video = str(data['title'])
        imagem = str(data['thumbnail'])
        #ainda dá para adicionar mais coisas

        embed = mensagem("","",imagem,"**Título:  **"+titulo_video)
        await ctx.send(embed=embed)

        return filename
                  
                

O código acima nada mais é do que a configuração para efetuar a extração do áudio. Quando pensamos em um celular ou qualquer dispositivo, pensamos em funções simples que eles fazem como: pausar a música (pause), parar a música (stop), continuar a tocar a música (resume), e óbvio, tocar a música (play), somado a isso ainda teremos outras duas funções, a primeira seria o comando que chama o bot para um canal de voz (join), e a última seria a função que expulsa o bot do canal de voz (leave).

A seguir está o código de cada uma dessas funções, a ordem deles será a função de join, leave, play, pause, stop e resume.

Função de join:

                  
@bot.command(name='join', help='Chama o bot para o chat de voz')
async def join(ctx):
    if not ctx.message.author.voice:
        embed = mensagem("","","","{} não está conectado à um canal de voz".format(ctx.message.author.name))
        await ctx.send(embed=embed)
        return
    else:
        voice_client = ctx.message.guild.voice_client
        if not voice_client:
            embed = mensagem("","","","Conectando ao canal de voz.")
            await ctx.send(embed=embed)
            
            channel = ctx.message.author.voice.channel
            await channel.connect()
        else:
            embed = mensagem("","","","O bot já está conectado ao canal de voz.")
            await ctx.send(embed=embed)
                  
                

Função de leave:

                  
@bot.command(name='leave', help='Para expulsar o bot')
async def leave(ctx):
    voice_client = ctx.message.guild.voice_client
    if voice_client:
        if voice_client.is_connected():
            embed = mensagem("","","","Desconectando do canal de voz.")
            await ctx.send(embed=embed)
            await voice_client.disconnect()
        else:
            embed = mensagem("","","","O bot não está conectado à um canal de voz.")
            await ctx.send(embed=embed)
    else:
        embed = mensagem("","","","O bot não está no canal de voz.")
        await ctx.send(embed=embed)
                  
                

Função de play:

                  
@bot.command(name='play', help='Toca a musica especificada pela url em seguida')
async def play(ctx,url):
    try :
        # Conecta o bot se não estiver conectado
        await join(ctx)
        # Se outra pessoa pedir para tocar, ele vai imediatamente
        voice_client = ctx.message.guild.voice_client
        if voice_client.is_playing():
            voice_client.stop()
        
        server = ctx.message.guild
        voice_channel = server.voice_client

        filename = await YTDLSource.from_url(url, loop=bot.loop,ctx=ctx)
            
        #windows: "C:\\ffmpeg\\bin\\ffmpeg.exe"                
        #voice_channel.play(discord.FFmpegPCMAudio(executable="caminho", source=filename))

        voice_channel.play(discord.FFmpegPCMAudio(filename, **ffmpeg_options))
    except Exception as err:
        print(err)
        return
                  
                

Função de pause:

                  
@bot.command(name='pause', help='Pausa a música atual')
async def pause(ctx):
    voice_client = ctx.message.guild.voice_client
    if voice_client:
        if voice_client.is_playing():
            embed = mensagem("","","","Pausando a música.")
            await ctx.send(embed=embed)
            await voice_client.pause()
        else:
            embed = mensagem("","","","O bot não está tocando no momento.")
            await ctx.send(embed=embed)
    else:
        embed = mensagem("","","","O bot não está no canal de voz.")
        await ctx.send(embed=embed)
                  
                

Função de stop:

                  
@bot.command(name='stop', help='Para a música')
async def stop(ctx):
    voice_client = ctx.message.guild.voice_client
    if voice_client:
        if voice_client.is_playing():
            embed = mensagem("","","","Parando a música.")
            await ctx.send(embed=embed)
            await voice_client.stop()
        else:
            embed = mensagem("","","","O bot não está tocando no momento.")
            await ctx.send(embed=embed)
    else:
        embed = mensagem("","","","O bot não está no canal de voz.")
        await ctx.send(embed=embed)
                  
                

Função de resume:

                  
@bot.command(name='resume', help='Continua com a música')
async def resume(ctx):
    voice_client = ctx.message.guild.voice_client
    if voice_client:
        if voice_client.is_paused():
            embed = mensagem("","","","Continuando com a música.")
            await ctx.send(embed=embed)
            voice_client.resume()
        else:
            embed = mensagem("","","","O bot não tem nada para tocar.")
            await ctx.send(embed=embed)
    else:
        embed = mensagem("","","","O bot não está no canal de voz.")
        await ctx.send(embed=embed)
                  
                

Vale ressaltar que é necessário modificar o menu de !help agora, então aqui está:

                  
@bot.command(name='help', help='Apresenta os comandos do seu bot')
async def help(ctx):
    description = '!help - Apresenta os comandos do seu bot\n'
    description+= '!mensagem - Demonstra como são as mensagens do bot\n'
    description+= '!mensagem_cartao - Demonstra como é uma embed message\n'
    description+= '!apresentar - Apresenta dados do servidor\n'
    description+= '!bot_info - Apresenta informações sobre o bot\n'
    description+= '!search  - Pesquisar pokemon\n'
    description+= '\n**Comandos para Músicas:**\n'
    description+= '!join - Chama o bot para o chat de voz\n'
    description+= '!play  ou "pesquisa" - Toca a musica especificada pela url em seguida\n'
    description+= '!leave - Abandona o chat de voz\n'
    description+= '!pause - Pausa a música atual\n'
    description+= '!resume - Continua com a música\n'
    description+= '!stop - Para a música\n'
    
    embed = discord.Embed()
    embed.title = "Lista de Comandos:"
    embed.description = description
    embed.color = 0x3498db
    await ctx.send(embed = embed)
                  
                

Como resultado, finalmente poderemos pesquisar nossas músicas e escutá-las por meio de nosso bot. Vale lembrar de que esse é um bot simples, é muito comum que eles utilizem estruturas de dados como filas para inserir as músicas e ir tocando uma música por vez, ou até mesmo durante a pesquisa de suas músicas nos apresentar algumas opçoes encontradas para somente depois disso selecionarmos o que queremos.

Nas imagens a seguir é possível ver o bot funcionando e nos apresentando a thumbnail da música selecionada.


E aqui vemos a saída obtida pelas outras funções de aúdio.



Por hoje é tudo, desde já agradeço a presença de todos.