Исправлены все основные проблемы: - Исправлена логика фильтрации сообщений по топикам в 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>
198 lines
9.4 KiB
Python
198 lines
9.4 KiB
Python
#!/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) |