In this article, we will create a Discord Bot in Python with recording capabilities from scratch several times.
discord.py is a wrapper library for Discord API. In this article, we will divide it into several times, from creating a bot with discord.py to adding your own functions. The order is to start with the environment construction, implement a set of functions, and then implement the recording function.
As I added explanations about various things, it became a ridiculously long article, so I decided to post it separately.
We plan to write 7 times in total, and have finished writing up to 5 articles.
There may be an error in the implementation method in this article. In that case, please point out in the comment section.
Discord.py has all the features that can reach the itch. For example, when you want to make music ** playback ** Bot, you should originally "authenticate with WebSocket → connect to WebSocket for Voice Channel and prepare to send voice → make UDP connection and Opus encode with RTP You have to perform complicated processing such as "encrypt and send 20ms of music data" [^ 1], but this discord.py has all such functions implemented and can be used easily.
[^ 1]: You don't have to read the inside of these brackets now. It's OK to recognize that you are doing difficult things.
However, regarding the ** recording ** function (reception of voice data),
https://github.com/Rapptz/discord.py/issues/1094
As mentioned in this issue, there have been voices requesting implementation for several years, but PR to the head family has not yet been carried out. For the time being, the recording function itself seems to be implemented in Discord.js, which is a wrapper for the JS version. If it wasn't impossible, I tried to implement it myself, so I started thinking about writing an introductory article on discord.py along with an introduction to it.
--I will not give a rudimentary explanation of Python itself.
Please see here for Pipenv.
Please see here for docker-compose.
First, create a bot from Discord's Developer Portal. Create it by naming it appropriately from New Application. (The name of the bot is DBot here)
When the creation is completed, the screen will look like this. You can also change the icon image.
Next, we will issue the tokens required to create the bot. If this token is leaked ** For example, if the token is leaked for a bot with administrator privileges, a third party can operate the server with the administrator privileges for all the servers to which the bot is added. It will be possible. It is necessary to manage it with the utmost care so that it will never leak. ** **
To issue a token, select [Add Bot] from the Bot menu.
For Bot tokens, click the button below from the screen after creating the Bot screen.
This completes the creation of the bot, but the authentication URL for adding the bot to the server has not been issued. You can issue the authentication URL from the OAuth2 menu.
Select bot in SCOPE and select bot authority in BOT PERMISSIONS. There is no problem if you put "Administrator" authority in the development stage. There are many existing bots that require Administrator, but it may seem a bit dangerous just because it is a bot that requires administrator privileges, so it may be necessary to review it when it is open to the public.
You can invite the bot to the server by clicking the URL that appears in the center of the screen. ** In order to install Bot, you need to have the management authority of the server you want to install. ** [^ 2]
[^ 2]: Server owner or user with the role of "administrator" or "server administrator"
In this project, we will develop based on the following directory structure. You don't need to create it yet because it will be created in order.
.
|- src #All roots of source code for services
| |- app #Source code for the application
| | |- [bot name] #Choose the name of your bot as you like.
| | | |- cogs # discord.Collect files for adding functions to py in this folder
| | | |- core #The definition of the bot itself, auxiliary functions, extensions, etc. are summarized here.
| | | |- __init__.py #Treat as a module
| | | |- __main__.py #File for startup
| | |
| | |- config.ini #Various settings are made outside the module
| | |- Pipfile # (It is automatically generated so you don't have to make it)
| | |- entrypoint.dev.sh
| |
| |- dev.dockerfile
|
|- docker-compose.dev.yml
If you just want to run it, you only need one file, but here we have a slightly complicated directory structure assuming that you want to run a larger service.
First, run Python. Create a virtual environment with Pipenv and run the bot in that environment.
$ mkdir src
$ cd src
$ mkdir app
$ cd app
$ mkdir dbot
$ echo 'print("Yay")' > ./dbot/__main__.py
$ pipenv --python 3.8.2
This completes the Python environment. I am using a special file name called __main__.py
, but by writing Python code in this file The call to __main__.py
#If Python is originally on your PC
$ python -m dbot
Yay
#When running in the created Pipenv environment
$ pipenv run python -m dbot
You can do it with. When executing a self-made module, this one is cleaner and better.
Now that you can run the Python code, let's actually install discord.py and make the bot work.
I divided the directories in detail in the previous section, but since the purpose of this article is to check the operation, I will write all the code in __main__.py
.
$ pipenv install discord.py[voice]
After the installation is complete, edit __main__.py
with an editor.
__main__.py
import discord
#A character string like this is a token(The token below is suitable)
TOKEN = "MTE0NTE0MzY0MzY0ODEwOTMx.Adiade.Oosak0_Majide_Hampana1tteee"
#Creating an object to move the bot
client = discord.Client()
@client.event
async def on_ready():
#This function is called when the bot is ready to start
print("Started")
@client.event
async def on_message(message):
#This function is displayed when a message is sent
#Message contains various information about the message sent by the user.
#Does not respond to messages from bots
if message.author.bot:
return
print("Message was sent")
print("sender", message.author.display_name)
print("Contents", message.content)
#If a sentence with the content Yay is sent...
if message.author != client and message.content == 'Yay':
#Send the message back to the channel to which the message was sent
await message.channel.send("You're on discord.py!")
client.run(TOKEN)
If you can input up to this point, save it and execute it again.
When it starts and the console displays Started
, try typing the word Yay on the server where the bot is actually installed.
The following is displayed on the console.
$ pipenv run python -m dbot
Started
Message was sent
Sender Admin
Content Yay
Yay! You're on discord.py!
It's a very short code, but it's enough to make a simple bot work. Below are some supplements.
In this code, there are some places where @ client.event
is attached on top of the function [^ 3] called ʻasync def on_Nanchara`. These are based on Python functionality, and each one
[^ 3]: Strictly speaking, ʻasync def ~~` is a coroutine function
async
~ await
--Grammar used when writing asynchronous processing@client.event
--@
is a function called a decorator
--By writing this in advance, the function below it will be fired when an event for the bot occurs.There is a function / meaning. If you are a beginner, it is okay to recognize that you should write this for the time being.
By adding a @ client.event
decorator to a function that starts with ʻon_, such as ʻon_ready
, ʻon_message`, ** when a specific event occurs **, ** perform specific processing **, etc. Can be easily described. A list of supported events can be found on the API reference page (there are quite a few).
https://discordpy.readthedocs.io/ja/latest/api.html#event-reference
Also, there are places where ʻawait is used to call the function, but how to determine whether it is a function that requires ʻawait
is Reference Page. In the description of the function (/latest/api.html),
This function is a coroutine.
If there is a place where it is written, the function must be prefixed with ʻawait`.
Also, in discord.py, information such as the content of the message and the sender of the message can be acquired as class properties. Therefore, even if you do not know the actual state of the code, it is easy to understand what kind of processing is being performed just by looking at the code. On the other hand, until you get used to it, you need to look at which property or method is in which class as a reference. ([Supplement](# 1-% E3% 82% 88% E3% 81% 8F% E4% BD% BF% E3% 81% 86% E3% 82% AF% E3% 83% A9% E3% 82% B9% E3% 81% A8% E3% 82% A4% E3% 83% 99% E3% 83% B3% E3% 83% 88) introduces some of the discord.py classes.)
Well, in short, if you're in trouble, take a look at the reference. The answer is 95% there. ** RTFM !!!**
Also, here we defined the event by directly instantiating discord.Client
, but the same processing is possible even if you define your own class that inherits discord.Client
. (This writing method is used for the reference page.)
__main__.py
import discord
TOKEN = "..."
class Bot(discord.Client):
async def on_ready(self):
print("Started")
async def on_message(self, message):
if message.author.bot:
return
print("Message was sent")
print("sender", message.author.display_name)
print("Contents", message.content)
if message.content == 'Yay':
await message.channel.send("You're on discord.py!")
Bot().run(TOKEN)
For the time being, let's run this service on a Docker container, assuming that we will add Bot functions later.
Create three types of Docker-related files as seen in the directory configuration example at the beginning. As it is named dev
, we are considering separating the environment during development and production execution.
How to write Dockerfile
[this article](https://qiita.com/Aruneko/items/796d7eeb61e1f36ae4a0#dockerfile%E3%81%AE%E6%9B%B8%E3%81%8D%E6%96% Following B9), create an image for execution after installing the Python package with the builder.
dev.dockerfile
FROM python:3.8 as builder
WORKDIR /bot
RUN apt update -y && \
apt upgrade -y
COPY ./app/Pipfile ./app/Pipfile.lock /bot/
RUN pip install pipenv && \
pipenv install --system
FROM python:3.8-slim
WORKDIR /bot
RUN apt update -y && \
apt upgrade -y && \
apt install -y nodejs npm curl && \
npm install -g n && \
n stable && \
apt purge -y nodejs npm && \
apt install -y ffmpeg && \
apt autoremove -y
RUN npm install -g nodemon
ENV PYTHONBUFFERED=1
COPY --from=builder /usr/local/lib/python3.8/site-packages /usr/local/lib/python3.8/site-packages
COPY . /bot
In the build part of the image for execution, nodemon and ffmpeg are installed in the middle. nodemon is used to detect file changes and restart without having to stop and restart the bot during development, and ffmpeg is used to perform media-related processing such as music playback.
ʻEntrypoint.dev.sh` starts using nodemon.
shell:entrypoint.dev.sh
nodemon --signal SIGINT -e py,ini --exec python -m dbot
Finally, write docker-compose.dev.yml
as follows
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
volumes:
- ./src:/bot
With these settings, the following src
will be mounted on the bot
of the container, and when the script is updated on the host, the change will be detected and the bot will be restarted. By making it a docker environment, it can be easily expanded when you want to add a database or Web front later.
After writing so far, return to the project root and execute the following command.
$ chmod +x ./src/app/entrypoint.dev.sh
$ docker-compose -f docker-compose.dev.yml -p dev build #Creating an image
$ docker-compose -f docker-compose.dev.yml -p dev up #Start-up
It has a long description of docker-compose -f docker-compose.dev.yml -p dev
, but it is necessary to describe it like this because the environment is divided. If it is troublesome, you can be happy if you make a script like the following
run.sh
#!/bin/bash
cmd="docker-compose -f docker-compose.$1.yml -p $1 ${@:2}"
echo $cmd
eval $cmd
$ ./run.sh dev build
$ ./run.sh dev up
After confirming the startup, let's edit __main__.py
as a trial.
dbot_1 | [nodemon] 2.0.4
dbot_1 | [nodemon] to restart at any time, enter `rs`
dbot_1 | [nodemon] watching path(s): *.*
dbot_1 | [nodemon] watching extensions: py,ini
dbot_1 | [nodemon] starting `python -m dbot`
dbot_1 |Started
dbot_1 | [nodemon] restarting due to changes...
dbot_1 | [nodemon] starting `python -m dbot`
dbot_1 |It has started
Detecting the save, the bot was restarted. This makes it possible to greatly improve development efficiency.
In this article, we have set up an environment suitable for basic bot construction and development.
Next time, I will explain the design for making a larger bot and the embedded elements that are not introduced in this article.
-** Notation ** - List[type] --List of element type type - Optional[type] --Type is type or None - Union[type1, type2, ...] --Type is one of type1, type2, ... - *args --Variadic
Not all function arguments are listed, only frequently used ones are picked up.
discord.Message
https://discordpy.readthedocs.io/ja/latest/api.html#message
Property name | Mold | Description |
---|---|---|
id | int | Unique identifier |
author | discord.Member | The person who posted the message |
content | str | Message content |
guild | discord.Guild | The server where the message was posted(guild) |
channel | If it's on the serverdiscord.TextChannel | The channel where the message was posted(DM etc. will be another class, but I will not mention it here) |
mentions | List[discord.Member] | List of people who skipped mentions |
reactions | List[discord.Reaction] | Emoji reaction to the message |
created_at | datetime.datetime | Post date and time |
edited_at | Optional[datetime.datetime] | Edit date and time(If uneditedNone ) |
jump_url | str | Link to jump to that message |
await delete()
await edit(content: str, ...)
edit (content = "hoge") code>. dd>
- It must be a message posted by the bot itself. dd>
await add_reaction (emoji: str etc.) code> dt>
- Add reaction to message dd>
- Unicode pictograms can be entered as is: ramen: dd>
await remove_reaction (emoji: str etc, member: discord.Member) code> dt>
- Delete the specified emoji reaction of the specified member dd>
await clear_reaction (emoji: str etc.) code> dt>
- Delete the specified emoji reaction dd>
discord.Member
Property name | Mold | Description |
---|---|---|
id | int | Unique identifier |
name | str | username |
nick | Optional[str] | Name set on the server |
display_name | str | If there is nick, nick,If not name |
mention | str | A string to mention to a member |
guild | discord.Guild | The server to which the member belongs |
roles | List[discord.Role] | List of all roles of members |
top_role | discord.Role | Member's best role |
avatar_url | str | Avatar image URL |
joined_at | datetime.datetime | Date and time when the member joined the server |
created_at | datetime.datetime | The date the member registered their Discord account |
The reason of the following method is displayed in the audit log.
await ban(reason: Optional[str], ...)
await unban(reason: Optional[str], ...)
await kick(reason: Optional[str], ...)
await add_roles(*roles: discord.Role)
await remove_roles(*roles: discord.Role)
discord.TextChannel
Property name | Mold | Description |
---|---|---|
id | int | Unique identifier |
name | str | Channel name |
guild | discord.Guild | Server with channel |
members | List[discord.Member] | List of members who have permission to view the channel |
mention | str | A string to mention to the channel |
created_at | datetime.datetime | Creation date and time |
await send(content: str, embed: discord.Embed, file: discord.File)
await purge(limit: int, check)
history(limit: int = 100) -> AsyncIterator
Call ʻasync for ... in ~~` as follows.
async for message in channel.history():
print(messane.author.name)
discord.Guild
Property name | Mold | Description |
---|---|---|
id | int | Unique identifier |
name | str | server name |
icon_url | str | URL of server icon |
owner | discord.Member | Server owner |
member_count | int | Number of members |
text_channels | List[discord.TextChannel] | All text channels in the server |
members | List[discord.Member] | All members in the server |
roles | List[discord.Role] | All roles in the server |
emojis | List[discord.Emoji] | A list of emojis created independently on the server |
created_at | datetime.datetime | Creation date and time |
There are various methods, but I will introduce them later.
<!-Attachment to the link below->
Recommended Posts