diff --git a/migrations/env.py b/migrations/env.py index 473b786..6e23c9b 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 @@ -60,7 +58,7 @@ def run_migrations_online(): """ configuration = config.get_section(config.config_ini_section) - configuration['sqlalchemy.url'] = str(settings.DB_DSN) + configuration["sqlalchemy.url"] = str(settings.DB_DSN) connectable = engine_from_config( configuration, prefix="sqlalchemy.", diff --git a/migrations/versions/75dab872e82b_init.py b/migrations/versions/75dab872e82b_init.py index 58773a9..f167812 100644 --- a/migrations/versions/75dab872e82b_init.py +++ b/migrations/versions/75dab872e82b_init.py @@ -1,7 +1,7 @@ """Init Revision ID: 75dab872e82b -Revises: +Revises: Create Date: 2023-02-25 19:05:46.473426 """ @@ -9,9 +9,8 @@ import sqlalchemy as sa from alembic import op - # revision identifiers, used by Alembic. -revision = '75dab872e82b' +revision = "75dab872e82b" down_revision = None branch_labels = None depends_on = None @@ -20,16 +19,16 @@ def upgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### op.create_table( - 'vk_user', - sa.Column('vk_id', sa.BIGINT(), nullable=False), - sa.Column('surname', sa.String(), nullable=False), - sa.Column('number', sa.String(), nullable=False), - sa.PrimaryKeyConstraint('vk_id'), + "vk_user", + sa.Column("vk_id", sa.BIGINT(), nullable=False), + sa.Column("surname", sa.String(), nullable=False), + sa.Column("number", sa.String(), nullable=False), + sa.PrimaryKeyConstraint("vk_id"), ) # ### end Alembic commands ### def downgrade() -> None: # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('vk_user') + op.drop_table("vk_user") # ### end Alembic commands ### diff --git a/src/__main__.py b/src/__main__.py index 4a9677d..b1674e3 100644 --- a/src/__main__.py +++ b/src/__main__.py @@ -1,14 +1,21 @@ +import asyncio import logging from src.handlers import event_loop - +from src.settings import get_settings, sync_from_server logging.getLogger("httpx").setLevel(logging.WARNING) -logging.basicConfig(format="%(asctime)s - %(levelname)s - %(message)s", level=logging.INFO, datefmt="%Y-%m-%d %H:%M:%S") +logging.basicConfig( + format="%(asctime)s - %(levelname)s - %(message)s", + level=logging.INFO, + datefmt="%Y-%m-%d %H:%M:%S", +) -if __name__ == '__main__': +if __name__ == "__main__": + asyncio.run(sync_from_server()) + settings = get_settings() logging.info("=== BOT START ===") while True: event_loop() diff --git a/src/answers.py b/src/answers.py index 50c23c8..207cfd5 100644 --- a/src/answers.py +++ b/src/answers.py @@ -3,68 +3,85 @@ from dataclasses import dataclass +from src.settings import get_settings + +settings = get_settings() + @dataclass class Answers: - hey: str = 'Привет!' - not_auth = 'Не авторизовано' - inst = 'Инструкция' - conf = 'Конфиденциальность' + hey: str = "Привет!" + not_auth = "Не авторизовано" + inst = "Инструкция" + conf = "Конфиденциальность" conf_full = ( - '❗️ Файлы, которые вы отправляете через бота, будут храниться в течение нескольких месяцев ' - 'на сервере в Москве, а также в этом чате ВКонтакте.' - '\nДоступ к файлам имеет узкий круг лиц, ответственных за работоспособность сервиса печати.' - '\nМы НЕ рекомендуем использовать данный сервис для печати конфиденциальных документов!' + "❗️ Файлы, которые вы отправляете через бота, будут храниться в течение нескольких месяцев " + "на сервере в Москве, а также в этом чате ВКонтакте." + "\nДоступ к файлам имеет узкий круг лиц, ответственных за работоспособность сервиса печати." + "\nМы НЕ рекомендуем использовать данный сервис для печати конфиденциальных документов!" ) - ask_help = ['привет', 'помогите', 'помощь', 'help', 'как ', 'инструкция', 'начать', 'Начать', 'старт', 'start'] + ask_help = [ + "привет", + "помогите", + "помощь", + "help", + "как ", + "инструкция", + "начать", + "Начать", + "старт", + "start", + ] help = ( - 'Я вк-бот бесплатного принтера профкома студентов физического факультета МГУ!\n\n' - '❔ Отправьте PDF файл и получите PIN для печати. Поддерживаются только .PDF файлы размером не более 3МБ.' - '\nС этим PIN необходимо подойти к принтеру и ввести его в терминал печати. ' - 'Либо отсканировать QR-код на принтере с помощью ссылки. После этого начнётся печать.' - '\n\n💻 Бот разработан группой программистов профкома, как и приложение Твой ФФ!' - '\nВ приложении вы сможете найти больше настроек печати, расписание и много других возможностей.' - '\nТак же есть Telegram-бот для печати.' + "Я вк-бот бесплатного принтера профкома студентов физического факультета МГУ!\n\n" + "❔ Отправьте PDF файл и получите PIN для печати. Поддерживаются только .PDF файлы размером не более 3МБ." + "\nС этим PIN необходимо подойти к принтеру и ввести его в терминал печати. " + "Либо отсканировать QR-код на принтере с помощью ссылки. После этого начнётся печать." + "\n\n💻 Бот разработан группой программистов профкома, как и приложение Твой ФФ!" + "\nВ приложении вы сможете найти больше настроек печати, расписание и много других возможностей." + "\nТак же есть Telegram-бот для печати." ) - err_bd = '❌ Ошибка базы данных. Попробуйте позже.' - err_print = '❌ Ошибка сервера печати. Попробуйте позже.' - err_payload = '🐩 Похоже бот обновился.\nВыполните команду /start' - err_vk = '🐩 Ошибка взаимодействия с ВКонтакте. Попробуйте позже.' - err_kb = '🐩 Ошибка клавиатуры, что-то пошло не так' - err_fatal = '🐩 Глубоко внутри меня что-то сломалось...' + err_bd = "❌ Ошибка базы данных. Попробуйте позже." + err_print = "❌ Ошибка сервера печати. Попробуйте позже." + err_payload = "🐩 Похоже бот обновился.\nВыполните команду /start" + err_vk = "🐩 Ошибка взаимодействия с ВКонтакте. Попробуйте позже." + err_kb = "🐩 Ошибка клавиатуры, что-то пошло не так" + err_fatal = "🐩 Глубоко внутри меня что-то сломалось..." - val_pass = '✅ Поздравляю! Проверка пройдена и данные сохранены для этого аккаунта ВК. Можете присылать pdf.' - val_name = 'Иванов\n1234567' - val_update_pass = '✅ Поздравляю! Проверка пройдена и данные обновлены.' - val_already = 'Вы уже успешно авторизованы. Можете присылать файл на печать.' - val_addition = '\n\nНо для начала нужно авторизоваться. Нажмите на кнопку ниже:' + val_pass = "✅ Поздравляю! Проверка пройдена и данные сохранены для этого аккаунта ВК. Можете присылать pdf." + val_name = "Иванов\n1234567" + val_update_pass = "✅ Поздравляю! Проверка пройдена и данные обновлены." + val_already = "Вы уже успешно авторизованы. Можете присылать файл на печать." + val_addition = "\n\nНо для начала нужно авторизоваться. Нажмите на кнопку ниже:" val_need = ( - '⚠ Для использования принтера необходимо авторизоваться.' - '\nВведите фамилию и номер профсоюзного билета в формате:' + "⚠ Для использования принтера необходимо авторизоваться." + "\nВведите фамилию и номер профсоюзного билета в формате:" ) val_fail = ( - '❌ Проверка не пройдена. Удостоверьтесь что вы состоите в профсоюзе и правильно ввели данные.' - '\n\nВведите фамилию и номер профсоюзного билета в формате:' + "❌ Проверка не пройдена. Удостоверьтесь что вы состоите в профсоюзе и правильно ввели данные." + "\n\nВведите фамилию и номер профсоюзного билета в формате:" ) val_fail_format = ( - 'Я вас не понимаю. Если вы хотите обновить данные авторизации введите фамилию и ' - 'номер профсоюзного билета в формате:' + "Я вас не понимаю. Если вы хотите обновить данные авторизации введите фамилию и " + "номер профсоюзного билета в формате:" ) val_unknown_message = ( - 'Сообщение не распознано. Если вы хотите обновить данные авторизации введите фамилию и ' - 'номер профсоюзного билета в формате:' + "Сообщение не распознано. Если вы хотите обновить данные авторизации введите фамилию и " + "номер профсоюзного билета в формате:" ) - warn_filesize = '⚠️ Файл слишком большой. Мы принимаем файлы размером ДО 3 МБ' - warn_many_files = '⚠️ Файлов слишком много. Прикрепите только один файл pdf.' - warn_unreadable_file = '⚠️ Я не смог прочитать файл. Проверьте его целостность и формат, я работаю только с pdf.' - warn_only_pdfs = '⚠️ Я умею печатать только документы в формате pdf.' + warn_filesize = f"⚠️ Файл слишком большой. Мы принимаем файлы размером ДО {settings.MAX_PDF_SIZE_MB} МБ" + warn_many_files = "⚠️ Файлов слишком много. Прикрепите только один файл pdf." + warn_unreadable_file = f"⚠️ Я не смог прочитать файл. Проверьте его целостность и формат, я работаю только с {settings.CONTENT_TYPES}." + warn_only_pdfs = ( + f"⚠️ Я умею печатать только документы в формате {settings.CONTENT_TYPES}." + ) send_to_print = '✅ Файл "{}" успешно загружен. Для печати подойдите к принтеру и введите PIN: \n\n{}' - qr_button_text = '📷 Печать по QR' + qr_button_text = "📷 Печать по QR" ans = Answers() diff --git a/src/auth.py b/src/auth.py index 9dfee29..69bf7a2 100644 --- a/src/auth.py +++ b/src/auth.py @@ -2,15 +2,19 @@ # 2023 import requests -from sqlalchemy.orm import Session - import src.vk as vk +from sqlalchemy.orm import Session from src.db import Session, VkUser -from src.settings import settings +from src.settings import get_settings + +settings = get_settings() def check_union_member(user: vk.EventUser, surname, number) -> None | tuple: - r = requests.get(url=settings.PRINT_URL + '/is_union_member', params=dict(surname=surname, number=number, v=1)) + r = requests.get( + url=settings.PRINT_URL + "/is_union_member", + params=dict(surname=surname, number=number, v=1), + ) if r.json(): return user.user_id, surname, number return None @@ -18,7 +22,9 @@ def check_union_member(user: vk.EventUser, surname, number) -> None | tuple: def check_user_in_db(user: vk.EventUser) -> None | tuple: with Session() as session: - data: VkUser | None = session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + data: VkUser | None = ( + session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + ) if data is not None: return user.user_id, data.surname, data.number return None @@ -30,10 +36,13 @@ def check(user: vk.EventUser) -> None | tuple: :return: db_requisites tuple or None if user not authenticated """ with Session() as session: - data: VkUser | None = session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + data: VkUser | None = ( + session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + ) if data is not None: r = requests.get( - url=settings.PRINT_URL + '/is_union_member', params=dict(surname=data.surname, number=data.number, v=1) + url=settings.PRINT_URL + "/is_union_member", + params=dict(surname=data.surname, number=data.number, v=1), ) if r.json(): return user.user_id, data.surname, data.number @@ -48,7 +57,9 @@ def add_user(user: vk.EventUser, surname, number) -> None: def update_user(user: vk.EventUser, surname, number) -> None: with Session() as session: - data: VkUser | None = session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + data: VkUser | None = ( + session.query(VkUser).filter(VkUser.vk_id == user.user_id).one_or_none() + ) data.surname = surname data.number = number session.commit() diff --git a/src/db.py b/src/db.py index bd029a3..a644d89 100644 --- a/src/db.py +++ b/src/db.py @@ -5,8 +5,6 @@ from sqlalchemy.ext.declarative import as_declarative, declared_attr from sqlalchemy.orm import Mapped, mapped_column, sessionmaker -from src.settings import settings - @as_declarative() class Base: @@ -24,7 +22,7 @@ def __repr__(self) -> str: attrs = [] for c in self.__table__.columns: attrs.append(f"{c.name}={getattr(self, c.name)}") - return "{}({})".format(self.__class__.__name__, ', '.join(attrs)) + return "{}({})".format(self.__class__.__name__, ", ".join(attrs)) class VkUser(Base): @@ -33,5 +31,7 @@ class VkUser(Base): number: Mapped[int] = mapped_column(sqlalchemy.String, nullable=False) -engine = create_engine(url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level="AUTOCOMMIT") +engine = create_engine( + url=str(settings.DB_DSN), pool_pre_ping=True, isolation_level="AUTOCOMMIT" +) Session = sessionmaker(bind=engine) diff --git a/src/handlers.py b/src/handlers.py index a859462..8d576d3 100644 --- a/src/handlers.py +++ b/src/handlers.py @@ -8,16 +8,17 @@ import psycopg2 import requests -from sqlalchemy.exc import SQLAlchemyError -from vk_api.bot_longpoll import VkBotEventType -from vk_api.exceptions import VkApiError - import src.auth as auth import src.keybords as kb import src.marketing as marketing import src.vk as vk +from sqlalchemy.exc import SQLAlchemyError from src.answers import ans -from src.settings import settings +from src.settings import get_settings +from vk_api.bot_longpoll import VkBotEventType +from vk_api.exceptions import VkApiError + +settings = get_settings() def event_loop(): @@ -25,9 +26,12 @@ def event_loop(): try: vk.reconnect() for event in vk.longpoll.listen(): - if event.type not in [VkBotEventType.MESSAGE_NEW, VkBotEventType.MESSAGE_ALLOW]: + if event.type not in [ + VkBotEventType.MESSAGE_NEW, + VkBotEventType.MESSAGE_ALLOW, + ]: if event.type != VkBotEventType.MESSAGE_REPLY: - logging.warning('UNKNOWN EVENT TYPE:' + str(event.type)) + logging.warning("UNKNOWN EVENT TYPE:" + str(event.type)) return user = vk.EventUser(event) @@ -76,7 +80,7 @@ def message_analyzer(user: vk.EventUser): kb.main_page(user) return # Если юзер прислал текст, но он не похож на обновление данных авторизации - if len(user.message.split('\n')) != 2: + if len(user.message.split("\n")) != 2: if db_requisites is None: vk.send(user, ans.val_need) vk.send(user, ans.val_name) @@ -85,7 +89,7 @@ def message_analyzer(user: vk.EventUser): vk.send(user, ans.val_name) return # Если юзер прислал текст, который похож на обновление данных авторизации - if len(user.message.split('\n')) == 2: + if len(user.message.split("\n")) == 2: register_bot_user(user, db_requisites) return # Если вообще непонятно что за сообщение пришло @@ -94,8 +98,8 @@ def message_analyzer(user: vk.EventUser): def register_bot_user(user: vk.EventUser, db_requisites): - surname = user.message.split('\n')[0].strip() - number = user.message.split('\n')[1].strip() + surname = user.message.split("\n")[0].strip() + number = user.message.split("\n")[1].strip() union_member = auth.check_union_member(user, surname, number) # Если юзер не состоит в профсоюзе @@ -122,18 +126,20 @@ def get_attachments(user: vk.EventUser): marketing.print_exc_many(file_count=len(user.attachments), vk_id=user.user_id) return - if user.attachments[0]['type'] != 'doc': + if user.attachments[0]["type"] != "doc": vk.send(user, ans.warn_only_pdfs) - marketing.print_exc_format(file_ext='image', vk_id=user.user_id) + marketing.print_exc_format(file_ext="image", vk_id=user.user_id) return - if user.attachments[0]['doc']['ext'] not in ['pdf', 'PDF']: + if user.attachments[0]["doc"]["ext"] not in ["pdf", "PDF"]: vk.send(user, ans.warn_only_pdfs) - marketing.print_exc_format(file_ext=len(user.attachments[0]['doc']['ext']), vk_id=user.user_id) + marketing.print_exc_format( + file_ext=len(user.attachments[0]["doc"]["ext"]), vk_id=user.user_id + ) return - title = user.attachments[0]['doc']['title'] - url = user.attachments[0]['doc']['url'] + title = user.attachments[0]["doc"]["title"] + url = user.attachments[0]["doc"]["url"] r = requests.get(url, allow_redirects=True) return r.content, title @@ -148,25 +154,37 @@ def order_print(user: vk.EventUser, db_requisites): content, title = file_content_title vk_id, surname, number = db_requisites r = requests.post( - settings.PRINT_URL + '/file', json={'surname': surname, 'number': number, 'filename': title, 'source': 'vkbot'} + settings.PRINT_URL + "/file", + json={ + "surname": surname, + "number": number, + "filename": title, + "source": "vkbot", + }, ) # If get pin error if r.status_code != 200: vk.send(user, ans.err_print) marketing.print_exc_other( - vk_id=vk_id, surname=surname, number=number, status_code=r.status_code, description='Fail on fetching code' + vk_id=vk_id, + surname=surname, + number=number, + status_code=r.status_code, + description="Fail on fetching code", ) return # Upload file with pin - pin = r.json()['pin'] - files = {'file': (title, content, 'application/pdf', {'Expires': '0'})} - r = requests.post(settings.PRINT_URL + '/file/' + pin, files=files) + pin = r.json()["pin"] + files = {"file": (title, content, "application/pdf", {"Expires": "0"})} + r = requests.post(settings.PRINT_URL + "/file/" + pin, files=files) # Send response if r.status_code == 200: - vk.send(user, ans.send_to_print.format(title, pin), keyboard=kb.file_settings(pin)) + vk.send( + user, ans.send_to_print.format(title, pin), keyboard=kb.file_settings(pin) + ) marketing.print_success(vk_id=vk_id, surname=surname, number=number, pin=pin) elif r.status_code == 413: vk.send(user, ans.warn_filesize) @@ -176,7 +194,7 @@ def order_print(user: vk.EventUser, db_requisites): number=number, pin=pin, status_code=r.status_code, - description='File is too big', + description="File is too big", ) elif r.status_code == 415: vk.send(user, ans.warn_unreadable_file) @@ -186,7 +204,7 @@ def order_print(user: vk.EventUser, db_requisites): number=number, pin=pin, status_code=r.status_code, - description='Unsupported Media Type', + description="Unsupported Media Type", ) else: vk.send(user, ans.err_print) @@ -196,5 +214,5 @@ def order_print(user: vk.EventUser, db_requisites): number=number, pin=pin, status_code=r.status_code, - description='Fail on file upload', + description="Fail on file upload", ) diff --git a/src/keybords.py b/src/keybords.py index de2bb0d..6716108 100644 --- a/src/keybords.py +++ b/src/keybords.py @@ -3,12 +3,13 @@ import json -from vk_api.keyboard import VkKeyboard - import src.auth as auth import src.vk as vk from src.answers import ans -from src.settings import settings +from src.settings import get_settings +from vk_api.keyboard import VkKeyboard + +settings = get_settings() def file_settings(pin): @@ -19,8 +20,8 @@ def file_settings(pin): def links_keyboard(): kb = VkKeyboard(inline=True) - kb.add_openlink_button('Твой ФФ!', link='https://app.profcomff.com/apps') - kb.add_openlink_button('Telegram-бот', link='https://t.me/profcomff_print_bot') + kb.add_openlink_button("Твой ФФ!", link="https://app.profcomff.com/apps") + kb.add_openlink_button("Telegram-бот", link="https://t.me/profcomff_print_bot") return kb.get_keyboard() @@ -28,9 +29,9 @@ def main_page(user): # Send hi message and keyboard msg = ans.hey kb = VkKeyboard(one_time=False) - kb.add_button(ans.inst, color='primary', payload='{"command":"help"}') + kb.add_button(ans.inst, color="primary", payload='{"command":"help"}') kb.add_line() - kb.add_button(ans.conf, color='primary', payload='{"command":"conf"}') + kb.add_button(ans.conf, color="primary", payload='{"command":"conf"}') vk.send(user, msg, keyboard=kb.get_keyboard()) # Send help message and inline-keyboard @@ -39,24 +40,26 @@ def main_page(user): # If user not authenticated add button if auth.check(user) is None: - kb.add_button(ans.not_auth, color='negative', payload='{"command":"auth_false"}') + kb.add_button( + ans.not_auth, color="negative", payload='{"command":"auth_false"}' + ) kb.add_line() msg += ans.val_addition - kb.add_openlink_button('Твой ФФ!', link='https://app.profcomff.com') - kb.add_openlink_button('Telegram-бот', link='https://t.me/profcomff_print_bot') + kb.add_openlink_button("Твой ФФ!", link="https://app.profcomff.com") + kb.add_openlink_button("Telegram-бот", link="https://t.me/profcomff_print_bot") vk.send(user, msg, keyboard=kb.get_keyboard()) def keyboard_browser(user: vk.EventUser, payload: str): - match json.loads(payload)['command']: - case 'start': + match json.loads(payload)["command"]: + case "start": main_page(user) - case 'help': + case "help": main_page(user) - case 'conf': + case "conf": vk.send(user, ans.conf_full) - case 'auth_false': + case "auth_false": if auth.check(user) is not None: # Prevent to tap old button vk.send(user, ans.val_already) return diff --git a/src/marketing.py b/src/marketing.py index 16209ec..d74ed23 100644 --- a/src/marketing.py +++ b/src/marketing.py @@ -1,8 +1,9 @@ import json import requests +from src.settings import get_settings -from src.settings import settings +settings = get_settings() def pass_if_exc(func): @@ -21,10 +22,10 @@ def register(**user_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot register', - 'additional_data': json.dumps(user_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot register", + "additional_data": json.dumps(user_info), + "path_from": "https://vk.com/im", }, ) @@ -34,10 +35,10 @@ def re_register(**user_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot repeat register', - 'additional_data': json.dumps(user_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot repeat register", + "additional_data": json.dumps(user_info), + "path_from": "https://vk.com/im", }, ) @@ -47,10 +48,10 @@ def register_exc_wrong(**user_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot register exc wrong creds', - 'additional_data': json.dumps(user_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot register exc wrong creds", + "additional_data": json.dumps(user_info), + "path_from": "https://vk.com/im", }, ) @@ -60,11 +61,11 @@ def print_success(**print_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot sent', - 'additional_data': json.dumps(print_info), - 'path_from': 'https://vk.com/im', - 'path_to': settings.PRINT_URL + f'/file/{print_info.get("pin")}', + "user_id": -2, + "action": "print bot sent", + "additional_data": json.dumps(print_info), + "path_from": "https://vk.com/im", + "path_to": settings.PRINT_URL + f'/file/{print_info.get("pin")}', }, ) @@ -74,10 +75,10 @@ def print_exc_many(**print_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot sent exc many', - 'additional_data': json.dumps(print_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot sent exc many", + "additional_data": json.dumps(print_info), + "path_from": "https://vk.com/im", }, ) @@ -87,10 +88,10 @@ def print_exc_format(**print_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot sent exc format', - 'additional_data': json.dumps(print_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot sent exc format", + "additional_data": json.dumps(print_info), + "path_from": "https://vk.com/im", }, ) @@ -100,9 +101,9 @@ def print_exc_other(**print_info): requests.post( settings.MARKETING_URL + "/v1/action", json={ - 'user_id': -2, - 'action': 'print bot sent exc other', - 'additional_data': json.dumps(print_info), - 'path_from': 'https://vk.com/im', + "user_id": -2, + "action": "print bot sent exc other", + "additional_data": json.dumps(print_info), + "path_from": "https://vk.com/im", }, ) diff --git a/src/settings.py b/src/settings.py index 8d3f307..f82b9c6 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,3 +1,7 @@ +from functools import lru_cache +from typing import List, Optional + +import requests from pydantic import ConfigDict, PostgresDsn from pydantic_settings import BaseSettings @@ -12,9 +16,40 @@ class Settings(BaseSettings): PRINT_URL: str PRINT_URL_QR: str # Hardcode settings - API_VERSION: str = '5.131' + API_VERSION: str = "5.131" + + # bot limitation + MAX_PDF_SIZE_MB: int + MAX_PAGE_COUNT: int + CONTENT_TYPES: List[str] model_config = ConfigDict(case_sensitive=True, env_file=".env", extra="allow") -settings = Settings() +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() diff --git a/src/vk.py b/src/vk.py index bfc0ccc..d1780db 100644 --- a/src/vk.py +++ b/src/vk.py @@ -2,12 +2,13 @@ # 2023 import logging +from src.settings import get_settings from vk_api import VkApi from vk_api.bot_longpoll import VkBotEvent, VkBotEventType, VkBotLongPoll from vk_api.keyboard import VkKeyboard from vk_api.utils import get_random_id -from src.settings import settings +settings = get_settings() # Auth with community token @@ -25,21 +26,21 @@ def reconnect(): class EventUser: def __init__(self, event: VkBotEvent): if event.type == VkBotEventType.MESSAGE_ALLOW: - self.user_id = event.obj['user_id'] - self.message = '' + self.user_id = event.obj["user_id"] + self.message = "" self.attachments = [] - r = vk.method('users.get', {'user_ids': self.user_id}) - self.first_name = r[0]['first_name'] - self.last_name = r[0]['last_name'] + r = vk.method("users.get", {"user_ids": self.user_id}) + self.first_name = r[0]["first_name"] + self.last_name = r[0]["last_name"] else: - self.user_id = event.message['from_id'] - self.message = event.message['text'] + self.user_id = event.message["from_id"] + self.message = event.message["text"] self.attachments = event.message.attachments - r = vk.method('users.get', {'user_ids': self.user_id}) - self.first_name = r[0]['first_name'] - self.last_name = r[0]['last_name'] + r = vk.method("users.get", {"user_ids": self.user_id}) + self.first_name = r[0]["first_name"] + self.last_name = r[0]["last_name"] logging.info( f"[{self.user_id} {self.first_name} {self.last_name}]: {repr(self.message)} {repr(self.attachments)}" ) @@ -48,8 +49,13 @@ def __init__(self, event: VkBotEvent): def send(user: EventUser, message: str, keyboard: VkKeyboard | None = None): if user is None: return - values = {'user_id': user.user_id, 'message': message, 'dont_parse_links': 1, 'random_id': get_random_id()} + values = { + "user_id": user.user_id, + "message": message, + "dont_parse_links": 1, + "random_id": get_random_id(), + } if keyboard is not None: - values['keyboard'] = keyboard + values["keyboard"] = keyboard - vk.method('messages.send', values) + vk.method("messages.send", values)