Завершение модульной рефакторизации и исправления
Исправлены все основные проблемы: - Исправлена логика фильтрации сообщений по топикам в 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:
187
vk_client.py
Normal file
187
vk_client.py
Normal 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 всегда включен в этой версии
|
Reference in New Issue
Block a user