- добавление скриптов для systemd

This commit is contained in:
2025-08-11 16:12:47 +03:00
parent de5a5e9248
commit 50d505f887
8 changed files with 492 additions and 29 deletions

Binary file not shown.

View File

@@ -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

View File

@@ -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()

127
install-service.sh Executable file
View File

@@ -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

155
service-control.sh Executable file
View File

@@ -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

View File

@@ -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()

68
uninstall-service.sh Executable file
View File

@@ -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 "Файлы проекта не затронуты и остались на месте."

View File

@@ -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()