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

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

@@ -0,0 +1,198 @@
#!/usr/bin/env python3
import re
import logging
import html2text
import urllib.parse
from bs4 import BeautifulSoup
class ContentProcessor:
def __init__(self):
self.logger = logging.getLogger(__name__)
def make_soup(self, response):
self.logger.debug("Создаем объект BeautifulSoup")
return BeautifulSoup(response.text, 'html.parser')
def html_to_text(self, html_content):
self.logger.debug(f"Конвертируем HTML в текст")
self.logger.debug(f"HTML на входе: {html_content}")
h = html2text.HTML2Text()
h.ignore_links = False
h.ignore_images = True
h.bypass_tables = True
h.reference_links = True
markdown_text = h.handle(html_content)
self.logger.debug(f"Текст до обработки регулярными выражениями: {markdown_text}")
# Удаление переносов строк из-за -
markdown_text = re.sub(r'-\s*\n\s*', '-', markdown_text, flags=re.DOTALL)
markdown_text = re.sub(r'-\s*\n*', '-', markdown_text, flags=re.DOTALL)
# Убираем переносы строк внутри круглых скобок ()
markdown_text = re.sub(r'\((.*?)\)', lambda x: '(' + x.group(1).replace('\n', ' ') + ')', markdown_text, flags=re.DOTALL)
# Убираем переносы строк внутри квадратных скобок []
markdown_text = re.sub(r'\[(.*?)\]', lambda x: '[' + x.group(1).replace('\n', ' ') + ']', markdown_text, flags=re.DOTALL)
# Удаление строк, содержащих '* * *'
markdown_text = re.sub(r'^.*\* \* \*.*$', '', markdown_text, flags=re.MULTILINE)
# Преобразование всех ссылок с параметрами URL
markdown_text = self.convert_links(markdown_text)
# Работа с #
patterns_to_remove = [
r'###',
r'##',
r'#',
r'\[scripts\]\(\/tag\/scripts\) version \d+ ',
r'##\[scripts\]\(\) version \d+ ',
r'\d{4}×\d{3} \d+ KB'
]
for pattern in patterns_to_remove:
markdown_text = re.sub(pattern, '', markdown_text)
# Удаление избыточных пустых строк после удаления строк
markdown_text = re.sub(r'\n\s*\n', '\n', markdown_text)
# Замена текстов типа "image1280×474 99.7 KB", "807×454 64.1 KB" на "."
markdown_text = re.sub(r'image\d+×\d+\s+\d+(\.\d+)?\s+KB', '.', markdown_text)
markdown_text = re.sub(r'\d+×\d+\s+\d+(\.\d+)?\s+KB', '.', markdown_text)
# Изменение ссылок без описания
markdown_text = re.sub(r'\[\]\((https:\/\/[^\)]+)\)', r'[.](\1)', markdown_text)
markdown_text = re.sub(r'\[\s]\((https:\/\/[^\)]+)\)', r'[.](\1)', markdown_text)
# Удаление дублирующихся ссылок
markdown_text = self.remove_duplicate_links(markdown_text)
# Удаление лишних отступов для строк, начинающихся с '*'
markdown_text = re.sub(r' \*', r'*', markdown_text)
# Перемещение ссылки на изображение в конец последней строки
image_link = "[.](https://linux-gaming.ru/uploads/default/original/1X/5cfa59077a5275971401fab0114e56f3ffdd0ec4.png)"
if image_link in markdown_text:
markdown_text = markdown_text.replace(image_link, '')
markdown_text = markdown_text + image_link
self.logger.debug(f"Текст после обработки: {markdown_text}")
return markdown_text
def convert_links(self, text):
self.logger.debug("Конвертируем ссылки")
url_pattern = re.compile(r'https?://[^\s\)]+')
url_pattern = url_pattern.sub(lambda match: self.decode_url_params(match.group(0)), text)
self.logger.debug(f"Результат конвертации ссылок: {url_pattern}")
return url_pattern
def decode_url_params(self, url):
self.logger.debug(f"Декодируем URL параметры: {url}")
parsed_url = urllib.parse.urlparse(url)
query_params = urllib.parse.parse_qs(parsed_url.query)
for key, values in query_params.items():
if key.lower() == 'to' and values:
return urllib.parse.unquote(values[0])
self.logger.debug(f"Возвращаем URL: {url}")
return url
def remove_empty_lines(self, text_data):
self.logger.debug("Удаляем пустые строки")
lines = text_data.splitlines()
non_empty_lines = [line for line in lines if line.strip()]
non_empty_lines = '\n'.join(non_empty_lines)
self.logger.debug(f"Результат удаления пустых строк: {non_empty_lines}")
return non_empty_lines
def remove_markdown_links(self, markdown_text):
self.logger.debug("Удаляем markdown ссылки")
markdown_text = re.sub(r'\[.*?\]\((https?://.*?)\)', r'\1', markdown_text)
self.logger.debug(f"Результат удаления markdown ссылок: {markdown_text}")
return markdown_text
def remove_duplicate_links(self, text):
self.logger.debug("Удаляем дубликаты ссылок")
seen_links = set()
def replace_link(match):
link = match.group(2)
if link in seen_links:
return ''
seen_links.add(link)
return match.group(0)
link_pattern = re.compile(r'(\[.*?\]\((https:\/\/.*?)\))')
text = re.sub(link_pattern, replace_link, text)
self.logger.debug(f"Результат удаления дубликатов ссылок: {text}")
return text
def extract_links(self, text):
self.logger.debug("Извлекаем ссылки из текста")
url_pattern = re.compile(r'https?://\S+')
links = url_pattern.findall(text)
self.logger.debug(f"Найденные ссылки: {links}")
return links
def format_for_vk(self, content):
"""Форматирование контента для VK"""
self.logger.debug("Форматируем контент для VK")
# Замена маркеров списка
content = re.sub(r'\* ', '', content)
content = re.sub(r'', '', content)
content = re.sub(r'', '', content)
# Удаление markdown ссылок
content = self.remove_markdown_links(content)
# Замена изображения
content = re.sub(
r'https://linux-gaming.ru/uploads/default/original/1X/5cfa59077a5275971401fab0114e56f3ffdd0ec4.png',
'\n',
content,
flags=re.DOTALL
)
return content
def format_for_telegram(self, content):
"""Форматирование контента для Telegram"""
self.logger.debug("Форматируем контент для Telegram")
return content # Telegram поддерживает markdown
def create_script_content(self, script_ver, next_version, response):
"""Создание контента для обновления скрипта"""
self.logger.debug(f"Создаем контент для версии скрипта {script_ver}")
soup = self.make_soup(response)
page_text = str(soup)
page_text = page_text.replace("Вы можете помочь развитию проекта: https://linux-gaming.ru/donate/", '')
last_text = f"###Scripts version {next_version}### / stable"
index_last_text = page_text.find(last_text)
if index_last_text != -1:
changelog_text_last = page_text[:index_last_text]
prev_text = f"###Scripts version {script_ver}### / stable"
index_script_ver = changelog_text_last.find(prev_text)
changelog_text = changelog_text_last[index_script_ver:]
changelog_text = re.sub(
r'###Scripts version (\d+)### / Дата: (\d{2}\.\d{2}\.\d{4}) / Размер скачиваемого обновления: \d+ \S+',
r'\1 - \2' + ":",
changelog_text
)
changelog_text = re.sub(
r'###Scripts version (\d+)### / stable / Дата: (\d{2}\.\d{2}\.\d{4}) / Размер скачиваемого обновления: \d+ \S+',
r'\1 - \2' + ":",
changelog_text
)
post_text = "-----------------------------\n" + changelog_text
self.logger.debug(f"Возвращаем post_text: {post_text}")
return post_text
return None