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

Исправлены все основные проблемы:
- Исправлена логика фильтрации сообщений по топикам в 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

198
site_api.py Normal file
View File

@@ -0,0 +1,198 @@
#!/usr/bin/env python3
import re
import time
import logging
import requests
from config import URL_POST, URL_NEWS, URL_CHANGELOG, HEADERS_SITE, SITE_CONFIG
class SiteAPI:
def __init__(self):
self.logger = logging.getLogger(__name__)
self.config = SITE_CONFIG
def make_request(self, url, headers=None, params=None, max_retries=10, retry_delay=10):
"""Универсальный метод для HTTP запросов с повторными попытками"""
for attempt in range(max_retries):
try:
if params:
response = requests.get(url, params=params)
elif headers:
response = requests.get(url, headers=headers)
else:
response = requests.get(url)
if response.status_code == 200:
return response
else:
self.logger.warning(f"HTTP {response.status_code} для {url}, попытка {attempt + 1}/{max_retries}")
except requests.RequestException as err:
self.logger.warning(f"Ошибка HTTP запроса к {url} (попытка {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} попыток запроса к {url} неудачны")
return None
def get_changelog(self):
"""Получение changelog с GitLab"""
self.logger.debug("Получаем changelog")
response = self.make_request(URL_CHANGELOG, HEADERS_SITE)
if response and response.status_code == 200:
matches = re.findall(r'###Scripts version (\d+)### / stable', response.text)
self.logger.debug(f"Найдены версии в истории изменений: {matches}")
if matches:
last_version = int(max(matches))
self.logger.info(f"Последняя стабильная версия в changelog: {last_version}")
return matches, last_version, response
self.logger.error(f'Ошибка при запросе changelog: {response.status_code if response else "No Response"}')
return None, None, None
def get_news(self):
"""Получение списка новостей с сайта"""
self.logger.debug("Получаем список новостей")
response = self.make_request(URL_NEWS, HEADERS_SITE)
if response and response.status_code == 200:
data = response.json()
topics = data['topic_list']['topics']
# Фильтруем новости, исключая описание категории
filtered_topics = [
(topic['id'], str(topic['title']))
for topic in topics
if topic['title'] != 'Описание категории «Новости»'
]
# Фильтруем по ID темы
filtered_topics = [
(topic_id, title)
for topic_id, title in filtered_topics
if topic_id >= self.config['start_topic_id']
]
self.logger.debug(f"Получено {len(filtered_topics)} новостей")
return filtered_topics
else:
self.logger.error(f"Ошибка при запросе новостей: {response.status_code if response else 'Нет доступа к сайту'}")
return []
def get_news_content(self, post_id, content_processor):
"""Получение содержимого конкретной новости"""
self.logger.debug(f"Получаем содержимое поста с ID: {post_id}")
url = f"https://linux-gaming.ru/t/{post_id}.json"
response = self.make_request(url, HEADERS_SITE)
if response and response.status_code == 200:
topic_data = response.json()
posts = topic_data.get('post_stream', {}).get('posts', [])
# Ищем первый пост
for post in posts:
if post.get('post_number') == 1:
html_content = post.get('cooked', 'Нет содержимого')
text_data = content_processor.html_to_text(html_content)
return text_data
self.logger.error(f"Первый пост не найден в теме с ID: {post_id}")
return None
else:
self.logger.error(f"Не удалось получить содержимое поста с ID: {post_id}")
return None
def post_to_site(self, post_data):
"""Публикация новости на сайт"""
title = post_data.get('title', 'Без названия')
while True:
try:
response = requests.post(url=URL_POST, headers=HEADERS_SITE, json=post_data)
if response.status_code == 200:
self.logger.info("Новость опубликована на сайте!")
return response
elif response.status_code == 422:
self.logger.warning(f'Новость "{title}" уже опубликована: {response.status_code}')
return response
else:
self.logger.error(f'Ошибка при отправке новости "{title}" на сайт: {response.status_code}')
except requests.RequestException as error:
self.logger.error(f'Ошибка при отправке новости "{title}" на сайт: {error}')
time.sleep(900) # Ждем 15 минут перед повторной попыткой
def create_script_update_post(self, script_ver, next_version, changelog_response, content_processor):
"""Создание поста для обновления скрипта"""
self.logger.debug(f"Создаем пост для обновления скрипта версии {script_ver}")
post_text = content_processor.create_script_content(script_ver, next_version, changelog_response)
if post_text:
site_text = f"[center][img]/uploads/default/original/1X/5cfa59077a5275971401fab0114e56f3ffdd0ec4.png[/img][/center]\n{post_text}"
post_data = {
"title": f"Кумулятивное обновление скриптов {script_ver}",
"raw": site_text,
"category": self.config['category_num']
}
self.logger.debug(f"Данные поста: {post_data}")
return post_text, post_data
return None, None
def check_script_versions(self, content_processor):
"""Проверка и публикация новых версий скриптов"""
self.logger.info("Проверяем новые версии скриптов")
# Получаем changelog
matches_changelog, last_changelog, resp_changelog = self.get_changelog()
if not matches_changelog or not last_changelog:
return
# Получаем существующие новости
news_list = self.get_news()
# Извлекаем номера версий из заголовков новостей
pattern = re.compile(r'Кумулятивное обновление скриптов (\d+)')
def extract_number(title):
match = pattern.search(title)
return int(match.group(1)) if match else None
numbers = [extract_number(title) for _, title in news_list if extract_number(title) is not None]
if numbers:
last_topics_script = max(numbers)
self.logger.info(f"Последняя новость на сайте о версии: {last_topics_script}")
if last_topics_script >= last_changelog:
self.logger.warning("Нет новых версий скриптов PortProton")
return
else:
self.logger.warning("На сайте нет новостей о скриптах")
# Публикуем новые версии
self._publish_new_script_versions(matches_changelog, resp_changelog, content_processor)
def _publish_new_script_versions(self, matches_changelog, resp_changelog, content_processor):
"""Публикация новых версий скриптов"""
for script_ver, next_version in zip(reversed(matches_changelog[:-1]), reversed(matches_changelog[1:])):
self.logger.info(f"Найдена новая версия скрипта {script_ver}")
post_text, post_data = self.create_script_update_post(
script_ver, next_version, resp_changelog, content_processor
)
if post_data:
self.logger.debug(f"Публикуем {post_data}")
self.post_to_site(post_data)