Cet article est une continuation du précédent Discord Bot avec fonction d'enregistrement commençant par Python: (2) Fonctions pratiques (extension de Bot, Cog, Embed).
Dans cet article, nous travaillerons avec la base de données qui sera nécessaire à mesure que le Bot se développera. Implémentez la fonction $ prefix
qui change le préfixe pour chaque serveur utilisant la base de données.
Nous prévoyons d'écrire un total de 7 fois et avons fini d'écrire jusqu'à 5 articles.
Jusqu'à présent, le jeton Bot a été bloqué dans le code source, mais c'est extrêmement gênant lorsque vous souhaitez le partager avec un tiers sur GitHub, etc. Utilisez donc la fonction de Docker Compose pour les collecter en tant que variables d'environnement.
Tout d'abord, créez un fichier appelé .env
dans la racine du projet et enregistrez-y les variables d'environnement.
sh:./.env
BOT_TOKEN=NDIAHJffoaj.adwdeg....
Ici, BOT_TOKEN = token
est défini. Modifiez docker-compose.dev.yml
pour rendre les variables d'environnement enregistrées de cette manière disponibles sur le conteneur Docker.
yml:./docker-compose.dev.yml
version: "3.8"
services:
dbot:
build:
context: ./src
dockerfile: dev.dockerfile
tty: true
working_dir: /bot/app
entrypoint: bash ./entrypoint.dev.sh
env_file: #Avec cette ligne
- .env #Cette ligne
volumes:
- ./src:/bot
La variable d'environnement créée en passant le chemin du fichier précédent à ʻenv_file` est passée au conteneur.
Ensuite, modifiez la partie jeton de __main __. Py
qui a été frappée directement jusqu'à présent comme suit.
python:./src/app/dbot/__main__.py
from dbot.core.bot import DBot
import os
DBot(os.environ["BOT_TOKEN"]).run()
En rassemblant des informations telles que des variables d'environnement que vous ne voulez pas que l'on connaisse, vous pouvez atteindre l'objectif en téléchargeant uniquement ce fichier vers un tiers dans un format privé. Par exemple, si vous ne voulez pas pousser ce .env
vers GitHub, créez un nouveau fichier appelé .gitignore
et ajoutez .env
pour qu'il ne soit pas surveillé par Git et ne soit pas poussé à distance.
.gitignore
.env
Si vous redémarrez le conteneur et qu'il peut être démarré normalement, il réussit. Veuillez noter que si vous modifiez le fichier de variable d'environnement, il sera reflété après le redémarrage du conteneur.
Au fur et à mesure que Bot devient plus complexe, vous souhaiterez peut-être stocker des données sur chaque serveur afin de pouvoir l'utiliser. Pour réaliser cela, par exemple, un fichier CSV peut être préparé et écrit directement, mais il existe divers problèmes étant donné que les demandes des utilisateurs arrivent de manière asynchrone. Alors cette fois, essayons de sauvegarder les données en utilisant la base de données.
MySQL est utilisé ici, mais tout est OK tant que vous avez votre moteur de base de données préféré. Configurons le service MySQL en tant que conteneur Docker. Modifiez docker-compose.dev.yml
comme suit. L'écriture suivante est basée sur cet article .. Ceux qui ont créé .gitignore
devraient exclure / db
.
yml:./docker-compose.dev.yml
version: "3.8"
services:
dbot:
#Abréviation
mysql:
image: mysql:8.0
restart: always
env_file:
- .env
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
volumes:
- ./db/data:/var/lib/mysql
- ./db/my.cnf:/etc/mysql/conf.d/my.cnf
- ./db/sql:/docker-entrypoint-initdb.d
Comme mentionné dans l'article précédent, le conteneur MySQL vous demande d'entrer le nom de la base de données à créer en premier et le mot de passe de l'utilisateur dans les variables d'environnement, alors mettez-les ensemble dans .env
.
sh:./.env
BOT_TOKEN=...
MYSQL_ROOT_PASSWORD=supersecret
MYSQL_USER=docker
MYSQL_DATABASE=discord
MYSQL_PASSWORD=veryverysecret
Après avoir créé jusqu'à ce point, tapez ./run.sh dev down
et ./run.sh dev up -d mysql
pour démarrer uniquement la base de données.
Vous devez manipuler SQL pour faire fonctionner la base de données, mais il est plus facile de définir uniquement le schéma de la base de données autant que possible et d'effectuer le travail de migration automatiquement.
Par conséquent, cette fois, nous utiliserons SQLAlchemy, qui est un mappage de relation d'objet (** ORM **) de SQL. Et, pour exécuter SQLAlchemy, un client qui exploite la base de données est requis, mais ici nous utilisons aiomysql, qui est un client qui répond à l'exigence de ** asynchrone et non bloquant **.
Utilisez ensuite Alembic écrit en Python comme outil de migration de base de données. Tout d'abord, installez ces trois.
À . / Src / app
$ pipenv install sqlalchemy aiomysql
$ pipenv install alembic --dev
Est exécuté.
Après l'installation, créez le dossier . / Src / app / dbot / models
et créez les fichiers suivants.
__init__.py
model.py
Modifiez model.py
comme suit.
from sqlalchemy import MetaData, Table, Column, BigInteger, String
meta = MetaData()
guild = Table(
"guild",
meta,
Column("id", BigInteger(), nullable=False, primary_key=True),
Column("prefix", String(8), server_default="$", nullable=False)
)
Bien qu'il s'agisse d'une grammaire propre à SQLAlchemy, une table est définie en combinant Table et Column. Le nom de la table est écrit dans le premier argument de Table et les informations de colonne de la table sont écrites après le troisième argument, mais quelle est l'identité de meta dans le deuxième argument est une variable qui stocke toutes les informations de définition de la base de données. Cela correspond. En transmettant cette méta à l'extérieur, vous pouvez utiliser les informations de la base de données créée par SQL Alchemy.
La table créée ici est une table pour changer le préfixe («$») pour chaque serveur.
Alembic va migrer la base de données en fonction de cette méta. En d'autres termes, lorsque vous souhaitez créer une nouvelle table, le développeur n'a pas à créer la table directement à l'aide de SQL et peut se concentrer sur la définition du schéma.
Pour utiliser Alembic, vous devez taper la commande ʻalembic initpour effectuer les réglages initiaux. Si vous créez un dossier
. / Src / app / alembic et exécutez ʻalembic init .
dans ce dossier, divers fichiers seront générés.
Les fichiers à éditer sont ʻenv.py et ʻalembic.ini
. ʻAlembic.ini est spécifié au format
mysql + pymysql: // nom d'utilisateur: mot de passe @ nom du conteneur de base de données / nom de la base de données` comme indiqué ci-dessous.
ini:./src/app/alembic/alembic.ini
# A generic, single database configuration.
[alembic]
#Abréviation
# the output encoding used when revision files
# are written from script.py.mako
# output_encoding = utf-8
sqlalchemy.url = mysql+pymysql://docker:veryverysecret@mysql/discord
#Abréviation
ʻEnv.py` doit importer la méta de plus tôt, mais comme le chemin vers dbot se trouve dans le répertoire parent, modifiez-le comme suit
python:./src/app/alembic/env.py
from logging.config import fileConfig
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from alembic import context
##Ajoutez ce qui suit##
import sys
import os
sys.path.append(os.pardir)
from dbot.models.model import meta
##Jusque là##
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
#Abréviation
##Changer cette valeur
target_metadata = meta
#Abréviation
Ensuite, si vous exécutez ʻalembic revision -m "Init" sur le dossier ʻalembic
, le fichier de schéma généré à la suite de la lecture de meta sera créé dans le dossier versions
.
Vous pouvez migrer ceci vers une base de données existante, mais la commande pour cela est ʻalembic upgrade head. Editez ʻentrypoint.dev.sh
pour exécuter ces commandes.
sh:./src/app/entrypoint.dev.sh
set -eu
cd alembic
alembic upgrade head
alembic revision --autogenerate
alembic upgrade head
cd ..
nodemon --signal SIGINT -e py,ini --exec python -m dbot
Enfin, installez ʻalembic sur la dernière ligne de
dev.dockerfile`.
dockerfile:./src/dev.dockerfile
#Omission
RUN pip install alembic
Maintenant, vous êtes prêt à partir. Chaque fois que vous démarrez le conteneur Docker, la migration sera exécutée.
Maintenant que nous l'avons rendu automatiquement sensible aux changements de schéma, mettons-les à la disposition de Bot.
Créez . / Src / app / dbot / db.py
pour définir la classe à connecter à la base de données.
python:./src/app/dbot/db.py
import os
import asyncio
from aiomysql.sa import create_engine
class DB:
async def __aenter__(self, loop=None):
if loop is None:
loop = asyncio.get_event_loop()
engine = await create_engine(
user=os.environ["MYSQL_USER"],
db=os.environ["MYSQL_DATABASE"],
host="mysql",
password=os.environ["MYSQL_PASSWORD"],
charset="utf8",
autocommit=True,
loop=loop
)
self._connection = await engine.acquire()
return self
async def __aexit__(self, *args, **kwargs):
await self._connection.close()
async def execute(self, query, *args, **kwargs):
return await self._connection.execute(query, *args, **kwargs)
L'implémentation ci-dessus est [cet article](https://qiita.com/halhorn/items/eb2951a024ae255e6a21#aiomysql-%E3%82%A8%E3%83%B3%E3%82%B8%E3%83%B3 % E3% 82% 92% E4% BD% BF% E3% 81% A3% E3% 81% A6% E5% AE% 9F% E9% 9A% 9B% E3% 81% AB% E5% AE% 9F% E8 % A1% 8C% E3% 81% 99% E3% 82% 8B) est utilisé comme référence. Des collouts inconnus tels que «aenter» sont utilisés avec «with». aenter signifie ʻa (= async) + ʻenter
, donc pour obtenir une connexion à cette base de données
async with DB() as db:
db.execute("Requete")
Vous pouvez l'utiliser en faisant comme.
Enfin, je ne veux pas écrire autant que possible SQL, peu importe la quantité d'ORM. Par conséquent, créez une classe qui peut CRUD (créer / acquérir / mettre à jour / supprimer) des données pour chaque table. Créez . / Src / app / dbot / models / guild.py
et modifiez-le comme suit.
python:./src/app/dbot/models/guild.py
from dbot.models import model
from dbot.db import DB
class CRUDBase:
@staticmethod
async def execute(query, *args, **kwargs):
async with DB() as db:
result = await db.execute(query, *args, **kwargs)
return result
class Guild(CRUDBase):
def __init__(self, guild_id):
self.guild_id = guild_id
async def get(self):
q = model.guild.select().where(self.guild_id == model.guild.c.id)
result = await self.execute(q)
return await result.fetchone()
async def set(self, **kwargs):
q = model.guild.update(None).where(
self.guild_id == model.guild.c.id
).values(**kwargs)
await self.execute(q)
return self
async def delete(self):
q = model.guild.delete(None).where(self.guild_id == model.guild.c.id)
await self.execute(q)
return self
@classmethod
async def create(cls, guild_id):
q = model.guild.insert(None).values(id=guild_id)
guild = cls(guild_id)
await cls.execute(q)
return guild
@staticmethod
async def get_all(cls):
q = model.guild.select()
results = await cls.execute(q)
return await results.fetchall()
L'explication de la façon d'écrire une requête dans SQLAlchemy sort du cadre de cet article, je vais donc l'omettre, mais vous pouvez écrire une requête avec une syntaxe similaire à SQL.
Vous pouvez maintenant récupérer des informations de la base de données en faisant ʻawait Guild (guild.id) .get () `sans vous soucier de SQL à chaque fois pendant le développement.
Pour modifier à nouveau le préfixe, suivez les étapes ci-dessous.
$ prefix>
arriveC'est une méthode pour changer le préfixe pour chaque serveur, mais cela consiste à changer la partie qui a été directement passée en tant que "$" dans __init__
de Bot avec command_prefix
en None
et ajouter séparément un collout appelé get_prefix
Créez-le. discord.py vérifie ce get_prefix à chaque fois qu'un message est touché, vous pouvez donc y obtenir l'ID du serveur et obtenir les informations de la base de données.
Pour que le serveur reçoive l'événement auquel le Bot a été ajouté, le gestionnaire d'événements ʻon_guild_joinintroduit la dernière fois doit être défini. Compte tenu de ceux-ci,
. / Src / app / dbot / core / bot.py` peut être modifié comme suit.
python:./src/app/dbot/core/bot.py
import discord
from discord.ext import commands
from dbot.models.guild import Guild
import traceback
class DBot(commands.Bot):
def __init__(self, token):
self.token = token
super().__init__(command_prefix=None)
self.load_cogs()
async def get_prefix(self, message: discord.Message):
guild = await Guild(message.guild.id).get()
if guild:
print("serveur:", message.guild.name)
print("Préfixe:", guild.prefix)
return guild.prefix
else:
guild = await Guild.create(message.guild.id)
guild = await guild.get()
print("serveur:", message.guild.name)
print("Préfixe:", guild.prefix)
return guild.prefix
async def on_guild_join(self, guild: discord.Guild):
guild = await Guild.create(guild.id)
guild = await guild.get()
print("serveur:", guild.name)
print("Préfixe:", guild.prefix)
#Abréviation
Ce que nous faisons est simple: nous obtenons et insérons des enregistrements basés sur l'ID du serveur.
Il ne vous reste plus qu'à implémenter la commande $ prefix
. Créons un Cog appelé ʻUtilset définissons-y
$ prefix`.
python:./src/app/cogs/Utils.py
import discord
from discord.ext import commands
from discord.ext.commands.errors import (
MissingPermissions,
MissingRequiredArgument
)
import random
from dbot.core.bot import DBot
from dbot.models.guild import Guild
class Utils(commands.Cog):
def __init__(self, bot: DBot):
self.bot = bot
@commands.command(ignore_extra=False)
@commands.has_permissions(administrator=True)
async def prefix(self, ctx: commands.Context, *, prefix: str):
if len(prefix) > 8:
return await ctx.send("Le préfixe ne doit pas dépasser 8 caractères")
guild = await Guild(ctx.guild.id).get()
await Guild(ctx.guild.id).set(prefix=prefix)
await ctx.send(f"Préfixe{guild.prefix}De{prefix}Changé en")
@prefix.error
async def on_prefix_error(self, ctx: commands.Context, error):
if isinstance(error, MissingPermissions):
return await ctx.send('Seul l'administrateur peut exécuter')
if isinstance(error, MissingRequiredArgument):
return await ctx.send('Pour l'argument, passez le nouveau préfixe dans les 8 caractères')
raise error
def setup(bot):
return bot.add_cog(Utils(bot))
Le préfixe peut maintenant être changé: tada:
En se concentrant sur l'argument de la commande prefix, il y a un *
à la position correspondant au troisième argument, qui est aussi [une des syntaxes de Python](https://qiita.com/LouiS0616/items/1bbe0a9bb93054f6c380#%E3%82 % A2% E3% 82% B9% E3% 82% BF% E3% 83% AA% E3% 82% B9% E3% 82% AF -% E4% BB% AE% E5% BC% 95% E6% 95 % B0% E5% 90% 8D% E3% 83% 8A% E3% 82% B7). En utilisant ce «*» dans discord.py, le comportement sera le suivant.
Comme vous pouvez le voir, même s'il y a des espaces entre les deux, ces zones sont considérées comme un seul argument.
Référence: https://discordpy.readthedocs.io/ja/latest/ext/commands/commands.html#keyword-only-arguments
Vous pouvez maintenant vous connecter à la base de données et créer des commandes plus complexes.
La prochaine fois, nous implémenterons la fonction ** send ** de la voix.
Recommended Posts