Dieser Artikel ist eine Fortsetzung des vorherigen Discord Bot mit Aufnahmefunktion beginnend mit Python: (2) Praktische Funktionen (Bot-Erweiterung, Cog, Embed).
In diesem Artikel werden wir mit der Datenbank arbeiten, die benötigt wird, wenn der Bot an Größe zunimmt. Implementieren Sie die Funktion "$ prefix", die das Präfix für jeden Server mithilfe der Datenbank ändert.
Wir planen insgesamt 7 Mal zu schreiben und haben bis zu 5 Artikel fertig geschrieben.
Bisher steckte das Bot-Token im Quellcode fest, dies ist jedoch äußerst unpraktisch, wenn Sie es mit einem Dritten auf GitHub usw. teilen möchten. Verwenden Sie also die Funktion von Docker Compose, um diese als Umgebungsvariablen zu erfassen.
Erstellen Sie zunächst eine Datei mit dem Namen ".env" im Projektstamm und registrieren Sie dort die Umgebungsvariablen.
sh:./.env
BOT_TOKEN=NDIAHJffoaj.adwdeg....
Hier wird BOT_TOKEN = Token
gesetzt. Bearbeiten Sie "docker-compose.dev.yml", um die auf diese Weise registrierten Umgebungsvariablen im Docker-Container verfügbar zu machen.
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: #Mit dieser Linie
- .env #Diese Linie
volumes:
- ./src:/bot
Die Umgebungsvariable, die durch Übergeben des Pfads zur vorherigen Datei an "env_file" erstellt wurde, wird an den Container übergeben.
Ändern Sie dann den Token-Teil von "__main __. Py", der bisher direkt getroffen wurde, wie folgt.
python:./src/app/dbot/__main__.py
from dbot.core.bot import DBot
import os
DBot(os.environ["BOT_TOKEN"]).run()
Indem Sie Informationen wie Umgebungsvariablen zusammenstellen, die Sie hier nicht kennen möchten, können Sie den Zweck erreichen, indem Sie nur diese Datei in einem privaten Format an einen Dritten hochladen. Wenn Sie diese ".env" beispielsweise nicht an GitHub senden möchten, erstellen Sie eine neue Datei mit dem Namen ".gitignore" und fügen Sie ".env" hinzu, damit sie nicht von Git überwacht und nicht remote übertragen wird.
.gitignore
.env
Wenn Sie den Container neu starten und er normal gestartet werden kann, ist er erfolgreich. Beachten Sie, dass die Umgebungsvariablendatei nach dem Neustart des Containers angezeigt wird, wenn Sie sie bearbeiten.
Wenn Bot komplexer wird, möchten Sie möglicherweise einige Daten auf jedem Server speichern, damit Sie sie verwenden können. Um dies zu realisieren, kann beispielsweise eine CSV-Datei direkt vorbereitet und geschrieben werden. Es gibt jedoch verschiedene Probleme, wenn man bedenkt, dass Anforderungen von Benutzern asynchron eingehen. Versuchen wir diesmal also, die Daten mithilfe der Datenbank zu speichern.
Hier wird MySQL verwendet, aber alles ist in Ordnung, solange Sie Ihre bevorzugte Datenbank-Engine haben. Lassen Sie uns den MySQL-Dienst als Docker-Container einrichten. Bearbeiten Sie docker-compose.dev.yml
wie folgt. Das folgende Schreiben basiert auf diesem Artikel .. Diejenigen, die ".gitignore" erstellt haben, sollten "/ db" ausschließen.
yml:./docker-compose.dev.yml
version: "3.8"
services:
dbot:
#Abkürzung
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
Wie im vorherigen Artikel erwähnt, müssen Sie für den MySQL-Container den Namen der zuerst zu erstellenden Datenbank und das Kennwort des Benutzers in die Umgebungsvariablen eingeben. Fügen Sie diese also in ".env" zusammen.
sh:./.env
BOT_TOKEN=...
MYSQL_ROOT_PASSWORD=supersecret
MYSQL_USER=docker
MYSQL_DATABASE=discord
MYSQL_PASSWORD=veryverysecret
Geben Sie nach dem Erstellen bis zu diesem Punkt "./run.sh dev down" und "./run.sh dev up -d mysql" ein, um nur die Datenbank zu starten.
Sie müssen SQL manipulieren, um die Datenbank zu betreiben. Es ist jedoch einfacher, nur das Schema der Datenbank so weit wie möglich zu definieren und die Migrationsarbeiten automatisch auszuführen.
Daher verwenden wir dieses Mal SQLAlchemy, eine Objektbeziehungszuordnung (** ORM **) von SQL. Um SQLAlchemy ausführen zu können, ist ein Client erforderlich, der die Datenbank betreibt. Hier verwenden wir jedoch aiomysql, einen Client, der die Anforderungen von ** asynchron und nicht blockierend ** erfüllt.
Verwenden Sie dann Alembic, das in Python geschrieben wurde, als Datenbankmigrationstool. Installieren Sie zuerst diese drei.
Bei . / Src / app
$ pipenv install sqlalchemy aiomysql
$ pipenv install alembic --dev
Wird ausgeführt.
Erstellen Sie nach der Installation den Ordner . / Src / app / dbot / models
und erstellen Sie die folgenden Dateien.
__init__.py
model.py
Bearbeiten Sie model.py
wie folgt.
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)
)
Obwohl es sich um eine SQLAlchemy-spezifische Grammatik handelt, wird eine Tabelle durch Kombinieren von Tabelle und Spalte definiert. Der Tabellenname wird im ersten Argument der Tabelle geschrieben, und die Spalteninformationen der Tabelle werden nach dem dritten Argument geschrieben. Die Identität des Metas im zweiten Argument ist jedoch eine Variable, in der alle Definitionsinformationen der Datenbank gespeichert sind. Es entspricht. Indem Sie dieses Meta nach außen übergeben, können Sie die Informationen der von SQL Alchemy erstellten Datenbank verwenden.
Die hier erstellte Tabelle ist eine Tabelle zum Ändern des Präfixes ($
) für jeden Server.
Alembic migriert die Datenbank basierend auf diesem Meta. Mit anderen Worten, wenn Sie eine neue Tabelle erstellen möchten, muss der Entwickler die Tabelle nicht direkt mit SQL erstellen und kann sich auf die Definition des Schemas konzentrieren.
Um Alembic verwenden zu können, müssen Sie den Befehl alembic init
eingeben, um die Anfangseinstellungen vorzunehmen. Wenn Sie einen Ordner ". / Src / app / alembic" erstellen und "alembic init." In diesem Ordner ausführen, werden verschiedene Dateien generiert.
Die zu bearbeitenden Dateien sind "env.py" und "alembic.ini". alembic.ini
wird im Format mysql + pymysql: // Benutzername: Passwort @ Datenbankcontainername / Datenbankname
wie unten gezeigt angegeben.
ini:./src/app/alembic/alembic.ini
# A generic, single database configuration.
[alembic]
#Abkürzung
# 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
#Abkürzung
env.py
muss das Meta von früher importieren, aber da sich der Pfad zu dbot im übergeordneten Verzeichnis befindet, bearbeiten Sie ihn wie folgt
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
##Fügen Sie Folgendes hinzu##
import sys
import os
sys.path.append(os.pardir)
from dbot.models.model import meta
##Bisher##
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
#Abkürzung
##Ändern Sie diesen Wert
target_metadata = meta
#Abkürzung
Wenn Sie dann "alembic revision -m" Init "" im Ordner "alembic" ausführen, wird die Schemadatei, die beim Lesen von Meta generiert wird, im Ordner "version" erstellt.
Sie können dies in eine vorhandene Datenbank migrieren, aber der Befehl dafür lautet "alembic upgrade head". Bearbeiten Sie "entrypoint.dev.sh", um diese Befehle auszuführen.
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
Zum Schluss installieren Sie alembic
in der letzten Zeile von dev.dockerfile
.
dockerfile:./src/dev.dockerfile
#Unterlassung
RUN pip install alembic
Jetzt können Sie loslegen. Jedes Mal, wenn Sie den Docker-Container starten, wird die Migration ausgeführt.
Nachdem wir es nun möglich gemacht haben, Schemaänderungen automatisch zu erkennen, stellen wir diese im Bot zur Verfügung.
Erstellen Sie . / Src / app / dbot / db.py
, um die Klasse zu definieren, die eine Verbindung zur Datenbank herstellen soll.
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)
Die obige Implementierung ist [dieser Artikel](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) wird als Referenz verwendet. Unbekannte Collouts wie "aenter" werden mit "with" verwendet. aenter bedeutet "a" (= asynchron) + "enter", um eine Verbindung zu dieser Datenbank herzustellen
async with DB() as db:
db.execute("Abfrage")
Sie können es verwenden, indem Sie wie tun.
Schließlich möchte ich nicht so viel SQL wie möglich schreiben, egal wie viel ORM. Erstellen Sie daher eine Klasse, die CRUD-Daten (Erstellen / Erfassen / Aktualisieren / Löschen) für jede Tabelle erstellen kann. Erstellen Sie . / Src / app / dbot / models / guild.py
und bearbeiten Sie es wie folgt.
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()
Die Erklärung zum Schreiben einer Abfrage in SQLAlchemy fällt nicht in den Geltungsbereich dieses Artikels. Ich werde sie daher weglassen. Sie können jedoch eine Abfrage mit einer SQL-ähnlichen Syntax schreiben.
Mit dieser Funktion können Sie Informationen aus der Datenbank abrufen, indem Sie "wait Guild (guild.id) .get ()" ausführen, ohne sich jedes Mal während der Entwicklung um SQL kümmern zu müssen.
Führen Sie die folgenden Schritte aus, um das Präfix erneut zu ändern.
Es ist eine Methode, das Präfix für jeden Server zu ändern, aber dies ist, um den Teil, der direkt als "$" in "init" von Bot mit "command_prefix" übergeben wurde, in "None" zu ändern und separat ein Collout mit dem Namen "get_prefix" hinzuzufügen Erstelle es. discord.py überprüft dieses get_prefix jedes Mal, wenn eine Nachricht getroffen wird, sodass Sie dort die Server-ID abrufen und die Informationen aus der Datenbank abrufen können.
Damit der Server das Ereignis empfängt, zu dem der Bot hinzugefügt wurde, sollte der zuletzt eingeführte Ereignishandler on_guild_join
definiert werden. In Anbetracht dessen kann . / Src / app / dbot / core / bot.py
wie folgt geändert werden.
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("Server:", message.guild.name)
print("Präfix:", guild.prefix)
return guild.prefix
else:
guild = await Guild.create(message.guild.id)
guild = await guild.get()
print("Server:", message.guild.name)
print("Präfix:", 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("Server:", guild.name)
print("Präfix:", guild.prefix)
#Abkürzung
Was wir tun, ist einfach: Wir erhalten und fügen Datensätze basierend auf der Server-ID ein.
Jetzt müssen Sie nur noch den Befehl $ prefix
implementieren. Lassen Sie uns ein Zahnrad namens "Utils" erstellen und darin "$ prefix" definieren.
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("Das Präfix darf nicht länger als 8 Zeichen sein")
guild = await Guild(ctx.guild.id).get()
await Guild(ctx.guild.id).set(prefix=prefix)
await ctx.send(f"Präfix{guild.prefix}Von{prefix}Gewechselt zu")
@prefix.error
async def on_prefix_error(self, ctx: commands.Context, error):
if isinstance(error, MissingPermissions):
return await ctx.send('Nur der Administrator kann ausgeführt werden')
if isinstance(error, MissingRequiredArgument):
return await ctx.send('Übergeben Sie für das Argument das neue Präfix innerhalb von 8 Zeichen')
raise error
def setup(bot):
return bot.add_cog(Utils(bot))
Das Präfix kann jetzt geändert werden: tada:
Wenn Sie sich auf das Argument des Präfixbefehls konzentrieren, befindet sich an der Position, die dem dritten Argument entspricht, ein *
, das auch [eine der Syntax von Python] ist (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). Wenn Sie dieses *
in discord.py verwenden, ist das Verhalten wie folgt.
Wie Sie sehen, werden diese Bereiche auch dann als ein Argument angesehen, wenn Zwischenräume vorhanden sind.
Referenz: https://discordpy.readthedocs.io/ja/latest/ext/commands/commands.html#keyword-only-arguments
Jetzt können Sie eine Verbindung zur Datenbank herstellen und komplexere Befehle erstellen.
Nächstes Mal werden wir die ** Sende ** -Funktion der Stimme implementieren.
Recommended Posts