- корректировка логики проверки дублей
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -8,4 +8,4 @@ keys_*.py
|
|||||||
!keys_example.py
|
!keys_example.py
|
||||||
venv/
|
venv/
|
||||||
.env
|
.env
|
||||||
*.pyc
|
*.pycpublished_history.json
|
||||||
|
70
config_updater.py
Normal file
70
config_updater.py
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigUpdater:
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.keys_file = 'keys.py'
|
||||||
|
|
||||||
|
def update_start_topic_id(self, new_topic_id):
|
||||||
|
"""Обновление start_topic_id в файле keys.py"""
|
||||||
|
try:
|
||||||
|
# Проверяем существование файла
|
||||||
|
if not os.path.exists(self.keys_file):
|
||||||
|
self.logger.error(f"Файл {self.keys_file} не найден")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Читаем содержимое файла
|
||||||
|
with open(self.keys_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Ищем строку с start_topic_id и заменяем значение
|
||||||
|
pattern = r'^(start_topic_id\s*=\s*)(.*)$'
|
||||||
|
replacement = rf'\g<1>{new_topic_id}'
|
||||||
|
|
||||||
|
new_content, count = re.subn(pattern, replacement, content, flags=re.MULTILINE)
|
||||||
|
|
||||||
|
if count == 0:
|
||||||
|
self.logger.error("Не найдена переменная start_topic_id в keys.py")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Записываем обновленное содержимое
|
||||||
|
with open(self.keys_file, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_content)
|
||||||
|
|
||||||
|
self.logger.info(f"Успешно обновлен start_topic_id на {new_topic_id} в {self.keys_file}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка при обновлении start_topic_id: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def get_current_start_topic_id(self):
|
||||||
|
"""Получение текущего значения start_topic_id из keys.py"""
|
||||||
|
try:
|
||||||
|
if not os.path.exists(self.keys_file):
|
||||||
|
self.logger.error(f"Файл {self.keys_file} не найден")
|
||||||
|
return None
|
||||||
|
|
||||||
|
with open(self.keys_file, 'r', encoding='utf-8') as f:
|
||||||
|
content = f.read()
|
||||||
|
|
||||||
|
# Ищем значение start_topic_id
|
||||||
|
match = re.search(r'^start_topic_id\s*=\s*(.*)$', content, flags=re.MULTILINE)
|
||||||
|
if match:
|
||||||
|
value = match.group(1).strip()
|
||||||
|
# Пытаемся преобразовать в число
|
||||||
|
try:
|
||||||
|
return int(value) if value and value != 'None' else None
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка при чтении start_topic_id: {e}")
|
||||||
|
return None
|
99
history_manager.py
Normal file
99
history_manager.py
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
class HistoryManager:
|
||||||
|
def __init__(self, history_file='published_history.json'):
|
||||||
|
self.logger = logging.getLogger(__name__)
|
||||||
|
self.history_file = history_file
|
||||||
|
self.history = self._load_history()
|
||||||
|
|
||||||
|
def _load_history(self):
|
||||||
|
"""Загрузка истории из файла"""
|
||||||
|
if os.path.exists(self.history_file):
|
||||||
|
try:
|
||||||
|
with open(self.history_file, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self.logger.info(f"Загружена история: {len(data.get('telegram', []))} записей для Telegram")
|
||||||
|
return data
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка загрузки истории: {e}")
|
||||||
|
return self._get_empty_history()
|
||||||
|
else:
|
||||||
|
self.logger.info("Файл истории не найден, создаём новый")
|
||||||
|
return self._get_empty_history()
|
||||||
|
|
||||||
|
def _get_empty_history(self):
|
||||||
|
"""Создание пустой структуры истории"""
|
||||||
|
return {
|
||||||
|
'telegram': [],
|
||||||
|
'vk': [],
|
||||||
|
'discord': []
|
||||||
|
}
|
||||||
|
|
||||||
|
def _save_history(self):
|
||||||
|
"""Сохранение истории в файл"""
|
||||||
|
try:
|
||||||
|
with open(self.history_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(self.history, f, ensure_ascii=False, indent=2)
|
||||||
|
self.logger.debug(f"История сохранена в {self.history_file}")
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"Ошибка сохранения истории: {e}")
|
||||||
|
|
||||||
|
def is_published(self, platform, title):
|
||||||
|
"""Проверка, была ли новость опубликована"""
|
||||||
|
if platform not in self.history:
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Проверяем по заголовку
|
||||||
|
for entry in self.history[platform]:
|
||||||
|
if entry['title'].lower().strip() == title.lower().strip():
|
||||||
|
self.logger.debug(f"Новость '{title}' уже была опубликована в {platform}")
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def add_published(self, platform, topic_id, title):
|
||||||
|
"""Добавление записи об опубликованной новости"""
|
||||||
|
if platform not in self.history:
|
||||||
|
self.history[platform] = []
|
||||||
|
|
||||||
|
entry = {
|
||||||
|
'topic_id': topic_id,
|
||||||
|
'title': title,
|
||||||
|
'published_at': datetime.now().isoformat()
|
||||||
|
}
|
||||||
|
|
||||||
|
self.history[platform].append(entry)
|
||||||
|
self.logger.info(f"Добавлена запись в историю {platform}: {title}")
|
||||||
|
self._save_history()
|
||||||
|
|
||||||
|
def cleanup_old_entries(self, days=365):
|
||||||
|
"""Удаление очень старых записей из истории (по умолчанию старше года)
|
||||||
|
|
||||||
|
ВНИМАНИЕ: Удаление записей может привести к повторной публикации старых новостей!
|
||||||
|
Используйте этот метод только если файл истории стал слишком большим.
|
||||||
|
"""
|
||||||
|
cutoff_date = datetime.now() - timedelta(days=days)
|
||||||
|
|
||||||
|
for platform in self.history:
|
||||||
|
original_count = len(self.history[platform])
|
||||||
|
self.history[platform] = [
|
||||||
|
entry for entry in self.history[platform]
|
||||||
|
if datetime.fromisoformat(entry['published_at']) > cutoff_date
|
||||||
|
]
|
||||||
|
removed = original_count - len(self.history[platform])
|
||||||
|
if removed > 0:
|
||||||
|
self.logger.info(f"Удалено {removed} старых записей из истории {platform} (старше {days} дней)")
|
||||||
|
|
||||||
|
self._save_history()
|
||||||
|
|
||||||
|
def get_published_titles(self, platform):
|
||||||
|
"""Получение списка опубликованных заголовков для платформы"""
|
||||||
|
if platform not in self.history:
|
||||||
|
return []
|
||||||
|
return [entry['title'] for entry in self.history[platform]]
|
@@ -11,6 +11,7 @@ from site_api import SiteAPI
|
|||||||
from telegram_client import TelegramNewsClient
|
from telegram_client import TelegramNewsClient
|
||||||
from vk_client import VKClient
|
from vk_client import VKClient
|
||||||
from discord_client import DiscordClient
|
from discord_client import DiscordClient
|
||||||
|
from config_updater import ConfigUpdater
|
||||||
|
|
||||||
|
|
||||||
class NewsBot:
|
class NewsBot:
|
||||||
@@ -21,6 +22,7 @@ class NewsBot:
|
|||||||
self.telegram_client = TelegramNewsClient(self.content_processor)
|
self.telegram_client = TelegramNewsClient(self.content_processor)
|
||||||
self.vk_client = VKClient(self.content_processor)
|
self.vk_client = VKClient(self.content_processor)
|
||||||
self.discord_client = DiscordClient(self.content_processor)
|
self.discord_client = DiscordClient(self.content_processor)
|
||||||
|
self.config_updater = ConfigUpdater()
|
||||||
|
|
||||||
self.logger.info("Бот инициализирован с модульной архитектурой")
|
self.logger.info("Бот инициализирован с модульной архитектурой")
|
||||||
if DISCORD_CONFIG['enabled']:
|
if DISCORD_CONFIG['enabled']:
|
||||||
@@ -42,19 +44,49 @@ class NewsBot:
|
|||||||
if news_list:
|
if news_list:
|
||||||
self.logger.info(f"Получено {len(news_list)} новостей для обработки")
|
self.logger.info(f"Получено {len(news_list)} новостей для обработки")
|
||||||
|
|
||||||
|
# Отслеживаем успешно опубликованные новости во всех источниках
|
||||||
|
published_in_telegram = []
|
||||||
|
published_in_vk = []
|
||||||
|
published_in_discord = []
|
||||||
|
|
||||||
# Публикация в Telegram
|
# Публикация в Telegram
|
||||||
if self.telegram_client.is_enabled():
|
if self.telegram_client.is_enabled():
|
||||||
await self.telegram_client.check_and_publish_news(news_list)
|
published_in_telegram = await self.telegram_client.check_and_publish_news(news_list)
|
||||||
|
|
||||||
# Публикация в VK
|
# Публикация в VK
|
||||||
if self.vk_client.is_enabled():
|
if self.vk_client.is_enabled():
|
||||||
await asyncio.get_event_loop().run_in_executor(
|
published_in_vk = await asyncio.get_event_loop().run_in_executor(
|
||||||
None, self.vk_client.check_and_publish_news, news_list
|
None, self.vk_client.check_and_publish_news, news_list
|
||||||
)
|
)
|
||||||
|
|
||||||
# Публикация в Discord (если включен)
|
# Публикация в Discord (если включен)
|
||||||
if self.discord_client.is_enabled():
|
if self.discord_client.is_enabled():
|
||||||
await self.discord_client.check_and_publish_news(news_list)
|
published_in_discord = await self.discord_client.check_and_publish_news(news_list)
|
||||||
|
|
||||||
|
# Определяем новости, опубликованные во ВСЕХ активных источниках
|
||||||
|
all_sources = []
|
||||||
|
if self.telegram_client.is_enabled():
|
||||||
|
all_sources.append(set(published_in_telegram))
|
||||||
|
if self.vk_client.is_enabled():
|
||||||
|
all_sources.append(set(published_in_vk or []))
|
||||||
|
if self.discord_client.is_enabled():
|
||||||
|
all_sources.append(set(published_in_discord or []))
|
||||||
|
|
||||||
|
if all_sources:
|
||||||
|
# Находим пересечение - новости, опубликованные везде
|
||||||
|
successfully_published_everywhere = set.intersection(*all_sources)
|
||||||
|
|
||||||
|
if successfully_published_everywhere:
|
||||||
|
# Находим максимальный topic_id среди успешно опубликованных
|
||||||
|
max_topic_id = max(successfully_published_everywhere)
|
||||||
|
|
||||||
|
# Обновляем start_topic_id в keys.py
|
||||||
|
if self.config_updater.update_start_topic_id(max_topic_id):
|
||||||
|
self.logger.info(f"start_topic_id обновлен на {max_topic_id} после успешной публикации во всех источниках")
|
||||||
|
else:
|
||||||
|
self.logger.warning("Не удалось обновить start_topic_id в keys.py")
|
||||||
|
else:
|
||||||
|
self.logger.info("Нет новостей, опубликованных во всех активных источниках")
|
||||||
else:
|
else:
|
||||||
self.logger.warning("Новостей для обработки не найдено")
|
self.logger.warning("Новостей для обработки не найдено")
|
||||||
|
|
||||||
|
@@ -7,6 +7,7 @@ from telethon import TelegramClient
|
|||||||
from telethon.errors import FloodWaitError
|
from telethon.errors import FloodWaitError
|
||||||
|
|
||||||
from config import TELEGRAM_CONFIG
|
from config import TELEGRAM_CONFIG
|
||||||
|
from history_manager import HistoryManager
|
||||||
|
|
||||||
|
|
||||||
class TelegramNewsClient:
|
class TelegramNewsClient:
|
||||||
@@ -14,6 +15,7 @@ class TelegramNewsClient:
|
|||||||
self.logger = logging.getLogger(__name__)
|
self.logger = logging.getLogger(__name__)
|
||||||
self.content_processor = content_processor
|
self.content_processor = content_processor
|
||||||
self.config = TELEGRAM_CONFIG
|
self.config = TELEGRAM_CONFIG
|
||||||
|
self.history_manager = HistoryManager()
|
||||||
|
|
||||||
async def get_messages(self, client, channel_username):
|
async def get_messages(self, client, channel_username):
|
||||||
"""Получение сообщений из Telegram канала/топика"""
|
"""Получение сообщений из Telegram канала/топика"""
|
||||||
@@ -221,8 +223,13 @@ class TelegramNewsClient:
|
|||||||
raise
|
raise
|
||||||
|
|
||||||
async def check_and_publish_news(self, news_list):
|
async def check_and_publish_news(self, news_list):
|
||||||
"""Проверка и публикация новостей в Telegram"""
|
"""Проверка и публикация новостей в Telegram
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Список topic_id успешно опубликованных новостей
|
||||||
|
"""
|
||||||
self.logger.info("Начинаем проверку новостей для Telegram")
|
self.logger.info("Начинаем проверку новостей для Telegram")
|
||||||
|
published_topics = [] # Список успешно опубликованных топиков
|
||||||
|
|
||||||
client = TelegramClient(
|
client = TelegramClient(
|
||||||
self.config['session_file'],
|
self.config['session_file'],
|
||||||
@@ -238,23 +245,30 @@ class TelegramNewsClient:
|
|||||||
|
|
||||||
if not news_list:
|
if not news_list:
|
||||||
self.logger.warning("Список новостей пуст")
|
self.logger.warning("Список новостей пуст")
|
||||||
return
|
return published_topics
|
||||||
|
|
||||||
# Фильтруем новости для публикации
|
# Фильтруем новости для публикации
|
||||||
list_for_public = []
|
list_for_public = []
|
||||||
for topic_id, topic_title in news_list:
|
for topic_id, topic_title in news_list:
|
||||||
# Улучшенная проверка дубликатов
|
# Сначала проверяем в постоянной истории
|
||||||
|
if self.history_manager.is_published('telegram', topic_title):
|
||||||
|
self.logger.debug(f"Новость '{topic_title}' найдена в истории публикаций, пропускаем")
|
||||||
|
continue
|
||||||
|
|
||||||
|
# Затем проверяем в последних сообщениях канала
|
||||||
is_duplicate = self._check_duplicate_news(topic_title, tg_titles, tg_messages)
|
is_duplicate = self._check_duplicate_news(topic_title, tg_titles, tg_messages)
|
||||||
|
|
||||||
if not is_duplicate:
|
if not is_duplicate:
|
||||||
list_for_public.append((topic_id, topic_title))
|
list_for_public.append((topic_id, topic_title))
|
||||||
self.logger.debug(f"Новость '{topic_title}' добавлена в список для публикации")
|
self.logger.debug(f"Новость '{topic_title}' добавлена в список для публикации")
|
||||||
else:
|
else:
|
||||||
self.logger.debug(f"Новость '{topic_title}' уже существует в Telegram, пропускаем")
|
# Добавляем в историю, если новость есть в канале, но нет в истории
|
||||||
|
self.history_manager.add_published('telegram', topic_id, topic_title)
|
||||||
|
self.logger.debug(f"Новость '{topic_title}' уже существует в Telegram, добавлена в историю")
|
||||||
|
|
||||||
if not list_for_public:
|
if not list_for_public:
|
||||||
self.logger.warning("Новостей для публикации в Telegram нет")
|
self.logger.warning("Новостей для публикации в Telegram нет")
|
||||||
return
|
return published_topics
|
||||||
|
|
||||||
self.logger.info(f"Новости для публикации в Telegram: {list_for_public}")
|
self.logger.info(f"Новости для публикации в Telegram: {list_for_public}")
|
||||||
|
|
||||||
@@ -273,9 +287,16 @@ class TelegramNewsClient:
|
|||||||
self.config['channel_username'],
|
self.config['channel_username'],
|
||||||
content
|
content
|
||||||
)
|
)
|
||||||
|
# Сохраняем в историю после успешной публикации
|
||||||
|
self.history_manager.add_published('telegram', topic_id, topic_title)
|
||||||
|
published_topics.append(topic_id) # Добавляем в список успешно опубликованных
|
||||||
|
self.logger.info(f"Новость '{topic_title}' добавлена в историю публикаций")
|
||||||
|
|
||||||
await asyncio.sleep(10.0) # 10 секундная задержка между сообщениями
|
await asyncio.sleep(10.0) # 10 секундная задержка между сообщениями
|
||||||
else:
|
else:
|
||||||
self.logger.warning(f"Не удалось получить содержимое новости {topic_id}")
|
self.logger.warning(f"Не удалось получить содержимое новости {topic_id}")
|
||||||
|
|
||||||
|
return published_topics
|
||||||
|
|
||||||
def is_enabled(self):
|
def is_enabled(self):
|
||||||
"""Проверка, включен ли Telegram клиент"""
|
"""Проверка, включен ли Telegram клиент"""
|
||||||
|
Reference in New Issue
Block a user