diff --git a/.github/workflows/build_and_publish.yml b/.github/workflows/build_and_publish.yml index 338b217..a9c47cc 100644 --- a/.github/workflows/build_and_publish.yml +++ b/.github/workflows/build_and_publish.yml @@ -131,6 +131,8 @@ jobs: --env DB_DSN='${{ secrets.DB_DSN }}' \ --env AUTH_URL='https://api.profcomff.com/auth' \ --env TELEGRAM_BOT_TOKEN='${{ secrets.TELEGRAM_BOT_TOKEN }}' \ + --env VK_BOT_GROUP_ID='{{ secrets.VK_BOT_GROUP_ID }}' \ + --env VK_BOT_TOKEN='{{ secrets.VK_BOT_TOKEN }}' \ --env GITHUB_APP_ID='${{ secrets.GH_APP_ID }}' \ --env GITHUB_PRIVATE_KEY='${{ secrets.GH_PRIVATE_KEY }}' \ --env DISCORD_PUBLIC_KEY='${{ secrets.DISCORD_PUBLIC_KEY }}' \ diff --git a/social/handlers_vk/__init__.py b/social/handlers_vk/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/social/handlers_vk/base.py b/social/handlers_vk/base.py new file mode 100644 index 0000000..9b59323 --- /dev/null +++ b/social/handlers_vk/base.py @@ -0,0 +1,35 @@ +import logging +from collections.abc import Callable + +from social.utils.events import EventProcessor +from social.utils.vk_groups import approve_vk_chat + + +logger = logging.getLogger(__name__) +EVENT_PROCESSORS: list[EventProcessor] = [] + + +def event(**filters: str): + """Помечает функцию как обработчик событий, задает фильтры для запуска""" + + def deco(func: Callable): + EVENT_PROCESSORS.append(EventProcessor(filters, func)) + return func + + return deco + + +def process_event(event: dict): + for processor in EVENT_PROCESSORS: + if processor.check_and_process(event): + break + else: + logger.debug("Event without processor") + + +@event( + type="message_new", + object=lambda i: i.get("message", {}).get("text", "").startswith("/validate"), +) +def validate_group(event: dict): + approve_vk_chat(event) diff --git a/social/models/group.py b/social/models/group.py index 1b2276a..393b8ea 100644 --- a/social/models/group.py +++ b/social/models/group.py @@ -12,7 +12,7 @@ class Group(Base): owner_id: Mapped[int | None] is_deleted: Mapped[bool] = mapped_column(default=False) - last_active_ts: Mapped[datetime] + last_active_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC)) create_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC)) update_ts: Mapped[datetime] = mapped_column(default=lambda: datetime.now(UTC), onupdate=lambda: datetime.now(UTC)) diff --git a/social/routes/vk.py b/social/routes/vk.py index c226a7b..f44b361 100644 --- a/social/routes/vk.py +++ b/social/routes/vk.py @@ -1,16 +1,17 @@ import logging -from datetime import UTC, datetime from auth_lib.fastapi import UnionAuth -from fastapi import APIRouter, Depends, Request +from fastapi import APIRouter, BackgroundTasks, Depends, Request from fastapi.responses import PlainTextResponse from fastapi_sqlalchemy import db from pydantic import BaseModel, ConfigDict -from social.models.group import VkChat, VkGroup +from social.handlers_vk.base import process_event +from social.models.group import VkGroup from social.models.webhook_storage import WebhookStorage, WebhookSystems from social.settings import get_settings from social.utils.string import random_string +from social.utils.vk_groups import create_vk_chat router = APIRouter(prefix="/vk", tags=['vk']) @@ -31,7 +32,7 @@ class VkGroupCreateResponse(BaseModel): @router.post('', tags=["webhooks"]) -async def vk_webhook(request: Request) -> str: +async def vk_webhook(request: Request, background_tasks: BackgroundTasks) -> str: """Принимает любой POST запрос от VK""" request_data = await request.json() logger.debug(request_data) @@ -53,27 +54,8 @@ async def vk_webhook(request: Request) -> str: ) db.session.commit() - if request_data.get("type") == "message_new": - # Получение сообщения в чате ВК - try: - peer_id = request_data["object"]["message"]["peer_id"] - obj = db.session.query(VkChat).where(VkChat.peer_id == peer_id).one_or_none() - if obj is None: - # Надо будет добавлять название группы - # conversation = requests.post("https://api.vk.com/method/messages.getConversationsById", json={ - # "peer_ids": peer_id, - # "group_id": 222099060, - # "access_token": settings.VK_BOT_TOKEN, - # "v": 5.199, - # }) - # chat_title = conversation["response"]["items"][0]["chat_settings"]["title"] - obj = VkChat(chat_id=peer_id) - db.session.add(obj) - obj.last_active_ts = datetime.now(UTC) - db.session.commit() - except Exception as exc: - logger.exception(exc) - + background_tasks.add_task(create_vk_chat, request_data) + background_tasks.add_task(process_event, request_data) return PlainTextResponse('ok') diff --git a/social/settings.py b/social/settings.py index f8b2283..83fbf6b 100644 --- a/social/settings.py +++ b/social/settings.py @@ -20,6 +20,7 @@ class Settings(BaseSettings): TELEGRAM_BOT_TOKEN: str | None = None + VK_BOT_GROUP_ID: int | None = None VK_BOT_TOKEN: str | None = None GITHUB_APP_ID: int | None = None diff --git a/social/utils/telegram_groups.py b/social/utils/telegram_groups.py index 51a985e..554dc9b 100644 --- a/social/utils/telegram_groups.py +++ b/social/utils/telegram_groups.py @@ -11,6 +11,7 @@ def create_telegram_group(update: Update): + """Создает запись о телеграмм группе в внутренней БД приложения или возвращает существующий""" chat = update.effective_chat obj = None if chat.type in ['group', 'supergroup']: @@ -33,6 +34,7 @@ def create_telegram_group(update: Update): def approve_telegram_group(update: Update): + """Если получено сообщение команды /validate, то за группой закрепляется владелец""" logger.debug("Validation started") group = create_telegram_group(update) text = update.effective_message.text @@ -41,7 +43,7 @@ def approve_telegram_group(update: Update): return text = text.removeprefix('/validate').removeprefix('@ViribusSocialBot').strip() request = db.session.query(CreateGroupRequest).where(CreateGroupRequest.secret_key == text).one_or_none() - request.mapped_group_id=group.id + request.mapped_group_id = group.id group.owner_id = request.owner_id db.session.commit() logger.info("Telegram group %d validated (secret=%s)", group.id, text) diff --git a/social/utils/vk_groups.py b/social/utils/vk_groups.py new file mode 100644 index 0000000..fcfe0c2 --- /dev/null +++ b/social/utils/vk_groups.py @@ -0,0 +1,66 @@ +import logging +from datetime import UTC, datetime + +import requests +from fastapi_sqlalchemy import db + +from social.models import CreateGroupRequest, VkChat +from social.settings import get_settings + + +logger = logging.getLogger(__name__) +settings = get_settings() + + +def get_chat_name(peer_id): + """Получить название чата ВК""" + conversation = requests.post( + "https://api.vk.com/method/messages.getConversationsById", + json={ + "peer_ids": peer_id, + "group_id": settings.VK_BOT_GROUP_ID, + "access_token": settings.VK_BOT_TOKEN, + "v": 5.199, + }, + ) + try: + return conversation["response"]["items"][0]["chat_settings"]["title"] + except Exception as exc: + logger.exception(exc) + return None + + +def create_vk_chat(request_data: dict[str]): + """Создает запись о вк чате в внутренней БД приложения или возвращает существующий""" + if ( + request_data.get("group_id") == settings.VK_BOT_GROUP_ID # peer_id чатов уникальные для каждого пользователя ВК + and request_data.get("type") == "message_new" + ): + # Получение сообщения в чате ВК + try: + peer_id = request_data["object"]["message"]["peer_id"] + obj = db.session.query(VkChat).where(VkChat.peer_id == peer_id).one_or_none() + if obj is None: + obj = VkChat(peer_id=peer_id) + db.session.add(obj) + obj.last_active_ts = datetime.now(UTC) + db.session.commit() + except Exception as exc: + logger.exception(exc) + return obj + return None + + +def approve_vk_chat(request_data: dict[str]): + """Если получено сообщение команды /validate, то за группой закрепляется владелец""" + logger.debug("Validation started") + group = create_vk_chat(request_data) + text = request_data.get("object", {}).get("message", {}).get("text", "").removeprefix("/validate").strip() + if not text or not group or group.owner_id is not None: + logger.error("Telegram group not validated (secret=%s, group=%s)", text, group) + return + request = db.session.query(CreateGroupRequest).where(CreateGroupRequest.secret_key == text).one_or_none() + request.mapped_group_id = group.id + group.owner_id = request.owner_id + db.session.commit() + logger.info("VK group %d validated (secret=%s)", group.id, text)