Привет, Хабр!


Хотел поделиться опытом, как я писал бота c экономикой для discord сервера с использованием sqlite и другой мелочи.

Подготовительный этап


Создание бота


Итак, перед тем, как написать бота, нам нужно его создать и получить токен:

  1. Перейти на сайт для разработчиков
  2. Нажать на кнопку «New Application» и назвать бота
  3. Получить токен бота, войдя в вашего бота и найдя в списке «Settings» вкладку «Bot»

Необходимые модули


Как-никак мы пишем на python, а не на ASM, поэтому нам будут необходимы некоторые модули

$ pip install discord.py #само api для работы с ботом

$ pip install tabulate #небольшой модуль для красивых таблиц

Этап написания


Импорт модулей


Импортировать модули также просто, как и написать одну страницу на HTML+CSS.

import sqlite3 #модуль sqlite
import discord #модуль discord api
from discord.ext import commands #необходимый класс для обработки команд
from tabulate import tabulate #удобный модуль для рисования таблиц
import json #используется только для обработки инвентаря, но ему можно найти и другое применение

Подключение к sqlite


Здесь нет ничего сложного. Просто после импорта прописываем следующее:

conn = sqlite3.connect("Discord.db") # или :memory:
cursor = conn.cursor()

Подготовка базы данных


В базе данных будет 2 таблицы shop и users.

CREATE TABLE "shop" (
	"id"	INT,
	"type"	TEXT,
	"name"	TEXT,
	"cost"	INT
)

CREATE TABLE "users" (
	"id"	INT,
	"nickname"	TEXT,
	"mention"	TEXT,
	"money"	INT,
	"rep_rank"	TEXT,
	"inventory"	TEXT,
	"lvl"	INT,
	"xp"	INT
)

Подготовка к написанию логики бота


Создаем переменную bot.

bot = commands.Bot(command_prefix="_")#в строчке command_prefix можно указать любые знак, букву, слово, словосочетания и т.д.

В конце всего кода мы пишем метод, который запускает нашего бота.

bot.run("Здесь токен, который вы получили на этапе создания бота")

Теперь начнем писать нашего бота.

bot = commands.Bot(command_prefix="_")
#Здесь будет логика вашего бота
bot.run("Здесь токен, который вы получили на этапе создания бота")

Дальше пишем событие on_ready(), отвечающее за готовность бота.

@bot.event
async def on_ready():
    print("Bot Has been runned")#сообщение о готовности
    for guild in bot.guilds:#т.к. бот для одного сервера, то и цикл выводит один сервер
        print(guild.id)#вывод id сервера
        serv=guild#без понятия зачем это
        for member in guild.members:#цикл, обрабатывающий список участников
            cursor.execute(f"SELECT id FROM users where id={member.id}")#проверка, существует ли участник в БД
            if cursor.fetchone()==None:#Если не существует
                cursor.execute(f"INSERT INTO users VALUES ({member.id}, '{member.name}', '<@{member.id}>', 50000, 'S','[]',0,0)")#вводит все данные об участнике в БД
            else:#если существует
                pass
            conn.commit()#применение изменений в БД

После этого стоит, чтобы лишний раз не перезапускать бота, написать метод on_member_join()

@bot.event
async def on_member_join(member):
    cursor.execute(f"SELECT id FROM users where id={member.id}")#все также, существует ли участник в БД
    if cursor.fetchone()==None:#Если не существует
        cursor.execute(f"INSERT INTO users VALUES ({member.id}, '{member.name}', '<@{member.id}>', 50000, 'S','[]',0,0)")#вводит все данные об участнике в БД
    else:#Если существует
        pass
    conn.commit()#применение изменений в БД

Если бот у нас экономический, значит должна быть валюта, ее заработок и ее трата. Заработок можно организовать с помощью experience системы.

@bot.event
async def on_message(message):
    if len(message.content) > 10:#за каждое сообщение длиной > 10 символов...
        for row in cursor.execute(f"SELECT xp,lvl,money FROM users where id={message.author.id}"):
            expi=row[0]+random.randint(5, 40)#к опыту добавляется случайное число
            cursor.execute(f'UPDATE users SET xp={expi} where id={message.author.id}')
            lvch=expi/(row[1]*1000)
            print(int(lvch))
            lv=int(lvch)
            if row[1] < lv:#если текущий уровень меньше уровня, который был рассчитан формулой выше,...
                await message.channel.send(f'Новый уровень!')#то появляется уведомление...
                bal=1000*lv
                cursor.execute(f'UPDATE users SET lvl={lv},money={bal} where id={message.author.id}')#и участник получает деньги
    await bot.process_commands(message)#Далее это будет необходимо для ctx команд
    conn.commit()#применение изменений в БД

Одну из главных частей мы написали. Осталось написать команды по типу аккаунт, магазин и т.д. Думаю дальше интуитивно понятно.

@bot.command()
async def account(ctx): #команда _account (где "_", ваш префикс указаный в начале)
    table=[["nickname","money","lvl","xp"]]
    for row in cursor.execute(f"SELECT nickname,money,lvl,xp FROM users where id={ctx.author.id}"):
        table.append([row[0],row[1],row[2],row[3]])
        await ctx.send(f">\n{tabulate(table)}")

@bot.command()
async def inventory(ctx):#команда _inventory (где "_", ваш префикс указаный в начале)

    counter=0
    for row in cursor.execute(f"SELECT inventory FROM users where id={ctx.author.id}"):
        data=json.loads(row[0])
        table=[["id","type","name"]]
        for row in data:
            prt=row
            for row in cursor.execute(f"SELECT id,type,name FROM shop where id={prt}"):
                counter+=1
                table.append([row[0],row[1],row[2]])
                
                if counter==len(data):
                    await ctx.send(f'>\n{tabulate(table)}')


@bot.command()
async def shop(ctx):#команда _shop (где "_", ваш префикс указаный в начале)
    counter=0
    table=[["id","type","name","cost"]]
    for row in cursor.execute(f"SELECT id,type,name,cost FROM shop"):
        counter+=1
        table.append([row[0],row[1],row[2],row[3]])
        if counter==4:
            await ctx.send(f'>\n{tabulate(table)}')

Если есть магазин, значит можно покупать? Не так-ли?

async def buy(ctx, a: int):
    uid=ctx.author.id
    await ctx.send('Обработка... Если ответа не последует, указан неверный id предмета [buy {id}]')
    for row in cursor.execute(f"SELECT money FROM users where id={uid}"):
        money = row[0]
        for row in cursor.execute(f"SELECT id,name,cost FROM shop where id={a}"):
            cost=row[2]
            if money >= cost:#если у вас достаточно денег,то...
                money -=cost
                await ctx.send(f'Вы приобрели "{row[1]}" за {row[2]}')
                
                for row in cursor.execute(f"SELECT inventory FROM users where id={uid}"):
                    data=json.loads(row[0])
                    data.append(a)
                    daed=json.dumps(data)
                    cursor.execute('UPDATE users SET money=?,inventory = ? where id=?',(money,daed,uid))#добавляет предмет вам в инвентарь
                    pass
            if money < cost:#иначе сообщает о недостатке
                await ctx.send(f'Недостаточно средств')
                pass
    conn.commit()#применение изменений в БД

Заключение


Вот такой простенький бот у нас получился. Надеюсь это кому-либо поможет.

Я понимаю что в него можно (необходимо) добавить множество функций и фишек, но это голый вариант кода, который могут использовать новички для понимания как работает discord.py, модуль sqlite и встроенные методы python.

Всем спасибо за внимание. До связи!

Комментарии (2)


  1. Agamendon
    17.04.2020 23:29

    При попытке установки discord.py выдает:
    C:\Users\Пользователь>pip install discord.py
    Collecting discord.py
    Using cached https://files.pythonhosted.org/packages/7c/93/30fa8b5307328e5f37a37d13afb33677ef68e4cb4e98646427d6b400958c/discord.py-1.2.5-py3-none-any.whl
    Collecting websockets<7.0,>=6.0
    Using cached https://files.pythonhosted.org/packages/4e/2a/56e60bb4c3696bc736998cc13c3fa1a36210609d7e1a3f2519857b420245/websockets-6.0.tar.gz
    Collecting aiohttp<3.6.0,>=3.3.0
    Using cached https://files.pythonhosted.org/packages/0f/58/c8b83f999da3b13e66249ea32f325be923791c0c10aee6cf16002a3effc1/aiohttp-3.5.4.tar.gz
    Installing build dependencies ... done
    Getting requirements to build wheel ... done
    Preparing wheel metadata ... error
    ERROR: Command errored out with exit status 1:
    command: 'e:\python_system\python.exe' 'e:\python_system\lib\site-packages\pip\_vendor\pep517\_in_process.py' prepare_metadata_for_build_wheel 'C:\Users\73B5~1\AppData\Local\Temp\tmpf91b68cp'
    cwd: C:\Users\Пользователь\AppData\Local\Temp\pip-install-5srpnsv4\aiohttp
    Complete output (24 lines):
    running dist_info
    creating C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info
    writing C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\PKG-INFO
    writing dependency_links to C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\dependency_links.txt
    writing requirements to C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\requires.txt
    writing top-level names to C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\top_level.txt
    writing manifest file 'C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\SOURCES.txt'
    reading manifest file 'C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\SOURCES.txt'
    reading manifest template 'MANIFEST.in'
    Error in sitecustomize; set PYTHONVERBOSE for traceback:
    SyntaxError: (unicode error) 'utf-8' codec can't decode byte 0xcf in position 0: invalid continuation byte (sitecustomize.py, line 21)
    warning: no files found matching 'aiohttp' anywhere in distribution
    warning: no previously-included files matching '*.pyc' found anywhere in distribution
    warning: no previously-included files matching '*.pyd' found anywhere in distribution
    warning: no previously-included files matching '*.so' found anywhere in distribution
    warning: no previously-included files matching '*.lib' found anywhere in distribution
    warning: no previously-included files matching '*.dll' found anywhere in distribution
    warning: no previously-included files matching '*.a' found anywhere in distribution
    warning: no previously-included files matching '*.obj' found anywhere in distribution
    warning: no previously-included files found matching 'aiohttp\*.html'
    no previously-included directories found matching 'docs\_build'
    writing manifest file 'C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.egg-info\SOURCES.txt'
    creating 'C:\Users\Пользователь\AppData\Local\Temp\pip-modern-metadata-q8bjye7e\aiohttp.dist-info'
    error: invalid command 'bdist_wheel'
    ----------------------------------------
    ERROR: Command errored out with exit status 1: 'e:\python_system\python.exe' 'e:\python_system\lib\site-packages\pip\_vendor\pep517\_in_process.py' prepare_metadata_for_build_wheel 'C:\Users\73B5~1\AppData\Local\Temp\tmpf91b68cp' Check the logs for full command output.

    Что делать?


    1. Gear_up Автор
      17.04.2020 23:35

      Скорее всего проблема в вашем антивирусе. Попробуйте отключить его на время установки пакета. Если это не помогло, то пробуйте в консоли прописать

      $ pip3 install wheel

      Далее пропишите
      $ python3 setup.py bdist_wheel

      Вполне возможно что у вас установлено две версии python. попробуйте прописать
      $ pip3 install discord.py

      Если все выше перечисленное не помогло, то рекомендую обратиться на форум stackoverflow с этой проблемой.