Compare commits

..

1 Commits

2
.gitignore vendored

@ -4,6 +4,8 @@
# Python
env/
venv/
__pycache__/
# Editors
.vscode/

@ -4,7 +4,7 @@ Descrizione TODO
## Development
Installare tutti i pacchetti necessari con:
### Prerequisiti
```bash
pip install -r requirements.txt
@ -17,18 +17,9 @@ echo "BOT_TOKEN=123456789:ABCdefGhIjKlMnOpQrStUvWxYz" > .env
```
## Funzionalità del bot di ingresso
### Funzionalità
* [x] Manda un messaggio di benvenuto
* [ ] Spunta un bottone per autenticarsi con Google con account di ateneo
* [ ] Viene generato un link unico per del tipo `bot.phc.dm.unipi.it/login?sid=1234567890ABCDEF`
* [ ] Una volta che clicca questo link, viene salvato nel database: user_id telegram, sid
* [ ] Se il login va a buon fine (ovvero se è di matematica), manda un messaggio dove è possibile visualizzare (con caselle carine organizzati per anno / area matematica) i link di accesso per i gruppi
> **NOTA:** Questo implica che vi sarà un link pubblico, quindi la gente potrebbe accedere ai gruppi senza essere autenticata se qualcuno dovesse passargli quel link. Per evitare spam ed esterni indesiderati, il bot inserito nel gruppo, ogni volta che un utente accede, controlla se il suo ID è presente nel database. Se si, non ci sono problemi, altrimenti viene silenziato, e viene mandato un messaggio (pubblico) sul gruppo dicendo qualcosa del tipo "Ciao @utente, per accedere ai gruppi devi prima autenticarti con il bot di ingresso. Per farlo, clicca qui" e spunta sotto un bottone (lo stesso che abbiamo usato nel bot di ingresso)
## Side notes
* [ ] Scegliere se per ogni gruppo creare un topic "domande esame" o se utilizzare gli `#hashtags`
* [ ] Scegliere se i canali tipo "Rappresentati" e "PHC" debbano essere effettivamente canali o un gruppo normale
- [x] Comandi `/start` e `/help`
- [ ] Creare effettivamente i primi gruppi di beta testing ed aggiungere i link effettivi ai comandi `/start` e `/help`
- [ ] Funzione per autenticare tramite account di ateneo usando Google tramite [go-maths-api](https://git.phc.dm.unipi.it/phc/go-maths-api)
- [ ] Letteralmente tutto il resto

@ -1,49 +1,64 @@
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)
""",
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',
"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)
@ -51,4 +66,7 @@ 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())
async def polling():
print("Starting telegram bot polling...")
return await bot.polling()

@ -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")

Binary file not shown.

@ -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

@ -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())

@ -0,0 +1,2 @@
[tool.black]
line-length = 100

@ -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

@ -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()
Loading…
Cancel
Save