Files
bot-news-linux-gaming/discord_client.py

205 lines
9.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
import re
import time
import logging
import asyncio
from config import DISCORD_CONFIG
# Discord импорты - только если Discord включен
if DISCORD_CONFIG['enabled']:
try:
import discord
except ImportError:
logging.error("Discord.py не установлен, но Discord включен в конфигурации")
DISCORD_CONFIG['enabled'] = False
class DiscordClient:
def __init__(self, content_processor):
self.logger = logging.getLogger(__name__)
self.content_processor = content_processor
self.config = DISCORD_CONFIG
if not self.config['enabled']:
self.logger.info("Discord клиент отключен в конфигурации")
async def get_messages(self, client, channel_id):
"""Получение сообщений из Discord канала"""
if not self.is_enabled():
return [], []
self.logger.debug("Получаем сообщения из Discord канала")
channel = client.get_channel(channel_id)
if not channel:
self.logger.error(f"ID канала Discord {channel_id} не существует")
return [], []
messages = []
titles = []
async for message in channel.history(limit=100):
self.logger.debug(f"Сообщение Discord: {message.content}")
messages.append(message.content)
# Извлекаем заголовок из сообщения
title = self._extract_title_from_message(message.content)
if title:
titles.append(title)
self.logger.debug(f"Найдено {len(messages)} сообщений и {len(titles)} заголовков в Discord")
return messages, titles
def _extract_title_from_message(self, message_text):
"""Извлечение заголовка из текста сообщения"""
if not message_text:
return None
# Ищем заголовки в формате "### Заголовок\t"
pattern = re.compile(r'^### (.*?)\t', re.MULTILINE)
match = pattern.search(message_text)
if match:
return match.group(1).strip()
# Ищем заголовки в формате "------ ### Заголовок\t"
pattern2 = re.compile(r'--+\n### (.*?)\t', re.MULTILINE)
match2 = pattern2.search(message_text)
if match2:
return match2.group(1).strip()
# Если не найдено, ищем в первой строке
lines = message_text.strip().split('\n')
if lines:
first_line = lines[0].strip()
# Убираем префиксы типа "###", "**", и табы
first_line = re.sub(r'^[#*\s\t-]+', '', first_line)
first_line = re.sub(r'\t.*$', '', first_line)
return first_line.strip() if first_line else None
return None
async def send_message(self, channel, content):
"""Отправка сообщения в Discord канал с разбивкой длинных сообщений"""
if not self.is_enabled():
return
try:
# Discord лимит: 2000 символов
max_length = 2000
if len(content) <= max_length:
# Короткое сообщение - отправляем как есть
await channel.send(content)
else:
# Длинное сообщение - разбиваем умно по строкам
self.logger.warning(f"Сообщение слишком длинное ({len(content)} символов), разбиваем на части")
parts = []
current_part = ""
for line in content.split('\n'):
# Если добавление этой строки превысит лимит
if len(current_part + line + '\n') > max_length:
if current_part:
parts.append(current_part.rstrip())
current_part = ""
current_part += line + '\n'
# Добавляем последнюю часть
if current_part:
parts.append(current_part.rstrip())
self.logger.info(f"Сообщение разбито на {len(parts)} частей для Discord")
# Отправляем каждую часть
for i, part in enumerate(parts, 1):
if len(parts) > 1:
part_content = f"[Часть {i}/{len(parts)}]\n\n{part}"
else:
part_content = part
await channel.send(part_content)
# Небольшая задержка между частями
if i < len(parts):
await asyncio.sleep(1)
self.logger.info("Сообщение успешно отправлено в Discord")
except Exception as e:
self.logger.error(f"Ошибка отправки сообщения в Discord: {e}")
async def check_and_publish_news(self, news_list):
"""Проверка и публикация новостей в Discord"""
if not self.is_enabled():
self.logger.info("Discord отключен, пропускаем публикацию")
return
self.logger.info("Начинаем проверку новостей для Discord")
intents = discord.Intents.default()
intents.messages = True
client = discord.Client(intents=intents)
@client.event
async def on_ready():
self.logger.debug(f"Успешный логин в Discord: {client.user}")
channel_id = self.config['channel_id']
# Получаем существующие сообщения и заголовки
discord_messages, discord_titles = await self.get_messages(client, channel_id)
if not news_list:
self.logger.warning("Список новостей пуст")
await client.close()
return
# Фильтруем новости для публикации
list_for_public = []
for topic_id, topic_title in news_list:
# Проверяем по заголовкам и по полному тексту сообщений
title_exists = any(topic_title == title for title in discord_titles)
text_contains = any(topic_title in (msg or '') for msg in discord_messages)
if not title_exists and not text_contains:
list_for_public.append((topic_id, topic_title))
else:
self.logger.debug(f"Новость '{topic_title}' уже есть в Discord (title_exists={title_exists}, text_contains={text_contains})")
if not list_for_public:
self.logger.warning("Новостей для публикации в Discord нет")
await client.close()
return
self.logger.info(f"Новости для публикации в Discord: {list_for_public}")
channel = client.get_channel(channel_id)
if not channel:
self.logger.error(f"ID канала Discord {channel_id} не существует")
await client.close()
return
# Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте
for topic_id, topic_title in reversed(list_for_public):
from site_api import SiteAPI
site_api = SiteAPI()
text_data = site_api.get_news_content(topic_id, self.content_processor)
if text_data:
content = f"----------------------------------------------------------\n### {topic_title}\t\n" + text_data + "\n@here"
await self.send_message(channel, content)
time.sleep(1.0)
else:
self.logger.warning(f"Не удалось получить содержимое новости {topic_id}")
await client.close()
try:
await client.start(self.config['token'])
except Exception as e:
self.logger.error(f"Ошибка запуска Discord клиента: {e}")
def is_enabled(self):
"""Проверка, включен ли Discord клиент"""
return (self.config['enabled'] and
self.config['token'] and
self.config['channel_id'])