add admin schedule uploads
This commit is contained in:
parent
f5febefa92
commit
7dbfb29a21
3 changed files with 182 additions and 1 deletions
|
|
@ -22,6 +22,7 @@ from .main.register_user import *
|
||||||
# #? Another code
|
# #? Another code
|
||||||
from .analytics import *
|
from .analytics import *
|
||||||
from .iternal import *
|
from .iternal import *
|
||||||
|
from .admin_schedule import *
|
||||||
from .inline import *
|
from .inline import *
|
||||||
from .main.home import *
|
from .main.home import *
|
||||||
from .schedule.search import *
|
from .schedule.search import *
|
||||||
|
|
|
||||||
175
bot/admin_schedule.py
Normal file
175
bot/admin_schedule.py
Normal file
|
|
@ -0,0 +1,175 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import html
|
||||||
|
import json
|
||||||
|
import secrets
|
||||||
|
import tempfile
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from telebot.types import CallbackQuery, Message
|
||||||
|
from telebot.util import quick_markup
|
||||||
|
|
||||||
|
import config
|
||||||
|
from models import logger
|
||||||
|
from models.bot import bot, send
|
||||||
|
from models.schedule import clear_schedule_cache
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PendingArchive:
|
||||||
|
admin_id: int
|
||||||
|
filename: str
|
||||||
|
path: Path
|
||||||
|
|
||||||
|
|
||||||
|
PENDING_ARCHIVES: dict[str, PendingArchive] = {}
|
||||||
|
UPLOAD_CALLBACK_PREFIX = "$schedule_upload:"
|
||||||
|
CANCEL_CALLBACK_PREFIX = "$schedule_cancel:"
|
||||||
|
|
||||||
|
|
||||||
|
def _is_admin(user_id: int) -> bool:
|
||||||
|
return user_id in config.ADMINS
|
||||||
|
|
||||||
|
|
||||||
|
def _api_url(path: str) -> str:
|
||||||
|
return f"{config.SCHEDULE_BASE_URL.rstrip('/')}/{path.lstrip('/')}"
|
||||||
|
|
||||||
|
|
||||||
|
def _download_archive(message: Message) -> PendingArchive:
|
||||||
|
file_info = bot.get_file(message.document.file_id)
|
||||||
|
content = bot.download_file(file_info.file_path)
|
||||||
|
filename = message.document.file_name or "schedule.zip"
|
||||||
|
with tempfile.NamedTemporaryFile(prefix="zatups-schedule-", suffix=".zip", delete=False) as file:
|
||||||
|
file.write(content)
|
||||||
|
path = Path(file.name)
|
||||||
|
return PendingArchive(message.from_user.id, filename, path)
|
||||||
|
|
||||||
|
|
||||||
|
def _upload_archive(archive: PendingArchive) -> dict[str, Any]:
|
||||||
|
with archive.path.open("rb") as file:
|
||||||
|
response = requests.post(
|
||||||
|
_api_url("schedule/default/upload"),
|
||||||
|
files={"file": (archive.filename, file, "application/zip")},
|
||||||
|
timeout=(10, 300),
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def _clear_database() -> dict[str, Any]:
|
||||||
|
response = requests.delete(
|
||||||
|
_api_url("admin/database"),
|
||||||
|
params={"confirm": "DELETE_ALL_DATA"},
|
||||||
|
timeout=30,
|
||||||
|
)
|
||||||
|
response.raise_for_status()
|
||||||
|
return response.json()
|
||||||
|
|
||||||
|
|
||||||
|
def _format_upload_result(payload: dict[str, Any]) -> str:
|
||||||
|
serialized = json.dumps(payload, ensure_ascii=False, indent=2)
|
||||||
|
if len(serialized) > 3400:
|
||||||
|
serialized = serialized[:3400] + "\n..."
|
||||||
|
heading = "База применена" if payload.get("ok") else "Архив обработан с ошибками"
|
||||||
|
return f"<b>{heading}, результат:</b>\n<pre>{html.escape(serialized)}</pre>"
|
||||||
|
|
||||||
|
|
||||||
|
def _format_request_error(error: requests.RequestException) -> str:
|
||||||
|
response = error.response
|
||||||
|
if response is None:
|
||||||
|
return str(error)
|
||||||
|
try:
|
||||||
|
detail = response.json()
|
||||||
|
except ValueError:
|
||||||
|
detail = response.text
|
||||||
|
return json.dumps(detail, ensure_ascii=False) if isinstance(detail, dict) else str(detail)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(
|
||||||
|
content_types=["document", "file"],
|
||||||
|
func=lambda message: _is_admin(message.from_user.id)
|
||||||
|
and bool(message.document)
|
||||||
|
and (message.document.file_name or "").lower().endswith(".zip"),
|
||||||
|
)
|
||||||
|
def schedule_archive(message: Message):
|
||||||
|
try:
|
||||||
|
archive = _download_archive(message)
|
||||||
|
except Exception as error:
|
||||||
|
logger.error("Schedule", f"Archive download failed: {error}")
|
||||||
|
return send(message, f"Не удалось скачать архив: <code>{html.escape(str(error))}</code>")
|
||||||
|
|
||||||
|
token = secrets.token_urlsafe(8)
|
||||||
|
PENDING_ARCHIVES[token] = archive
|
||||||
|
return send(
|
||||||
|
message,
|
||||||
|
"Вы уверены, что хотите отправить архив в API?\n"
|
||||||
|
"Сбросить БД в API можно командой /reset_schedule_db",
|
||||||
|
reply_markup=quick_markup(
|
||||||
|
{
|
||||||
|
"Отправить": {"callback_data": UPLOAD_CALLBACK_PREFIX + token},
|
||||||
|
"Отменить": {"callback_data": CANCEL_CALLBACK_PREFIX + token},
|
||||||
|
},
|
||||||
|
row_width=1,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@bot.callback_query_handler(func=lambda call: str(call.data).startswith(CANCEL_CALLBACK_PREFIX))
|
||||||
|
def cancel_schedule_upload(call: CallbackQuery):
|
||||||
|
if not _is_admin(call.from_user.id):
|
||||||
|
return
|
||||||
|
token = str(call.data).removeprefix(CANCEL_CALLBACK_PREFIX)
|
||||||
|
archive = PENDING_ARCHIVES.get(token)
|
||||||
|
if archive and archive.admin_id == call.from_user.id:
|
||||||
|
PENDING_ARCHIVES.pop(token, None)
|
||||||
|
archive.path.unlink(missing_ok=True)
|
||||||
|
bot.answer_callback_query(call.id, "Отменено")
|
||||||
|
|
||||||
|
|
||||||
|
@bot.callback_query_handler(func=lambda call: str(call.data).startswith(UPLOAD_CALLBACK_PREFIX))
|
||||||
|
def upload_schedule_archive(call: CallbackQuery):
|
||||||
|
if not _is_admin(call.from_user.id):
|
||||||
|
return
|
||||||
|
token = str(call.data).removeprefix(UPLOAD_CALLBACK_PREFIX)
|
||||||
|
archive = PENDING_ARCHIVES.get(token)
|
||||||
|
if not archive or archive.admin_id != call.from_user.id:
|
||||||
|
return bot.answer_callback_query(call.id, "Архив не найден или уже отправлен")
|
||||||
|
|
||||||
|
bot.answer_callback_query(call.id, "Отправляю архив")
|
||||||
|
try:
|
||||||
|
payload = _upload_archive(archive)
|
||||||
|
except requests.RequestException as error:
|
||||||
|
logger.error("Schedule", f"Archive upload failed: {error}")
|
||||||
|
return send(
|
||||||
|
call.from_user.id,
|
||||||
|
f"API отклонил архив: <code>{html.escape(_format_request_error(error))}</code>",
|
||||||
|
)
|
||||||
|
except (OSError, ValueError) as error:
|
||||||
|
logger.error("Schedule", f"Archive upload failed: {error}")
|
||||||
|
return send(call.from_user.id, f"Не удалось отправить архив: <code>{html.escape(str(error))}</code>")
|
||||||
|
|
||||||
|
PENDING_ARCHIVES.pop(token, None)
|
||||||
|
archive.path.unlink(missing_ok=True)
|
||||||
|
clear_schedule_cache()
|
||||||
|
return send(call.from_user.id, _format_upload_result(payload))
|
||||||
|
|
||||||
|
|
||||||
|
@bot.message_handler(commands=["reset_schedule_db"], func=lambda message: _is_admin(message.from_user.id))
|
||||||
|
def reset_schedule_database(message: Message):
|
||||||
|
try:
|
||||||
|
_clear_database()
|
||||||
|
except requests.RequestException as error:
|
||||||
|
logger.error("Schedule", f"Database reset failed: {error}")
|
||||||
|
return send(
|
||||||
|
message,
|
||||||
|
f"Не удалось очистить базу: <code>{html.escape(_format_request_error(error))}</code>",
|
||||||
|
)
|
||||||
|
except ValueError as error:
|
||||||
|
logger.error("Schedule", f"Database reset failed: {error}")
|
||||||
|
return send(message, f"API вернул некорректный ответ: <code>{html.escape(str(error))}</code>")
|
||||||
|
|
||||||
|
clear_schedule_cache()
|
||||||
|
return send(message, "База очищена")
|
||||||
|
|
@ -47,7 +47,12 @@ def sendConfirm(message: Message):
|
||||||
'Нeт': {'callback_data': 'ignore'}
|
'Нeт': {'callback_data': 'ignore'}
|
||||||
}, row_width=1))
|
}, row_width=1))
|
||||||
|
|
||||||
@bot.message_handler(content_types = ['document', 'file'], func = lambda m: m.from_user.id in config.ADMINS)
|
@bot.message_handler(
|
||||||
|
content_types=['document', 'file'],
|
||||||
|
func=lambda message: message.from_user.id in config.ADMINS
|
||||||
|
and bool(message.document)
|
||||||
|
and (message.document.file_name or '').lower().endswith('.json'),
|
||||||
|
)
|
||||||
def massSendHandle(message: Message):
|
def massSendHandle(message: Message):
|
||||||
global REMEMBER_MESSAGE_DATA, REMEMBER_JSON_DATA, REMEMBER_PHOTO_ID
|
global REMEMBER_MESSAGE_DATA, REMEMBER_JSON_DATA, REMEMBER_PHOTO_ID
|
||||||
REMEMBER_PHOTO_ID = False
|
REMEMBER_PHOTO_ID = False
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue