commit 0c3180d789dd01325cd4bc317130bbb814d2a47d Author: Timo Schmidt Date: Tue Jun 11 23:31:42 2024 +0200 Add code diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c49bd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.env diff --git a/main.py b/main.py new file mode 100644 index 0000000..e650e2f --- /dev/null +++ b/main.py @@ -0,0 +1,137 @@ +import os +import time +import json + +import httpx +import requests +import pandas as pd +from fastapi import FastAPI, Request +from dotenv import load_dotenv + + +load_dotenv() +TOKEN = os.environ.get('BOT_TOKEN') +BASE_URL = f"https://api.telegram.org/bot{TOKEN}" +BASE_FILE_URL = f"https://api.telegram.org/file/bot{TOKEN}" + +client = httpx.AsyncClient() + +app = FastAPI() + +async def send(text: str, chat_id: int) -> None: + await client.get(f"{BASE_URL}/sendMessage?chat_id={chat_id}&text={text}") + +async def extract_cmd(text: str, chat_id: int) -> tuple[str, str]: + orig_text = text + cmd, *text = text.strip().split(maxsplit=1) + if cmd[0] != '/': + await send(f'Invalid command: {orig_text}', chat_id) + return 'error', '' + cmd = cmd[1:] + if cmd == 'bulk': + if not text: + await send('bulk command requires argument', chat_id) + return 'error', '' + return cmd, text + elif cmd == 'test': + if not text: + await send('test command requires argument', chat_id) + return 'error', '' + return cmd, text + else: + await send(f'Unknown command: {cmd}', chat_id) + return 'error', '' + +async def handle_bulk(arg: str, chat_id: int) -> None: + await send('BULK HANDLED', chat_id) + +async def handle_test(arg: str, chat_id: int) -> None: + await send('TEST HANDLED', chat_id) + +async def handle_cmd(text: str, chat_id: int) -> None: + cmd, arg = await extract_cmd(text, chat_id) + if cmd == 'error': + pass + elif cmd == 'bulk': + await handle_bulk(arg, chat_id) + elif cmd == 'test': + await handle_test(arg, chat_id) + else: + await send(f"Command {cmd} not implemented.", chat_id) + +def is_valid_phonenumber(phone: str) -> bool: + phone = phone.strip() + if phone[0] == '+': + phone = phone[1:] + if not phone.startswith('998'): + return False + if len(phone) != 12: + return False + if not phone.isascii() or not phone.isdecimal(): + return False + return True + +async def phone_already_in_db(users: dict, phone: str, name: str, chat_id: int) -> bool: + for user in users: + if user['phone'] == phone: + if user['name'] != name: + await send(f'Phone {phone} already has user {user["name"]} associated with it. Cannot overwrite with new user {name}.', chat_id) + return True + return False + +async def import_document(document: dict, chat_id: int) -> None: + file_name = document['file_name'] + file_id = document['file_id'] + resp = requests.get(f'{BASE_URL}/getFile?file_id={file_id}') + if resp.status_code != 200: + await send(f"Couldn't save file: {file_name}", chat_id) + return + file_meta = resp.json() + file_path = file_meta['result']['file_path'] + resp = requests.get(f'{BASE_FILE_URL}/{file_path}') + local_file_path = f'tmpfile_{int(time.time())}.xlsx' + with open(local_file_path, 'wb') as xlsx: + xlsx.write(resp.content) + await send('File saved successfully', chat_id) + + try: + workbook = pd.read_excel(local_file_path) + except Exception as e: + await send(f"Couldn't open file: {file_name}", chat_id) + return + + try: + with open('users.json', encoding='utf-8') as users_file: + users = json.load(users_file) + except FileNotFoundError: + users = [] + + for index, row in workbook.iterrows(): + name = str(row[0]).strip() + phone = str(row[1]).strip() + if not is_valid_phonenumber(phone): + await send(f'User {name} has invalid phone number: {phone}.', chat_id) + continue + if await phone_already_in_db(users, phone, name, chat_id): + continue + users.append({'name': name, 'phone': phone}) + + os.rename('users.json', 'users.bak.json') + with open('users.json', 'w', encoding='utf-8') as users_file: + json.dump(users, users_file, indent=2, ensure_ascii=False) + +@app.post("/webhook/") +async def webhook(req: Request): + data = await req.json() + try: + chat_id = data['message']['chat']['id'] + if data['message'].get('document') is not None: + await import_document(data['message']['document'], chat_id) + elif data['message'].get('text') is not None: + text = data['message']['text'] + await handle_cmd(text, chat_id) + else: + await send('Unknown message type.', chat_id) + except KeyError as e: + print(e) + print(data) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..77ee464 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,47 @@ +annotated-types==0.7.0 +anyio==4.4.0 +certifi==2024.6.2 +charset-normalizer==3.3.2 +click==8.1.7 +dnspython==2.6.1 +email_validator==2.1.1 +et-xmlfile==1.1.0 +exceptiongroup==1.2.1 +fastapi==0.111.0 +fastapi-cli==0.0.4 +h11==0.14.0 +httpcore==1.0.5 +httptools==0.6.1 +httpx==0.27.0 +idna==3.7 +Jinja2==3.1.4 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +mdurl==0.1.2 +numpy==1.26.4 +openpyxl==3.1.3 +orjson==3.10.4 +pandas==2.2.2 +pydantic==2.7.3 +pydantic_core==2.18.4 +Pygments==2.18.0 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-multipart==0.0.9 +pytz==2024.1 +PyYAML==6.0.1 +requests==2.32.3 +rich==13.7.1 +shellingham==1.5.4 +six==1.16.0 +sniffio==1.3.1 +starlette==0.37.2 +typer==0.12.3 +typing_extensions==4.12.2 +tzdata==2024.1 +ujson==5.10.0 +urllib3==2.2.1 +uvicorn==0.30.1 +uvloop==0.19.0 +watchfiles==0.22.0 +websockets==12.0 diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..6680835 --- /dev/null +++ b/run.sh @@ -0,0 +1,2 @@ +#!/bin/sh +fastapi dev --port 8080 main.py