From 94a12fc853cfc97d0d36a6c2e89b48b6e2dbc3e8 Mon Sep 17 00:00:00 2001
From: Mark Arzangulyan <15670678+Arzangulyan@users.noreply.github.com>
Date: Fri, 7 Mar 2025 22:32:34 +0300
Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?=
=?UTF-8?q?=D0=BD=20=D1=84=D1=83=D0=BD=D0=BA=D1=86=D0=B8=D0=BE=D0=BD=D0=B0?=
=?UTF-8?q?=D0=BB=20=D0=BC=D0=BE=D0=BD=D0=B8=D1=82=D0=BE=D1=80=D0=B8=D0=BD?=
=?UTF-8?q?=D0=B3=D0=B0=20=D0=B3=D1=80=D1=83=D0=BF=D0=BF=D1=8B=20=D0=92?=
=?UTF-8?q?=D0=9A=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=8B=D0=BB?=
=?UTF-8?q?=D0=BA=D0=B8=20=D0=BF=D0=BE=D1=81=D1=82=D0=BE=D0=B2=20=D0=B2=20?=
=?UTF-8?q?Telegram-=D0=BA=D0=B0=D0=BD=D0=B0=D0=BB?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
social/handlers_vk/base.py | 162 +++++++++++++++++++++++++++++++++++++
social/routes/vk.py | 105 +++++++++++++++++++-----
social/settings.py | 2 +
3 files changed, 247 insertions(+), 22 deletions(-)
diff --git a/social/handlers_vk/base.py b/social/handlers_vk/base.py
index b8fbeca..b218aa5 100644
--- a/social/handlers_vk/base.py
+++ b/social/handlers_vk/base.py
@@ -1,12 +1,18 @@
import logging
from collections.abc import Callable
+import json
+import re
+import requests
from social.utils.events import EventProcessor
from social.utils.vk_groups import approve_vk_chat
+from social.settings import get_settings
+from telegram import Bot, InputMediaPhoto
logger = logging.getLogger(__name__)
EVENT_PROCESSORS: list[EventProcessor] = []
+settings = get_settings()
def event(**filters: str):
@@ -34,3 +40,159 @@ def process_event(event: dict):
def validate_group(event: dict):
"""Если получено сообщение команды /validate, то за группой закрепляется владелец"""
approve_vk_chat(event)
+
+
+async def send_to_telegram(message: str, photos: list = None):
+ """Отправляет сообщение и фотографии в Telegram канал"""
+ if not settings.TELEGRAM_BOT_TOKEN or not settings.TELEGRAM_TARGET_CHANNEL_ID:
+ logger.warning("Telegram bot token or channel ID not configured")
+ return
+
+ bot = Bot(token=settings.TELEGRAM_BOT_TOKEN)
+
+ try:
+ if not photos:
+ # Если нет фотографий, отправляем только текст
+ await bot.send_message(
+ chat_id=settings.TELEGRAM_TARGET_CHANNEL_ID,
+ text=message,
+ parse_mode='HTML',
+ disable_web_page_preview=False
+ )
+ elif len(photos) == 1:
+ # Если только одна фотография, отправляем ее с подписью
+ await bot.send_photo(
+ chat_id=settings.TELEGRAM_TARGET_CHANNEL_ID,
+ photo=photos[0],
+ caption=message,
+ parse_mode='HTML'
+ )
+ else:
+ # Если несколько фотографий, отправляем их как медиагруппу
+ media_group = []
+
+ # Первая фотография с подписью (текстом сообщения)
+ media_group.append(InputMediaPhoto(
+ media=photos[0],
+ caption=message,
+ parse_mode='HTML'
+ ))
+
+ # Все остальные фотографии без подписи
+ for photo_url in photos[1:]:
+ media_group.append(InputMediaPhoto(
+ media=photo_url
+ ))
+
+ await bot.send_media_group(
+ chat_id=settings.TELEGRAM_TARGET_CHANNEL_ID,
+ media=media_group
+ )
+
+ logger.info(f"Message successfully sent to Telegram channel {settings.TELEGRAM_TARGET_CHANNEL_ID}")
+ except Exception as e:
+ logger.error(f"Failed to send message to Telegram: {e}")
+
+
+@event(
+ type="wall_post_new",
+ group_id=lambda i: int(i) == settings.VK_MONITORED_GROUP_ID if settings.VK_MONITORED_GROUP_ID else False,
+)
+def handle_new_post(event: dict):
+ """Обрабатывает событие нового поста в группе ВК и пересылает его в Telegram канал"""
+ logger.info("New post detected in monitored VK group")
+
+ try:
+ # Получаем данные поста
+ post = event.get("object", {})
+ post_id = post.get("id")
+ owner_id = post.get("owner_id")
+ text = post.get("text", "")
+ attachments = post.get("attachments", [])
+
+ # Форматируем сообщение для Telegram
+ message = f"Новый пост в группе ВК:\n\n{text}"
+
+ # Добавляем информацию о вложениях, кроме фото (их отправим отдельно)
+ attachment_texts = []
+
+ for attachment in attachments:
+ attachment_type = attachment.get("type")
+
+ # Обрабатываем видео
+ if attachment_type == "video":
+ video_data = attachment.get("video", {})
+ video_id = video_data.get("id")
+ video_owner_id = video_data.get("owner_id")
+ video_title = video_data.get("title", "Видео")
+
+ if video_id and video_owner_id:
+ attachment_texts.append(
+ f"\n\n📹 {video_title}: "
+ f"Смотреть видео"
+ )
+
+ # Обрабатываем ссылки
+ elif attachment_type == "link":
+ link_data = attachment.get("link", {})
+ link_url = link_data.get("url")
+ link_title = link_data.get("title", "Ссылка")
+
+ if link_url:
+ attachment_texts.append(
+ f"\n\n🔗 {link_title}: Открыть ссылку"
+ )
+
+ # Обрабатываем документы
+ elif attachment_type == "doc":
+ doc_data = attachment.get("doc", {})
+ doc_url = doc_data.get("url")
+ doc_title = doc_data.get("title", "Документ")
+
+ if doc_url:
+ attachment_texts.append(
+ f"\n\n📄 {doc_title}: Скачать документ"
+ )
+
+ # Обрабатываем аудио
+ elif attachment_type == "audio":
+ audio_data = attachment.get("audio", {})
+ audio_id = audio_data.get("id")
+ audio_owner_id = audio_data.get("owner_id")
+ audio_artist = audio_data.get("artist", "")
+ audio_title = audio_data.get("title", "Аудиозапись")
+
+ if audio_id and audio_owner_id:
+ attachment_texts.append(
+ f"\n\n🎵 {audio_artist} - {audio_title}: "
+ f"Слушать"
+ )
+
+ # Добавляем информацию о вложениях к сообщению
+ if attachment_texts:
+ message += "".join(attachment_texts)
+
+ # Добавляем ссылку на оригинальный пост в конце
+ message += f"\n\nОригинальный пост ВКонтакте"
+
+ # Собираем фотографии из вложений
+ photos = []
+ for attachment in attachments:
+ if attachment.get("type") == "photo":
+ photo_data = attachment.get("photo", {})
+ # Выбираем максимальное разрешение фото
+ sizes = photo_data.get("sizes", [])
+ if sizes:
+ # Сортируем по размеру (width * height)
+ sizes.sort(key=lambda x: x.get("width", 0) * x.get("height", 0), reverse=True)
+ photo_url = sizes[0].get("url")
+ if photo_url:
+ photos.append(photo_url)
+
+ # Отправляем в Telegram
+ import asyncio
+ asyncio.run(send_to_telegram(message, photos))
+
+ logger.info(f"Post content forwarded to Telegram channel from VK post {owner_id}_{post_id}")
+ except Exception as e:
+ logger.exception(f"Error processing new VK post: {e}")
diff --git a/social/routes/vk.py b/social/routes/vk.py
index f44b361..b0a0637 100644
--- a/social/routes/vk.py
+++ b/social/routes/vk.py
@@ -31,32 +31,65 @@ class VkGroupCreateResponse(BaseModel):
model_config = ConfigDict(from_attributes=True)
+class MonitoringConfig(BaseModel):
+ vk_group_id: int
+ telegram_channel_id: int
+
+
@router.post('', tags=["webhooks"])
async def vk_webhook(request: Request, background_tasks: BackgroundTasks) -> str:
"""Принимает любой POST запрос от VK"""
- request_data = await request.json()
- logger.debug(request_data)
- group_id = request_data["group_id"] # Fail if no group
- group = db.session.query(VkGroup).where(VkGroup.group_id == group_id).one() # Fail if no settings
-
- # Проверка на создание нового вебхука со страничка ВК
- if request_data.get("type", "") == "confirmation":
- return PlainTextResponse(group.confirmation_token)
-
- if request_data.get("secret") != group.secret_key:
- raise Exception("Not a valid secret")
-
- db.session.add(
- WebhookStorage(
- system=WebhookSystems.VK,
- message=request_data,
+ try:
+ request_data = await request.json()
+ logger.debug(f"Received VK webhook: {request_data}")
+ group_id = request_data.get("group_id") # Получаем ID группы
+
+ if not group_id:
+ logger.warning("Received VK webhook without group_id")
+ return PlainTextResponse('ok') # Возвращаем ok, чтобы VK не пытался повторить запрос
+
+ # Проверяем наличие группы в базе данных
+ group = db.session.query(VkGroup).where(VkGroup.group_id == group_id).one_or_none()
+
+ if not group:
+ logger.warning(f"Received VK webhook for unknown group_id: {group_id}")
+ return PlainTextResponse('ok')
+
+ # Проверка на создание нового вебхука со страницы ВК
+ if request_data.get("type", "") == "confirmation":
+ logger.info(f"Received confirmation request for group_id: {group_id}")
+ return PlainTextResponse(group.confirmation_token)
+
+ # Проверка секретного ключа
+ if request_data.get("secret") != group.secret_key:
+ logger.warning(f"Received VK webhook with invalid secret for group_id: {group_id}")
+ return PlainTextResponse('ok') # Возвращаем ok, но не обрабатываем
+
+ event_type = request_data.get("type", "unknown")
+ logger.info(f"Processing VK webhook, type: {event_type}, group_id: {group_id}")
+
+ # Сохраняем событие в базу данных
+ db.session.add(
+ WebhookStorage(
+ system=WebhookSystems.VK,
+ message=request_data,
+ )
)
- )
- db.session.commit()
-
- background_tasks.add_task(create_vk_chat, request_data)
- background_tasks.add_task(process_event, request_data)
- return PlainTextResponse('ok')
+ db.session.commit()
+
+ # Проверяем, это ли событие создания записи на стене
+ is_wall_post = event_type == "wall_post_new"
+ if is_wall_post:
+ logger.info(f"Received new wall post event for group_id: {group_id}")
+
+ # Запускаем обработку в фоновом режиме
+ background_tasks.add_task(create_vk_chat, request_data)
+ background_tasks.add_task(process_event, request_data)
+
+ return PlainTextResponse('ok')
+ except Exception as e:
+ logger.exception(f"Error processing VK webhook: {e}")
+ return PlainTextResponse('ok') # Всегда возвращаем ok, чтобы VK не повторял запрос
@router.put('/{group_id}')
@@ -80,3 +113,31 @@ def create_or_replace_group(
db.session.commit()
return group
+
+
+@router.post('/monitoring/configure')
+def configure_monitoring(
+ config: MonitoringConfig,
+ user: dict[str] = Depends(UnionAuth(["social.monitoring.configure"])),
+) -> dict:
+ """Настраивает мониторинг группы ВК и пересылку постов в Telegram канал"""
+ # Здесь мы обновляем настройки приложения
+ # В реальном приложении нужно будет сохранять эти настройки в базу данных
+ # и загружать их при старте, а не менять глобальный объект
+
+ settings = get_settings()
+
+ # В данном примере мы напрямую изменяем настройки
+ # Но лучше будет сохранить их в базу и обновлять при перезапуске
+ # Для этого потребуется создать соответствующую модель БД
+ settings.VK_MONITORED_GROUP_ID = config.vk_group_id
+ settings.TELEGRAM_TARGET_CHANNEL_ID = config.telegram_channel_id
+
+ logger.info(f"Monitoring configured for VK group {config.vk_group_id} with Telegram channel {config.telegram_channel_id}")
+
+ return {
+ "status": "success",
+ "message": "Мониторинг настроен успешно",
+ "vk_group_id": config.vk_group_id,
+ "telegram_channel_id": config.telegram_channel_id
+ }
diff --git a/social/settings.py b/social/settings.py
index 83fbf6b..0b8bdda 100644
--- a/social/settings.py
+++ b/social/settings.py
@@ -19,9 +19,11 @@ class Settings(BaseSettings):
CORS_ALLOW_HEADERS: list[str] = ['*']
TELEGRAM_BOT_TOKEN: str | None = None
+ TELEGRAM_TARGET_CHANNEL_ID: int | None = None # ID канала Telegram для пересылки постов
VK_BOT_GROUP_ID: int | None = None
VK_BOT_TOKEN: str | None = None
+ VK_MONITORED_GROUP_ID: int | None = None # ID группы ВК для мониторинга
GITHUB_APP_ID: int | None = None
GITHUB_WEBHOOK_SECRET: str | None = None