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