Compare commits

..

1 Commits

Author SHA1 Message Date
Luca Lombardo 2ae8c4bdef updatated README su come gestire gli utenti non autenticati 9 months ago

4
.gitignore vendored

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

@ -4,7 +4,7 @@ Descrizione TODO
## Development ## Development
### Prerequisiti Installare tutti i pacchetti necessari con:
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
@ -17,9 +17,18 @@ echo "BOT_TOKEN=123456789:ABCdefGhIjKlMnOpQrStUvWxYz" > .env
``` ```
### Funzionalità ## Funzionalità del bot di ingresso
- [x] Comandi `/start` e `/help` * [x] Manda un messaggio di benvenuto
- [ ] Creare effettivamente i primi gruppi di beta testing ed aggiungere i link effettivi ai comandi `/start` e `/help` * [ ] Spunta un bottone per autenticarsi con Google con account di ateneo
- [ ] Funzione per autenticare tramite account di ateneo usando Google tramite [go-maths-api](https://git.phc.dm.unipi.it/phc/go-maths-api) * [ ] Viene generato un link unico per del tipo `bot.phc.dm.unipi.it/login?sid=1234567890ABCDEF`
- [ ] Letteralmente tutto il resto * [ ] 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

@ -1,72 +1,54 @@
import os import os
import asyncio import asyncio
from dotenv import load_dotenv
from telebot import types from telebot import types
from telebot.async_telebot import AsyncTeleBot from telebot.async_telebot import AsyncTeleBot
import config load_dotenv()
BOT_TOKEN = os.getenv("BOT_TOKEN")
bot = AsyncTeleBot(config.BOT_TOKEN)
bot = AsyncTeleBot(BOT_TOKEN)
def is_mathematician(user_id) -> bool: def is_mathematician(user_id) -> bool:
return True return True
# Handle '/start' and '/help' # Handle '/start' and '/help'
@bot.message_handler(commands=["help", "start"]) @bot.message_handler(commands=['help', 'start'])
async def send_welcome(message): async def send_welcome(message):
markup = types.InlineKeyboardMarkup() markup = types.InlineKeyboardMarkup()
btn = types.InlineKeyboardButton("Autenticati", callback_data="authenticate") btn = types.InlineKeyboardButton('Autenticati', callback_data='authenticate')
markup.add(btn) markup.add(btn)
await bot.reply_to( await bot.reply_to(message, """\
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!\ 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',
buttons = { # {name of the button: courses} 'Terzo Anno 📚': 'Meccanica Razionale 🧲: http://example.com/meccraz\n Calcolo Scientifico 🧮: http://example.com/cs\n LPL 💻: http://example.com/lpl',
"Primo Anno 📚": "Analisi 1 🧮: http://example.com/matematica\n Fisica 🧲: http://example.com/fisica\n Informatica 💻: http://example.com/informatica", 'Magistrale 🎓': 'Intelligenza Artificiale 🤖: http://example.com/intelligenzaartificiale\n Data Science 📊: http://example.com/datascience\n Cyber Security 🔒: http://example.com/cybersecurity',
"Secondo Anno 📚": "Algebra 1 : http://example.com/algebra\n Analisi 2 🧮: http://example.com/analisi2\n Programmazione 💻: http://example.com/programmazione", 'Miscellanea 📝': 'Spam 📬: http://example.com/spam\n Eventi 🎉: http://example.com/eventi\n• PHC 🏥: http://example.com/phc',
"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 # 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): async def handle_authenticate(call):
await bot.answer_callback_query( 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)
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): if is_mathematician(call.from_user.id):
markup = types.InlineKeyboardMarkup() markup = types.InlineKeyboardMarkup()
for button in buttons: for button in buttons:
markup.add(types.InlineKeyboardButton(button, callback_data=button)) markup.add(types.InlineKeyboardButton(button, callback_data=button))
await bot.send_message( await bot.send_message(call.message.chat.id, 'Autenticazione riuscita! Adesso puoi accedere a tutti i gruppi disponibili presenti', reply_markup=markup)
call.message.chat.id,
"Autenticazione riuscita! Adesso puoi accedere a tutti i gruppi disponibili presenti",
reply_markup=markup,
)
else: else:
await bot.send_message( 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.')
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 # Handle button click
@bot.callback_query_handler(func=lambda call: call.data in buttons) @bot.callback_query_handler(func=lambda call: call.data in buttons)
async def handle_buttons(call): 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.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]) 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()

@ -1,19 +0,0 @@
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.

@ -1,76 +0,0 @@
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

@ -1,19 +0,0 @@
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())

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

@ -1,34 +1,2 @@
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 pyTelegramBotAPI==4.15.2
python-dotenv==1.0.0 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

@ -1,66 +0,0 @@
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