From 25300f4421ee1d6dddbe1555867a3863fa7a9fd9 Mon Sep 17 00:00:00 2001 From: Antonio De Lucreziis Date: Thu, 18 Jan 2024 14:31:25 +0100 Subject: [PATCH] primo prototipo di server e bot insieme --- .gitignore | 4 ++- bot.py | 62 ++++++++++++++++++++++++-------------- config.py | 19 ++++++++++++ database.db | Bin 0 -> 20480 bytes database.py | 76 +++++++++++++++++++++++++++++++++++++++++++++++ main.py | 19 ++++++++++++ pyproject.toml | 2 ++ requirements.txt | 32 ++++++++++++++++++++ server.py | 66 ++++++++++++++++++++++++++++++++++++++++ 9 files changed, 257 insertions(+), 23 deletions(-) create mode 100644 config.py create mode 100644 database.db create mode 100644 database.py create mode 100644 main.py create mode 100644 pyproject.toml create mode 100644 server.py diff --git a/.gitignore b/.gitignore index 4ee541b..44de62e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ # Python env/ +venv/ +__pycache__/ # Editors -.vscode/ \ No newline at end of file +.vscode/ diff --git a/bot.py b/bot.py index 2bb6f7d..17ca6f6 100644 --- a/bot.py +++ b/bot.py @@ -1,54 +1,72 @@ import os import asyncio -from dotenv import load_dotenv from telebot import types from telebot.async_telebot import AsyncTeleBot -load_dotenv() -BOT_TOKEN = os.getenv("BOT_TOKEN") +import config + +bot = AsyncTeleBot(config.BOT_TOKEN) -bot = AsyncTeleBot(BOT_TOKEN) def is_mathematician(user_id) -> bool: return True + # Handle '/start' and '/help' -@bot.message_handler(commands=['help', 'start']) +@bot.message_handler(commands=["help", "start"]) async def send_welcome(message): markup = types.InlineKeyboardMarkup() - btn = types.InlineKeyboardButton('Autenticati', callback_data='authenticate') + btn = types.InlineKeyboardButton("Autenticati", callback_data="authenticate") markup.add(btn) - await bot.reply_to(message, """\ + await bot.reply_to( + message, + """\ Benvenuto nel bot di ingresso per i gruppi telegram del dipartimento di matematica dell'universitร  di Pisa! Per poter accedere, fai l'autenticazione con il tuo account di ateneo tramite Google. Clicca il pulsante qui sotto!\ -""", reply_markup=markup) - -buttons = { # {name of the button: courses} - 'Primo Anno ๐Ÿ“š': 'Analisi 1 ๐Ÿงฎ: http://example.com/matematica\n Fisica ๐Ÿงฒ: http://example.com/fisica\n Informatica ๐Ÿ’ป: http://example.com/informatica', - 'Secondo Anno ๐Ÿ“š': 'Algebra 1 โž•: http://example.com/algebra\n Analisi 2 ๐Ÿงฎ: http://example.com/analisi2\n Programmazione ๐Ÿ’ป: http://example.com/programmazione', - 'Terzo Anno ๐Ÿ“š': 'Meccanica Razionale ๐Ÿงฒ: http://example.com/meccraz\n Calcolo Scientifico ๐Ÿงฎ: http://example.com/cs\n LPL ๐Ÿ’ป: http://example.com/lpl', - 'Magistrale ๐ŸŽ“': 'Intelligenza Artificiale ๐Ÿค–: http://example.com/intelligenzaartificiale\n Data Science ๐Ÿ“Š: http://example.com/datascience\n Cyber Security ๐Ÿ”’: http://example.com/cybersecurity', - 'Miscellanea ๐Ÿ“': 'Spam ๐Ÿ“ฌ: http://example.com/spam\n Eventi ๐ŸŽ‰: http://example.com/eventi\nโ€ข PHC ๐Ÿฅ: http://example.com/phc', +""", + reply_markup=markup, + ) + + +buttons = { # {name of the button: courses} + "Primo Anno ๐Ÿ“š": "Analisi 1 ๐Ÿงฎ: http://example.com/matematica\n Fisica ๐Ÿงฒ: http://example.com/fisica\n Informatica ๐Ÿ’ป: http://example.com/informatica", + "Secondo Anno ๐Ÿ“š": "Algebra 1 โž•: http://example.com/algebra\n Analisi 2 ๐Ÿงฎ: http://example.com/analisi2\n Programmazione ๐Ÿ’ป: http://example.com/programmazione", + "Terzo Anno ๐Ÿ“š": "Meccanica Razionale ๐Ÿงฒ: http://example.com/meccraz\n Calcolo Scientifico ๐Ÿงฎ: http://example.com/cs\n LPL ๐Ÿ’ป: http://example.com/lpl", + "Magistrale ๐ŸŽ“": "Intelligenza Artificiale ๐Ÿค–: http://example.com/intelligenzaartificiale\n Data Science ๐Ÿ“Š: http://example.com/datascience\n Cyber Security ๐Ÿ”’: http://example.com/cybersecurity", + "Miscellanea ๐Ÿ“": "Spam ๐Ÿ“ฌ: http://example.com/spam\n Eventi ๐ŸŽ‰: http://example.com/eventi\nโ€ข PHC ๐Ÿฅ: http://example.com/phc", } + # Handle button click -@bot.callback_query_handler(func=lambda call: call.data == 'authenticate') +@bot.callback_query_handler(func=lambda call: call.data == "authenticate") async def handle_authenticate(call): - await bot.answer_callback_query(call.id, text="Autenticazione completata") # Avoids to leave the button pressed and sends a message on screen (not in chat) + await bot.answer_callback_query( + call.id, text="Autenticazione completata" + ) # Avoids to leave the button pressed and sends a message on screen (not in chat) if is_mathematician(call.from_user.id): - markup = types.InlineKeyboardMarkup() for button in buttons: markup.add(types.InlineKeyboardButton(button, callback_data=button)) - await bot.send_message(call.message.chat.id, 'Autenticazione riuscita! Adesso puoi accedere a tutti i gruppi disponibili presenti', reply_markup=markup) + await bot.send_message( + call.message.chat.id, + "Autenticazione riuscita! Adesso puoi accedere a tutti i gruppi disponibili presenti", + reply_markup=markup, + ) else: - await bot.send_message(call.message.chat.id, 'Sembra che tu non sia iscritto al corso di Laurea in matematica. Se pensi che sia un errore o desideri essere aggiunto, manda una mail a macchinisti@phc.dm.unipi.it. Altrimenti puoi chiedere di farti aggiungere manualmente ai gruppi a cui sei interessato da altri studenti di matematica che sono giร  iscritti.') - + await bot.send_message( + call.message.chat.id, + "Sembra che tu non sia iscritto al corso di Laurea in matematica. Se pensi che sia un errore o desideri essere aggiunto, manda una mail a macchinisti@phc.dm.unipi.it. Altrimenti puoi chiedere di farti aggiungere manualmente ai gruppi a cui sei interessato da altri studenti di matematica che sono giร  iscritti.", + ) + + # Handle button click @bot.callback_query_handler(func=lambda call: call.data in buttons) async def handle_buttons(call): await bot.answer_callback_query(call.id) # Avoids to leave the button pressed, no message is sent on screen await bot.send_message(call.message.chat.id, buttons[call.data]) -asyncio.run(bot.polling()) \ No newline at end of file + +async def polling(): + print("Starting telegram bot polling...") + return await bot.polling() diff --git a/config.py b/config.py new file mode 100644 index 0000000..c3398f0 --- /dev/null +++ b/config.py @@ -0,0 +1,19 @@ +from dotenv import load_dotenv +import os + +load_dotenv() + + +BOT_TOKEN = os.getenv("BOT_TOKEN") + +OAUTH_CLIENT_ID = os.getenv("OAUTH_CLIENT_ID") +OAUTH_CLIENT_SECRET = os.getenv("OAUTH_CLIENT_SECRET") +OAUTH_AUTH_URL = os.getenv("OAUTH_AUTH_URL") +OAUTH_TOKEN_HOST = os.getenv("OAUTH_TOKEN_HOST") +OAUTH_TOKEN_PATH = os.getenv("OAUTH_TOKEN_PATH") +OAUTH_REDIRECT_URL = os.getenv("OAUTH_REDIRECT_URL") +OAUTH_USER_INFO_URL = os.getenv("OAUTH_USER_INFO_URL") +OAUTH_SCOPES = os.getenv("OAUTH_SCOPES") + + +DATABASE_URL = os.getenv("DATABASE_URL") diff --git a/database.db b/database.db new file mode 100644 index 0000000000000000000000000000000000000000..b2f21c414c9069ce1f04a8d4ee03de1c9db25ff5 GIT binary patch literal 20480 zcmeI%zfQtH0LSqwR52u4w#s&+L}G#tPDarjgrHSQO=M~?Rwbf@wkEFdD87&H<7odt z3Q=}LzL)f_?X}nYeR@mNZs&3kdh&7PzxG3UBuYZl#F>;rh+MR7MBBW?$NKyrYPE&^ zRl7NH;D2r#-$LIx7RI6RwR3DNj=AbS&Le;T0tg_000IagfB*sr{11UiJ6|x%WoCTUT|v z7e~uxJBptC9P2DI{25|??JP$nw`rc3t8yeWa%)MGkCxF^yp*=%suWig1Q0*~0R#|0 z009ILKmY**5Lj1%VjPU`|8;%60R#|0009IL mKmY**5I_Kd^%vm(|N6gU5+Z;A0tg_000IagfB*srAn*ek%#Jkx literal 0 HcmV?d00001 diff --git a/database.py b/database.py new file mode 100644 index 0000000..65cc306 --- /dev/null +++ b/database.py @@ -0,0 +1,76 @@ +import secrets +import sqlite3 + +conn = sqlite3.connect("database.db") + +conn.execute( + """ + CREATE TABLE IF NOT EXISTS users ( + telegram_id INTEGER PRIMARY KEY, + oauth_id TEXT, + UNIQUE (telegram_id, oauth_id) + ) + """ +) +conn.execute( + """ + CREATE TABLE IF NOT EXISTS tokens ( + telegram_id INTEGER, + token TEXT, + FOREIGN KEY (telegram_id) REFERENCES users (telegram_id), + UNIQUE (telegram_id, token) + ) + """ +) +conn.commit() + + +def create_user(telegram_id, oauth_id): + # insert the user into the database + conn.execute( + """ + INSERT INTO users (telegram_id, oauth_id) VALUES (?, ?) + """, + (telegram_id, oauth_id), + ) + conn.commit() + + +def create_token(telegram_id): + # delete any existing tokens for this user + conn.execute( + """ + DELETE FROM tokens WHERE telegram_id = ? + """, + (telegram_id,), + ) + + # generate a new random token using crypt + token = secrets.token_hex(16) + + # insert the token into the database + conn.execute( + """ + INSERT INTO tokens (telegram_id, token) VALUES (?, ?) + """, + (telegram_id, token), + ) + conn.commit() + + return token + + +def get_telegram_id(token): + # get the telegram id from the database + cursor = conn.execute( + """ + SELECT telegram_id FROM tokens WHERE token = ? + """, + (token,), + ) + + # fetch the result + result = cursor.fetchone() + + # return the telegram id + return result[0] if result else None diff --git a/main.py b/main.py new file mode 100644 index 0000000..fcfa501 --- /dev/null +++ b/main.py @@ -0,0 +1,19 @@ +import asyncio + +import config +import bot +import server + +import signal +import sys + + +async def main(): + await asyncio.gather( + bot.polling(), + server.start(), + ) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..aa4949a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,2 @@ +[tool.black] +line-length = 100 diff --git a/requirements.txt b/requirements.txt index 07ab991..c1f8d19 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,34 @@ +aiohttp==3.9.1 +aiosignal==1.3.1 +annotated-types==0.6.0 +anyio==4.2.0 +attrs==23.2.0 +black==23.12.1 +certifi==2023.11.17 +charset-normalizer==3.3.2 +click==8.1.7 +fastapi==0.109.0 +frozenlist==1.4.1 +h11==0.14.0 +httptools==0.6.1 +idna==3.6 +multidict==6.0.4 +mypy-extensions==1.0.0 +packaging==23.2 +pathspec==0.12.1 +platformdirs==4.1.0 +pydantic==2.5.3 +pydantic_core==2.14.6 pyTelegramBotAPI==4.15.2 python-dotenv==1.0.0 +PyYAML==6.0.1 +requests==2.31.0 +sniffio==1.3.0 +starlette==0.35.1 +typing_extensions==4.9.0 +urllib3==2.1.0 +uvicorn==0.26.0 +uvloop==0.19.0 +watchfiles==0.21.0 +websockets==12.0 +yarl==1.9.4 diff --git a/server.py b/server.py new file mode 100644 index 0000000..64577d1 --- /dev/null +++ b/server.py @@ -0,0 +1,66 @@ +from typing import Union + +import uvicorn + +from fastapi import FastAPI +from fastapi.responses import RedirectResponse + +import config +import database + +app = FastAPI() + + +@app.get("/login") +async def login(token: str): + return RedirectResponse( + f"{config.OAUTH_AUTH_URL}?client_id={config.OAUTH_CLIENT_ID}&redirect_uri={config.OAUTH_REDIRECT_URL}&response_type=code&scope=email&state={token}" + ) + + +@app.get("/callback") +async def oauth_callback(code: str, state: str): + # get the telegram id from the database using the state token + telegram_id = database.get_telegram_id(state) + + # do a user info request to get the email address + import requests + + response = requests.post( + config.OAUTH_TOKEN_HOST + config.OAUTH_TOKEN_PATH, + data={ + "code": code, + "client_id": config.OAUTH_CLIENT_ID, + "client_secret": config.OAUTH_CLIENT_SECRET, + "redirect_uri": config.OAUTH_REDIRECT_URL, + "grant_type": "authorization_code", + }, + ) + + print(response.json()) + + access_token = response.json()["access_token"] + + response = requests.get( + config.OAUTH_USER_INFO_URL, + headers={"Authorization": f"Bearer {access_token}"}, + ) + + print(response.json()) + + email = response.json()["email"] + + # store the username in the database, split at "@" and take the first part + username = email.split("@")[0] + telegram_id = database.get_telegram_id(state) + + database.create_user(telegram_id, username) + + # redirect to the bot + return RedirectResponse(f"https://t.me/MyBot", status_code=303) + + +async def start(): + config = uvicorn.Config(app=app, port=8000, log_level="info", loop="asyncio") + server = uvicorn.Server(config) + await server.serve()