diff --git a/.gitignore b/.gitignore
index f67c040..2ca8c94 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,27 @@
-auth.ini
-userdata
-logs*.txt
-*.log
-*.logs
-.env
\ No newline at end of file
+# Text backup files
+*.bak
+
+# Database
+*.sqlite3
+*.db
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+tests/.cache/
+
+# virtualenv
+venv/
+static/**
+
+# PyCharm
+.idea
+
+# dotenv
+.env
+
+# CI/CD
+.venv/
+.wheels/
+.core
diff --git a/migrations/env.py b/migrations/env.py
index 19c985c..1be270f 100644
--- a/migrations/env.py
+++ b/migrations/env.py
@@ -2,11 +2,9 @@
from alembic import context
from sqlalchemy import engine_from_config, pool
-
from src.db import Base
from src.settings import Settings
-
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
diff --git a/migrations/versions/14de3a5684ea_bigint.py b/migrations/versions/14de3a5684ea_bigint.py
index 2277f0f..ab11753 100644
--- a/migrations/versions/14de3a5684ea_bigint.py
+++ b/migrations/versions/14de3a5684ea_bigint.py
@@ -9,7 +9,6 @@
import sqlalchemy as sa
from alembic import op
-
# revision identifiers, used by Alembic.
revision = "14de3a5684ea"
down_revision = "bd42bc6088a1"
@@ -19,7 +18,9 @@
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
- op.execute("ALTER TABLE tg_user ALTER COLUMN tg_id TYPE bigint USING tg_id::bigint;")
+ op.execute(
+ "ALTER TABLE tg_user ALTER COLUMN tg_id TYPE bigint USING tg_id::bigint;"
+ )
# ### end Alembic commands ###
diff --git a/migrations/versions/56584d8792af_init.py b/migrations/versions/56584d8792af_init.py
index 4634c87..2c64f37 100644
--- a/migrations/versions/56584d8792af_init.py
+++ b/migrations/versions/56584d8792af_init.py
@@ -1,7 +1,7 @@
"""Init
Revision ID: 56584d8792af
-Revises:
+Revises:
Create Date: 2023-01-12 18:59:05.426265
"""
@@ -9,7 +9,6 @@
import sqlalchemy as sa
from alembic import op
-
# revision identifiers, used by Alembic.
revision = "56584d8792af"
down_revision = None
diff --git a/migrations/versions/bd42bc6088a1_nullable.py b/migrations/versions/bd42bc6088a1_nullable.py
index e77da55..4b2f63d 100644
--- a/migrations/versions/bd42bc6088a1_nullable.py
+++ b/migrations/versions/bd42bc6088a1_nullable.py
@@ -9,7 +9,6 @@
import sqlalchemy as sa
from alembic import op
-
# revision identifiers, used by Alembic.
revision = "bd42bc6088a1"
down_revision = "56584d8792af"
diff --git a/src/__main__.py b/src/__main__.py
index ff7f86c..b6661c8 100644
--- a/src/__main__.py
+++ b/src/__main__.py
@@ -1,27 +1,22 @@
# Marakulin Andrey https://github.com/Annndruha
# 2023
+import asyncio
import logging
-from telegram.ext import ApplicationBuilder, CallbackQueryHandler, CommandHandler, MessageHandler, filters
-
from src.errors_solver import native_error_handler
-from src.handlers import (
- handler_auth,
- handler_button_browser,
- handler_help,
- handler_mismatch_doctype,
- handler_other_messages,
- handler_print,
- handler_register,
- handler_start,
- handler_unknown_command,
-)
-from src.settings import Settings
-
+from src.handlers import (handler_auth, handler_button_browser, handler_help,
+ handler_mismatch_doctype, handler_other_messages,
+ handler_print, handler_register, handler_start,
+ handler_unknown_command)
+from src.settings import get_settings, sync_from_server
+from telegram.ext import (ApplicationBuilder, CallbackQueryHandler,
+ CommandHandler, MessageHandler, filters)
tg_log_handler = logging.FileHandler("tgbot_telegram_updater.log")
-tg_log_handler.setFormatter(logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s"))
+tg_log_handler.setFormatter(
+ logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
+)
tg_logger = logging.getLogger("telegram.ext._updater")
tg_logger.propagate = False
tg_logger.addHandler(tg_log_handler)
@@ -35,16 +30,29 @@
)
if __name__ == "__main__":
- settings = Settings()
+ asyncio.run(sync_from_server())
+ settings = get_settings()
application = ApplicationBuilder().token(settings.BOT_TOKEN).build()
application.add_handler(CallbackQueryHandler(handler_button_browser))
- application.add_handler(CommandHandler("start", handler_start, filters=filters.UpdateType.MESSAGE))
- application.add_handler(CommandHandler("help", handler_help, filters=filters.UpdateType.MESSAGE))
- application.add_handler(CommandHandler("auth", handler_auth, filters=filters.UpdateType.MESSAGE))
+ application.add_handler(
+ CommandHandler("start", handler_start, filters=filters.UpdateType.MESSAGE)
+ )
+ application.add_handler(
+ CommandHandler("help", handler_help, filters=filters.UpdateType.MESSAGE)
+ )
+ application.add_handler(
+ CommandHandler("auth", handler_auth, filters=filters.UpdateType.MESSAGE)
+ )
application.add_handler(MessageHandler(filters.COMMAND, handler_unknown_command))
application.add_handler(MessageHandler(filters.Document.PDF, handler_print))
- application.add_handler(MessageHandler(filters.Document.ALL, handler_mismatch_doctype))
- application.add_handler(MessageHandler(filters.UpdateType.MESSAGE & filters.TEXT, handler_register))
- application.add_handler(MessageHandler(filters.UpdateType.MESSAGE, handler_other_messages))
+ application.add_handler(
+ MessageHandler(filters.Document.ALL, handler_mismatch_doctype)
+ )
+ application.add_handler(
+ MessageHandler(filters.UpdateType.MESSAGE & filters.TEXT, handler_register)
+ )
+ application.add_handler(
+ MessageHandler(filters.UpdateType.MESSAGE, handler_other_messages)
+ )
application.add_error_handler(native_error_handler)
application.run_polling()
diff --git a/src/answers.py b/src/answers.py
index 09f39ca..10fd02c 100644
--- a/src/answers.py
+++ b/src/answers.py
@@ -3,78 +3,97 @@
from dataclasses import dataclass
+from src.settings import get_settings
+
+settings = get_settings()
+
@dataclass
class Answers:
- auth = '🔑 Авторизация'
- about = '📄 Описание'
- back = '◀️ Назад'
- qr = '📷 Печать по QR'
- kb_print = '⚙️ Настройки печати'
- kb_print_copies = '📑 Копий:'
- kb_print_side = '📎 Односторонняя печать'
- kb_print_two_side = '🖇 Двухсторонняя печать'
- hello = '👋🏻 Привет! Я телеграм-бот бесплатного принтера.\n' 'Отправьте PDF файл и получите PIN для печати.'
+ auth = "🔑 Авторизация"
+ about = "📄 Описание"
+ back = "◀️ Назад"
+ qr = "📷 Печать по QR"
+ kb_print = "⚙️ Настройки печати"
+ kb_print_copies = "📑 Копий:"
+ kb_print_side = "📎 Односторонняя печать"
+ kb_print_two_side = "🖇 Двухсторонняя печать"
+ hello = (
+ "👋🏻 Привет! Я телеграм-бот бесплатного принтера.\n"
+ f"Отправьте файл формата {settings.CONTENT_TYPES} и получите PIN для печати."
+ )
help = (
- 'Я телеграм-бот бесплатного принтера профкома студентов физического факультета МГУ!\n\n'
- '❔ Отправьте PDF файл и получите PIN для печати. '
- 'Поддерживаются только .pdf файлы не более 3МБ.\n'
- 'С этим PIN необходимо подойти к принтеру и ввести его в терминал печати. '
- 'Либо отсканировать QR-код на принтере с помощью кнопки. После этого начнётся печать.'
- '\n\n'
- '⚙️ Настройки печати можно изменять после отправки файла, они сохраняются автоматически. '
- 'В момент печати используются самые последние настройки.\n\n'
- '❗️ Файлы, которые вы отправляете через бота, будут храниться в течение нескольких месяцев'
- ' на сервере в Москве, а также в этом чате Telegram.\n'
- 'Доступ к файлам имеет узкий круг лиц, ответственных за работоспособность сервиса печати.\n'
- 'Мы НЕ рекомендуем использовать данный сервис для печати конфиденциальных документов!\n\n'
- '💻 Бот разработан группой программистов профкома, '
+ "Я телеграм-бот бесплатного принтера профкома студентов физического факультета МГУ!\n\n"
+ f"❔ Отправьте файл формата {settings.CONTENT_TYPES} и получите PIN для печати. "
+ f"Поддерживаются только .pdf файлы не более {settings.MAX_PDF_SIZE_MB} .\n"
+ "С этим PIN необходимо подойти к принтеру и ввести его в терминал печати. "
+ "Либо отсканировать QR-код на принтере с помощью кнопки. После этого начнётся печать."
+ "\n\n"
+ "⚙️ Настройки печати можно изменять после отправки файла, они сохраняются автоматически. "
+ "В момент печати используются самые последние настройки.\n\n"
+ "❗️ Файлы, которые вы отправляете через бота, будут храниться в течение нескольких месяцев"
+ " на сервере в Москве, а также в этом чате Telegram.\n"
+ "Доступ к файлам имеет узкий круг лиц, ответственных за работоспособность сервиса печати.\n"
+ "Мы НЕ рекомендуем использовать данный сервис для печати конфиденциальных документов!\n\n"
+ "💻 Бот разработан группой программистов профкома, "
'как и приложение Твой ФФ! '
- 'В приложении вы сможете найти больше настроек печати, расписание и много других возможностей.\n'
+ "В приложении вы сможете найти больше настроек печати, расписание и много других возможностей.\n"
'Так же есть бот для печати ВКонтакте.'
)
val_fail = (
- '⚠️ Проверка не пройдена. Удостоверьтесь что вы состоите в профсоюзе и правильно ввели данные.\n\n'
- 'Введите фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567'
+ "⚠️ Проверка не пройдена. Удостоверьтесь что вы состоите в профсоюзе и правильно ввели данные.\n\n"
+ "Введите фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567"
)
- val_pass = '🥳 Поздравляю! Проверка пройдена и данные сохранены для этого телеграм-аккаунта. Можете присылать pdf.'
+ val_pass = "🥳 Поздравляю! Проверка пройдена и данные сохранены для этого телеграм-аккаунта. Можете присылать pdf."
val_need = (
- '👤 Для использования принтера необходимо авторизоваться.\n'
- 'Отправьте фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567'
+ "👤 Для использования принтера необходимо авторизоваться.\n"
+ "Отправьте фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567"
)
val_update_fail = (
- 'Сообщение не распознано.\nЧтобы открыть инструкцию введите: /help\n'
- 'Для того чтобы обновить данные авторизации введите фамилию и номер'
- 'профсоюзного билета в формате:\n\nИванов\n1234567'
+ "Сообщение не распознано.\nЧтобы открыть инструкцию введите: /help\n"
+ "Для того чтобы обновить данные авторизации введите фамилию и номер"
+ "профсоюзного билета в формате:\n\nИванов\n1234567"
)
- val_update_pass = '🥳 Поздравляю! Проверка пройдена и данные обновлены.'
- val_addition = '\n\nНо для начала нужно авторизоваться. Нажмите на кнопку ниже:'
+ val_update_pass = "🥳 Поздравляю! Проверка пройдена и данные обновлены."
+ val_addition = "\n\nНо для начала нужно авторизоваться. Нажмите на кнопку ниже:"
val_info = (
- 'Вы авторизованы!\n'
- 'Ваш id в телеграм: {}\n'
- 'Фамилия: {}\n'
- 'Номер профсоюзного билета: {}'
+ "Вы авторизованы!\n"
+ "Ваш id в телеграм: {}\n"
+ "Фамилия: {}\n"
+ "Номер профсоюзного билета: {}"
+ )
+ unknown_command = (
+ "Неизвестная команда.\n" "У бота лишь три команды: /start /help /auth"
)
- unknown_command = 'Неизвестная команда.\n' 'У бота лишь три команды: /start /help /auth'
- only_pdf = 'Документы на печать принимаются только в формате PDF'
+ only_pdf = "Документы на печать принимаются только в формате PDF"
doc_not_accepted = (
- '⚠️ Документ не принят, сначала авторизуйтесь.\n'
- 'Отправьте фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567'
+ "⚠️ Документ не принят, сначала авторизуйтесь.\n"
+ "Отправьте фамилию и номер профсоюзного билета в формате:\n\nИванов\n1234567"
+ )
+ file_size_error = (
+ f"⚠️ Принимаются только файлы размером меньше {settings.MAX_PDF_SIZE_MB} MB.\n"
+ "Файл {} не принят."
)
- file_size_error = '⚠️ Принимаются только файлы размером меньше 3 MB.\n' 'Файл {} не принят.'
send_to_print = (
- '✅ Файл {} успешно загружен. Для печати подойдите к принтеру и введите PIN:\n\n'
- '{}\n\n'
- 'Для быстрой печати отсканируйте QR код на экране принтера.'
+ "✅ Файл {} успешно загружен. Для печати подойдите к принтеру и введите PIN:\n\n"
+ "{}\n\n"
+ "Для быстрой печати отсканируйте QR код на экране принтера."
+ )
+ unreadable_file_error = (
+ "⚠️ Я не смог прочитать файл {}."
+ f"Проверьте его целостность и формат, я работаю только с {settings.CONTENT_TYPES}."
+ )
+ qr_print = "{}{}"
+ settings_warning = "Настройки сохраняются автоматически."
+ settings_change_fail = (
+ "Что-то сломалось, настройки печати не изменены, попробуйте через пару минут."
+ )
+ unknown_keyboard_payload = "Видимо бот обновился, выполните команду /start"
+ im_broken = "Глубоко внутри меня что-то сломалось...\nПопробуйте через пару минут."
+ download_error = "Ошибка получения файла, попробуйте позже."
+ print_err = "😵 Ошибка сервера печати. Попробуйте позже."
+ db_err = "😵 Ошибка базы данных. Попробуйте ещё раз, если не получилось, то попробуйте позже."
+ err_message_type = (
+ "Сообщение не распознано.\nЧтобы открыть инструкцию введите: /help"
)
- unreadable_file_error = '⚠️ Я не смог прочитать файл {}. Проверьте его целостность и формат, я работаю только с pdf.'
- qr_print = '{}{}'
- settings_warning = 'Настройки сохраняются автоматически.'
- settings_change_fail = 'Что-то сломалось, настройки печати не изменены, попробуйте через пару минут.'
- unknown_keyboard_payload = 'Видимо бот обновился, выполните команду /start'
- im_broken = 'Глубоко внутри меня что-то сломалось...\nПопробуйте через пару минут.'
- download_error = 'Ошибка получения файла, попробуйте позже.'
- print_err = '😵 Ошибка сервера печати. Попробуйте позже.'
- db_err = '😵 Ошибка базы данных. Попробуйте ещё раз, если не получилось, то попробуйте позже.'
- err_message_type = 'Сообщение не распознано.\nЧтобы открыть инструкцию введите: /help'
diff --git a/src/errors_solver.py b/src/errors_solver.py
index 958d70f..708a61c 100644
--- a/src/errors_solver.py
+++ b/src/errors_solver.py
@@ -6,13 +6,11 @@
import psycopg2
from sqlalchemy.exc import SQLAlchemyError
+from src.answers import Answers
from telegram import Update
from telegram.error import TelegramError
from telegram.ext import ContextTypes
-from src.answers import Answers
-
-
ans = Answers()
@@ -31,11 +29,13 @@ async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE):
try:
await func(update, context)
except TelegramError as err:
- logging.error(f'TelegramError: {str(err.message)}')
+ logging.error(f"TelegramError: {str(err.message)}")
except (SQLAlchemyError, psycopg2.Error) as err:
logging.error(err)
traceback.print_tb(err.__traceback__)
- await context.bot.send_message(chat_id=update.message.chat.id, text=ans.db_err)
+ await context.bot.send_message(
+ chat_id=update.message.chat.id, text=ans.db_err
+ )
except Exception as err:
logging.error(err)
traceback.print_tb(err.__traceback__)
diff --git a/src/handlers.py b/src/handlers.py
index b44c7a5..b399c0a 100644
--- a/src/handlers.py
+++ b/src/handlers.py
@@ -7,37 +7,42 @@
import requests
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
-from telegram import InlineKeyboardButton, InlineKeyboardMarkup, Update, WebAppInfo
-from telegram.constants import ParseMode
-from telegram.error import TelegramError
-from telegram.ext import CallbackContext, ContextTypes
-
from src import marketing
from src.answers import Answers
from src.db import TgUser
from src.errors_solver import errors_solver
from src.log_formatter import log_actor, log_formatter
-from src.settings import Settings
-
+from src.settings import get_settings
+from telegram import (InlineKeyboardButton, InlineKeyboardMarkup, Update,
+ WebAppInfo)
+from telegram.constants import ParseMode
+from telegram.error import TelegramError
+from telegram.ext import CallbackContext, ContextTypes
ans = Answers()
-settings = Settings()
-engine = create_engine(url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level='AUTOCOMMIT')
+settings = get_settings()
+engine = create_engine(
+ url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level="AUTOCOMMIT"
+)
Session = sessionmaker(bind=engine)
@errors_solver
@log_formatter
async def handler_start(update: Update, context: ContextTypes.DEFAULT_TYPE):
- keyboard_base = [[InlineKeyboardButton(ans.about, callback_data='to_about')]]
+ keyboard_base = [[InlineKeyboardButton(ans.about, callback_data="to_about")]]
text, reply_markup = __change_message_by_auth(update, ans.hello, keyboard_base)
- await update.message.reply_text(text=text, reply_markup=reply_markup, disable_web_page_preview=True)
+ await update.message.reply_text(
+ text=text, reply_markup=reply_markup, disable_web_page_preview=True
+ )
@errors_solver
@log_formatter
async def handler_help(update: Update, context: ContextTypes.DEFAULT_TYPE):
- await update.message.reply_text(ans.help, disable_web_page_preview=True, parse_mode=ParseMode('HTML'))
+ await update.message.reply_text(
+ ans.help, disable_web_page_preview=True, parse_mode=ParseMode("HTML")
+ )
@errors_solver
@@ -47,25 +52,27 @@ async def handler_auth(update: Update, context: ContextTypes.DEFAULT_TYPE):
if requisites is None:
await update.message.reply_text(ans.val_need)
else:
- await update.message.reply_text(ans.val_info.format(*requisites), parse_mode=ParseMode('HTML'))
+ await update.message.reply_text(
+ ans.val_info.format(*requisites), parse_mode=ParseMode("HTML")
+ )
@errors_solver
@log_formatter
async def handler_button_browser(update: Update, context: CallbackContext) -> None:
- if update.callback_query.data == 'to_hello':
- keyboard_base = [[InlineKeyboardButton(ans.about, callback_data='to_about')]]
+ if update.callback_query.data == "to_hello":
+ keyboard_base = [[InlineKeyboardButton(ans.about, callback_data="to_about")]]
text, reply_markup = __change_message_by_auth(update, ans.hello, keyboard_base)
- elif update.callback_query.data == 'to_about':
- keyboard_base = [[InlineKeyboardButton(ans.back, callback_data='to_hello')]]
+ elif update.callback_query.data == "to_about":
+ keyboard_base = [[InlineKeyboardButton(ans.back, callback_data="to_hello")]]
text, reply_markup = __change_message_by_auth(update, ans.help, keyboard_base)
- elif update.callback_query.data == 'to_auth':
- keyboard_base = [[InlineKeyboardButton(ans.back, callback_data='to_hello')]]
+ elif update.callback_query.data == "to_auth":
+ keyboard_base = [[InlineKeyboardButton(ans.back, callback_data="to_hello")]]
text, reply_markup = ans.val_need, InlineKeyboardMarkup(keyboard_base)
- elif update.callback_query.data.startswith('print_'):
+ elif update.callback_query.data.startswith("print_"):
await __print_settings_solver(update, context)
return
@@ -76,7 +83,7 @@ async def handler_button_browser(update: Update, context: CallbackContext) -> No
text=text,
reply_markup=reply_markup,
disable_web_page_preview=True,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
@@ -91,50 +98,65 @@ async def handler_unknown_command(update: Update, context: ContextTypes.DEFAULT_
async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
requisites = __auth(update)
if requisites is None:
- await context.bot.send_message(chat_id=update.message.chat.id, text=ans.doc_not_accepted)
- logging.warning(f'{log_actor(update)} try print with no auth')
+ await context.bot.send_message(
+ chat_id=update.message.chat.id, text=ans.doc_not_accepted
+ )
+ logging.warning(f"{log_actor(update)} try print with no auth")
return
try:
filebytes, filename = await __get_attachments(update, context)
- logging.info(f'{log_actor(update)} get attachments OK')
+ logging.info(f"{log_actor(update)} get attachments OK")
except FileSizeError:
await update.message.reply_text(
text=ans.file_size_error.format(update.message.document.file_name),
reply_to_message_id=update.message.id,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
- logging.warning(f'{log_actor(update)} get attachments FileSizeError')
+ logging.warning(f"{log_actor(update)} get attachments FileSizeError")
return
except TelegramError:
- await update.message.reply_text(text=ans.download_error, reply_to_message_id=update.message.id)
- logging.warning(f'{log_actor(update)} get attachments download_error')
+ await update.message.reply_text(
+ text=ans.download_error, reply_to_message_id=update.message.id
+ )
+ logging.warning(f"{log_actor(update)} get attachments download_error")
return
r = requests.post(
- settings.PRINT_URL + '/file',
- json={'surname': requisites[1], 'number': requisites[2], 'filename': filename, 'source': 'tgbot'},
+ settings.PRINT_URL + "/file",
+ json={
+ "surname": requisites[1],
+ "number": requisites[2],
+ "filename": filename,
+ "source": "tgbot",
+ },
)
if r.status_code == 200:
- pin = r.json()['pin']
+ pin = r.json()["pin"]
files = {
- 'file': (
+ "file": (
filename,
filebytes.getvalue(),
- 'application/pdf',
- {'Expires': '0'},
+ "application/pdf",
+ {"Expires": "0"},
)
}
- rfile = requests.post(settings.PRINT_URL + '/file/' + pin, files=files)
+ rfile = requests.post(settings.PRINT_URL + "/file/" + pin, files=files)
if rfile.status_code == 200:
reply_markup = InlineKeyboardMarkup(
[
[
InlineKeyboardButton(
text=ans.qr,
- web_app=WebAppInfo(ans.qr_print.format(settings.PRINT_URL_QR, pin)),
+ web_app=WebAppInfo(
+ ans.qr_print.format(settings.PRINT_URL_QR, pin)
+ ),
+ )
+ ],
+ [
+ InlineKeyboardButton(
+ ans.kb_print, callback_data=f"print_settings_{pin}"
)
],
- [InlineKeyboardButton(ans.kb_print, callback_data=f'print_settings_{pin}')],
]
)
await update.message.reply_text(
@@ -142,9 +164,9 @@ async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
reply_markup=reply_markup,
reply_to_message_id=update.message.id,
disable_web_page_preview=True,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
- logging.info(f'{log_actor(update)} print success')
+ logging.info(f"{log_actor(update)} print success")
marketing.print_success(
tg_id=update.message.chat.id,
surname=requisites[1],
@@ -153,28 +175,30 @@ async def handler_print(update: Update, context: ContextTypes.DEFAULT_TYPE):
return
elif rfile.status_code == 415:
await update.message.reply_text(
- text=ans.unreadable_file_error.format(update.message.document.file_name),
+ text=ans.unreadable_file_error.format(
+ update.message.document.file_name
+ ),
reply_to_message_id=update.message.id,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
- logging.warning(f'{log_actor(update)} print api 415 UnreadableErr')
+ logging.warning(f"{log_actor(update)} print api 415 UnreadableErr")
return
elif r.status_code == 413:
await update.message.reply_text(
text=ans.file_size_error.format(update.message.document.file_name),
reply_to_message_id=update.message.id,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
- logging.warning(f'{log_actor(update)} print api 413 SizeErr')
+ logging.warning(f"{log_actor(update)} print api 413 SizeErr")
return
await context.bot.send_message(
chat_id=update.effective_user.id,
text=ans.print_err,
- parse_mode=ParseMode('HTML'),
+ parse_mode=ParseMode("HTML"),
)
- logging.warning(f'{log_actor(update)} print unknown error')
+ logging.warning(f"{log_actor(update)} print unknown error")
@errors_solver
@@ -196,65 +220,86 @@ async def handler_register(update: Update, context: ContextTypes.DEFAULT_TYPE):
text = update.message.text
chat_id = update.message.chat.id
- if text is None or len(text.split('\n')) != 2:
+ if text is None or len(text.split("\n")) != 2:
with Session() as session:
- if session.query(TgUser).filter(TgUser.tg_id == chat_id).one_or_none() is None:
+ if (
+ session.query(TgUser).filter(TgUser.tg_id == chat_id).one_or_none()
+ is None
+ ):
await context.bot.send_message(chat_id=chat_id, text=ans.val_need)
- logging.warning(f'{log_actor(update)} val_need')
+ logging.warning(f"{log_actor(update)} val_need")
else:
- await context.bot.send_message(chat_id=chat_id, text=ans.val_update_fail)
- logging.warning(f'{log_actor(update)} val_update_fail')
+ await context.bot.send_message(
+ chat_id=chat_id, text=ans.val_update_fail
+ )
+ logging.warning(f"{log_actor(update)} val_update_fail")
return
- if len(text.split('\n')) == 2:
- surname = text.split('\n')[0].strip()
- number = text.split('\n')[1].strip()
+ if len(text.split("\n")) == 2:
+ surname = text.split("\n")[0].strip()
+ number = text.split("\n")[1].strip()
r = requests.get(
- settings.PRINT_URL + '/is_union_member',
+ settings.PRINT_URL + "/is_union_member",
params=dict(surname=surname, v=1, number=number),
)
with Session() as session:
- data: TgUser | None = session.query(TgUser).filter(TgUser.tg_id == update.effective_user.id).one_or_none()
+ data: TgUser | None = (
+ session.query(TgUser)
+ .filter(TgUser.tg_id == update.effective_user.id)
+ .one_or_none()
+ )
if r.json() and data is None:
session.add(TgUser(tg_id=chat_id, surname=surname, number=number))
session.commit()
await context.bot.send_message(chat_id=chat_id, text=ans.val_pass)
marketing.register(tg_id=chat_id, surname=surname, number=number)
- logging.info(f'{log_actor(update)} register OK: {surname} {number}')
+ logging.info(f"{log_actor(update)} register OK: {surname} {number}")
return True
elif r.json() and data is not None:
data.surname = surname
data.number = number
session.commit()
- await context.bot.send_message(chat_id=chat_id, text=ans.val_update_pass)
+ await context.bot.send_message(
+ chat_id=chat_id, text=ans.val_update_pass
+ )
marketing.re_register(tg_id=chat_id, surname=surname, number=number)
- logging.info(f'{log_actor(update)} register repeat OK: {surname} {number}')
+ logging.info(
+ f"{log_actor(update)} register repeat OK: {surname} {number}"
+ )
return True
elif r.json() is False:
await context.bot.send_message(chat_id=chat_id, text=ans.val_fail)
- marketing.register_exc_wrong(tg_id=chat_id, surname=surname, number=number)
- logging.info(f'{log_actor(update)} register val_fail: {surname} {number}')
+ marketing.register_exc_wrong(
+ tg_id=chat_id, surname=surname, number=number
+ )
+ logging.info(
+ f"{log_actor(update)} register val_fail: {surname} {number}"
+ )
async def __print_settings_solver(update: Update, context: CallbackContext):
- _, button, pin = update.callback_query.data.split('_')
+ _, button, pin = update.callback_query.data.split("_")
- r = requests.get(settings.PRINT_URL + f'''/file/{pin}''')
+ r = requests.get(settings.PRINT_URL + f"""/file/{pin}""")
if r.status_code == 200:
- options = r.json()['options']
+ options = r.json()["options"]
else:
await update.callback_query.message.reply_text(ans.settings_change_fail)
return
- if button == 'copies':
- options['copies'] = options['copies'] % 5 + 1
- elif button == 'twosided':
- options['two_sided'] = not options['two_sided']
+ if button == "copies":
+ options["copies"] = options["copies"] % 5 + 1
+ elif button == "twosided":
+ options["two_sided"] = not options["two_sided"]
else:
- await context.bot.answer_callback_query(update.callback_query.id, ans.settings_warning)
+ await context.bot.answer_callback_query(
+ update.callback_query.id, ans.settings_warning
+ )
- r = requests.patch(settings.PRINT_URL + f'''/file/{pin}''', json={'options': options})
+ r = requests.patch(
+ settings.PRINT_URL + f"""/file/{pin}""", json={"options": options}
+ )
if r.status_code != 200:
await update.callback_query.message.reply_text(ans.settings_change_fail)
return
@@ -269,13 +314,13 @@ async def __print_settings_solver(update: Update, context: CallbackContext):
[
InlineKeyboardButton(
f'{ans.kb_print_copies} {options["copies"]}',
- callback_data=f'print_copies_{pin}',
+ callback_data=f"print_copies_{pin}",
)
],
[
InlineKeyboardButton(
- ans.kb_print_two_side if options['two_sided'] else ans.kb_print_side,
- callback_data=f'print_twosided_{pin}',
+ ans.kb_print_two_side if options["two_sided"] else ans.kb_print_side,
+ callback_data=f"print_twosided_{pin}",
)
],
]
@@ -285,7 +330,11 @@ async def __print_settings_solver(update: Update, context: CallbackContext):
def __auth(update):
with Session() as session:
- data: TgUser | None = session.query(TgUser).filter(TgUser.tg_id == update.effective_user.id).one_or_none()
+ data: TgUser | None = (
+ session.query(TgUser)
+ .filter(TgUser.tg_id == update.effective_user.id)
+ .one_or_none()
+ )
if data is not None:
return data.tg_id, data.surname, data.number
@@ -293,7 +342,7 @@ def __auth(update):
def __change_message_by_auth(update, text, keyboard):
if __auth(update) is None:
text += ans.val_addition
- keyboard.append([InlineKeyboardButton(ans.auth, callback_data='to_auth')])
+ keyboard.append([InlineKeyboardButton(ans.auth, callback_data="to_auth")])
return text, InlineKeyboardMarkup(keyboard)
diff --git a/src/log_formatter.py b/src/log_formatter.py
index 145331c..cab9c1e 100644
--- a/src/log_formatter.py
+++ b/src/log_formatter.py
@@ -16,18 +16,26 @@ def log_formatter(func):
@functools.wraps(func)
async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE):
- actor_handler = f'[{update.effective_user.id} {update.effective_user.full_name}] [{func.__name__}]'
+ actor_handler = f"[{update.effective_user.id} {update.effective_user.full_name}] [{func.__name__}]"
if update.callback_query is not None:
- logging.info(f'{actor_handler} [callback {update.callback_query.message.id}]: {update.callback_query.data}')
+ logging.info(
+ f"{actor_handler} [callback {update.callback_query.message.id}]: {update.callback_query.data}"
+ )
elif update.message is not None:
if update.message.text is not None:
- logging.info(f'{actor_handler} [text]: {repr(update.message.text)}')
+ logging.info(f"{actor_handler} [text]: {repr(update.message.text)}")
elif update.message.document is not None:
- logging.info(f'{actor_handler} [document]: {repr(update.message.document.file_name)}')
+ logging.info(
+ f"{actor_handler} [document]: {repr(update.message.document.file_name)}"
+ )
else:
- logging.info(f'{actor_handler} [UNKNOWN MESSAGE TYPE: {effective_message_type(update)}]')
+ logging.info(
+ f"{actor_handler} [UNKNOWN MESSAGE TYPE: {effective_message_type(update)}]"
+ )
else:
- logging.info(f'{actor_handler} [UNKNOWN UPDATE TYPE: {effective_message_type(update)}]')
+ logging.info(
+ f"{actor_handler} [UNKNOWN UPDATE TYPE: {effective_message_type(update)}]"
+ )
await func(update, context)
@@ -35,4 +43,4 @@ async def wrapper(update: Update, context: ContextTypes.DEFAULT_TYPE):
def log_actor(update):
- return f'[{update.effective_user.id} {update.effective_user.full_name}] >'
+ return f"[{update.effective_user.id} {update.effective_user.full_name}] >"
diff --git a/src/marketing.py b/src/marketing.py
index bec207f..a96980b 100644
--- a/src/marketing.py
+++ b/src/marketing.py
@@ -3,11 +3,9 @@
import traceback
import requests
+from src.settings import get_settings
-from src.settings import Settings
-
-
-settings = Settings()
+settings = get_settings()
def pass_if_exc(func):
diff --git a/src/settings.py b/src/settings.py
index 104b350..144b588 100644
--- a/src/settings.py
+++ b/src/settings.py
@@ -1,15 +1,50 @@
+from functools import lru_cache
+from typing import List, Optional
+
+import requests
from pydantic import ConfigDict, PostgresDsn
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
- """Application settings"""
+ """Application settings - DATA ONLY"""
BOT_TOKEN: str
DB_DSN: PostgresDsn
MARKETING_URL: str
PRINT_URL: str
PRINT_URL_QR: str
- MAX_PDF_SIZE_MB: float
+ MAX_PDF_SIZE_MB: int
+ MAX_PAGE_COUNT: int
+ CONTENT_TYPES: List[str]
model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="allow")
+
+
+sync_settings: Optional[Settings]
+
+
+@lru_cache(maxsize=1)
+def get_settings() -> Settings:
+ global sync_settings
+ if sync_settings is not None:
+ return sync_settings
+ return Settings()
+
+
+async def sync_from_server():
+ """Syncs the settings with server"""
+ settings = get_settings()
+ response = requests.get(
+ "app.profcomff.com/admin/settings", # Вот тут не уверен с адресом...
+ headers={"Authorization": f"Bearer {settings.BOT_TOKEN}"},
+ timeout=10,
+ )
+
+ if response.status_code == 200:
+ server_data = response.json()
+ current_data = get_settings().model_dump()
+ updated_data = {**current_data, **server_data}
+
+ sync_settings = Settings(**updated_data)
+ get_settings.cache_clear()