"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > Raspberry Pi에서 Discord Bot 실행하기

Raspberry Pi에서 Discord Bot 실행하기

2024-11-07에 게시됨
검색:774

Unsplash의 Daniel Tafjord 표지 사진

최근에 소프트웨어 엔지니어링 부트캠프를 마치고 LeetCode의 쉬운 질문 작업을 시작했으며, 질문 해결을 위해 매일 알림을 받으면 책임감을 갖는 데 도움이 될 것이라고 느꼈습니다. 나는 다음을 수행하는 24시간 일정(물론 내 믿음직한 라즈베리 파이에서)으로 실행되는 디스코드 봇을 사용하여 이를 구현하기로 결정했습니다.

  • 쉬운 리트코드 질문이 있는 사전 정의된 데이터뱅크로 이동
  • 디스코드 채널에 게시되지 않은 질문을 받아보세요
  • 리트코드 질문을 디스코드 채널에 스레드로 게시하세요(그래서 솔루션을 쉽게 추가할 수 있습니다)
  • 질문이 채널에 다시 게시되지 않도록 게시된 것으로 표시됩니다.

Running a Discord Bot on Raspberry Pi

LeetCode에 가서 하루에 하나씩 문제를 해결하는 것이 더 쉬울 수도 있다는 것을 알고 있지만, 이 미니 프로젝트에서 ChatGPT의 도움으로 Python과 Discord에 대해 많은 것을 배울 수 있었습니다. 저도 처음으로 스케치노트를 시도하는거라서 조금만 기다려주세요 ㅋㅋㅋ

Running a Discord Bot on Raspberry Pi

설정

1. Python 가상 환경 사용
2. 종속성 설치
3. Leetcode 쉬운 질문 데이터베이스 설정
4. 환경변수 설정
5. Discord 앱 만들기
6. 봇을 실행하세요!

1. Python 가상 환경을 사용하세요

Python 가상 환경 사용을 권장합니다. Ubuntu 24.04에서 처음 테스트했을 때 아래 오류가 발생했기 때문입니다.

Running a Discord Bot on Raspberry Pi

설정은 비교적 쉽습니다. 다음 명령을 실행하면 짜잔, Python 가상 환경이 됩니다!

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

2. 종속성 설치

다음 종속성이 필요합니다.

  • AWS CLI

다음을 실행하여 AWS CLI를 설치합니다.

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

그런 다음 awsconfigure를 실행하여 필요한 자격 증명을 추가합니다. AWS CLI 문서 구성을 참조하세요.

  • 핍 종속성

pip install -r 요구 사항.txt를 실행하여 요구 사항 파일과 함께 다음 pip 종속성을 설치할 수 있습니다.

# 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. Leetcode 쉬운 질문 데이터베이스 설정

Leetscrape는 이 단계에서 매우 중요했습니다. 이에 대해 자세히 알아보려면 Leetscrape 문서를 참조하세요.
나는 단지 leetcode 쉬운 질문에 대해서만 작업하고 싶기 때문에(나에게는 상당히 어렵습니다) 다음을 수행했습니다:

  • leetscrape를 사용하여 leetcode에서 모든 질문 목록을 가져오고 목록을 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")
  • Amazon DynamoDB 테이블을 생성하고 이전 단계에서 저장된 csv에서 필터링된 쉬운 질문 목록으로 채웁니다.
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. 환경 변수 설정

환경 변수를 저장할 .env 파일 생성

DISCORD_BOT_TOKEN=*****

5. 디스코드 앱 만들기

Discord 개발자 문서의 지침에 따라 적절한 권한이 있는 Discord 앱과 봇을 만드세요. 최소한 다음 OAuth 권한으로 봇을 승인해야 합니다.

  • 메시지 보내기
  • 공개 스레드 생성
  • 스레드에서 메시지 보내기

6. 봇을 실행하세요!

다음은 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)

봇을 실행하는 데는 여러 가지 옵션이 있습니다. 지금은 tmux 셸에서 실행하고 있지만 Docker 컨테이너나 AWS, Azure, DigitalOcean 또는 기타 클라우드 제공업체의 VPC에서도 실행할 수 있습니다.

이제 실제로 Leetcode 문제를 해결해 보아야 하는데...

릴리스 선언문 이 기사는 https://dev.to/beretests/running-a-discord-bot-on-raspberry-pi-4la4?1에서 복제됩니다.1 침해 내용이 있는 경우, [email protected]으로 연락하여 삭제하시기 바랍니다.
최신 튜토리얼 더>

부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

Copyright© 2022 湘ICP备2022001581号-3