feat: Telegram bot @wasrusgen1_consulting_bot — webhook handler /start /status /help, tg_chat_id in projects

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
wasrusgen 2026-05-30 16:27:57 +03:00
parent 9b82fea46a
commit 3014b89d0f
3 changed files with 89 additions and 5 deletions

View File

@ -92,7 +92,8 @@ def init_db():
niche TEXT, niche TEXT,
description TEXT, description TEXT,
status TEXT DEFAULT 'interview', status TEXT DEFAULT 'interview',
created_at TEXT created_at TEXT,
tg_chat_id TEXT
); );
CREATE TABLE IF NOT EXISTS messages ( CREATE TABLE IF NOT EXISTS messages (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -131,6 +132,14 @@ def init_db():
accepted_at TEXT NOT NULL -- append-only, не редактируется accepted_at TEXT NOT NULL -- append-only, не редактируется
); );
""") """)
# Миграции — добавляем колонки если нет (идемпотентно)
for sql in [
"ALTER TABLE projects ADD COLUMN tg_chat_id TEXT",
]:
try:
con.execute(sql)
except Exception:
pass
con.commit() con.commit()
con.close() con.close()
@ -1092,6 +1101,81 @@ def get_project_state(token):
"documents": [json.loads(r["data_json"]) and {"filename": json.loads(r["data_json"])["filename"], "size": json.loads(r["data_json"]).get("size",0)} for r in db().execute("SELECT data_json FROM artifacts WHERE project_id=? AND kind='document' ORDER BY id", (proj["id"],)).fetchall()] "documents": [json.loads(r["data_json"]) and {"filename": json.loads(r["data_json"])["filename"], "size": json.loads(r["data_json"]).get("size",0)} for r in db().execute("SELECT data_json FROM artifacts WHERE project_id=? AND kind='document' ORDER BY id", (proj["id"],)).fetchall()]
}) })
# ── Telegram Bot ─────────────────────────────────────
TG_TOKEN = "8767209545:AAEVgfL-bAhg6j0fHUyKWUze4SLTfJbLklM"
TG_API = f"https://api.telegram.org/bot{TG_TOKEN}"
CABINET_URL = "https://wasrusgen1.ru/consulting/cabinet.html"
def tg_send(chat_id, text, reply_markup=None):
import urllib.request as ur
payload = {"chat_id": chat_id, "text": text, "parse_mode": "HTML"}
if reply_markup:
payload["reply_markup"] = json.dumps(reply_markup)
body = json.dumps(payload).encode()
req = ur.Request(f"{TG_API}/sendMessage", data=body,
headers={"Content-Type": "application/json"})
try:
ur.urlopen(req, timeout=8)
except Exception as e:
app.logger.error(f"tg_send error: {e}")
@app.route("/consulting/api/tg/webhook", methods=["POST"])
def tg_webhook():
data = request.get_json(silent=True) or {}
msg = data.get("message") or data.get("callback_query", {}).get("message")
if not msg:
return jsonify({"ok": True})
chat_id = msg["chat"]["id"]
text = (msg.get("text") or "").strip()
cmd = text.split()[0].split("@")[0].lower() if text.startswith("/") else ""
# /start [token] — открыть кабинет или прислать ссылку
if cmd == "/start":
parts = text.split()
token = parts[1] if len(parts) > 1 else None
if token:
proj = get_project(token)
if proj:
url = f"{CABINET_URL}?t={token}"
tg_send(chat_id,
f"Привет! Ваш проект: <b>{proj['client_name'] or 'без названия'}</b>\n"
f"Кабинет: {url}",
reply_markup={"inline_keyboard": [[
{"text": "Открыть кабинет", "web_app": {"url": url}}
]]}
)
return jsonify({"ok": True})
tg_send(chat_id,
"Добро пожаловать в <b>@wasrusgen1 | КОНСАЛТИНГ</b>\n\n"
"Для доступа к кабинету вам нужна персональная ссылка от консультанта.\n\n"
"Напишите нам: <a href=\"https://t.me/wasrusgen1\">@wasrusgen1</a>",
)
# /status — статус проекта по tg_id (если привязан)
elif cmd == "/status":
row = db().execute(
"SELECT token, client_name, status FROM projects WHERE tg_chat_id=? ORDER BY id DESC LIMIT 1",
(str(chat_id),)
).fetchone()
if row:
tg_send(chat_id,
f"Проект: <b>{row['client_name']}</b>\n"
f"Статус: {row['status']}\n"
f"Кабинет: {CABINET_URL}?t={row['token']}"
)
else:
tg_send(chat_id, "Привязанный проект не найден. Откройте кабинет по ссылке от консультанта.")
elif cmd == "/help":
tg_send(chat_id,
"/start — открыть кабинет\n"
"/status — статус проекта\n\n"
"По вопросам: @wasrusgen1"
)
return jsonify({"ok": True})
if __name__ == "__main__": if __name__ == "__main__":
init_db() init_db()
app.run(host="0.0.0.0", port=5002) app.run(host="0.0.0.0", port=5002)

View File

@ -2,7 +2,7 @@
# @wasrusgen1 | КОНСАЛТИНГ · ИП Васильев Р.Г. # @wasrusgen1 | КОНСАЛТИНГ · ИП Васильев Р.Г.
# Редакция от «__» __________ 20__ г. · версия 1.0 # Редакция от «__» __________ 20__ г. · версия 1.0
> ⚠️ Заполнить: 781909921730, 325784700271898, 40802810355710022284, СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК, 044030653, 30101810500000000653, [адрес], i@wasrusgen.ru, [телефон], wasrusgen1.ru/consulting > ⚠️ Заполнить: 781909921730, 325784700271898, 40802810355710022284, СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК, 044030653, 30101810500000000653, [адрес], i@wasrusgen.ru, +7 911 279-45-42, wasrusgen1.ru/consulting
> Подготовлено ⚖️ Юрием. УСН 6%. Перед публикацией — проверка профильным юристом + подать уведомление в РКН об обработке ПДн. > Подготовлено ⚖️ Юрием. УСН 6%. Перед публикацией — проверка профильным юристом + подать уведомление в РКН об обработке ПДн.
--- ---
@ -118,5 +118,5 @@
ИНН: 781909921730 · ОГРНИП: 325784700271898 ИНН: 781909921730 · ОГРНИП: 325784700271898
Адрес: [адрес] Адрес: [адрес]
Р/с: 40802810355710022284 · Банк: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК · БИК: 044030653 · К/с: 30101810500000000653 Р/с: 40802810355710022284 · Банк: СЕВЕРО-ЗАПАДНЫЙ БАНК ПАО СБЕРБАНК · БИК: 044030653 · К/с: 30101810500000000653
E-mail: i@wasrusgen.ru · Телефон: [телефон] · Сайт: wasrusgen1.ru/consulting E-mail: i@wasrusgen.ru · Телефон: +7 911 279-45-42 · Сайт: wasrusgen1.ru/consulting
УСН, объект «доходы» (НДС не облагается) УСН, объект «доходы» (НДС не облагается)

View File

@ -2,7 +2,7 @@
# @wasrusgen1 | КОНСАЛТИНГ · ИП Васильев Р.Г. # @wasrusgen1 | КОНСАЛТИНГ · ИП Васильев Р.Г.
# Редакция от «__» __________ 20__ г. · версия 1.0 # Редакция от «__» __________ 20__ г. · версия 1.0
> ⚠️ Заполнить плейсхолдеры: 781909921730, 325784700271898, [адрес], i@wasrusgen.ru, [телефон], wasrusgen1.ru/consulting > ⚠️ Заполнить плейсхолдеры: 781909921730, 325784700271898, [адрес], i@wasrusgen.ru, +7 911 279-45-42, wasrusgen1.ru/consulting
> Подготовлено ⚖️ Юрием по 63-ФЗ. Перед публикацией — проверка профильным юристом. > Подготовлено ⚖️ Юрием по 63-ФЗ. Перед публикацией — проверка профильным юристом.
--- ---
@ -94,4 +94,4 @@
8.3. Недействительность отдельного положения не влечёт недействительности остальных. 8.3. Недействительность отдельного положения не влечёт недействительности остальных.
**Исполнитель: ИП Васильев Руслан Геннадьевич** **Исполнитель: ИП Васильев Руслан Геннадьевич**
ИНН 781909921730 · ОГРНИП 325784700271898 · [адрес] · i@wasrusgen.ru · [телефон] ИНН 781909921730 · ОГРНИП 325784700271898 · [адрес] · i@wasrusgen.ru · +7 911 279-45-42