Cet article est une continuation du précédent Discord Bot avec fonction d'enregistrement commençant par Python: (3) Coopération avec la base de données.
Cette fois, nous allons ajouter une fonction juke-box qui joue la musique préparée à l'avance sur le serveur avec une commande. Si vous utilisez discord.py, vous pouvez lire de l'audio avec un processus très simple. Ici, nous allons ajouter une fonction qui permet la lecture continue en implémentant une file d'attente de chansons au lieu de simplement la jouer.
Nous prévoyons d'écrire un total de 7 fois et avons fini d'écrire jusqu'à 5 articles.
Dans discord.py, c'est une partie qui n'est pas directement falsifiée, il n'y a donc pas grand chose à savoir, mais en plus de l'API REST utilisée lors de l'envoi de messages avec des requêtes HTTP et de l'obtention d'informations sur le serveur, bidirectionnel Il existe trois méthodes de transmission / réception d'informations: la communication WebSocket pour la communication et la communication RTP pour la transmission / réception de la voix.
Dans le cas de discord.py, c'est un mécanisme très intuitif pour obtenir une instance du canal vocal à partir de Context etc. et se connecter au canal vocal en exécutant le collout connect
de cette instance. Ce qui est retourné par ce connect
est une instance de la classe VoiceClient
.
Dans VoiceClient
, les parties ci-dessus telles que la communication WebSocket et d'autres communications cryptées sont masquées. Par conséquent, si vous souhaitez manipuler les informations de canal vocal pour chaque serveur, vous pouvez effectuer diverses opérations sur cette instance VoiceClient
.
Lors de l'examen du traitement des canaux vocaux de plusieurs serveurs, un mécanisme de connexion de cette instance au serveur (ID) est nécessaire. Ici, le VoiceClient est exploité à l'aide d'un tableau de dictionnaire pour la gestion comme Implémentation officielle dans le référentiel discord.py. Essayez ensuite de lire la musique localement (sur le serveur exécutant le Bot).
Comme mentionné ci-dessus, entrer et sortir du canal vocal est très facile. Ici, nous allons créer un rouage appelé «Voice», entrer dans la pièce avec «$ join» et partir avec «$ Leave». Un exemple de mise en œuvre est le suivant.
python:./src/app/dbot/cogs/Voice.py
import discord
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot
class Voice(commands.Cog):
def __init__(self, bot: DBot):
self.bot = bot
self.voice_clients: Dict[int, discord.VoiceClient] = {}
@commands.command()
async def join(self, ctx: commands.Context):
#Ne participe pas à Voice Channel
if not ctx.author.voice or not ctx.author.voice.channel:
return await ctx.send('Rejoignez d'abord le canal vocal')
vc = await ctx.author.voice.channel.connect()
self.voice_clients[ctx.guild.id] = vc
@commands.command()
async def leave(self, ctx: commands.Context):
vc = self.voice_clients.get(ctx.guild.id)
if vc is None:
return await ctx.send('Je n'ai pas encore rejoint le canal vocal')
await vc.disconnect()
del self.voice_clients[ctx.guild.id]
def setup(bot):
return bot.add_cog(Voice(bot))
La propriété voice
of discord.Member
renvoie une instance de la classe VoiceState si le membre est joint à un canal vocal. Ce VoiceState a une propriété appelée «channel» en plus de l'état tel que muet du membre, et en faisant référence à cela, une instance de la classe de canal vocal (discord.VoiceChannel) actuellement dans le membre est obtenue. peut faire.
L'appel du collout connect
de VoiceChannel
renvoie VoiceClient
et le Bot rejoint le canal vocal. Le VoiceClient
est stocké dans le dictionnaire en utilisant l'ID du serveur comme clé. Au contraire, «$ Leave» recherche «VoiceClient» à partir de l'ID du serveur et appelle le collout «disconnect». Le processus d'entrée / sortie est maintenant terminé.
Tout d'abord, préparez la musique à lire et enregistrez-la dans le dossier . / Src / app / music /
.
Donnez-lui un nom approprié pour l'implémenter afin qu'il recherche en fonction du nom. Pour le moment, jouez avec «$ play song title» et arrêtez avec «$ stop».
python:./src/app/dbot/cogs/Voice.py
import discord
from glob import glob
import os
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot
class Voice(commands.Cog):
#Abréviation
@commands.command()
async def play(self, ctx: commands.Context, *, title: str = ''):
vc: discord.VoiceClient = self.voice_clients.get(ctx.guild.id)
if vc is None:
await ctx.invoke(self.join)
vc = self.voice_clients[ctx.guild.id]
music_pathes = glob('./music/**.mp3')
music_titles = [
os.path.basename(path).rstrip('.mp3')
for path in music_pathes
]
if not title in music_titles:
return await ctx.send('Il n'y a pas de chanson spécifiée.')
idx = music_titles.index(title)
src = discord.FFmpegPCMAudio(music_pathes[idx])
vc.play(src)
await ctx.send(f'{title}Jouer')
@commands.command()
async def stop(self, ctx: commands.Context):
vc: discord.VoiceClient = self.voice_clients.get(ctx.guild.id)
if vc is None:
return await ctx.send('Bot n'a pas encore rejoint le canal vocal')
if not vc.is_playing:
return await ctx.send('Déjà arrêté')
await vc.stop()
await ctx.send('Arrêté')
#Abréviation
Pour lire de la musique à l'aide de duscird.py, vous devez transmettre la source audio au VoiceClient Play Collout. Ffmpeg est requis pour créer AudioSource. Si vous avez un environnement ffmpeg, vous pouvez jouer de la musique très facilement en donnant simplement le chemin d'accès au fichier.
De même, pour arrêter la lecture, appelez le collout vc.stop.
Dans l'implémentation actuelle, la musique change dès que vous appelez $ play
. Remplacez cela par une spécification qui ajoute de la musique à la file d'attente lorsque $ play
est exécuté et lit la musique suivante lorsque la musique précédente est terminée (ce que l'on appelle la playlist).
Pour l'implémentation de la playlist, utilisez ʻasyncio.Queue et ʻasyncio.Event
. Ils sont souvent utilisés pour les implémentations qui attendent qu'un élément soit ajouté à la file d'attente, et pour les implémentations qui attendent qu'un indicateur soit défini ailleurs.
python:./src/app/dbot/cogs/Voice.py
import discord
from glob import glob
import os
import asyncio
from discord.ext import commands
from typing import Dict
from dbot.core.bot import DBot
class AudioQueue(asyncio.Queue):
def __init__(self):
super().__init__(100)
def __getitem__(self, idx):
return self._queue[idx]
def to_list(self):
return list(self._queue)
def reset(self):
self._queue.clear()
class AudioStatus:
def __init__(self, ctx: commands.Context, vc: discord.VoiceClient):
self.vc: discord.VoiceClient = vc
self.ctx: commands.Context = ctx
self.queue = AudioQueue()
self.playing = asyncio.Event()
asyncio.create_task(self.playing_task())
async def add_audio(self, title, path):
await self.queue.put([title, path])
def get_list(self):
return self.queue.to_list()
async def playing_task(self):
while True:
self.playing.clear()
try:
title, path = await asyncio.wait_for(self.queue.get(), timeout=180)
except asyncio.TimeoutError:
asyncio.create_task(self.leave())
src = discord.FFmpegPCMAudio(path)
self.vc.play(src, after=self.play_next)
await self.ctx.send(f'{title}Jouer...')
await self.playing.wait()
def play_next(self, err=None):
self.playing.set()
async def leave(self):
self.queue.reset()
if self.vc:
await self.vc.disconnect()
self.vc = None
@property
def is_playing(self):
return self.vc.is_playing()
def stop(self):
self.vc.stop()
class Voice(commands.Cog):
def __init__(self, bot: DBot):
self.bot = bot
self.audio_statuses: Dict[int, AudioStatus] = {}
@commands.command()
async def join(self, ctx: commands.Context):
#Ne participe pas à Voice Channel
if not ctx.author.voice or not ctx.author.voice.channel:
return await ctx.send('Rejoignez d'abord le canal vocal')
vc = await ctx.author.voice.channel.connect()
self.audio_statuses[ctx.guild.id] = AudioStatus(ctx, vc)
@commands.command()
async def play(self, ctx: commands.Context, *, title: str = ''):
status = self.audio_statuses.get(ctx.guild.id)
if status is None:
await ctx.invoke(self.join)
status = self.audio_statuses[ctx.guild.id]
music_pathes = glob('./music/**.mp3')
music_titles = [
os.path.basename(path).rstrip('.mp3')
for path in music_pathes
]
if not title in music_titles:
return await ctx.send('Il n'y a pas de chanson spécifiée.')
idx = music_titles.index(title)
await status.add_audio(title, music_pathes[idx])
await ctx.send(f'{title}A été ajouté à la playlist')
@commands.command()
async def stop(self, ctx: commands.Context):
status = self.audio_statuses.get(ctx.guild.id)
if status is None:
return await ctx.send('Bot n'a pas encore rejoint le canal vocal')
if not status.is_playing:
return await ctx.send('Déjà arrêté')
await status.stop()
await ctx.send('Arrêté')
@commands.command()
async def leave(self, ctx: commands.Context):
status = self.audio_statuses.get(ctx.guild.id)
if status is None:
return await ctx.send('Je n'ai pas encore rejoint le canal vocal')
await status.leave()
del self.audio_statuses[ctx.guild.id]
@commands.command()
async def queue(self, ctx: commands.Context):
status = self.audio_statuses.get(ctx.guild.id)
if status is None:
return await ctx.send('Rejoignez d'abord le canal vocal')
queue = status.get_list()
songs = ""
for i, (title, _) in enumerate(queue):
songs += f"{i+1}. {title}\n"
await ctx.send(songs)
def setup(bot):
return bot.add_cog(Voice(bot))
Créez une nouvelle classe appelée ʻAudioStatus` pour enregistrer ensemble VoiceClient et les informations du serveur, et enregistrez l'instance.
ʻAudioStatus appelle une fonction appelée ʻasyncio.create_task
à l'initialisation. Comme son nom l'indique, la tâche de jouer de la musique est créée à partir du collout. En faisant cela, un accès asynchrone tel que la réponse aux commandes d'autres serveurs tout en effectuant d'autres traitements du côté des tâches est possible. La propriété playing
est ʻasyncio.Event, qui est utilisée lorsque vous voulez définir un drapeau lorsqu'une condition spécifique est remplie et attendre sans rien faire jusqu'à ce que le drapeau soit défini.
play_task appelle
clear et
wait, et
play_nextappelle
set, mais" met le drapeau à "False" "," attend que le drapeau devienne "True" ", et" C'est un processus de "réglage du drapeau sur" True "".
play_next est passé à l'argument after de
vc.play, qui donne à l'argument ce que vous voulez faire lorsque la lecture d'une chanson est terminée. En définissant le drapeau de lecture du morceau suivant à la fin du morceau, la boucle
play_task` recommencera.
La propriété queue
de cet ʻAudioStatus hérite de ʻasyncio.Queue
et facilite l'obtention de la liste des chansons, mais cette ʻasyncio.Queue n'a aucune valeur à renvoyer lors de l'appel
get. Attend là que de nouveaux éléments soient ajoutés à la volée. Cela permet de définir un temps d'attente pour l'entrée de morceau. Et s'il n'y a pas d'entrée pendant 3 minutes, il appelle le collout
Leave` et quitte automatiquement le canal vocal.
Cela permet d'ajouter une fonction de lecture de musique avec un repère de morceau.
La fonction de lecture de musique est maintenant implémentée! Je suis content que ce soit facile! La fonction d'enregistrement vocal est tout aussi simple, n'est-ce pas? ?? ??
Il semble que cela ne puisse pas être dit, donc pour préparer la mise en œuvre de la fonction d'enregistrement, nous examinerons de plus près comment la fonction API de Discord est gérée en la touchant directement et en l'implémentant sans utiliser discord.py. ..
Recommended Posts