"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Executando um Discord Bot no Raspberry Pi

Executando um Discord Bot no Raspberry Pi

Publicado em 2024-11-07
Navegar:253

Foto da capa de Daniel Tafjord no Unsplash

Recentemente concluí um bootcamp de engenharia de software, comecei a trabalhar nas perguntas fáceis do LeetCode e senti que ajudaria a me manter responsável se eu tivesse um lembrete diário para resolver questões. Decidi implementar isso usando um bot discord rodando em uma programação de 24 horas (no meu confiável Raspberry Pi, é claro), que faria o seguinte:

  • vá para um banco de dados predefinido de perguntas fáceis sobre leetcode
  • pegue uma pergunta que não foi postada no canal discord
  • poste a pergunta do leetcode como um tópico no canal discord (para que você possa adicionar facilmente sua solução)
  • pergunta marcada como postada para evitar postá-la novamente no canal

Running a Discord Bot on Raspberry Pi

Sei que pode ser mais fácil simplesmente ir ao LeetCode e resolver uma questão por dia, mas aprendi muito sobre Python e Discord com a ajuda do ChatGPT neste miniprojeto. Esta também é minha primeira tentativa de fazer esboços, então tenha paciência haha

Running a Discord Bot on Raspberry Pi

Configurar

1. Use ambiente virtual python
2. Instale dependências
3. Configure o banco de dados de perguntas fáceis Leetcode
4. Configure variáveis ​​de ambiente
5. Crie o aplicativo Discord
6. Execute o bot!

1. Use ambiente virtual python

Eu recomendo o uso de um ambiente virtual python porque quando testei isso inicialmente no Ubuntu 24.04, encontrei o erro abaixo

Running a Discord Bot on Raspberry Pi

Configurar é relativamente fácil, basta executar os seguintes comandos e pronto, você está em um ambiente virtual python!

python3 -m venv ~/py_envs
ls ~/py_envs  # to confirm the environment was created
source ~/py_envs/bin/activate

2. Instale dependências

As seguintes dependências são obrigatórias:

  • AWS CLI

Instale o AWS CLI executando o seguinte:

curl -O 'https://awscli.amazonaws.com/awscli-exe-linux-aarch64.zip'
unzip awscli-exe-linux-aarch64.zip 
sudo ./aws/install
aws --version

Em seguida, execute aws configure para adicionar as credenciais necessárias. Consulte o documento Configurar o AWS CLI.

  • dependências de pip

As seguintes dependências pip podem ser instaladas com um arquivo de requisitos executando pip install -r requisitos.txt.

# requirements.txt

discord.py
# must install this version of numpy to prevent conflict with
# pandas, both of which are required by leetscrape
numpy==1.26.4   
leetscrape
python-dotenv

3. Configure o banco de dados de perguntas fáceis do leetcode

Leetscrape foi vital para esta etapa. Para saber mais sobre isso, consulte a documentação do Leetscrape.
Eu só quero trabalhar em questões fáceis de leetcode (para mim, elas são até bastante difíceis), então fiz o seguinte:

  • pegue a lista de todas as perguntas do leetcode usando leetscrape e salve a lista em csv
from leetscrape import GetQuestionsList

ls = GetQuestionsList()
ls.scrape() # Scrape the list of questions
ls.questions.head() # Get the list of questions
ls.to_csv(directory="path/to/csv/file")
  • crie uma tabela do Amazon DynamoDB e preencha-a com uma lista de perguntas fáceis filtradas do csv salvo na etapa anterior.
import csv
import boto3
from botocore.exceptions import BotoCoreError, ClientError

# Initialize the DynamoDB client
dynamodb = boto3.resource('dynamodb')

def filter_and_format_csv_for_dynamodb(input_csv):
    result = []

    with open(input_csv, mode='r') as file:
        csv_reader = csv.DictReader(file)

        for row in csv_reader:
            # Filter based on difficulty and paidOnly fields
            if row['difficulty'] == 'Easy' and row['paidOnly'] == 'False':
                item = {
                    'QID': {'N': str(row['QID'])},  
                    'titleSlug': {'S': row['titleSlug']}, 
                    'topicTags': {'S': row['topicTags']},  
                    'categorySlug': {'S': row['categorySlug']},  
                    'posted': {'BOOL': False}  
                }
                result.append(item)

    return result

def upload_to_dynamodb(items, table_name):
    table = dynamodb.Table(table_name)

    try:
        with table.batch_writer() as batch:
            for item in items:
                batch.put_item(Item={
                    'QID': int(item['QID']['N']),  
                    'titleSlug': item['titleSlug']['S'],
                    'topicTags': item['topicTags']['S'],
                    'categorySlug': item['categorySlug']['S'],
                    'posted': item['posted']['BOOL']
                })
        print(f"Data uploaded successfully to {table_name}")

    except (BotoCoreError, ClientError) as error:
        print(f"Error uploading data to DynamoDB: {error}")

def create_table():
    try:
        table = dynamodb.create_table(
            TableName='leetcode-easy-qs',
            KeySchema=[
                {
                    'AttributeName': 'QID',
                    'KeyType': 'HASH'  # Partition key
                }
            ],
            AttributeDefinitions=[
                {
                    'AttributeName': 'QID',
                    'AttributeType': 'N'  # Number type
                }
            ],
            ProvisionedThroughput={
                'ReadCapacityUnits': 5,
                'WriteCapacityUnits': 5
            }
        )

        # Wait until the table exists
        table.meta.client.get_waiter('table_exists').wait(TableName='leetcode-easy-qs')
        print(f"Table {table.table_name} created successfully!")

    except Exception as e:
        print(f"Error creating table: {e}")

# Call function to create the table
create_table()

# Example usage
input_csv = 'getql.pyquestions.csv'  # Your input CSV file
table_name = 'leetcode-easy-qs'      # DynamoDB table name

# Step 1: Filter and format the CSV data
questions = filter_and_format_csv_for_dynamodb(input_csv)

# Step 2: Upload data to DynamoDB
upload_to_dynamodb(questions, table_name)

4. Configure variáveis ​​de ambiente

Crie um arquivo .env para armazenar variáveis ​​de ambiente

DISCORD_BOT_TOKEN=*****

5. Crie o aplicativo Discord

Siga as instruções nos documentos do Discord Developer para criar um aplicativo e bot Discord com permissões adequadas. Certifique-se de autorizar o bot com pelo menos as seguintes permissões OAuth:

  • Enviar mensagens
  • Criar tópicos públicos
  • Enviar mensagens em tópicos

6. Execute o bot!

Abaixo está o código do bot que pode ser executado com o comando python3 discord-leetcode-qs.py.

import os
import discord
import boto3
from leetscrape import GetQuestion
from discord.ext import tasks
from dotenv import load_dotenv
import re
load_dotenv()

# Discord bot token
TOKEN = os.getenv('DISCORD_TOKEN')

# Set the intents for the bot
intents = discord.Intents.default()
intents.message_content = True # Ensure the bot can read messages

# Initialize the bot
bot = discord.Client(intents=intents)
# DynamoDB setup
dynamodb = boto3.client('dynamodb')

TABLE_NAME = 'leetcode-easy-qs'
CHANNEL_ID = 1211111111111111111  # Replace with the actual channel ID

# Function to get the first unposted item from DynamoDB
def get_unposted_item():
    response = dynamodb.scan(
        TableName=TABLE_NAME,
        FilterExpression='posted = :val',
        ExpressionAttributeValues={':val': {'BOOL': False}},
    )
    items = response.get('Items', [])
    if items:
        return items[0]
    return None

# Function to mark the item as posted in DynamoDB
def mark_as_posted(qid):
    dynamodb.update_item(
        TableName=TABLE_NAME,
        Key={'QID': {'N': str(qid)}},
        UpdateExpression='SET posted = :val',
        ExpressionAttributeValues={':val': {'BOOL': True}}
    )

MAX_MESSAGE_LENGTH = 2000
AUTO_ARCHIVE_DURATION = 2880

# Function to split a question into words by spaces or newlines
def split_question(question, max_length):
    parts = []
    while len(question) > max_length:
        split_at = question.rfind(' ', 0, max_length)
        if split_at == -1:
            split_at = question.rfind('\n', 0, max_length)
        if split_at == -1:
            split_at = max_length

        parts.append(question[:split_at].strip())
        # Continue with the remaining text
        question = question[split_at:].strip()

    if question:
        parts.append(question)

    return parts

def clean_question(question):
    first_line, _, remaining_question = message.partition('\n')
    return re.sub(r'\n{3,}', '\n', remaining_question)

def extract_first_line(question):
    lines = question.splitlines()
    return lines[0] if lines else ""

# Task that runs on a schedule
@tasks.loop(minutes=1440) 
async def scheduled_task():
    channel = bot.get_channel(CHANNEL_ID)
    item = get_unposted_item()

    if item:
        title_slug = item['titleSlug']['S']
        qid = item['QID']['N']
        question = "%s" % (GetQuestion(titleSlug=title_slug).scrape())

        first_line = extract_first_line(question)
        cleaned_question = clean_message(question)
        parts = split_message(cleaned_question, MAX_MESSAGE_LENGTH)

        thread = await channel.create_thread(
            name=first_line, 
            type=discord.ChannelType.public_thread
        )

        for part in parts:
            await thread.send(part)

        mark_as_posted(qid)
    else:
        print("No unposted items found.")

@bot.event
async def on_ready():
    print(f'{bot.user} has connected to Discord!')
    scheduled_task.start()

@bot.event
async def on_thread_create(thread):
    await thread.send("\nYour challenge starts here! Good Luck!")

# Run the bot
bot.run(TOKEN)

Existem várias opções para executar o bot. No momento, estou apenas executando isso em um shell tmux, mas você também pode executar isso em um contêiner docker ou em um VPC da AWS, Azure, DigitalOcean ou outros provedores de nuvem.

Agora só preciso tentar resolver as questões do Leetcode...

Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/beretests/running-a-discord-bot-on-raspberry-pi-4la4?1 Se houver alguma violação, entre em contato com [email protected] para excluí-la
Tutorial mais recente Mais>

Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

Copyright© 2022 湘ICP备2022001581号-3