Maison  >  Article  >  développement back-end  >  Exécuter un robot Discord sur Raspberry Pi

Exécuter un robot Discord sur Raspberry Pi

Susan Sarandon
Susan Sarandonoriginal
2024-10-01 12:11:02599parcourir

Photo de couverture par Daniel Tafjord sur Unsplash

J'ai récemment terminé un bootcamp en génie logiciel, j'ai commencé à travailler sur des questions faciles de LeetCode et j'ai pensé que cela m'aiderait à rester responsable si j'avais un rappel quotidien pour résoudre les questions. J'ai décidé de l'implémenter en utilisant un robot Discord fonctionnant selon un horaire de 24 heures (sur mon fidèle Raspberry Pi, bien sûr) qui ferait ce qui suit :

  • accéder à une banque de données prédéfinie de questions leetcode faciles
  • saisissez une question qui n'a pas été publiée sur la chaîne Discord
  • postez la question leetcode sous forme de fil de discussion sur le canal Discord (afin que vous puissiez facilement ajouter votre solution)
  • la question est marquée comme publiée pour éviter de la publier à nouveau sur la chaîne

Running a Discord Bot on Raspberry Pi

Je réalise qu'il est peut-être plus facile d'aller simplement sur LeetCode et de résoudre une question par jour, mais j'ai appris beaucoup de choses sur Python et Discord avec l'aide de ChatGPT sur ce mini-projet. C'est aussi ma première tentative de sketchnoting, alors s'il vous plaît, soyez patient mdr

Running a Discord Bot on Raspberry Pi

Installation

1. Utiliser l'environnement virtuel Python
2. Installer les dépendances
3. Configurez la base de données de questions faciles Leetcode
4. Configurer les variables d'environnement
5. Créez une application Discord
6. Exécutez le bot !

1. Utiliser l'environnement virtuel Python

Je recommande l'utilisation d'un environnement virtuel python car lorsque je l'ai initialement testé sur Ubuntu 24.04, j'ai rencontré l'erreur ci-dessous

Running a Discord Bot on Raspberry Pi

Le configurer est relativement simple, exécutez simplement les commandes suivantes et le tour est joué, vous êtes dans un environnement virtuel python !

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

2. Installer les dépendances

Les dépendances suivantes sont requises :

  • AWS CLI

Installez AWS CLI en exécutant ce qui suit :

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

Ensuite, exécutez aws configure pour ajouter les informations d'identification requises. Voir Configurer la documentation AWS CLI.

  • dépendances pip

Les dépendances pip suivantes peuvent être installées avec un fichier d'exigences en exécutant pip install -r Requirements.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. Configurer la base de données de questions faciles leetcode

Leetscrape était vital pour cette étape. Pour en savoir plus, consultez la documentation Leetscrape.
Je veux seulement travailler sur des questions faciles avec leetcode (pour moi, elles sont même assez difficiles) alors j'ai fait ce qui suit :

  • récupérez la liste de toutes les questions de leetcode en utilisant leetscrape et enregistrez la liste au format 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")
  • créez une table Amazon DynamoDB et remplissez-la avec une liste de questions faciles filtrées à partir du fichier CSV enregistré à l'étape précédente.
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. Configurer les variables d'environnement

Créez un fichier .env pour stocker les variables d'environnement

DISCORD_BOT_TOKEN=*****

5. Créer une application Discord

Suivez les instructions dans la documentation du développeur Discord pour créer une application et un bot Discord avec les autorisations adéquates. Assurez-vous d'autoriser le bot avec au moins les autorisations OAuth suivantes :

  • Envoyer des messages
  • Créer des fils de discussion publics
  • Envoyer des messages dans les fils de discussion

6. Exécutez le robot !

Vous trouverez ci-dessous le code du bot qui peut être exécuté avec la commande 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)

Il existe plusieurs options pour exécuter le bot. Pour le moment, je l'exécute simplement dans un shell tmux, mais vous pouvez également l'exécuter dans un conteneur Docker ou sur un VPC d'AWS, Azure, DigitalOcean ou d'autres fournisseurs de cloud.

Maintenant, je dois juste essayer de résoudre les questions du Leetcode...

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn