Unsplash의 Daniel Tafjord 표지 사진
최근에 소프트웨어 엔지니어링 부트캠프를 마치고 LeetCode의 쉬운 질문 작업을 시작했으며, 질문 해결을 위해 매일 알림을 받으면 책임감을 갖는 데 도움이 될 것이라고 느꼈습니다. 나는 다음을 수행하는 24시간 일정(물론 내 믿음직한 라즈베리 파이에서)으로 실행되는 디스코드 봇을 사용하여 이를 구현하기로 결정했습니다.
LeetCode에 가서 하루에 하나씩 문제를 해결하는 것이 더 쉬울 수도 있다는 것을 알고 있지만, 이 미니 프로젝트에서 ChatGPT의 도움으로 Python과 Discord에 대해 많은 것을 배울 수 있었습니다. 저도 처음으로 스케치노트를 시도하는거라서 조금만 기다려주세요 ㅋㅋㅋ
1. Python 가상 환경 사용
2. 종속성 설치
3. Leetcode 쉬운 질문 데이터베이스 설정
4. 환경변수 설정
5. Discord 앱 만들기
6. 봇을 실행하세요!
Python 가상 환경 사용을 권장합니다. Ubuntu 24.04에서 처음 테스트했을 때 아래 오류가 발생했기 때문입니다.
설정은 비교적 쉽습니다. 다음 명령을 실행하면 짜잔, Python 가상 환경이 됩니다!
python3 -m venv ~/py_envs ls ~/py_envs # to confirm the environment was created source ~/py_envs/bin/activate
다음 종속성이 필요합니다.
다음을 실행하여 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
Leetscrape는 이 단계에서 매우 중요했습니다. 이에 대해 자세히 알아보려면 Leetscrape 문서를 참조하세요.
나는 단지 leetcode 쉬운 질문에 대해서만 작업하고 싶기 때문에(나에게는 상당히 어렵습니다) 다음을 수행했습니다:
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")
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)
환경 변수를 저장할 .env 파일 생성
DISCORD_BOT_TOKEN=*****
Discord 개발자 문서의 지침에 따라 적절한 권한이 있는 Discord 앱과 봇을 만드세요. 최소한 다음 OAuth 권한으로 봇을 승인해야 합니다.
다음은 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 문제를 해결해 보아야 하는데...
부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.
Copyright© 2022 湘ICP备2022001581号-3