added i18n + minor improvements / cleanup
This commit is contained in:
parent
b7bf0e7fa2
commit
379f5dfb50
162
src/beebot.py
162
src/beebot.py
@ -6,13 +6,13 @@ import os
|
||||
import sqlite3
|
||||
import subprocess
|
||||
from sqlite3 import Connection
|
||||
from typing import Optional
|
||||
from typing import Optional, Callable
|
||||
|
||||
import requests
|
||||
import telegram.constants
|
||||
from bs4 import BeautifulSoup
|
||||
from telegram import Update, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes, CallbackQueryHandler
|
||||
from telegram.ext import ApplicationBuilder, CommandHandler, ContextTypes, CallbackQueryHandler, Application
|
||||
|
||||
log_dir = os.getenv("BEEBOT_LOGS")
|
||||
if not log_dir:
|
||||
@ -65,27 +65,27 @@ class BeeBot:
|
||||
}
|
||||
CATEGORIES = {
|
||||
"E": {
|
||||
"name": "Étudiant"
|
||||
"name": "category.student"
|
||||
},
|
||||
"D": {
|
||||
"name": "Doctorant"
|
||||
"name": "category.phd_student"
|
||||
},
|
||||
"C": {
|
||||
"name": "Campus"
|
||||
"name": "category.campus"
|
||||
},
|
||||
"V": {
|
||||
"name": "Visiteur"
|
||||
"name": "category.visitor"
|
||||
}
|
||||
}
|
||||
|
||||
def __init__(self, token: str):
|
||||
self.tg_token: str = token
|
||||
self.tg_app = None
|
||||
self.tg_app: Optional[Application] = None
|
||||
self.cache: dict[str, str] = {}
|
||||
self.db_con: Optional[Connection] = None
|
||||
self.locks: dict[str, bool] = {}
|
||||
self.fetch_lock = asyncio.Lock()
|
||||
self.gen_lock = asyncio.Lock()
|
||||
self.langs: Optional[dict[str, dict[str, str]]] = None
|
||||
self.fetch_lock: asyncio.Lock = asyncio.Lock()
|
||||
self.gen_lock: asyncio.Lock = asyncio.Lock()
|
||||
|
||||
def mainloop(self):
|
||||
logger.info("Initialization")
|
||||
@ -93,15 +93,27 @@ class BeeBot:
|
||||
self.db_con = sqlite3.connect(self.DB_PATH)
|
||||
self.check_database()
|
||||
self.load_cache()
|
||||
self.load_i18n()
|
||||
|
||||
app = ApplicationBuilder().token(self.tg_token).build()
|
||||
app.add_handler(CommandHandler("week", self.cmd_week))
|
||||
app.add_handler(CommandHandler("today", self.cmd_today))
|
||||
app.add_handler(CommandHandler("settings", self.cmd_settings))
|
||||
app.add_handler(CallbackQueryHandler(self.cb_handler))
|
||||
self.tg_app = ApplicationBuilder().token(self.tg_token).build()
|
||||
self.tg_app.add_handler(CommandHandler("week", self.cmd_week))
|
||||
self.tg_app.add_handler(CommandHandler("today", self.cmd_today))
|
||||
self.tg_app.add_handler(CommandHandler("settings", self.cmd_settings))
|
||||
self.tg_app.add_handler(CallbackQueryHandler(self.cb_handler))
|
||||
|
||||
logger.info("Starting bot")
|
||||
app.run_polling()
|
||||
self.tg_app.run_polling()
|
||||
|
||||
def load_i18n(self) -> None:
|
||||
with open(os.path.join(self.BASE_DIR, "lang.json"), "r") as f:
|
||||
self.langs = json.load(f)
|
||||
|
||||
def i18n(self, lang: str, key: str) -> str:
|
||||
if lang not in self.langs:
|
||||
lang = "en"
|
||||
if key not in self.langs[lang]:
|
||||
return f"[{key}]"
|
||||
return self.langs[lang][key]
|
||||
|
||||
async def cmd_week(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.debug("Received /week")
|
||||
@ -113,9 +125,10 @@ class BeeBot:
|
||||
|
||||
async def cmd_settings(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.debug("Received /settings")
|
||||
menu = self.get_settings_menu(update)
|
||||
lang = self.get_user_pref(update, context)["lang"]
|
||||
menu = self.get_settings_menu(context)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_chat.send_message(text="Your preferences", reply_markup=reply_markup)
|
||||
await update.effective_chat.send_message(text=self.i18n(lang, "menu.settings"), reply_markup=reply_markup)
|
||||
|
||||
async def cb_handler(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
query = update.callback_query
|
||||
@ -128,15 +141,22 @@ class BeeBot:
|
||||
elif query.data.startswith("toggle_category"):
|
||||
await self.cb_toggle_category(update, context)
|
||||
elif query.data == "back_to_settings":
|
||||
menu = self.get_settings_menu(update)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_message.edit_text(text="Your preferences", reply_markup=reply_markup)
|
||||
await self.show_menu(update, context, "menu.settings", self.get_settings_menu)
|
||||
|
||||
async def show_menu(self,
|
||||
update: Update,
|
||||
context: ContextTypes.DEFAULT_TYPE,
|
||||
text_key: str,
|
||||
menu_func: Callable[[ContextTypes.DEFAULT_TYPE], list[list[InlineKeyboardButton]]]) -> None:
|
||||
reply_markup = InlineKeyboardMarkup(menu_func(context))
|
||||
await update.effective_message.edit_text(
|
||||
text=self.i18n(context.user_data["lang"], text_key),
|
||||
reply_markup=reply_markup
|
||||
)
|
||||
|
||||
async def cb_change_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.debug("Clicked 'Change language'")
|
||||
menu = self.get_language_menu(update)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_message.edit_text(text="Choose a language", reply_markup=reply_markup)
|
||||
await self.show_menu(update, context, "menu.languages", self.get_language_menu)
|
||||
|
||||
async def cb_set_language(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
lang = update.callback_query.data.split(":")[1]
|
||||
@ -148,21 +168,17 @@ class BeeBot:
|
||||
)
|
||||
self.db_con.commit()
|
||||
cur.close()
|
||||
menu = self.get_language_menu(update)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_message.edit_text(text="Choose a language", reply_markup=reply_markup)
|
||||
self.get_user_pref(update, context)
|
||||
await self.show_menu(update, context, "menu.languages", self.get_language_menu)
|
||||
|
||||
async def cb_change_categories(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
logger.debug("Clicked 'Change categories'")
|
||||
menu = self.get_categories_menu(update)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_message.edit_text(text="Choose the price categories to display", reply_markup=reply_markup)
|
||||
await self.show_menu(update, context, "menu.categories", self.get_categories_menu)
|
||||
|
||||
async def cb_toggle_category(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
category = update.callback_query.data.split(":")[1]
|
||||
logger.debug(f"Clicked 'Toggle category {category}'")
|
||||
categories: set[str] = self.get_user_pref(update)["categories"]
|
||||
|
||||
categories: set[str] = context.user_data["categories"]
|
||||
if category in categories:
|
||||
categories.remove(category)
|
||||
else:
|
||||
@ -175,9 +191,8 @@ class BeeBot:
|
||||
)
|
||||
self.db_con.commit()
|
||||
cur.close()
|
||||
menu = self.get_categories_menu(update)
|
||||
reply_markup = InlineKeyboardMarkup(menu)
|
||||
await update.effective_message.edit_text(text="Choose the price categories to display", reply_markup=reply_markup)
|
||||
self.get_user_pref(update, context)
|
||||
await self.show_menu(update, context, "menu.categories", self.get_categories_menu)
|
||||
|
||||
async def request_menu(self, update: Update, context: ContextTypes.DEFAULT_TYPE, today_only: bool) -> None:
|
||||
chat_id = update.effective_chat.id
|
||||
@ -196,17 +211,20 @@ class BeeBot:
|
||||
# If menu needs to be fetched
|
||||
if not os.path.exists(menu_path) or self.is_outdated(menu_id, today_only):
|
||||
# Notify user
|
||||
msg = await update.message.reply_text("The menu is being updated, please wait...")
|
||||
msg = await update.message.reply_text(self.i18n(prefs["lang"], "notif.wait_updating"))
|
||||
async with self.fetch_lock:
|
||||
if today_only:
|
||||
await self.fetch_today_menu()
|
||||
else:
|
||||
await self.fetch_week_menu()
|
||||
if not os.path.exists(menu_path) or self.is_outdated(menu_id, today_only):
|
||||
if today_only:
|
||||
await self.fetch_today_menu()
|
||||
else:
|
||||
await self.fetch_week_menu()
|
||||
await msg.delete()
|
||||
|
||||
# If image needs to be (re)generated
|
||||
if not os.path.exists(img_path) or self.is_outdated(img_id, today_only):
|
||||
await self.gen_image(today_only, categories, img_path, img_id)
|
||||
async with self.gen_lock:
|
||||
if not os.path.exists(img_path) or self.is_outdated(img_id, today_only):
|
||||
await self.gen_image(today_only, categories, img_path, img_id)
|
||||
|
||||
await context.bot.send_photo(chat_id=chat_id, photo=img_path)
|
||||
|
||||
@ -225,25 +243,31 @@ class BeeBot:
|
||||
self.db_con.commit()
|
||||
cur.close()
|
||||
|
||||
def get_user_pref(self, update: Update) -> dict:
|
||||
def get_user_pref(self, update: Update, context: Optional[ContextTypes.DEFAULT_TYPE] = None) -> dict:
|
||||
user_id = update.effective_user.id
|
||||
cur = self.db_con.cursor()
|
||||
res = cur.execute("SELECT categories, lang FROM user WHERE telegram_id=?", (user_id,))
|
||||
user = res.fetchone()
|
||||
cur.close()
|
||||
|
||||
prefs = {
|
||||
"categories": {"E", "D", "C", "V"},
|
||||
"lang": "fr"
|
||||
}
|
||||
if user is None:
|
||||
self.create_user(user_id)
|
||||
return {
|
||||
"categories": {"E", "D", "C", "V"},
|
||||
"lang": "fr"
|
||||
else:
|
||||
categories = set(user[0].split(","))
|
||||
prefs = {
|
||||
"categories": categories,
|
||||
"lang": user[1]
|
||||
}
|
||||
|
||||
categories = set(user[0].split(","))
|
||||
return {
|
||||
"categories": categories,
|
||||
"lang": user[1]
|
||||
}
|
||||
if context is not None:
|
||||
context.user_data["categories"] = prefs["categories"]
|
||||
context.user_data["lang"] = prefs["lang"]
|
||||
|
||||
return prefs
|
||||
|
||||
def create_user(self, telegram_id: int) -> None:
|
||||
logger.debug(f"New user with id {telegram_id}")
|
||||
@ -377,23 +401,29 @@ class BeeBot:
|
||||
|
||||
return menus
|
||||
|
||||
def get_settings_menu(self, update: Update) -> list[list[InlineKeyboardButton]]:
|
||||
prefs = self.get_user_pref(update)
|
||||
def get_settings_menu(self, context: ContextTypes.DEFAULT_TYPE) -> list[list[InlineKeyboardButton]]:
|
||||
lang = context.user_data["lang"]
|
||||
menu = [
|
||||
[
|
||||
InlineKeyboardButton(f"Language: {prefs['lang']}", callback_data="change_language")
|
||||
InlineKeyboardButton(
|
||||
self.i18n(lang, "setting.language").format(lang),
|
||||
callback_data="change_language"
|
||||
)
|
||||
],
|
||||
[
|
||||
InlineKeyboardButton(f"Categories: {' / '.join(prefs['categories'])}", callback_data="change_categories")
|
||||
InlineKeyboardButton(
|
||||
self.i18n(lang, "setting.categories").format(" / ".join(context.user_data["categories"])),
|
||||
callback_data="change_categories"
|
||||
)
|
||||
]
|
||||
]
|
||||
return menu
|
||||
|
||||
def get_language_menu(self, update: Update) -> list[list[InlineKeyboardButton]]:
|
||||
prefs = self.get_user_pref(update)
|
||||
def get_language_menu(self, context: ContextTypes.DEFAULT_TYPE) -> list[list[InlineKeyboardButton]]:
|
||||
user_lang = context.user_data["lang"]
|
||||
buttons = []
|
||||
for lang, data in self.LANGUAGES.items():
|
||||
extra = " ✅" if prefs["lang"] == lang else ""
|
||||
extra = " ✅" if user_lang == lang else ""
|
||||
buttons.append(
|
||||
InlineKeyboardButton(
|
||||
data["emoji"] + extra,
|
||||
@ -402,24 +432,30 @@ class BeeBot:
|
||||
)
|
||||
menu = [buttons[i:i+2] for i in range(0, len(buttons), 2)]
|
||||
menu.append([
|
||||
InlineKeyboardButton("Back to settings", callback_data="back_to_settings")
|
||||
InlineKeyboardButton(
|
||||
self.i18n(user_lang, "menu.back_to_settings"),
|
||||
callback_data="back_to_settings"
|
||||
)
|
||||
])
|
||||
return menu
|
||||
|
||||
def get_categories_menu(self, update: Update) -> list[list[InlineKeyboardButton]]:
|
||||
prefs = self.get_user_pref(update)
|
||||
def get_categories_menu(self, context: ContextTypes.DEFAULT_TYPE) -> list[list[InlineKeyboardButton]]:
|
||||
lang = context.user_data["lang"]
|
||||
buttons = []
|
||||
for categ, data in self.CATEGORIES.items():
|
||||
extra = " ✅" if categ in prefs["categories"] else " ❌"
|
||||
extra = " ✅" if categ in context.user_data["categories"] else " ❌"
|
||||
buttons.append(
|
||||
InlineKeyboardButton(
|
||||
data["name"] + extra,
|
||||
self.i18n(lang, data["name"]) + extra,
|
||||
callback_data=f"toggle_category:{categ}"
|
||||
)
|
||||
)
|
||||
menu = [buttons[i:i+2] for i in range(0, len(buttons), 2)]
|
||||
menu.append([
|
||||
InlineKeyboardButton("Back to settings", callback_data="back_to_settings")
|
||||
InlineKeyboardButton(
|
||||
self.i18n(lang, "menu.back_to_settings"),
|
||||
callback_data="back_to_settings"
|
||||
)
|
||||
])
|
||||
return menu
|
||||
|
||||
|
41
src/lang.json
Normal file
41
src/lang.json
Normal file
@ -0,0 +1,41 @@
|
||||
{
|
||||
"fr": {
|
||||
"category.student": "Étudiant",
|
||||
"category.phd_student": "Doctorant",
|
||||
"category.campus": "Campus",
|
||||
"category.visitor": "Visiteur",
|
||||
"menu.settings": "Vos préférences",
|
||||
"menu.languages": "Choisissez une langue\n(Ceci n'affectera pas la langue des menus)",
|
||||
"menu.categories": "Choisissez les catégories de prix à afficher",
|
||||
"menu.back_to_settings": "Retour aux paramètres",
|
||||
"setting.language": "Langue: {}",
|
||||
"setting.categories": "Catégories: {}",
|
||||
"notif.wait_updating": "Le menu est en train d'être mis à jour, veuillez patienter..."
|
||||
},
|
||||
"en": {
|
||||
"category.student": "Student",
|
||||
"category.phd_student": "PhD Student",
|
||||
"category.campus": "Campus",
|
||||
"category.visitor": "Visitor",
|
||||
"menu.settings": "Your preferences",
|
||||
"menu.languages": "Choose a language\n(This will not affect the menu language)",
|
||||
"menu.categories": "Choose the price categories to display",
|
||||
"menu.back_to_settings": "Back to settings",
|
||||
"setting.language": "Language: {}",
|
||||
"setting.categories": "Categories: {}",
|
||||
"notif.wait_updating": "The menu is being updated, please wait..."
|
||||
},
|
||||
"de": {
|
||||
"category.student": "Student",
|
||||
"category.phd_student": "Doktorand",
|
||||
"category.campus": "Campus",
|
||||
"category.visitor": "Besucher",
|
||||
"menu.settings": "Ihre Präferenzen",
|
||||
"menu.languages": "Wählen Sie eine Sprache\n(Dies hat keinen Einfluss auf die Menüsprache)",
|
||||
"menu.categories": "Wählen Sie die anzuzeigenden Preiskategorien",
|
||||
"menu.back_to_settings": "Zurück zu Einstellungen",
|
||||
"setting.language": "Sprache: {}",
|
||||
"setting.categories": "Kategorien: {}",
|
||||
"notif.wait_updating": "Das Menü wird gerade aktualisiert, bitte warten Sie..."
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user