diff --git a/LG_news.session-journal b/LG_news.session-journal deleted file mode 100644 index 48f1a4a..0000000 Binary files a/LG_news.session-journal and /dev/null differ diff --git a/bot-news-linux-gaming.service b/bot-news-linux-gaming.service new file mode 100644 index 0000000..222236b --- /dev/null +++ b/bot-news-linux-gaming.service @@ -0,0 +1,33 @@ +[Unit] +Description=Linux Gaming News Bot +Documentation=https://github.com/xpamych/bot-news-linux-gaming +After=network.target network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=xpamych +Group=xpamych +WorkingDirectory=/home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming +ExecStart=/usr/bin/python3 /home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming/news-bot-modular.py +Restart=always +RestartSec=10 + +# Переменные окружения +Environment=PYTHONPATH=/home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming +Environment=PYTHONUNBUFFERED=1 + +# Ограничения безопасности +NoNewPrivileges=yes +PrivateTmp=yes +ProtectSystem=strict +ProtectHome=read-only +ReadWritePaths=/home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming + +# Логирование +StandardOutput=journal +StandardError=journal +SyslogIdentifier=news-bot + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/discord_client.py b/discord_client.py index 214fdf1..db37e3c 100644 --- a/discord_client.py +++ b/discord_client.py @@ -80,14 +80,50 @@ class DiscordClient: return None async def send_message(self, channel, content): - """Отправка сообщения в Discord канал""" + """Отправка сообщения в Discord канал с разбивкой длинных сообщений""" if not self.is_enabled(): return try: - # Разбиваем содержимое на части по 2000 символов (лимит Discord) - for i in range(0, len(content), 2000): - await channel.send(content[i:i+2000]) + # 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}") @@ -142,7 +178,7 @@ class DiscordClient: await client.close() return - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI() diff --git a/install-service.sh b/install-service.sh new file mode 100755 index 0000000..a3facc5 --- /dev/null +++ b/install-service.sh @@ -0,0 +1,127 @@ +#!/bin/bash + +# Установочный скрипт для Linux Gaming News Bot +# Создает systemd service и настраивает его для автозапуска + +set -e # Выход при любой ошибке + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Функции для цветного вывода +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Проверка запуска от root +if [ "$EUID" -ne 0 ]; then + print_error "Скрипт должен быть запущен с правами root (sudo)" + print_info "Используйте: sudo ./install-service.sh" + exit 1 +fi + +# Определение текущего пользователя (не root) +REAL_USER=$(who am i | awk '{print $1}') +if [ -z "$REAL_USER" ]; then + REAL_USER=$(logname 2>/dev/null || echo $SUDO_USER) +fi + +if [ -z "$REAL_USER" ]; then + print_error "Не удалось определить имя пользователя" + exit 1 +fi + +print_info "Установка сервиса для пользователя: $REAL_USER" + +# Определение директории проекта +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_DIR="$SCRIPT_DIR" + +print_info "Директория проекта: $PROJECT_DIR" + +# Проверка существования файлов +if [ ! -f "$PROJECT_DIR/news-bot-modular.py" ]; then + print_error "Не найден файл news-bot-modular.py в $PROJECT_DIR" + exit 1 +fi + +if [ ! -f "$PROJECT_DIR/bot-news-linux-gaming.service" ]; then + print_error "Не найден файл bot-news-linux-gaming.service в $PROJECT_DIR" + exit 1 +fi + +# Проверка наличия keys.py +if [ ! -f "$PROJECT_DIR/keys.py" ]; then + print_warning "Не найден файл keys.py с настройками" + print_info "Создайте keys.py на основе keys_example.py перед запуском сервиса:" + print_info " cp keys_example.py keys.py" + print_info " nano keys.py # заполните реальными ключами" +fi + +# Создание временного файла сервиса с правильными путями +TEMP_SERVICE=$(mktemp) +sed "s|/home/xpamych/Yandex.Disk/IdeaProjects/bot-news-linux-gaming|$PROJECT_DIR|g" "$PROJECT_DIR/bot-news-linux-gaming.service" > "$TEMP_SERVICE" +sed -i "s|User=xpamych|User=$REAL_USER|g" "$TEMP_SERVICE" +sed -i "s|Group=xpamych|Group=$REAL_USER|g" "$TEMP_SERVICE" + +print_info "Копирование systemd unit файла..." +cp "$TEMP_SERVICE" /etc/systemd/system/bot-news-linux-gaming.service +rm "$TEMP_SERVICE" + +print_info "Установка прав доступа..." +chmod 644 /etc/systemd/system/bot-news-linux-gaming.service +chown root:root /etc/systemd/system/bot-news-linux-gaming.service + +print_info "Перезагрузка systemd..." +systemctl daemon-reload + +print_success "Сервис успешно установлен!" +print_info "" +print_info "Для управления сервисом используйте команды:" +print_info " sudo systemctl enable bot-news-linux-gaming # Включить автозапуск" +print_info " sudo systemctl start bot-news-linux-gaming # Запустить сервис" +print_info " sudo systemctl status bot-news-linux-gaming # Посмотреть статус" +print_info " sudo systemctl stop bot-news-linux-gaming # Остановить сервис" +print_info " sudo systemctl disable bot-news-linux-gaming # Отключить автозапуск" +print_info "" +print_info "Логи сервиса:" +print_info " sudo journalctl -u bot-news-linux-gaming -f # Следить за логами" +print_info " sudo journalctl -u bot-news-linux-gaming -n 50 # Последние 50 строк" +print_info "" + +# Предложение сразу включить и запустить +read -p "Включить автозапуск и запустить сервис сейчас? (y/N): " -n 1 -r +echo +if [[ $REPLY =~ ^[Yy]$ ]]; then + print_info "Включение автозапуска..." + systemctl enable bot-news-linux-gaming + + print_info "Запуск сервиса..." + systemctl start bot-news-linux-gaming + + sleep 2 + + print_info "Статус сервиса:" + systemctl status bot-news-linux-gaming --no-pager + + print_success "Сервис запущен и добавлен в автозагрузку!" +else + print_info "Сервис установлен, но не запущен. Запустите его командой:" + print_info " sudo systemctl enable --now bot-news-linux-gaming" +fi \ No newline at end of file diff --git a/service-control.sh b/service-control.sh new file mode 100755 index 0000000..57764ed --- /dev/null +++ b/service-control.sh @@ -0,0 +1,155 @@ +#!/bin/bash + +# Простой скрипт управления Linux Gaming News Bot + +SERVICE_NAME="bot-news-linux-gaming" + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Функция показа статуса +show_status() { + print_info "Статус сервиса $SERVICE_NAME:" + systemctl status $SERVICE_NAME --no-pager -l +} + +# Функция показа логов +show_logs() { + local lines=${1:-50} + print_info "Последние $lines строк логов:" + sudo journalctl -u $SERVICE_NAME -n $lines --no-pager +} + +# Функция следения за логами +follow_logs() { + print_info "Следование за логами (Ctrl+C для выхода):" + sudo journalctl -u $SERVICE_NAME -f +} + +# Функция показа помощи +show_help() { + echo "Управление Linux Gaming News Bot" + echo "" + echo "Использование: $0 [команда]" + echo "" + echo "Команды:" + echo " start - Запустить сервис" + echo " stop - Остановить сервис" + echo " restart - Перезапустить сервис" + echo " status - Показать статус сервиса" + echo " enable - Включить автозапуск" + echo " disable - Отключить автозапуск" + echo " logs - Показать последние логи" + echo " logs N - Показать последние N строк логов" + echo " follow - Следить за логами в реальном времени" + echo " install - Установить сервис (требует sudo)" + echo " uninstall - Удалить сервис (требует sudo)" + echo " help - Показать эту справку" + echo "" +} + +# Проверка существования сервиса +check_service_exists() { + if ! systemctl list-unit-files | grep -q "^$SERVICE_NAME.service"; then + print_error "Сервис $SERVICE_NAME не установлен" + print_info "Запустите: sudo ./install-service.sh" + exit 1 + fi +} + +# Основная логика +case "${1}" in + "start") + check_service_exists + print_info "Запуск сервиса..." + sudo systemctl start $SERVICE_NAME + show_status + ;; + "stop") + check_service_exists + print_info "Остановка сервиса..." + sudo systemctl stop $SERVICE_NAME + show_status + ;; + "restart") + check_service_exists + print_info "Перезапуск сервиса..." + sudo systemctl restart $SERVICE_NAME + show_status + ;; + "status") + check_service_exists + show_status + ;; + "enable") + check_service_exists + print_info "Включение автозапуска..." + sudo systemctl enable $SERVICE_NAME + print_success "Автозапуск включен" + ;; + "disable") + check_service_exists + print_info "Отключение автозапуска..." + sudo systemctl disable $SERVICE_NAME + print_success "Автозапуск отключен" + ;; + "logs") + check_service_exists + if [ -n "$2" ] && [[ "$2" =~ ^[0-9]+$ ]]; then + show_logs $2 + else + show_logs + fi + ;; + "follow") + check_service_exists + follow_logs + ;; + "install") + if [ ! -f "./install-service.sh" ]; then + print_error "Файл install-service.sh не найден" + exit 1 + fi + sudo ./install-service.sh + ;; + "uninstall") + if [ ! -f "./uninstall-service.sh" ]; then + print_error "Файл uninstall-service.sh не найден" + exit 1 + fi + sudo ./uninstall-service.sh + ;; + "help"|"--help"|"-h") + show_help + ;; + "") + print_warning "Команда не указана" + show_help + exit 1 + ;; + *) + print_error "Неизвестная команда: $1" + show_help + exit 1 + ;; +esac \ No newline at end of file diff --git a/telegram_client.py b/telegram_client.py index 7d435d8..aa09f02 100644 --- a/telegram_client.py +++ b/telegram_client.py @@ -82,37 +82,81 @@ class TelegramNewsClient: return None async def send_message(self, client, channel_username, content): - """Отправка сообщения в Telegram канал/топик с обработкой flood wait""" + """Отправка сообщения в Telegram канал/топик с обработкой flood wait и длинных сообщений""" try: # Получаем entity канала entity = await client.get_entity(channel_username) - while True: - try: - # Если указан topic_id, отправляем в топик - if self.config['topic_id']: - self.logger.debug(f"Отправка в топик {self.config['topic_id']}") - await client.send_message( - entity, - content, - reply_to=self.config['topic_id'] - ) - self.logger.info(f"Сообщение успешно отправлено в Telegram топик {self.config['topic_id']}") + # Telegram лимит: 4096 символов + max_length = 4096 + + # Если сообщение слишком длинное, разбиваем его + if len(content) > max_length: + 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)} частей") + + # Отправляем каждую часть + for i, part in enumerate(parts, 1): + if len(parts) > 1: + part_content = f"[Часть {i}/{len(parts)}]\n\n{part}" else: - # Обычная отправка в канал - await client.send_message(entity, content) - self.logger.info("Сообщение успешно отправлено в Telegram канал") - break - except FloodWaitError as e: - self.logger.warning(f"Flood wait error: нужно подождать {e.seconds} секунд") - await asyncio.sleep(e.seconds) - except Exception as e: - self.logger.error(f"Ошибка отправки сообщения в Telegram: {e}") - break + part_content = part + + await self._send_single_message(client, entity, part_content) + + # Небольшая задержка между частями + if i < len(parts): + await asyncio.sleep(1) + else: + # Обычная отправка + await self._send_single_message(client, entity, content) + except Exception as e: self.logger.error(f"Ошибка получения entity канала '{channel_username}': {e}") self.logger.info("Убедитесь, что имя канала указано правильно и бот имеет доступ") + async def _send_single_message(self, client, entity, content): + """Отправка одного сообщения с обработкой flood wait""" + while True: + try: + # Если указан topic_id, отправляем в топик + if self.config['topic_id']: + self.logger.debug(f"Отправка в топик {self.config['topic_id']}") + await client.send_message( + entity, + content, + reply_to=self.config['topic_id'] + ) + self.logger.info(f"Сообщение успешно отправлено в Telegram топик {self.config['topic_id']}") + else: + # Обычная отправка в канал + await client.send_message(entity, content) + self.logger.info("Сообщение успешно отправлено в Telegram канал") + break + except FloodWaitError as e: + self.logger.warning(f"Flood wait error: нужно подождать {e.seconds} секунд") + await asyncio.sleep(e.seconds) + except Exception as e: + self.logger.error(f"Ошибка отправки сообщения в Telegram: {e}") + raise + async def check_and_publish_news(self, news_list): """Проверка и публикация новостей в Telegram""" self.logger.info("Начинаем проверку новостей для Telegram") @@ -151,7 +195,7 @@ class TelegramNewsClient: self.logger.info(f"Новости для публикации в Telegram: {list_for_public}") - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI() diff --git a/uninstall-service.sh b/uninstall-service.sh new file mode 100755 index 0000000..e1b0219 --- /dev/null +++ b/uninstall-service.sh @@ -0,0 +1,68 @@ +#!/bin/bash + +# Скрипт удаления Linux Gaming News Bot systemd service + +set -e # Выход при любой ошибке + +# Цвета для вывода +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Функции для цветного вывода +print_info() { + echo -e "${BLUE}[INFO]${NC} $1" +} + +print_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +print_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +print_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Проверка запуска от root +if [ "$EUID" -ne 0 ]; then + print_error "Скрипт должен быть запущен с правами root (sudo)" + print_info "Используйте: sudo ./uninstall-service.sh" + exit 1 +fi + +SERVICE_NAME="bot-news-linux-gaming" +SERVICE_FILE="/etc/systemd/system/${SERVICE_NAME}.service" + +print_info "Удаление сервиса $SERVICE_NAME..." + +# Остановка сервиса если он запущен +if systemctl is-active --quiet $SERVICE_NAME; then + print_info "Остановка сервиса..." + systemctl stop $SERVICE_NAME +fi + +# Отключение автозапуска если включен +if systemctl is-enabled --quiet $SERVICE_NAME; then + print_info "Отключение автозапуска..." + systemctl disable $SERVICE_NAME +fi + +# Удаление файла сервиса +if [ -f "$SERVICE_FILE" ]; then + print_info "Удаление файла сервиса..." + rm "$SERVICE_FILE" +else + print_warning "Файл сервиса не найден: $SERVICE_FILE" +fi + +# Перезагрузка systemd +print_info "Перезагрузка systemd..." +systemctl daemon-reload + +print_success "Сервис $SERVICE_NAME успешно удален!" +print_info "Файлы проекта не затронуты и остались на месте." \ No newline at end of file diff --git a/vk_client.py b/vk_client.py index a767eb7..3eab634 100644 --- a/vk_client.py +++ b/vk_client.py @@ -155,7 +155,7 @@ class VKClient: self.logger.info(f"Новости для публикации в VK: {list_for_public}") - # Публикуем новости + # Публикуем новости в обратном порядке, чтобы новые оказались сверху в ленте for topic_id, topic_title in reversed(list_for_public): from site_api import SiteAPI site_api = SiteAPI()