Завершение модульной рефакторизации и исправления

Исправлены все основные проблемы:
- Исправлена логика фильтрации сообщений по топикам в 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>
This commit is contained in:
2025-08-11 15:11:39 +03:00
parent 188acdd812
commit 845f96209d
15 changed files with 569071 additions and 1 deletions

187
vk_client.py Normal file
View File

@@ -0,0 +1,187 @@
#!/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 всегда включен в этой версии