шиза
This commit is contained in:
parent
5986f420b1
commit
b9e9b5d51b
11 changed files with 204 additions and 35 deletions
|
|
@ -1,9 +1,11 @@
|
||||||
from fastapi import APIRouter, Depends, Request
|
from fastapi import APIRouter, Depends, Form, Request
|
||||||
from fastapi.responses import HTMLResponse
|
from fastapi.responses import HTMLResponse, RedirectResponse
|
||||||
|
|
||||||
from admin.auth import require_admin
|
from admin.auth import require_admin
|
||||||
from admin.templates_env import templates
|
from admin.templates_env import templates
|
||||||
from database.queries.message_log import get_chat_list, get_message_log
|
from bot.bot_instance import get_bot
|
||||||
|
from database.queries.bot_users import get_bot_user_by_max_id
|
||||||
|
from database.queries.message_log import get_chat_list, get_message_log, log_message
|
||||||
|
|
||||||
router = APIRouter(prefix="/chat-log", dependencies=[Depends(require_admin)])
|
router = APIRouter(prefix="/chat-log", dependencies=[Depends(require_admin)])
|
||||||
|
|
||||||
|
|
@ -19,8 +21,32 @@ async def chat_list(request: Request):
|
||||||
@router.get("/{max_user_id}", response_class=HTMLResponse)
|
@router.get("/{max_user_id}", response_class=HTMLResponse)
|
||||||
async def chat_history(request: Request, max_user_id: int):
|
async def chat_history(request: Request, max_user_id: int):
|
||||||
messages = await get_message_log(max_user_id=max_user_id, limit=200)
|
messages = await get_message_log(max_user_id=max_user_id, limit=200)
|
||||||
|
bot_user = await get_bot_user_by_max_id(max_user_id)
|
||||||
return templates.TemplateResponse(
|
return templates.TemplateResponse(
|
||||||
request=request,
|
request=request,
|
||||||
name="chat_log/history.html",
|
name="chat_log/history.html",
|
||||||
context={"messages": messages, "max_user_id": max_user_id},
|
context={
|
||||||
|
"messages": messages,
|
||||||
|
"max_user_id": max_user_id,
|
||||||
|
"bot_user": bot_user,
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@router.post("/{max_user_id}/send")
|
||||||
|
async def send_message_to_user(max_user_id: int, text: str = Form(...)):
|
||||||
|
bot_user = await get_bot_user_by_max_id(max_user_id)
|
||||||
|
if bot_user is None or not bot_user.get("max_chat_id"):
|
||||||
|
return RedirectResponse(f"/admin/chat-log/{max_user_id}", status_code=303)
|
||||||
|
|
||||||
|
chat_id = bot_user["max_chat_id"]
|
||||||
|
bot = get_bot()
|
||||||
|
await bot.send_message(chat_id=chat_id, text=text)
|
||||||
|
await log_message(
|
||||||
|
direction="out",
|
||||||
|
max_user_id=max_user_id,
|
||||||
|
max_chat_id=chat_id,
|
||||||
|
message_id=None,
|
||||||
|
text=text,
|
||||||
|
)
|
||||||
|
return RedirectResponse(f"/admin/chat-log/{max_user_id}", status_code=303)
|
||||||
|
|
|
||||||
|
|
@ -1,38 +1,92 @@
|
||||||
{% extends "base.html" %}
|
{% extends "base.html" %}
|
||||||
{% block title %}История чата {{ max_user_id }}{% endblock %}
|
{% block title %}Чат — {{ bot_user.first_name or bot_user.username or max_user_id if bot_user else max_user_id }}{% endblock %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h1>История чата</h1>
|
|
||||||
<a href="/admin/chat-log" class="btn btn-secondary" style="margin-bottom:16px;display:inline-block">← К списку чатов</a>
|
|
||||||
|
|
||||||
<div class="card">
|
<div style="display:flex;align-items:center;gap:12px;margin-bottom:16px">
|
||||||
<div style="display:flex;flex-direction:column;gap:10px">
|
<a href="/admin/chat-log" class="btn btn-secondary">← К списку</a>
|
||||||
|
<div>
|
||||||
|
<strong>
|
||||||
|
{{ bot_user.first_name or bot_user.username or ('MAX ID: ' ~ max_user_id) if bot_user else ('MAX ID: ' ~ max_user_id) }}
|
||||||
|
</strong>
|
||||||
|
{% if bot_user %}
|
||||||
|
<span style="color:#666;font-size:.85rem;margin-left:8px">
|
||||||
|
MAX ID: {{ max_user_id }}
|
||||||
|
{% if bot_user.phone %} · {{ bot_user.phone }}{% endif %}
|
||||||
|
{% if bot_user.org_name %} · {{ bot_user.org_name }}{% endif %}
|
||||||
|
</span>
|
||||||
|
<span style="margin-left:8px">
|
||||||
|
{% if bot_user.is_blocked %}
|
||||||
|
<span class="badge badge-blocked">Заблокирован</span>
|
||||||
|
{% elif bot_user.is_authorized %}
|
||||||
|
<span class="badge badge-auth">Авторизован</span>
|
||||||
|
{% else %}
|
||||||
|
<span class="badge badge-pending">Ожидает</span>
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{# Лента сообщений #}
|
||||||
|
<div class="card" style="padding:16px;margin-bottom:12px">
|
||||||
|
<div style="display:flex;flex-direction:column;gap:10px;max-height:60vh;overflow-y:auto;padding-right:4px" id="chat-feed">
|
||||||
{% for msg in messages|reverse %}
|
{% for msg in messages|reverse %}
|
||||||
<div style="display:flex;{% if msg.direction == 'out' %}justify-content:flex-end{% endif %}">
|
<div style="display:flex;{% if msg.direction == 'out' %}justify-content:flex-end{% endif %}">
|
||||||
<div style="
|
<div style="
|
||||||
max-width:70%;
|
max-width:72%;
|
||||||
padding:10px 14px;
|
padding:10px 14px;
|
||||||
border-radius:12px;
|
border-radius:12px;
|
||||||
font-size:.9rem;
|
font-size:.9rem;
|
||||||
line-height:1.5;
|
line-height:1.5;
|
||||||
|
word-break:break-word;
|
||||||
{% if msg.direction == 'out' %}
|
{% if msg.direction == 'out' %}
|
||||||
background:#1a73e8;color:white;border-bottom-right-radius:2px;
|
background:#1a73e8;color:white;border-bottom-right-radius:2px;
|
||||||
{% else %}
|
{% else %}
|
||||||
background:#f1f3f4;color:#333;border-bottom-left-radius:2px;
|
background:#f1f3f4;color:#333;border-bottom-left-radius:2px;
|
||||||
{% endif %}
|
{% endif %}
|
||||||
">
|
">
|
||||||
{% if msg.text %}<div>{{ msg.text }}</div>{% endif %}
|
{% if msg.direction == 'out' %}
|
||||||
|
<div style="font-size:.7rem;opacity:.7;margin-bottom:4px;text-transform:uppercase;letter-spacing:.03em">Поддержка</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if msg.text %}<div style="white-space:pre-wrap">{{ msg.text }}</div>{% endif %}
|
||||||
{% if msg.attachment_json and msg.attachment_json != 'null' %}
|
{% if msg.attachment_json and msg.attachment_json != 'null' %}
|
||||||
<div style="font-size:.8rem;opacity:.8;margin-top:4px">📎 Вложение</div>
|
<div style="font-size:.8rem;opacity:.8;margin-top:4px">📎 Вложение</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<div style="font-size:.75rem;opacity:.7;margin-top:4px;text-align:right">
|
<div style="font-size:.72rem;opacity:.65;margin-top:6px;text-align:right">
|
||||||
{{ msg.created_at[:16] }}
|
{{ msg.created_at[:16] }}
|
||||||
{% if msg.direction == 'in' %} · входящее{% else %} · исходящее{% endif %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<p style="color:#999;text-align:center">История пуста</p>
|
<p style="color:#999;text-align:center;margin:32px 0">История пуста</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{# Форма отправки #}
|
||||||
|
{% if bot_user and bot_user.max_chat_id %}
|
||||||
|
<div class="card" style="padding:16px">
|
||||||
|
<form action="/admin/chat-log/{{ max_user_id }}/send" method="post"
|
||||||
|
style="display:flex;gap:8px;align-items:flex-end">
|
||||||
|
<textarea name="text" rows="3" required placeholder="Напишите сообщение от лица бота..."
|
||||||
|
style="flex:1;resize:vertical;padding:8px 12px;border:1px solid #ddd;border-radius:8px;font-size:.9rem;font-family:inherit"
|
||||||
|
onkeydown="if(event.ctrlKey&&event.key==='Enter'){this.form.submit()}"></textarea>
|
||||||
|
<button class="btn btn-primary" type="submit" style="height:fit-content;padding:10px 20px">
|
||||||
|
Отправить
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
<div style="font-size:.75rem;color:#999;margin-top:6px">Ctrl+Enter для отправки</div>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class="card" style="padding:16px;color:#999;font-size:.9rem">
|
||||||
|
ℹ️ Отправка недоступна: нет chat_id (пользователь ещё не начал диалог с ботом).
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Прокрутить ленту вниз при загрузке
|
||||||
|
const feed = document.getElementById('chat-feed');
|
||||||
|
if (feed) feed.scrollTop = feed.scrollHeight;
|
||||||
|
</script>
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
20
bot/bot_instance.py
Normal file
20
bot/bot_instance.py
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
"""Глобальное хранилище экземпляра бота для доступа из admin-панели."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from maxapi.bot import Bot
|
||||||
|
|
||||||
|
_bot: "Bot | None" = None
|
||||||
|
|
||||||
|
|
||||||
|
def set_bot(bot: "Bot") -> None:
|
||||||
|
global _bot
|
||||||
|
_bot = bot
|
||||||
|
|
||||||
|
|
||||||
|
def get_bot() -> "Bot":
|
||||||
|
if _bot is None:
|
||||||
|
raise RuntimeError("Bot not initialized — call set_bot() first")
|
||||||
|
return _bot
|
||||||
|
|
@ -22,6 +22,7 @@ from maxapi.types.updates.message_created import MessageCreated
|
||||||
|
|
||||||
from bot.keyboards import done_ticket_kb, main_menu_kb
|
from bot.keyboards import done_ticket_kb, main_menu_kb
|
||||||
from bot.logging_setup import get_logger
|
from bot.logging_setup import get_logger
|
||||||
|
from bot.send_helper import bot_send
|
||||||
from bot.states import TicketStates
|
from bot.states import TicketStates
|
||||||
|
|
||||||
logger = get_logger("bot.handlers.create_ticket")
|
logger = get_logger("bot.handlers.create_ticket")
|
||||||
|
|
@ -70,8 +71,10 @@ async def cb_create_ticket(
|
||||||
if chat_id is None:
|
if chat_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text=(
|
text=(
|
||||||
"📝 Опишите вашу проблему.\n\n"
|
"📝 Опишите вашу проблему.\n\n"
|
||||||
"Вы также можете прикрепить файлы, фото или голосовые сообщения.\n"
|
"Вы также можете прикрепить файлы, фото или голосовые сообщения.\n"
|
||||||
|
|
@ -85,7 +88,7 @@ async def cb_create_ticket(
|
||||||
async def on_ticket_description(
|
async def on_ticket_description(
|
||||||
event: MessageCreated, context: BaseContext
|
event: MessageCreated, context: BaseContext
|
||||||
) -> None:
|
) -> None:
|
||||||
chat_id, _ = event.get_ids()
|
chat_id, user_id = event.get_ids()
|
||||||
if chat_id is None:
|
if chat_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -99,8 +102,10 @@ async def on_ticket_description(
|
||||||
)
|
)
|
||||||
await context.set_state(TicketStates.collecting_attachments)
|
await context.set_state(TicketStates.collecting_attachments)
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text=(
|
text=(
|
||||||
"✏️ Описание принято. Вы можете добавить ещё файлы "
|
"✏️ Описание принято. Вы можете добавить ещё файлы "
|
||||||
"или нажмите «Готово» для отправки заявки."
|
"или нажмите «Готово» для отправки заявки."
|
||||||
|
|
@ -145,8 +150,10 @@ async def cb_done_ticket(
|
||||||
|
|
||||||
if not description:
|
if not description:
|
||||||
await event.answer(notification="Сначала опишите проблему!")
|
await event.answer(notification="Сначала опишите проблему!")
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text="❗ Пожалуйста, сначала напишите описание проблемы.",
|
text="❗ Пожалуйста, сначала напишите описание проблемы.",
|
||||||
attachments=[done_ticket_kb()],
|
attachments=[done_ticket_kb()],
|
||||||
)
|
)
|
||||||
|
|
@ -173,8 +180,10 @@ async def cb_done_ticket(
|
||||||
await context.clear()
|
await context.clear()
|
||||||
|
|
||||||
await event.answer(notification="Заявка создана!")
|
await event.answer(notification="Заявка создана!")
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text=(
|
text=(
|
||||||
f"✅ Заявка №{ticket_id} успешно создана!\n"
|
f"✅ Заявка №{ticket_id} успешно создана!\n"
|
||||||
"Наши специалисты свяжутся с вами в ближайшее время."
|
"Наши специалисты свяжутся с вами в ближайшее время."
|
||||||
|
|
|
||||||
|
|
@ -5,12 +5,15 @@ from maxapi.types.updates.message_callback import MessageCallback
|
||||||
from maxapi.context import BaseContext
|
from maxapi.context import BaseContext
|
||||||
|
|
||||||
from bot.keyboards import main_menu_kb
|
from bot.keyboards import main_menu_kb
|
||||||
|
from bot.send_helper import bot_send
|
||||||
|
|
||||||
router = Router(router_id="menu")
|
router = Router(router_id="menu")
|
||||||
|
|
||||||
|
|
||||||
@router.message_callback(F.callback.payload == "main_menu")
|
@router.message_callback(F.callback.payload == "main_menu")
|
||||||
async def cb_main_menu(event: MessageCallback, context: BaseContext) -> None:
|
async def cb_main_menu(
|
||||||
|
event: MessageCallback, context: BaseContext, bot_user: dict
|
||||||
|
) -> None:
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.answer(notification="")
|
await event.answer(notification="")
|
||||||
|
|
||||||
|
|
@ -18,8 +21,10 @@ async def cb_main_menu(event: MessageCallback, context: BaseContext) -> None:
|
||||||
if chat_id is None:
|
if chat_id is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text="Главное меню:",
|
text="Главное меню:",
|
||||||
attachments=[main_menu_kb()],
|
attachments=[main_menu_kb()],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from maxapi.filters.command import Command
|
||||||
|
|
||||||
from bot.keyboards import contact_request_kb, main_menu_kb
|
from bot.keyboards import contact_request_kb, main_menu_kb
|
||||||
from bot.logging_setup import get_logger
|
from bot.logging_setup import get_logger
|
||||||
|
from bot.send_helper import bot_send
|
||||||
from bot.states import AuthStates
|
from bot.states import AuthStates
|
||||||
|
|
||||||
logger = get_logger("bot.handlers.start")
|
logger = get_logger("bot.handlers.start")
|
||||||
|
|
@ -53,8 +54,10 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
|
|
||||||
if bot_user["is_blocked"]:
|
if bot_user["is_blocked"]:
|
||||||
logger.info("🚫 bot_started blocked | user_id=%s", user.user_id)
|
logger.info("🚫 bot_started blocked | user_id=%s", user.user_id)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
|
user_id=user.user_id,
|
||||||
text=(
|
text=(
|
||||||
"⛔ Ваш номер не найден в системе. Доступ заблокирован.\n"
|
"⛔ Ваш номер не найден в системе. Доступ заблокирован.\n"
|
||||||
"Обратитесь к администратору для подключения."
|
"Обратитесь к администратору для подключения."
|
||||||
|
|
@ -65,8 +68,10 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
if bot_user["is_authorized"]:
|
if bot_user["is_authorized"]:
|
||||||
logger.info("✅ bot_started returning user | user_id=%s", user.user_id)
|
logger.info("✅ bot_started returning user | user_id=%s", user.user_id)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
|
user_id=user.user_id,
|
||||||
text="👋 С возвращением! Выберите действие:",
|
text="👋 С возвращением! Выберите действие:",
|
||||||
attachments=[main_menu_kb()],
|
attachments=[main_menu_kb()],
|
||||||
)
|
)
|
||||||
|
|
@ -75,8 +80,10 @@ async def on_bot_started(event: BotStarted, context: BaseContext) -> None:
|
||||||
# Новый пользователь — запрашиваем контакт
|
# Новый пользователь — запрашиваем контакт
|
||||||
logger.info("👋 bot_started new user | user_id=%s", user.user_id)
|
logger.info("👋 bot_started new user | user_id=%s", user.user_id)
|
||||||
await context.set_state(AuthStates.waiting_contact)
|
await context.set_state(AuthStates.waiting_contact)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=event.chat_id,
|
chat_id=event.chat_id,
|
||||||
|
user_id=user.user_id,
|
||||||
text=(
|
text=(
|
||||||
"👋 Добро пожаловать в систему технической поддержки!\n\n"
|
"👋 Добро пожаловать в систему технической поддержки!\n\n"
|
||||||
"Для входа нажмите кнопку ниже и поделитесь своим номером телефона."
|
"Для входа нажмите кнопку ниже и поделитесь своим номером телефона."
|
||||||
|
|
@ -101,8 +108,10 @@ async def on_start_command(event: MessageCreated, context: BaseContext) -> None:
|
||||||
first_name=None,
|
first_name=None,
|
||||||
)
|
)
|
||||||
await context.set_state(AuthStates.waiting_contact)
|
await context.set_state(AuthStates.waiting_contact)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text=(
|
text=(
|
||||||
"👋 Добро пожаловать!\n\n"
|
"👋 Добро пожаловать!\n\n"
|
||||||
"Поделитесь номером телефона для доступа к системе."
|
"Поделитесь номером телефона для доступа к системе."
|
||||||
|
|
@ -112,24 +121,30 @@ async def on_start_command(event: MessageCreated, context: BaseContext) -> None:
|
||||||
return
|
return
|
||||||
|
|
||||||
if bot_user["is_blocked"]:
|
if bot_user["is_blocked"]:
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="⛔ Доступ заблокирован. Обратитесь к администратору.",
|
text="⛔ Доступ заблокирован. Обратитесь к администратору.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
if not bot_user["is_authorized"]:
|
if not bot_user["is_authorized"]:
|
||||||
await context.set_state(AuthStates.waiting_contact)
|
await context.set_state(AuthStates.waiting_contact)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="Поделитесь номером телефона для доступа:",
|
text="Поделитесь номером телефона для доступа:",
|
||||||
attachments=[contact_request_kb()],
|
attachments=[contact_request_kb()],
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="Главное меню:",
|
text="Главное меню:",
|
||||||
attachments=[main_menu_kb()],
|
attachments=[main_menu_kb()],
|
||||||
)
|
)
|
||||||
|
|
@ -148,8 +163,10 @@ async def on_contact_shared(
|
||||||
# Извлекаем телефон из VCF
|
# Извлекаем телефон из VCF
|
||||||
payload = contact.payload
|
payload = contact.payload
|
||||||
if not isinstance(payload, ContactAttachmentPayload):
|
if not isinstance(payload, ContactAttachmentPayload):
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="Не удалось прочитать контакт. Попробуйте ещё раз.",
|
text="Не удалось прочитать контакт. Попробуйте ещё раз.",
|
||||||
attachments=[contact_request_kb()],
|
attachments=[contact_request_kb()],
|
||||||
)
|
)
|
||||||
|
|
@ -158,8 +175,10 @@ async def on_contact_shared(
|
||||||
vcf = payload.vcf
|
vcf = payload.vcf
|
||||||
raw_phone = vcf.phone
|
raw_phone = vcf.phone
|
||||||
if not raw_phone:
|
if not raw_phone:
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="В контакте не найден номер телефона. Попробуйте ещё раз.",
|
text="В контакте не найден номер телефона. Попробуйте ещё раз.",
|
||||||
attachments=[contact_request_kb()],
|
attachments=[contact_request_kb()],
|
||||||
)
|
)
|
||||||
|
|
@ -183,8 +202,10 @@ async def on_contact_shared(
|
||||||
logger.warning("🚫 phone not found, blocking | user_id=%s phone=%s", user_id, phone)
|
logger.warning("🚫 phone not found, blocking | user_id=%s phone=%s", user_id, phone)
|
||||||
await block_bot_user(max_user_id=user_id, phone=phone)
|
await block_bot_user(max_user_id=user_id, phone=phone)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text=(
|
text=(
|
||||||
"⛔ Ваш номер не найден в системе.\n"
|
"⛔ Ваш номер не найден в системе.\n"
|
||||||
"Доступ заблокирован. Обратитесь к администратору для подключения."
|
"Доступ заблокирован. Обратитесь к администратору для подключения."
|
||||||
|
|
@ -202,8 +223,10 @@ async def on_contact_shared(
|
||||||
)
|
)
|
||||||
await context.set_state(None)
|
await context.set_state(None)
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=user_id,
|
||||||
text="✅ Вы успешно авторизованы! Выберите действие:",
|
text="✅ Вы успешно авторизованы! Выберите действие:",
|
||||||
attachments=[main_menu_kb()],
|
attachments=[main_menu_kb()],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ from maxapi.types.updates.message_callback import MessageCallback
|
||||||
|
|
||||||
from bot.filters import PayloadStartsWith
|
from bot.filters import PayloadStartsWith
|
||||||
from bot.keyboards import ticket_detail_kb, tickets_list_kb
|
from bot.keyboards import ticket_detail_kb, tickets_list_kb
|
||||||
|
from bot.send_helper import bot_send
|
||||||
from database.queries.ticket_attachments import get_attachments
|
from database.queries.ticket_attachments import get_attachments
|
||||||
from database.queries.tickets import (
|
from database.queries.tickets import (
|
||||||
PAGE_SIZE,
|
PAGE_SIZE,
|
||||||
|
|
@ -43,15 +44,19 @@ async def _send_tickets_page(
|
||||||
return
|
return
|
||||||
|
|
||||||
if not tickets:
|
if not tickets:
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text="📭 У вашей организации пока нет заявок.",
|
text="📭 У вашей организации пока нет заявок.",
|
||||||
attachments=[ticket_detail_kb(page=0)],
|
attachments=[ticket_detail_kb(page=0)],
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text=f"📋 Заявки организации (стр. {page + 1}):",
|
text=f"📋 Заявки организации (стр. {page + 1}):",
|
||||||
attachments=[tickets_list_kb(tickets, page=page, total=total)],
|
attachments=[tickets_list_kb(tickets, page=page, total=total)],
|
||||||
)
|
)
|
||||||
|
|
@ -127,8 +132,10 @@ async def cb_ticket_detail(
|
||||||
att_lines.append(line)
|
att_lines.append(line)
|
||||||
text += "\n\n📎 Вложения:\n" + "\n".join(att_lines)
|
text += "\n\n📎 Вложения:\n" + "\n".join(att_lines)
|
||||||
|
|
||||||
await event.bot.send_message(
|
await bot_send(
|
||||||
|
event.bot,
|
||||||
chat_id=chat_id,
|
chat_id=chat_id,
|
||||||
|
user_id=bot_user["max_user_id"],
|
||||||
text=text,
|
text=text,
|
||||||
attachments=[ticket_detail_kb(page=0)],
|
attachments=[ticket_detail_kb(page=0)],
|
||||||
)
|
)
|
||||||
|
|
|
||||||
23
bot/send_helper.py
Normal file
23
bot/send_helper.py
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
"""Обёртка над bot.send_message с автоматическим логированием исходящих."""
|
||||||
|
from database.queries.message_log import log_message
|
||||||
|
|
||||||
|
|
||||||
|
async def bot_send(
|
||||||
|
bot,
|
||||||
|
*,
|
||||||
|
chat_id: int,
|
||||||
|
user_id: int | None,
|
||||||
|
text: str,
|
||||||
|
attachments=None,
|
||||||
|
) -> None:
|
||||||
|
kw: dict = {"chat_id": chat_id, "text": text}
|
||||||
|
if attachments is not None:
|
||||||
|
kw["attachments"] = attachments
|
||||||
|
await bot.send_message(**kw)
|
||||||
|
await log_message(
|
||||||
|
direction="out",
|
||||||
|
max_user_id=user_id,
|
||||||
|
max_chat_id=chat_id,
|
||||||
|
message_id=None,
|
||||||
|
text=text,
|
||||||
|
)
|
||||||
|
|
@ -3,12 +3,14 @@ from maxapi.context import MemoryContext
|
||||||
from maxapi.dispatcher import Dispatcher, Router
|
from maxapi.dispatcher import Dispatcher, Router
|
||||||
|
|
||||||
import config
|
import config
|
||||||
|
from bot.bot_instance import set_bot
|
||||||
from bot.middleware import AuthMiddleware
|
from bot.middleware import AuthMiddleware
|
||||||
from bot.handlers import start, menu, create_ticket, view_tickets
|
from bot.handlers import start, menu, create_ticket, view_tickets
|
||||||
|
|
||||||
|
|
||||||
def create_bot_and_dispatcher() -> tuple[Bot, Dispatcher]:
|
def create_bot_and_dispatcher() -> tuple[Bot, Dispatcher]:
|
||||||
bot = Bot(token=config.BOT_TOKEN)
|
bot = Bot(token=config.BOT_TOKEN)
|
||||||
|
set_bot(bot)
|
||||||
|
|
||||||
dp = Dispatcher(router_id="main", storage=MemoryContext)
|
dp = Dispatcher(router_id="main", storage=MemoryContext)
|
||||||
dp.outer_middleware(AuthMiddleware())
|
dp.outer_middleware(AuthMiddleware())
|
||||||
|
|
|
||||||
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue