Initial test commit

This commit is contained in:
Alexey Barabanov
2025-08-11 13:38:20 +03:00
parent d85ae1316a
commit 3e670d3722
9 changed files with 4459 additions and 0 deletions

927
AF_script_test_5.1.py Normal file
View File

@@ -0,0 +1,927 @@
import os
from dotenv import load_dotenv
import logging
import logging.config
from pprint import pprint
import pandas as pd
from transliterate import translit
import requests
from time import sleep
import datetime
import sys
from urllib.parse import urlparse
from pathlib import PureWindowsPath
import telebot
from telebot import types
from synology_drive_api.drive import SynologyDrive
from flask import Flask, jsonify
import threading
from functools import wraps
# Инициализация Flask приложения для панели мониторинга
flask_app = Flask(__name__)
flask_app.config['SECRET_KEY'] = os.getenv('FLASK_SECRET', 'default-secret-key')
# Загрузка переменных окружения
load_dotenv('AF_environment.env')
class Monitoring:
"""Класс для сбора статистики и мониторинга"""
def __init__(self):
self.jobs_history = []
self.system_stats = {
'total_jobs': 0,
'successful_jobs': 0,
'failed_jobs': 0,
'active_jobs': 0,
'users': {}
}
def add_job(self, job_data):
"""Добавление информации о новой задаче"""
self.jobs_history.append(job_data)
self.system_stats['total_jobs'] += 1
self.system_stats['active_jobs'] += 1
user_id = job_data.get('user_id')
if user_id:
if user_id not in self.system_stats['users']:
self.system_stats['users'][user_id] = {
'total_jobs': 0,
'successful_jobs': 0,
'failed_jobs': 0
}
self.system_stats['users'][user_id]['total_jobs'] += 1
def job_completed(self, job_id, success=True, user_id=None):
"""Обновление статуса завершенной задачи"""
self.system_stats['active_jobs'] -= 1
if success:
self.system_stats['successful_jobs'] += 1
else:
self.system_stats['failed_jobs'] += 1
if user_id and user_id in self.system_stats['users']:
if success:
self.system_stats['users'][user_id]['successful_jobs'] += 1
else:
self.system_stats['users'][user_id]['failed_jobs'] += 1
# Обновляем статус в истории
for job in self.jobs_history:
if job.get('job_id') == job_id:
job['status'] = 'completed' if success else 'failed'
job['completed_at'] = datetime.datetime.now()
break
def get_stats(self):
"""Получение текущей статистики"""
return self.system_stats
def get_recent_jobs(self, limit=10):
"""Получение последних задач"""
return self.jobs_history[-limit:] if self.jobs_history else []
def get_user_stats(self, user_id):
"""Получение статистики по конкретному пользователю"""
return self.system_stats['users'].get(user_id, {
'total_jobs': 0,
'successful_jobs': 0,
'failed_jobs': 0
})
class Config:
"""Класс для работы с конфигурацией приложения"""
def __init__(self):
self.nas_user = os.getenv('NAS_USER')
self.nas_pass = os.getenv('NAS_PASS')
self.nas_ip = os.getenv('NAS_IP')
self.nas_port = os.getenv('NAS_PORT')
self.nas_file = os.getenv('NAS_FILE')
self.token = os.getenv('TELEGRAM_TOKEN')
self.group_chat = os.getenv('TELEGRAM_GROUP_CHAT')
self.nexrender_url = os.getenv('NEXRENDER_URL')
self.admin_password = os.getenv('ADMIN_PASSWORD', 'admin123')
self._validate_config()
def _validate_config(self):
"""Проверка наличия обязательных переменных окружения"""
required_vars = {
'NAS_USER': self.nas_user,
'NAS_PASS': self.nas_pass,
'NAS_IP': self.nas_ip,
'TELEGRAM_TOKEN': self.token,
'NEXRENDER_URL': self.nexrender_url
}
missing = [k for k, v in required_vars.items() if not v]
if missing:
raise ValueError(f"Отсутствуют обязательные переменные окружения: {', '.join(missing)}")
class JobManager:
"""Основной класс для управления задачами рендеринга"""
def __init__(self, config, monitoring):
self.config = config
self.monitoring = monitoring
self.bot = telebot.TeleBot(config.token)
self.PLACEHOLDER = sys.platform == 'win32'
self.setup_logging()
self.setup_handlers()
if self.PLACEHOLDER:
self._init_placeholders()
def _init_placeholders(self):
"""Инициализация заглушек для тестирования"""
from random import random, choices
def send_job_dumb(data):
if random() < 0.8:
uid = ''.join(choices('abcdefghijklmnopqrstuvwxyz_', k=8))
return {'uid': uid, 'outname': data['outfile_name']}
return None
class FakeResp:
def __init__(self, state='queued', *args, **kwargs):
self.state = state
self.status_code = 200
def json(self):
return {'state': self.state}
def fake_get(*args, **kwargs):
rand = random()
if rand < 0.8:
return FakeResp()
elif rand < 0.95:
return FakeResp('finished')
else:
return FakeResp('error')
self._send_job_real = self.send_job
self.send_job = send_job_dumb
self._fake_get = fake_get
def setup_logging(self):
"""Настройка системы логирования"""
LOG_CONFIG = {
'version': 1,
'formatters': {
'detailed': {
'format': '%(asctime)s %(levelname)-8s %(name)-15s %(message)s',
'datefmt': '%Y-%m-%d %H:%M:%S'
}
},
'handlers': {
'console': {
'class': 'logging.StreamHandler',
'level': 'INFO',
'formatter': 'detailed'
},
'file': {
'class': 'logging.FileHandler',
'filename': 'af_bot.log',
'mode': 'a',
'level': 'DEBUG',
'formatter': 'detailed'
}
},
'loggers': {
'': {
'handlers': ['console', 'file'],
'level': 'DEBUG',
'propagate': True
}
}
}
logging.config.dictConfig(LOG_CONFIG)
self.logger = logging.getLogger(__name__)
def setup_handlers(self):
"""Настройка обработчиков команд Telegram"""
@self.bot.message_handler(commands=['start', 'help', 'menu'])
def send_welcome(message):
self.show_main_menu(message)
@self.bot.message_handler(func=lambda message: True)
def handle_text(message):
if message.text == '📊 Статистика':
self.show_stats(message)
elif message.text == '🔄 Создать анонс':
self.start_ibash(message)
elif message.text == '❌ Отменить задачи':
self.cancel_jobs(message)
elif message.text == '👤 Моя статистика':
self.show_user_stats(message)
else:
self.bot.reply_to(message, "Используйте меню для навигации")
@self.bot.callback_query_handler(func=lambda call: True)
def handle_callback(call):
if call.data == 'ibash_all':
self.process_ibash(call.message, all_announcements=True)
elif call.data == 'ibash_new':
self.process_ibash(call.message, all_announcements=False)
elif call.data == 'cancel':
self.bot.edit_message_text(
"Действие отменено",
call.message.chat.id,
call.message.message_id
)
self.show_main_menu(call.message)
def show_main_menu(self, message):
"""Отображение главного меню с кнопками"""
markup = types.ReplyKeyboardMarkup(
row_width=2,
resize_keyboard=True,
one_time_keyboard=False
)
buttons = [
types.KeyboardButton('🔄 Создать анонс'),
types.KeyboardButton('📊 Статистика'),
types.KeyboardButton('👤 Моя статистика'),
types.KeyboardButton('❌ Отменить задачи')
]
markup.add(*buttons)
self.bot.send_message(
message.chat.id,
"📱 *Главное меню*:\nВыберите действие:",
reply_markup=markup,
parse_mode='Markdown'
)
def show_stats(self, message):
"""Отображение статистики системы"""
stats = self.monitoring.get_stats()
recent_jobs = self.monitoring.get_recent_jobs(5)
stats_text = (
"📈 *Статистика системы*\n\n"
f"Всего задач: {stats['total_jobs']}\n"
f"• Успешных: {stats['successful_jobs']}\n"
f"• Неудачных: {stats['failed_jobs']}\n"
f"• Активных: {stats['active_jobs']}\n\n"
"⏱ *Последние задачи*:\n"
)
for job in recent_jobs:
status_icon = '' if job.get('status') == 'completed' else '' if job.get('status') == 'failed' else '🔄'
stats_text += f"{status_icon} {job.get('name', 'N/A')} ({job.get('user', 'system')})\n"
self.bot.send_message(
message.chat.id,
stats_text,
parse_mode='Markdown'
)
def show_user_stats(self, message):
"""Отображение статистики пользователя"""
user_id = message.from_user.id
username = message.from_user.username or message.from_user.first_name
user_stats = self.monitoring.get_user_stats(user_id)
stats_text = (
f"👤 *Ваша статистика* ({username})\n\n"
f"Всего задач: {user_stats['total_jobs']}\n"
f"• Успешных: {user_stats['successful_jobs']}\n"
f"• Неудачных: {user_stats['failed_jobs']}\n"
f"• Процент успеха: {user_stats['successful_jobs'] / user_stats['total_jobs'] * 100:.1f}%"
if user_stats['total_jobs'] > 0 else "0%"
)
self.bot.send_message(
message.chat.id,
stats_text,
parse_mode='Markdown'
)
def start_ibash(self, message):
"""Начало процесса создания анонсов с интерактивным меню"""
self.logger.info(f"Start ibash requested by {message.from_user.username}")
markup = types.InlineKeyboardMarkup()
markup.row(
types.InlineKeyboardButton("Все анонсы", callback_data="ibash_all"),
types.InlineKeyboardButton("Только новые", callback_data="ibash_new")
)
markup.row(types.InlineKeyboardButton("Отмена", callback_data="cancel"))
self.bot.send_message(
message.chat.id,
"🔧 *Создание анонсов*\n\nВыберите тип обработки:",
reply_markup=markup,
parse_mode='Markdown'
)
def process_ibash(self, message, all_announcements=False):
"""Обработка создания анонсов"""
user_id = message.from_user.id
username = message.from_user.username or message.from_user.first_name
self.bot.send_chat_action(message.chat.id, 'typing')
try:
# Загрузка данных
osheet = self.load_osheet(message)
start = self.get_sheet_data(osheet, 'Start', header=1)
if not all_announcements:
start = start[start['STATE'] == False] # Только новые анонсы
pack = self.get_sheet_data(osheet, 'SPORT', header=0, index_col='SPORT')
pack = pack[pack.index.notna()]
logos = self.get_sheet_data(osheet, 'TEAMS', header=0, index_col=[0, 1])
# Очистка старых задач
self.cleanup_old_jobs()
# Создание задач
self.bot.send_chat_action(message.chat.id, 'record_video')
watch_list = []
for i, row in start.iterrows():
dd = self.make_data_dict(row)
jobs = self.make_job_dicts(dd, pack, logos, message)
for job in jobs:
if job:
job_data = {
'job_id': job['uid'],
'name': job['outname'],
'user_id': user_id,
'user': username,
'status': 'started',
'started_at': datetime.datetime.now()
}
self.monitoring.add_job(job_data)
watch_list.append(job)
self.logger.info(f"В очереди {len(watch_list)} задач")
self.bot.send_message(
message.chat.id,
f"🚀 Запущено {len(watch_list)} задач на рендеринг",
parse_mode='Markdown'
)
# Отслеживание выполнения
self.track_jobs(message, watch_list, user_id)
except Exception as e:
self.logger.exception("Ошибка в process_ibash")
self.bot.send_message(
message.chat.id,
f"❌ Ошибка при создании анонсов: {str(e)}",
parse_mode='Markdown'
)
def track_jobs(self, message, watch_list, user_id):
"""Отслеживание выполнения задач"""
while watch_list:
self.bot.send_chat_action(message.chat.id, 'record_video')
sleep(25)
for job in watch_list[:]:
try:
if self.PLACEHOLDER:
r = self._fake_get()
else:
r = requests.get(f"{self.config.nexrender_url}/{job['uid']}")
if r.status_code == 200:
state = r.json()['state']
if state == 'finished':
watch_list.remove(job)
self.monitoring.job_completed(job['uid'], True, user_id)
self.logger.info(f"{job['outname']} готов, осталось {len(watch_list)}")
self.bot.send_message(
message.chat.id,
f"✅ *{job['outname']}* готов\nОсталось задач: {len(watch_list)}",
parse_mode='Markdown'
)
elif state == 'error':
watch_list.remove(job)
self.monitoring.job_completed(job['uid'], False, user_id)
self.logger.warning(f"{job['outname']} завершился с ошибкой")
self.bot.send_message(
message.chat.id,
f"❌ *{job['outname']}* завершился с ошибкой",
parse_mode='Markdown'
)
except Exception as e:
self.logger.error(f"Ошибка проверки статуса задачи {job['uid']}: {e}")
self.bot.send_message(
message.chat.id,
"🎉 Все задачи завершены!",
reply_markup=types.ReplyKeyboardRemove(),
parse_mode='Markdown'
)
self.show_main_menu(message)
def cleanup_old_jobs(self):
"""Очистка завершенных задач"""
try:
r = requests.get(self.config.nexrender_url)
if r.status_code == 200:
jobs = r.json()
for job in jobs:
if job['state'] in ('finished', 'error'):
requests.delete(f"{self.config.nexrender_url}/{job['uid']}")
except Exception as e:
self.logger.error(f"Ошибка очистки старых задач: {e}")
def cancel_jobs(self, message):
"""Отмена всех активных задач"""
try:
r = requests.get(self.config.nexrender_url)
if r.status_code == 200:
jobs = r.json()
cancelled = 0
for job in jobs:
if job['state'] in ('queued', 'picked'):
requests.delete(f"{self.config.nexrender_url}/{job['uid']}")
cancelled += 1
self.logger.info(f"Отменено {cancelled} задач")
self.bot.send_message(
message.chat.id,
f"⏹ Отменено {cancelled} активных задач",
parse_mode='Markdown'
)
else:
self.bot.send_message(
message.chat.id,
"Не удалось получить список задач для отмены",
parse_mode='Markdown'
)
except Exception as e:
self.logger.error(f"Ошибка отмены задач: {e}")
self.bot.send_message(
message.chat.id,
f"❌ Ошибка при отмене задач: {str(e)}",
parse_mode='Markdown'
)
def load_osheet(self, message):
"""Загрузка файла с Synology Drive"""
self.logger.debug('Получение данных')
try:
synd = SynologyDrive(
self.config.nas_user,
self.config.nas_pass,
self.config.nas_ip,
self.config.nas_port,
https=True,
dsm_version='7'
)
self.logger.debug(synd.login()) # Проверка сессии
try:
self.logger.debug('Попытка загрузки таблицы')
bio = synd.download_synology_office_file(self.config.nas_file)
self.logger.debug('Успешная загрузка')
return bio
except Exception as e:
self.logger.exception('Ошибка загрузки')
self.bot.send_message(
message.chat.id,
'<b>Не удалось скачать таблицу</b>',
parse_mode='html'
)
raise
except Exception as e:
self.logger.exception('Ошибка авторизации')
self.bot.send_message(
message.chat.id,
'<b>Не удалось авторизоваться</b>',
parse_mode='html'
)
raise
def get_sheet_data(self, osheet, sheet_name, **kwargs):
"""Получение данных из листа Excel"""
self.logger.debug(f'Чтение листа {sheet_name}')
try:
sheet = pd.read_excel(osheet, sheet_name=sheet_name, **kwargs)
self.logger.debug('Успешное чтение')
return sheet
except Exception as e:
self.logger.exception(f'Ошибка чтения листа {sheet_name}')
raise
def get_sport_logo(self, sport, pack, message):
"""Получение логотипа вида спорта"""
self.logger.info(f'Получение оформления для {sport}')
self.bot.send_message(
message.chat.id,
f'Ищем оформления для {sport}',
parse_mode='html'
)
try:
d = pack.loc[sport]['LINK']
self.logger.debug(d)
if pd.isna(d):
self.logger.warning(f'Нет LINK для вида спорта "{sport}"')
return ''
return d
except Exception as e:
self.logger.exception(f"Не удалось получить оформление для {sport}")
return ''
def get_team_logo(self, team, sport, logos, message):
"""Получение логотипа команды"""
self.logger.info(f'Получение логотипа {team}/{sport}')
self.bot.send_message(
message.chat.id,
f'Поиск логотипа {sport}-<b>{team}</b>',
parse_mode='html'
)
try:
d = logos.loc[team, sport]['LINK']
self.logger.debug(d)
return d
except KeyError:
self.logger.warning(f"Нет LINK для {team}/{sport}")
return ''
except Exception as e:
self.logger.exception(f"Ошибка при получении логотипа {sport}")
return ''
def make_data_dict(self, ds):
"""Создание словаря с данными"""
return {
'date': ds['DATA'],
'time': ds['TIME'],
'channel': ds['CHANEL'],
'sport': ds['SPORT'],
'league': ds['LEAGUE'],
'team_a': ds['TEAM A'],
'team_b': ds['TEAM B'],
'index': ds.name
}
def unc2uri(self, unc):
"""Преобразование UNC пути в URI"""
self.logger.debug('Преобразование пути')
try:
p = urlparse(unc)
if len(p.scheme) > 2 or not unc:
return unc
else:
p = PureWindowsPath(unc)
return p.as_uri()
except Exception as e:
self.logger.exception('Ошибка преобразования пути')
return unc
def send_job(self, data, message):
"""Отправка задачи на рендеринг"""
if self.PLACEHOLDER:
return self._send_job_dumb(data)
payload = {
"template": {
"src": "file:///c:/users/virtVmix-2/Downloads/PackShot_Sborka_eng.aepx",
"composition": "pack",
"outputModule": "Start_h264",
"outputExt": "mp4"
},
"actions": {
"postrender": [
{
"module": "@nexrender/action-encode",
"preset": "mp4",
"output": "encoded.mp4"
},
{
"module": "@nexrender/action-copy",
"input": "encoded.mp4",
"output": f"//10.10.35.3/edit/Auto_Anons/{data['outfile_name']}.mp4"
}
]
},
"assets": []
}
# Добавление данных в payload
self._add_data_to_payload(payload, data, message)
url = self.config.nexrender_url
try:
r = requests.post(url, json=payload)
if r.status_code == 200:
res = r.json()
uid = res['uid']
return {'uid': uid, 'outname': data['outfile_name']}
except Exception as e:
self.logger.exception('Ошибка отправки задачи')
return None
def _add_data_to_payload(self, payload, data, message):
"""Добавление данных в payload"""
# Добавление даты
self._add_date_data(payload, data, message)
# Добавление времени
self._add_time_data(payload, data, message)
# Добавление лиги
payload['assets'].append({
"type": "data",
"layerName": "LEAGUE",
"property": "Source Text",
"value": data['league']
})
# Добавление спорта
if data['sport']:
payload['assets'].append({
"type": "data",
"layerName": "SPORT",
"property": "Source Text",
"value": data['sport']
})
# Добавление команд и логотипов
self._add_team_data(payload, data, message, 'A')
self._add_team_data(payload, data, message, 'B')
# Добавление оформления
if data['pack']:
payload['assets'].append({
"src": data['pack'],
"type": "video",
"layerName": "TOP"
})
def _add_date_data(self, payload, data, message):
"""Добавление данных о дате"""
if data['data'] == 'сегодня':
self._add_specific_date_style(payload, data, message, "105", [0, 5])
elif data['data'] == 'завтра':
self._add_specific_date_style(payload, data, message, "115", [0, 25])
elif len(data['data']) < 6:
self._add_specific_date_style(payload, data, message, "120", [0, 20])
payload['assets'].append({
"type": "data",
"layerName": "DATA",
"property": "Source Text",
"value": data['data']
})
def _add_specific_date_style(self, payload, data, message, font_size, anchor_point):
"""Добавление стилей для конкретной даты"""
payload['assets'].extend([
{
"layerName": "DATA",
"property": "Source Text.fontSize",
"type": "data",
"value": font_size
},
{
"layerName": "DATA",
"property": "transform.anchorPoint",
"type": "data",
"value": anchor_point
}
])
self.logger.info(f'Для "{data["data"]}" шрифт установлен {font_size}')
self.bot.send_message(
message.chat.id,
f'Для "<i>{data["data"]}</i>" размер шрифта установлен <b>{font_size}</b>',
parse_mode='html'
)
self.logger.info(f'Сдвиг "{data["data"]}" на {anchor_point} пикселей')
self.bot.send_message(
message.chat.id,
f'<b>Сдвигаем</b> "<i>{data["data"]}</i>" на <b>{anchor_point}</b> пикселей',
parse_mode='html'
)
def _add_time_data(self, payload, data, message):
"""Добавление данных о времени"""
if len(data['time_h']) < 2:
anchor_point = [40, 0]
for layer in ["TIME_H", "TIME_M", "TIME"]:
payload['assets'].append({
"layerName": layer,
"property": "transform.anchorPoint",
"type": "data",
"value": anchor_point
})
self.logger.info(f'Сдвиг "{data["time_h"]}:{data["time_m"]}" на {anchor_point} пикселей')
self.bot.send_message(
message.chat.id,
f'<b>Сдвигаем</b> "<i>{data["time_h"]}:{data["time_m"]}</i>" на <b>{anchor_point}</b> пикседей',
parse_mode='html'
)
payload['assets'].extend([
{
"type": "data",
"layerName": "TIME_H",
"property": "Source Text",
"value": data['time_h']
},
{
"type": "data",
"layerName": "TIME_M",
"property": "Source Text",
"value": data['time_m']
}
])
def _add_team_data(self, payload, data, message, team):
"""Добавление данных о команде"""
team_key = f'team_{team.lower()}'
if data[team_key]:
payload['assets'].append({
"type": "data",
"layerName": f"TEAM_{team}",
"property": "Source Text",
"value": data[team_key]
})
logo_key = f'{team_key}_logo'
if data[logo_key]:
payload['assets'].append({
"src": data[logo_key],
"type": "image",
"layerName": f"TEAM_{team}_LOGO"
})
logo_res_key = f'{team_key}_logo_res'
if data.get(logo_res_key):
payload['assets'].append({
"property": "scale",
"type": "data",
"expression": f"if (width > height) {{max_size = width;}} else {{max_size = height;}} var real_size = {data[logo_res_key][0]}/max_size*100;[real_size,real_size]",
"layerName": f"TEAM_{team}_LOGO"
})
self.logger.info(f'{data[team_key]} логотип изменен до {data[logo_res_key][0]}')
self.bot.send_message(
message.chat.id,
f'<b>{data[team_key]}</b> масштабирован под <b>{data[logo_res_key][0]}</b> пикселей',
parse_mode='html'
)
def make_job_dicts(self, dd, pack, logos, message):
"""Создание задач рендеринга"""
self.logger.debug('Начало создания имени')
fn = ''
data = {}
empty_sport = pack.iloc[0].name
# Дата
if isinstance(dd['date'], str):
fn += f"{dd['date'][6:]}{dd['date'][3:5]}{dd['date'][0:2]}"
d = dd['date'].split('.')
data['data'] = f"{int(d[0])} {['','января','февраля','марта','апреля','мая','июня','июля','августа','сентября','октября','ноября','декабря'][int(d[1])]}"
elif isinstance(dd['date'], datetime.date):
fn += f"{dd['date'].year}{dd['date'].month:02}{dd['date'].day:02}"
data['data'] = f"{dd['date'].day} {['','января','февраля','марта','апреля','мая','июня','июля','августа','сентября','октября','ноября','декабря'][dd['date'].month]}"
# Вид спорта и оформление
if dd['sport'] != empty_sport:
fn += f"_{dd['sport']}"
data['sport'] = dd['sport']
data['pack'] = self.unc2uri(self.get_sport_logo(dd['sport'], pack, message))
else:
data['sport'] = ''
data['pack'] = ''
# Лига
if dd["league"][-1] == '.':
self.logger.debug('Точка в названии лиги!')
fn += f'_{dd["league"][:-1]}'
data['league'] = dd['league'][:-1]
else:
data['league'] = dd['league']
fn += f'_{dd["league"]}'
# Команды
self._process_team_data(dd, data, fn, 'A', logos, message)
self._process_team_data(dd, data, fn, 'B', logos, message)
# Канал
if not pd.isna(dd['channel']):
self.logger.debug('Канал установлен ' + dd['channel'])
fn += f"_{dd['channel']}"
# Финальное форматирование имени файла
fn = translit(fn, reversed=True)
fn = fn.replace(' ', '-').replace("'", '')
data['outfile_name'] = fn
# Время
if isinstance(dd['time'], str):
t = dd['time'].split(':')
data['time_h'] = t[0]
data['time_m'] = t[1]
elif isinstance(dd['time'], datetime.time):
data['time_h'] = str(dd['time'].hour)
data['time_m'] = str(dd['time'].minute)
self.logger.debug('Время ' + data['time_h'] + ':' + data['time_m'])
self.logger.debug("Конец создания имени")
# Создание задач
watch_list = []
watch_list.append(self.send_job(data, message))
if True: # TODO: Заменить на условие, если нужно
data['data'] = 'сегодня'
data['outfile_name'] = fn + '_Today'
watch_list.append(self.send_job(data, message))
data['data'] = 'завтра'
data['outfile_name'] = fn + '_Tomorrow'
watch_list.append(self.send_job(data, message))
pprint(watch_list)
return list(filter(None, watch_list))
def _process_team_data(self, dd, data, fn, team, logos, message):
"""Обработка данных команды"""
team_key = f'team_{team.lower()}'
if pd.isna(dd[f'TEAM {team}']):
self.logger.info(f'Нет команды {team}')
self.bot.send_message(
message.chat.id,
f'Нет команды {team}',
parse_mode='html'
)
data[team_key] = ''
data[f'{team_key}_logo'] = ''
data[f'{team_key}_logo_res'] = ''
else:
name = dd[f'TEAM {team}'].split('#')
fn += f"_{name[0]}"
data[f'{team_key}_logo_res'] = name[2:]
data[team_key] = name[0]
data[f'{team_key}_logo'] = self.unc2uri(
self.get_team_logo(dd[f'TEAM {team}'], dd['sport'], logos, message)
)
def run_flask():
"""Запуск Flask сервера для панели мониторинга"""
flask_app.run(host='0.0.0.0', port=5000)
@flask_app.route('/admin/stats')
def admin_stats():
"""API endpoint для получения статистики"""
stats = monitoring.get_stats()
return jsonify({
'status': 'success',
'data': stats
})
@flask_app.route('/admin/jobs')
def admin_jobs():
"""API endpoint для получения списка задач"""
jobs = monitoring.get_recent_jobs(50)
return jsonify({
'status': 'success',
'data': jobs
})
@flask_app.route('/admin/users')
def admin_users():
"""API endpoint для получения статистики по пользователям"""
stats = monitoring.get_stats()
return jsonify({
'status': 'success',
'data': stats.get('users', {})
})
if __name__ == '__main__':
try:
# Проверяем наличие файла окружения
if not os.path.exists('AF_environment.env'):
raise FileNotFoundError(
"Файл окружения AF_environment.env не найден. "
"Создайте его по образцу AF_environment.example.env"
)
# Инициализация компонентов
config = Config()
monitoring = Monitoring()
job_manager = JobManager(config, monitoring)
# Запуск Flask в отдельном потоке
flask_thread = threading.Thread(target=run_flask, daemon=True)
flask_thread.start()
# Запуск Telegram бота
job_manager.bot.infinity_polling()
# except FileNotFoundError