init
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
BOT_TOKEN=ВСТАВЬ_СЮДА_ТОКЕН_БОТА
|
||||
SUPPORT_CHAT_ID=0
|
||||
+225
@@ -0,0 +1,225 @@
|
||||
import logging
|
||||
import os
|
||||
import sqlite3
|
||||
from pathlib import Path
|
||||
|
||||
from telegram import Update
|
||||
from telegram.constants import ParseMode
|
||||
from telegram.ext import (
|
||||
Application,
|
||||
CommandHandler,
|
||||
ContextTypes,
|
||||
MessageHandler,
|
||||
filters,
|
||||
)
|
||||
|
||||
ENV_FILE = "/etc/support-bot.env"
|
||||
DB_FILE = "/var/lib/support-bot/messages.db"
|
||||
|
||||
START_TEXT = (
|
||||
"Добро пожаловать в StreamHelp.\n\n"
|
||||
"Для того чтобы мы могли проанализировать вашу ситуацию и оказать помощь, "
|
||||
"пожалуйста, отправьте следующее сообщение, содержащее:\n\n"
|
||||
"• краткое описание проблемы;\n"
|
||||
"• ссылку на ваш канал;\n"
|
||||
"• платформу, на которой осуществляется трансляция.\n\n"
|
||||
"После получения данной информации оператор ответит вам в данном чате."
|
||||
)
|
||||
|
||||
|
||||
def load_env(path: str) -> None:
|
||||
"""Загружает KEY=VALUE из env-файла без внешних библиотек."""
|
||||
env_path = Path(path)
|
||||
if not env_path.exists():
|
||||
return
|
||||
|
||||
for raw_line in env_path.read_text(encoding="utf-8").splitlines():
|
||||
line = raw_line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
|
||||
key, value = line.split("=", 1)
|
||||
key = key.strip()
|
||||
value = value.strip().strip('"').strip("'")
|
||||
|
||||
if key and key not in os.environ:
|
||||
os.environ[key] = value
|
||||
|
||||
|
||||
load_env(ENV_FILE)
|
||||
|
||||
BOT_TOKEN = os.getenv("BOT_TOKEN")
|
||||
SUPPORT_CHAT_ID_RAW = os.getenv("SUPPORT_CHAT_ID", "0")
|
||||
|
||||
if not BOT_TOKEN:
|
||||
raise RuntimeError(f"Не найден BOT_TOKEN в {ENV_FILE}")
|
||||
|
||||
try:
|
||||
SUPPORT_CHAT_ID = int(SUPPORT_CHAT_ID_RAW)
|
||||
except ValueError as exc:
|
||||
raise RuntimeError("SUPPORT_CHAT_ID должен быть числом") from exc
|
||||
|
||||
if SUPPORT_CHAT_ID == 0:
|
||||
raise RuntimeError(f"Укажи реальный SUPPORT_CHAT_ID в {ENV_FILE}")
|
||||
|
||||
|
||||
logging.basicConfig(
|
||||
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
|
||||
level=logging.INFO,
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def init_db() -> None:
|
||||
db_path = Path(DB_FILE)
|
||||
db_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS message_map (
|
||||
support_message_id INTEGER PRIMARY KEY,
|
||||
user_chat_id INTEGER NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def save_mapping(support_message_id: int, user_chat_id: int) -> None:
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
conn.execute(
|
||||
"""
|
||||
INSERT OR REPLACE INTO message_map
|
||||
(support_message_id, user_chat_id)
|
||||
VALUES (?, ?)
|
||||
""",
|
||||
(support_message_id, user_chat_id),
|
||||
)
|
||||
conn.commit()
|
||||
|
||||
|
||||
def get_user_chat_id(support_message_id: int) -> int | None:
|
||||
with sqlite3.connect(DB_FILE) as conn:
|
||||
row = conn.execute(
|
||||
"""
|
||||
SELECT user_chat_id
|
||||
FROM message_map
|
||||
WHERE support_message_id = ?
|
||||
""",
|
||||
(support_message_id,),
|
||||
).fetchone()
|
||||
|
||||
return row[0] if row else None
|
||||
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
if not update.effective_message:
|
||||
return
|
||||
|
||||
await update.effective_message.reply_text(START_TEXT)
|
||||
|
||||
|
||||
async def user_message(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Получает сообщение клиента и отправляет его в чат поддержки."""
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
chat = update.effective_chat
|
||||
|
||||
if not message or not user or not chat:
|
||||
return
|
||||
|
||||
if chat.id == SUPPORT_CHAT_ID:
|
||||
return
|
||||
|
||||
username = f"@{user.username}" if user.username else "нет"
|
||||
full_name = user.full_name or "Без имени"
|
||||
|
||||
header = await context.bot.send_message(
|
||||
chat_id=SUPPORT_CHAT_ID,
|
||||
text=(
|
||||
"<b>Новое сообщение в StreamHelp</b>\n\n"
|
||||
f"<b>Клиент:</b> {full_name}\n"
|
||||
f"<b>Username:</b> {username}\n"
|
||||
f"<b>User ID:</b> <code>{user.id}</code>\n\n"
|
||||
"Ответьте <b>реплаем на сообщение клиента ниже</b>."
|
||||
),
|
||||
parse_mode=ParseMode.HTML,
|
||||
)
|
||||
|
||||
copied = await context.bot.copy_message(
|
||||
chat_id=SUPPORT_CHAT_ID,
|
||||
from_chat_id=chat.id,
|
||||
message_id=message.message_id,
|
||||
reply_to_message_id=header.message_id,
|
||||
)
|
||||
|
||||
save_mapping(copied.message_id, chat.id)
|
||||
|
||||
|
||||
async def operator_reply(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
|
||||
"""Отправляет ответ оператора из SUPPORT_CHAT_ID обратно клиенту."""
|
||||
message = update.effective_message
|
||||
chat = update.effective_chat
|
||||
|
||||
if not message or not chat or chat.id != SUPPORT_CHAT_ID:
|
||||
return
|
||||
|
||||
if not message.reply_to_message:
|
||||
return
|
||||
|
||||
replied_message_id = message.reply_to_message.message_id
|
||||
user_chat_id = get_user_chat_id(replied_message_id)
|
||||
|
||||
if not user_chat_id:
|
||||
return
|
||||
|
||||
try:
|
||||
await context.bot.copy_message(
|
||||
chat_id=user_chat_id,
|
||||
from_chat_id=SUPPORT_CHAT_ID,
|
||||
message_id=message.message_id,
|
||||
)
|
||||
except Exception:
|
||||
logger.exception(
|
||||
"Не удалось отправить ответ пользователю %s",
|
||||
user_chat_id,
|
||||
)
|
||||
await message.reply_text(
|
||||
"Не удалось отправить ответ клиенту. "
|
||||
"Возможно, пользователь заблокировал бота."
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
init_db()
|
||||
|
||||
application = Application.builder().token(BOT_TOKEN).build()
|
||||
|
||||
application.add_handler(CommandHandler("start", start))
|
||||
|
||||
# Сначала обрабатываем ответы операторов в чате поддержки.
|
||||
application.add_handler(
|
||||
MessageHandler(
|
||||
filters.Chat(chat_id=SUPPORT_CHAT_ID)
|
||||
& filters.REPLY
|
||||
& ~filters.COMMAND,
|
||||
operator_reply,
|
||||
)
|
||||
)
|
||||
|
||||
# Все сообщения пользователей, кроме сообщений из чата поддержки.
|
||||
application.add_handler(
|
||||
MessageHandler(
|
||||
~filters.Chat(chat_id=SUPPORT_CHAT_ID)
|
||||
& ~filters.COMMAND,
|
||||
user_message,
|
||||
)
|
||||
)
|
||||
|
||||
logger.info("StreamHelp support bot started")
|
||||
application.run_polling(allowed_updates=Update.ALL_TYPES)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user