Исправлены все основные проблемы: - Исправлена логика фильтрации сообщений по топикам в Telegram - Исправлен бесконечный цикл в VK клиенте get_wall_posts() - Добавлена асинхронная поддержка для VK в главном файле - Дедупликация работает корректно для всех платформ - Добавлена полная документация в CLAUDE.md и README.md 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
187 lines
8.9 KiB
Python
187 lines
8.9 KiB
Python
#!/usr/bin/env python3
|
||
|
||
import re
|
||
import sys
|
||
import time
|
||
import logging
|
||
import requests
|
||
|
||
from config import VK_CONFIG, URL_VK_POST, URL_VK_GET, PARAMS_VK_GET
|
||
|
||
|
||
class VKClient:
|
||
def __init__(self, content_processor):
|
||
self.logger = logging.getLogger(__name__)
|
||
self.content_processor = content_processor
|
||
self.config = VK_CONFIG
|
||
|
||
def get_wall_posts(self, max_retries=10, retry_delay=10):
|
||
"""Получение постов со стены VK с повторными попытками"""
|
||
self.logger.debug("Получаем посты со стены VK")
|
||
wall_posts = []
|
||
params = PARAMS_VK_GET.copy()
|
||
|
||
while True:
|
||
success = False
|
||
for attempt in range(max_retries):
|
||
try:
|
||
response = requests.get(URL_VK_GET, params=params)
|
||
wall_data_json = response.json()
|
||
|
||
if 'error' in wall_data_json:
|
||
error_code = wall_data_json['error']['error_code']
|
||
error_msg = wall_data_json['error']['error_msg']
|
||
self.logger.error(f"Ошибка VK API {error_code}: {error_msg}")
|
||
return wall_posts, [] # Возвращаем то что получили
|
||
|
||
items = wall_data_json.get('response', {}).get('items', [])
|
||
if not items:
|
||
self.logger.warning("Постов на стене нет")
|
||
return wall_posts, [] # Завершаем цикл
|
||
|
||
wall_posts.extend((post['text'] for post in items if 'text' in post))
|
||
if len(items) < 100:
|
||
# Получили все посты
|
||
success = True
|
||
break
|
||
|
||
params['offset'] = str(int(params['offset']) + 100)
|
||
success = True
|
||
break
|
||
|
||
except requests.RequestException as e:
|
||
self.logger.warning(f"Ошибка при получении постов VK (попытка {attempt + 1}/{max_retries}): {e}")
|
||
if attempt < max_retries - 1:
|
||
self.logger.info(f"Повторная попытка через {retry_delay} секунд...")
|
||
time.sleep(retry_delay)
|
||
|
||
if not success:
|
||
self.logger.error(f"Все {max_retries} попыток получения постов VK неудачны")
|
||
break
|
||
|
||
# Если получили меньше 100 постов на последнем запросе, завершаем цикл
|
||
if len(items) < 100:
|
||
break
|
||
|
||
# Извлечение заголовков из постов
|
||
wall_titles = []
|
||
pattern = re.compile(r'### (.*?)\t', re.MULTILINE)
|
||
|
||
for message in wall_posts:
|
||
# Ищем заголовки в формате "### Заголовок\t"
|
||
matches = pattern.findall(message)
|
||
wall_titles.extend(matches)
|
||
|
||
# Также ищем в первой строке поста
|
||
lines = message.strip().split('\n') if message else []
|
||
if lines:
|
||
first_line = lines[0].strip()
|
||
# Убираем префиксы и табы
|
||
first_line = re.sub(r'^[#*\s\t]+', '', first_line)
|
||
first_line = re.sub(r'\t.*$', '', first_line)
|
||
if first_line and first_line not in wall_titles:
|
||
wall_titles.append(first_line)
|
||
|
||
self.logger.info(f"Найдено {len(wall_posts)} постов и {len(wall_titles)} заголовков в VK")
|
||
self.logger.debug(f"Заголовки VK: {wall_titles[:5]}...") # Показываем первые 5 заголовков
|
||
return wall_posts, wall_titles
|
||
|
||
def post_message(self, content, attachments=None, max_retries=10, retry_delay=10):
|
||
"""Отправка сообщения в VK с повторными попытками"""
|
||
params_post = {
|
||
'access_token': self.config['api_key'],
|
||
'v': '5.199',
|
||
'owner_id': str(self.config['owner_id'])
|
||
}
|
||
|
||
# Форматирование контента для VK
|
||
formatted_content = self.content_processor.format_for_vk(content)
|
||
|
||
data = {
|
||
'message': formatted_content
|
||
}
|
||
|
||
if attachments:
|
||
params_post['attachments'] = attachments
|
||
|
||
for attempt in range(max_retries):
|
||
try:
|
||
response = requests.post(url=URL_VK_POST, params=params_post, data=data)
|
||
|
||
if response.status_code == 200:
|
||
self.logger.info("Сообщение успешно опубликовано в VK")
|
||
self.logger.debug(response.json())
|
||
return response
|
||
else:
|
||
self.logger.warning(f"Ошибка при публикации в VK: {response.status_code} - {response.reason} (попытка {attempt + 1}/{max_retries})")
|
||
|
||
except requests.RequestException as err:
|
||
self.logger.warning(f"Ошибка VK API (попытка {attempt + 1}/{max_retries}): {err}")
|
||
|
||
if attempt < max_retries - 1:
|
||
self.logger.info(f"Повторная попытка публикации через {retry_delay} секунд...")
|
||
time.sleep(retry_delay)
|
||
|
||
self.logger.error(f"Все {max_retries} попыток публикации в VK неудачны")
|
||
return None
|
||
|
||
def check_and_publish_news(self, news_list):
|
||
"""Проверка и публикация новостей в VK"""
|
||
self.logger.info("Начинаем проверку новостей для VK")
|
||
|
||
vk_posts, vk_titles = self.get_wall_posts()
|
||
if not vk_posts:
|
||
self.logger.warning("Постов на стене VK нет")
|
||
|
||
if not news_list:
|
||
self.logger.warning("Список новостей пуст")
|
||
return
|
||
|
||
# Фильтруем новости для публикации
|
||
list_for_public = []
|
||
for topic_id, topic_title in news_list:
|
||
# Проверяем по заголовкам и по полному тексту сообщений
|
||
title_exists = any(topic_title == title for title in vk_titles)
|
||
text_contains = any(topic_title in vk_post for vk_post in vk_posts)
|
||
|
||
if not title_exists and not text_contains:
|
||
list_for_public.append((topic_id, topic_title))
|
||
else:
|
||
self.logger.debug(f"Новость '{topic_title}' уже есть в VK (title_exists={title_exists}, text_contains={text_contains})")
|
||
|
||
if not list_for_public:
|
||
self.logger.warning("Новостей для публикации в VK нет")
|
||
return
|
||
|
||
self.logger.info(f"Новости для публикации в VK: {list_for_public}")
|
||
|
||
# Публикуем новости
|
||
for topic_id, topic_title in reversed(list_for_public):
|
||
from site_api import SiteAPI
|
||
site_api = SiteAPI()
|
||
text_data = site_api.get_news_content(topic_id, self.content_processor)
|
||
|
||
if text_data:
|
||
content = f"{topic_title}\t\n" + text_data + "\n"
|
||
|
||
# Извлекаем ссылки для прикрепления
|
||
links = self.content_processor.extract_links(content)
|
||
|
||
# Специальная обработка для постов о скриптах
|
||
if "Кумулятивное обновление скриптов" in topic_title:
|
||
# Добавляем изображение для постов о скриптах
|
||
self.post_message(content, "photo-99238527_457244491")
|
||
else:
|
||
if links:
|
||
# Берем первую ссылку как прикрепление
|
||
self.post_message(content, links[0] if len(links) == 1 else None)
|
||
else:
|
||
self.post_message(content)
|
||
|
||
time.sleep(1.0) # Пауза между постами
|
||
else:
|
||
self.logger.warning(f"Не удалось получить содержимое новости {topic_id}")
|
||
|
||
def is_enabled(self):
|
||
"""Проверка, включен ли VK клиент"""
|
||
return True # VK всегда включен в этой версии |