Discord Bot mit Aufnahmefunktion beginnend mit Python: (3) Zusammenarbeit mit der Datenbank

Einführung

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.

  1. Discord Bot mit Aufnahmefunktion beginnend mit Python: (1) Erste Schritte discord.py
  2. Discord Bot mit Aufnahmefunktion beginnend mit Python: (2) Praktische Funktion (Bot-Erweiterung, Cog, Embed)
  3. Discord Bot mit Aufnahmefunktion beginnend mit Python: (3) Zusammenarbeit mit der Datenbank
  4. Discord Bot mit Aufnahmefunktion beginnend mit Python: (4) Musikdateien abspielen
  5. Discord Bot mit Aufnahmefunktion ab Python: (5) Discord API direkt bedienen

Verwenden Sie Umgebungsvariablen

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.

Verwenden wir die Datenbank

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.

Datenbank erstellen

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.

So verwenden Sie ORM und Migration ...

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.

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.

Überbrückung zwischen DBot und MySQL

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.

Berühren Sie nicht SQL usw.

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.

Befehl $ prefix implementiert

Führen Sie die folgenden Schritte aus, um das Präfix erneut zu ändern.

  1. [Hauptvoraussetzung] Ändern Sie die Spezifikationen so, dass das Präfix für jeden Server geändert werden kann.
  2. Wenn der Server einen Bot hinzufügt, fügen Sie den Datensatz (Server-ID, "$") zur Tabelle "Gilde" hinzu.
  3. Ändern Sie die Tabelle "Gilde", wenn ein Befehl wie "$ Präfix>" eingeht

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:

Image from Gyazo

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.

Image from Gyazo

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

Am Ende

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

Discord Bot mit Aufnahmefunktion beginnend mit Python: (3) Zusammenarbeit mit der Datenbank
Discord Bot mit Aufnahmefunktion beginnend mit Python: (1) Einführung discord.py
Discord Bot mit Aufnahmefunktion ab Python: (4) Musikdateien abspielen
Discord Bot mit Aufnahmefunktion ab Python: (5) Bedienen Sie die Discord API direkt
Discord Bot Himbeere Pi Null mit Python [Hinweis]
Python ab Windows 7
GRPC beginnend mit Python
So bedienen Sie die Discord API mit Python (Bot-Registrierung)
Python-Anfänger startet Discord Bot
Verbessertes Lernen ab Python
Python beginnend mit Hallo Welt!
Erstellen Sie einen Mastodon-Bot mit einer Funktion, die automatisch mit Python antwortet
Datenanalyse beginnend mit Python (Datenvisualisierung 1)
Datenanalyse beginnend mit Python (Datenvisualisierung 2)
Systemhandel ab Python3: langfristige Investition
Erstellen Sie mit Class einen Python-Funktionsdekorator
"Systemhandel beginnt mit Python3" Lesememo
Geschäftseffizienz von Grund auf mit Python
Starten Sie den Discord Python-Bot 24 Stunden lang.
Python lernen! Vergleich mit Java (Grundfunktion)
Datenanalyse beginnend mit Python (Datenvorverarbeitung - maschinelles Lernen)
"Erste elastische Suche" beginnend mit einem Python-Client
Machen wir einen Twitter-Bot mit Python!
Python-Funktion ①
[Python] -Funktion
Wenn Sie einen Discord-Bot mit Python erstellen möchten, verwenden wir ein Framework
Python-Funktion ②
Maschinelles Lernen beginnend mit Python Personal Memorandum Part2
Maschinelles Lernen beginnend mit Python Personal Memorandum Part1
Erstellen Sie mit Minette für Python einen LINE BOT
LINE BOT mit Python + AWS Lambda + API Gateway
Ich habe Funktionssynthese und Curry mit Python versucht
Hinweis zum Formatieren von Zahlen mit der Python-Formatierungsfunktion
Schritte zum Erstellen eines Twitter-Bots mit Python
Ich habe versucht, Linux mit Discord Bot zu betreiben
ARC037 Baum testet höflich mit rekursiver Python-Funktion
Mattermost Bot mit Python gemacht (+ Flask)
Systemhandel ab Python 3: Investition und Risiko
Erstellen Sie einen Discord-Bot, der einseitig mit Python benachrichtigt (nur Anfragen und JSON werden verwendet).