Python Discord Bot (Supabase) - Part 2 - [EN/PT-BR]

avatar
(Edited)


[EN]
This is the second part of this tutorial on how to create a curation bot in python for Hive blockchain. The first part can be found here: https://ecency.com/hive-139531/@mengao/python-discord-bot-part-1
[PT-BR]
Esta é a segunda parte do tutorial para criar um bot de curadoria para discord em python na Hive blockchain. A primeira parte pode ser encontrada aqui: https://ecency.com/hive-139531/@mengao/python-discord-bot-part-1


[EN]
As I said in the first part, I'm writing this post for those with no experience programming. Even though I'm not teaching any basic programing skills, I will try to explain in details how I progress in each step, all my thought process, to make it easy to understand. If you have no idea about programming, I suggest starting with something like this: https://www.codecademy.com/learn/learn-how-to-code and for basic python skills you can check this out: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers

[PT-BR]
Como eu disse na primeira parte, estou escrevendo este post para quem não tem nenhuma experiencia com programação. Mesmo que eu não esteja ensinando aqui os fundamentos da programação, eu vou tentar explicar em detalhes meu progresso a cada passo, todo o meu processo de pensamento, para que fique fácil de entender. Se voce não tem nenhuma ideia sobre o que é programação, eu sugiro olhar algo assim: https://www.codecademy.com/learn/learn-how-to-code e para o basico de python veja isso: https://wiki.python.org/moin/BeginnersGuide/NonProgrammers


[EN]
In this second part we are going to learn how to use Supabase as our database for the bot.
The bot needs to store some information to work. I don't have any experience with community curation. So I will try to keep it simple, since my bot will use a queue to vote, I need to store the link to be voted. I also read on discord that the bot needs to manage multiple curators, then I need to store these curators. So let's start with this, in this post we will make the bot insert, delete and view the curators in the database.
There are different ways to store information, I could use just a regular text file. It really depends on your needs, but in this case, I think that using Supabase will be super easy and efficient. Supabase is free for small projects and offers a Postgres database with an easy to use API.
[PT-BR]
Nesta segunda parte vamos aprender como utilizar o Supabase como o banco de dados do nosso bot.
O bot precisa armazenar informações para funcionar. Eu não tenho muita experiencia com curadoria de comunidades. Então vou manter a coisa simples, e como meu bot vai usar uma fila para votar, eu preciso armazenar o link que será votado. Eu também li no discord que o bot precisa gerenciar os curadores, por isso também preciso armazenar os curadores. Então vamos começar com isso, neste post vamos fazer o bot inserir, apagar e visualizar os curadores no banco de dados.
Existem formas diferentes de armazenar informações, eu poderia simplesmente utilizar um arquivo de texto. Depende das suas necessidades, mas nesse caso, eu acho que usar o Supabase será super fácil e eficiente. Supabase é grátis para projetos pequenos e oferece um banco de dados Postgres com um API fácil de utilizar.


[EN]
First step is to go to Supabase website and create an account. https://supabase.com/
After creating your account, click on New Project.
[PT-BR]
O primeiro passo é ir para o site do Supabase e criar uma conta. Depois de criar uma conta, clique em Criar Novo Projeto.

[EN]
Once you create your project, Supabase will give you the Project URL and the API Key.
[PT-BR]
Depois de criar o projeto, Supabase vai te mostrar o Project URL e o API Key.

[EN]
Remember the ".env" file from last post? We will open that file again and add two more variables. I will call them SUPABASE_URL and SUPABASE_KEY. We need these variables to connect our script to Supabase. This is what the file will look like now:
[PT-BR]
Voce se lembra do arquivo ".env" do primeiro post? Vamos abrir este arquivo novamente e adicionar mais duas variaveis. Eu vou chamar elas de SUPABASE_URL e SUPABASE_KEY. Nós vamos precisar dessas variaveis para conectar o nosso script ao Supabase. O arquivo vai ficar assim agora:

export DISCORD_TOKEN="___TOKEN YOU COPIED___"
export SUPABASE_URL="___COPY PROJECT URL HERE___"
export SUPABASE_KEY="___COPY API KEY HERE___"

[EN]
Next you will click on Table Editor (look in the left menu) and we will create our first table. As I said, for this post we will only do the part to manage the curators. We will need to create a table to store our curators. The fields for this table, for now, will be only the curator discord Id and their Hive username. So this is what is looks like:

[PT-BR]
A seguir vamos clicar em Table Editor (procure no menu do lado esquerdo) e vamos criar nossa primeira tabela. Como eu disse, nesse post vamos apenas fazer a parte de administrar os curadores. Vamos precisar criar uma tabela para armazenar os curadores. Os campos desta tabela vão ser, por agora, o discord Id e o username da Hive dos curadores. Então ela vai ficar assim:

[EN]
Now we are ready to move back to our python script. Before we start, we need to install the supabase-py library, using the pip command below in your terminal. Then let's look at the code where we left in the last post.
[PT-BR]
Agora estamos prontos para voltar para o nosso script em python. Antes de começar, precisamos instalar a biblioteca supabase-py, usando o comando pip abaixo no terminal. Depois vamos dar uma olhada no script python onde paramos no último post.

pip install supabase
# Importing libraries
import os
import discord
from dotenv import load_dotenv

# Using dotenv to load environment variables from .env file
# And getting the discord token that is saved on .env file
load_dotenv()
discordToken = os.getenv('DISCORD_TOKEN')

# Create the discord client instance
intents = discord.Intents.all()
client = discord.Client(command_prefix='!', intents=intents)

# This function will run only once when the bot loads
@client.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))

# This function will run everytime there is a new message in one of the channels the bot has access.
# If someone types "Hi" the bot will answer "Hello" back
@client.event
async def on_message(message):
    if message.author == client.user:
        return

    if message.content == 'Hi':
        await message.channel.send(f'Hello')

# Run the client
client.run(discordToken)

[EN]
I know that if you are a total beginner with basic programing skills, some of this script might not be easy to understand. But if you were able to get this to work up until now, then let's just focus in the easier part. Look for the on_message function, this function is bot's eyes, it's looking to all channels it has access to, and every time someone sends a message, that message is passed on as that message variable. The message variable is a Message Object from discordpy, check all it has in the API documentation. The first if statement in there says "If message.author == client.user:", here we are checking if the message is the bot's own message. If we don't have this, the bot might respond to itself and might even start an infinite loop. The second if statement is checking if the content of the message is equal to "Hi", and if it is, it sends back a message saying "Hello".
The idea here will be to add more if statements checking for the commands we want. Since the bot will be managing the curators, I want to have a command to add curator, list curators and remove curator. I will name these commands: addcurator, listcurators and removecurator. I will show now only the parts of the code that we are working on. So this is what it will look like:
[PT-BR]
Eu sei que se voce ainda é um iniciante em programação, talvez alguns dos conceitos nesse script sejam difíceis de entender. Mas se voce conseguiu fazer funcionar até aqui, então vamos focar na parte mais facil. Procure pela função on_message, essa função é a visão do bot, ela esta olhando para todos os canais a que ele tem acesso, e toda vez que alguem manda uma mensagem, essa mensagem é passada naquela variável message dentro da função. A variável message é um Objeto to tipo Message do discordpy, olhe a documentação da API para ver tudo o que ela tem.
Aquele primeiro "if" ali dentro diz "if message.author == client.user:", esta checando se essa mensagem foi enviada pelo próprio robo. Se não colocarmos isso, o bot pode responder para si mesmo, e pode até iniciar um loop infinito.
A segunda condição "if" esta checando se o conteúdo da mensagem é igual a "Hi", e se for, ele manda uma mensagem de volta dizendo "Hello.
A idéia aqui vai ser adicionar mais condições checando para os comandos que queremos criar. Como o bot vai gerenciar curadores, eu quero ter comandos para adicionar curador, listar curadores, e remover curador. Eu vou chamar esses comandos de: addcurator, listcurators and removecurator. A partir de agora vou mostrar somente a parte do código que estamos trabalhando. E vai ficar assim:

@client.event
async def on_message(message):
    if message.author == client.user:
        return
    
    if message.content.startswith('addcurator'):
        return
    
    if message.content.startswith('listcurators'):
        return
    
    if message.content.startswith('removecurator'):
        return

[EN]
I just realized that I forgot one step. Do you remember the variables to connect to supabase that we added to our .env file? We need to load them here in our bot. I also need to initialize the supabase client. Now I'm also thinking that these commands need to be restricted, so I decided to add one more variable in the .env file for the discord id of the bot admin user.
[PT-BR]
Eu acabei de perceber que eu esqueci um passo. Lembra das variáveis para conectar ao supabase que nós colocamos no arquivo .env? Nós precisamos carregar elas aqui no nosso script do bot. Também preciso inicializar o supabase. Agora também pensei que esses comandos não podem ficar liberados para todos, então decidi adicionar mais uma variável no .env com o discord id do usuário que será o admin do bot.

load_dotenv()

discordToken = os.environ.get("DISCORD_TOKEN")
discordAdmin = os.environ.get("DISCORD_ADMIN_ID")

supabaseURL = os.environ.get("SUPABASE_URL")
supabaseKEY = os.environ.get("SUPABASE_KEY")

# Initialize Supabase
supabase = create_client(supabaseURL, supabaseKEY)

[EN]
Next we are going to work on each of those commands. The first one is the addcurator. The command will be used like this: addcurator @discord_user hive_user. Where discord user will be a tag or mention.
[PT-BR]
Agora vamos trabalhar em cada um daqueles comandos. O primeiro é o addcurator. O comando será usado assim: addcurator @discord_user hive_user. Onde o discord user será um tag ou mention.

    if message.content.startswith('addcurator'):
# check for admin user
        if (str(message.author.id) == discordAdmin):
# split the message to separate the parameters
            params = message.content.split()
# discord user is the only mention in the message
            discordUserId = message.mentions[0].id
# hive user is the second parameter
            hiveUser = params[2]
# access supabase looking if the user is already a curator
            checkUser = supabase.table("bot_curators").select("*", count="exact").eq("discord_id", discordUserId).execute()
# if the user is not a curator yet, then insert one row in the database
            if checkUser.count == 0:
                supabase.table("bot_curators").insert({"discord_id": discordUserId, "hive_id": hiveUser}).execute()
# send back a confirmation
                responseText = "User: <@" + str(discordUserId) + "> added to curation list"      
                await message.channel.send(responseText)
            else:
                await message.channel.send("Error: Discord user is already a curator")
        else:
            await message.channel.send("Not Authorized")

[EN]
I commented the code to make it easier to understand. I suggest looking at the supabase-py documentation on how to use it. But as you can see it's very simple. This section of the code has one select and one insert example. This first select I'm only interested in getting the count of rows with that user in the database to check if that user already exists. The next part is the listcurators.
[PT-BR]
Eu deixei comentários no código para ficar mais fácil de entender. Eu sugiro olhar a documentação do supabase-py para entender como usar. Mas como voce pode ver é bem simples. Essa parte do código tem um exemplo de select e um de insert. Este primeiro select eu to interessado somente na contagem de linhas com aquele determinado usuário no banco de dados, para checar se ele já existe. A próxima parte é o listcurators.

    if message.content.startswith('listcurators'):
# Again checking if it's the admin user
        if (str(message.author.id) == discordAdmin):
# Getting the list of curators from the database
            result = supabase.table("bot_curators").select("*").execute().data
# Preparing the message that will be sent back
            responseText = 'Curators List:\n>>> '
            for curator in result:
                responseText = responseText + "discord: <@" + str(curator["discord_id"]) + ">" + " => Hive: @" + str(curator["hive_id"]) + '\n'
# sending back the list of curators        
            await message.channel.send(responseText)
            return
        else:
            await message.channel.send("Not Authorized")
            return

[EN]
Here we just got the list of curators from the database, and listed inside a message, then sent it back. The next one is removecurator.
[PT-BR]
Aqui nós buscamos a lista de curadores do banco de dados, listamos em uma mensagem, e mandamos de volta. A próxima é removecurator.

    if message.content.startswith('removecurator'):
# check if it's the admin user
        if (str(message.author.id) == discordAdmin):
# get the user mentioned in the message
            discordUserId = message.mentions[0].id
# check if user is in the database
            checkUser = supabase.table("bot_curators").select("*", count="exact").eq("discord_id", discordUserId).execute()
# if not in database send back error. 
           if checkUser == 0:
                await message.channel.send("This user is not a curator")
# else delete the user from the database
            else:
                result = supabase.table("bot_curators").delete().eq("discord_id", discordUserId).execute()
# send a confirmation back
                responseText = "User: <@" + str(discordUserId) + "> removed from curation list"
                await message.channel.send(responseText)
            return
        else:
            await message.channel.send("Not Authorized")
            return

[EN]
Now let's look again at the entire code.
[PT-BR]
Agora vamos olhar como ficou o código inteiro novamente.

import os
import discord
from dotenv import load_dotenv
from supabase import create_client

load_dotenv()

discordToken = os.environ.get("DISCORD_TOKEN")
discordAdmin = os.environ.get("DISCORD_ADMIN_ID")

supabaseURL = os.environ.get("SUPABASE_URL")
supabaseKEY = os.environ.get("SUPABASE_KEY")
dbCuratorsTable = "curation_curators"

supabase = create_client(supabaseURL, supabaseKEY)

intents = discord.Intents.all()
client = discord.Client(command_prefix='!', intents=intents)
 
@client.event
async def on_ready():
    print('We have logged in as {0.user}'.format(client))
 
@client.event
async def on_message(message):
    if message.author == client.user:
        return

    if message.content.startswith('addcurator'):
        if (str(message.author.id) == discordAdmin):
            params = message.content.split()
            discordUserId = message.mentions[0].id
            hiveUser = params[2]
            checkUser = supabase.table(dbCuratorsTable).select("*", count="exact").eq("discord_id", discordUserId).execute()
            if checkUser.count == 0:
                supabase.table(dbCuratorsTable).insert({"discord_id": discordUserId, "hive_id": hiveUser}).execute()
                responseText = "User: <@" + str(discordUserId) + "> added to curation list"      
                await message.channel.send(responseText)
            else:
                await message.channel.send("Error: Discord user is already a curator")
        else:
            await message.channel.send("Not Authorized")

    if message.content.startswith('listcurators'):
        if (str(message.author.id) == discordAdmin):
            result = supabase.table(dbCuratorsTable).select("*").execute().data
            responseText = 'Curators List:\n>>> '
            for curator in result:
                responseText = responseText + "discord: <@" + str(curator["discord_id"]) + ">" + " => Hive: @" + str(curator["hive_id"]) + '\n'
                
            await message.channel.send(responseText)
            return
        else:
            await message.channel.send("Not Authorized")
            return
    
    if message.content.startswith('removecurator'):
        if (str(message.author.id) == discordAdmin):
            discordUserId = message.mentions[0].id
            checkUser = supabase.table(dbCuratorsTable).select("*", count="exact").eq("discord_id", discordUserId).execute()
            if checkUser == 0:
                await message.channel.send("This user is not a curator")
            else:
                result = supabase.table(dbCuratorsTable).delete().eq("discord_id", discordUserId).execute()
                responseText = "User: <@" + str(discordUserId) + "> removed from curation list"
                await message.channel.send(responseText)
            return
        else:
            await message.channel.send("Not Authorized")
            return

client.run(discordToken)

[EN]
This is the end of this part. Now we have a bot that will respond to three commands. We can add, list and remove curators from our database.
In the next post, we will add the beem library to integrate with Hive blockchain, and start working on the curation queue. We will need one more table to store the links. Then be able to add, list and remove links from the queue, and finally create a function that will be checking the voting power every minute to cast a vote when it reaches 99%.

Hope this post was helpful.
See you next time!

[PT-BR]
Esse é o final desta parte. Agora nós temos um bot que ja responde a tres comandos. Nós podemos adicionar, listar e remover curadores do nosso banco de dados.
No próximo post, vamos adicionar a biblioteca beem para integrar com o blockchain da Hive, e começar a trabalhar na fila de curadoria. Vamos precisar de mais uma tabela no banco de dados para guardar os links. Depois poder adicionar, listar e remover links da fila, e finalmente criar uma função que vai checar o poder de voto a cada minuto e fazer um voto quando atingir 99%.

Espero que este post possa ajudar.
Até a próxima!



0
0
0.000
13 comments
avatar

Nice work!

I'm also working on a discord bot named Kem Bot, and it creates a curation post for @hiveph.
It's not using python, it's using nodeJS but it's almost the same code..
I noticed there are lots of people on Hive making a discord bot using python. Probably it's easier to code it in python than JS? I'm not so sure.
I read your first post so maybe using python for you is easier since you already have been using it.

I have been dealing with a problem on the curators part because the way it's coded right now is the discord name of the curator should be the same as their hive name. It can raise problems, especially now that discord is allowing users to change their username. For now, I have a temporary solution for this issue.

So now, you just gave me an idea of how to deal with it.
I could just store the list of creators in the DB, with their hive name and discord name, make a CRUD for it and I guess my problem will be solved.

Thanks for the idea! And thanks for teaching us how to do this!

0
0
0.000
avatar

Thanks!!
I think python is easier to learn then JS, maybe why more people are using it for discord bots. But JS is similar, shouldn't be much harder.
I would store the discord id for the user, as discord names can be changed. If you right click the user thumbnail on discord, the last option is to copy id.
I'm glad it helped you in some way!!

0
0
0.000
avatar

Yeah, that's what I have in mind when I thought of the idea.
It's so nice to have a conversation like this with another dev, thank you for existing for these ideas 😁

0
0
0.000
avatar

bookmarked! I have been looking more of these kind of knowledge sharing. Keep it up and thanks! :)

0
0
0.000
avatar

Thank you for stopping by! Will try to post more.

0
0
0.000
avatar

Muito interessante e parabéns esse foi uma ótima explicação, não tenho muita familiaridade com Python no entanto em algumas partes eu acho que Javascript se sairia melhor como na parte das variáveis para chamar o comando? Ótimo conteúdo para #HiveBR

0
0
0.000
avatar
(Edited)

Obrigado!! Mas não endendi o que voce quis dizer aqui:

como na parte das variáveis para chamar o comando?

0
0
0.000
avatar

Create the discord client instance
intents = discord.Intents.all()
client = discord.Client(command_prefix='!', intents=intents)

Esse modo de criar as instâncias, em NodeJs seria algo mais fácil de criar essas variáveis

0
0
0.000
avatar

Em JS o comando é parecido. Uma só linha tb. Continuo sem entender o que vc quer dizer com mais facil. Mas ok.
Obrigado pelo comentário!!

0
0
0.000