feat: TenChat auto-posting via refresh token (TCRF) auth
- Rewrite tenchat_publisher to TCRF-only flow: refresh -> Bearer access_token -> publish - Store only TCRF (one-time refresh token, rotates on each publish) - Add /tenchat page (GET/POST/disconnect) with username + TCRF fields - Add publish-to-TenChat button on post publish page - Update DB helpers: save/get/update/clear tenchat credentials
This commit is contained in:
1 parent
09b22446d4
commit
849e5c472a
6 files changed
+441
-2
No files matched your search
+81
-1
@@ -20,6 +20,7 @@ from ai_core.telegram_publisher import (
|
||||
publish_post,
|
||||
resolve_chat,
|
||||
)
|
||||
from ai_core.tenchat_publisher import publish_post as publish_post_tenchat
|
||||
from cabinet_service.brand_doc_generator import generate_brand_docx
|
||||
from config import SECRET_KEY
|
||||
from database import (
|
||||
@@ -63,9 +64,13 @@ from database import (
|
||||
save_post_for_user,
|
||||
save_post_image,
|
||||
save_provider_settings,
|
||||
clear_tenchat_credentials,
|
||||
get_tenchat_credentials,
|
||||
save_tenchat_credentials,
|
||||
update_plan_status,
|
||||
update_post_content,
|
||||
update_task_status,
|
||||
update_tenchat_tokens,
|
||||
update_user_field_by_id,
|
||||
)
|
||||
|
||||
@@ -352,6 +357,48 @@ def provider_submit(
|
||||
return RedirectResponse("/provider", status_code=303)
|
||||
|
||||
|
||||
@app.get("/tenchat")
|
||||
def tenchat_form(request: Request, user: dict = Depends(current_user)):
|
||||
creds = get_tenchat_credentials(user["id"])
|
||||
return render(request, "tenchat.html", user=user, creds=creds)
|
||||
|
||||
|
||||
@app.post("/tenchat")
|
||||
def tenchat_submit(
|
||||
request: Request,
|
||||
tenchat_username: str = Form(...),
|
||||
tenchat_tcrf: str = Form(...),
|
||||
user: dict = Depends(current_user),
|
||||
):
|
||||
errors = []
|
||||
username = tenchat_username.strip().lstrip("@")
|
||||
tcrf = tenchat_tcrf.strip()
|
||||
|
||||
if not username:
|
||||
errors.append("Укажите username в TenChat.")
|
||||
if not tcrf:
|
||||
errors.append("Укажите ключ TCRF (refresh token).")
|
||||
|
||||
if errors:
|
||||
return render(
|
||||
request, "tenchat.html", status_code=400,
|
||||
user=user, creds=get_tenchat_credentials(user["id"]),
|
||||
errors=errors,
|
||||
form={"tenchat_username": username, "tenchat_tcrf": tcrf},
|
||||
)
|
||||
|
||||
save_tenchat_credentials(user["id"], username, tcrf)
|
||||
flash(request, "TenChat подключён: @%s. Refresh token сохранён." % username)
|
||||
return RedirectResponse("/tenchat", status_code=303)
|
||||
|
||||
|
||||
@app.post("/tenchat/disconnect")
|
||||
def tenchat_disconnect(request: Request, user: dict = Depends(current_user)):
|
||||
clear_tenchat_credentials(user["id"])
|
||||
flash(request, "TenChat отключён. Ключи сессии удалены.")
|
||||
return RedirectResponse("/tenchat", status_code=303)
|
||||
|
||||
|
||||
@app.get("/generate")
|
||||
def generate_form(request: Request, user: dict = Depends(current_user),
|
||||
plan_id: str = Query(""), platform: str = Query(""),
|
||||
@@ -969,8 +1016,11 @@ def publish_form(request: Request, post_id: int, user: dict = Depends(current_us
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Пост не найден")
|
||||
channels = get_channels_for_user(user["id"])
|
||||
tenchat = get_tenchat_credentials(user["id"])
|
||||
return render(request, "publish.html", user=user, post=post, channels=channels,
|
||||
platform_label=platform_label)
|
||||
platform_label=platform_label,
|
||||
tenchat_connected=bool(tenchat),
|
||||
tenchat_username=tenchat["tenchat_username"] if tenchat else None)
|
||||
|
||||
|
||||
@app.post("/posts/{post_id}/publish")
|
||||
@@ -1018,6 +1068,36 @@ def publish_submit(
|
||||
return RedirectResponse(f"/posts/{post_id}", status_code=303)
|
||||
|
||||
|
||||
@app.post("/posts/{post_id}/publish/tenchat")
|
||||
def publish_tenchat_submit(request: Request, post_id: int, user: dict = Depends(current_user)):
|
||||
post = get_post_by_id_for_user(post_id, user["id"])
|
||||
if not post:
|
||||
raise HTTPException(status_code=404, detail="Пост не найден")
|
||||
|
||||
creds = get_tenchat_credentials(user["id"])
|
||||
if not creds:
|
||||
flash(request, "❌ TenChat не подключён. Настройте ключи сессии на странице /tenchat.")
|
||||
return RedirectResponse(f"/posts/{post_id}/publish", status_code=303)
|
||||
|
||||
ok, result, new_tcrf = publish_post_tenchat(
|
||||
creds["tenchat_tcrf"],
|
||||
post["content"],
|
||||
)
|
||||
|
||||
# Обновляем TCRF (одноразовый — после каждого refresh он меняется)
|
||||
if new_tcrf:
|
||||
update_tenchat_tokens(user["id"], new_tcrf)
|
||||
|
||||
if ok:
|
||||
mark_post_published(post_id, user["id"], "TenChat")
|
||||
url = result.get("url") if isinstance(result, dict) else None
|
||||
flash(request, f"✅ Пост опубликован в TenChat" + (f": {url}" if url else ""))
|
||||
else:
|
||||
flash(request, f"❌ Ошибка публикации в TenChat: {result}")
|
||||
|
||||
return RedirectResponse(f"/posts/{post_id}", status_code=303)
|
||||
|
||||
|
||||
# === ФОНОВАЯ ГЕНЕРАЦИЯ (AJAX) ===
|
||||
|
||||
def _run_post_generation(task_id, user_id, platform, style, wishes, survey_id, plan_id=None):
|
||||
|
||||
@@ -22,5 +22,11 @@
|
||||
<a href="/provider" class="copy-btn">⚙ Настроить провайдера</a>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>TenChat</h2>
|
||||
<p>Подключите аккаунт TenChat, чтобы публиковать посты автоматически.</p>
|
||||
<a href="/tenchat" class="copy-btn">🔑 Настроить TenChat</a>
|
||||
</div>
|
||||
|
||||
<p><a href="/dashboard">← Назад в кабинет</a></p>
|
||||
{% endblock %}
|
||||
@@ -28,7 +28,7 @@
|
||||
{% endfor %}
|
||||
</select>
|
||||
</label>
|
||||
<button type="submit">📤 Опубликовать</button>
|
||||
<button type="submit">📤 Опубликовать в Telegram</button>
|
||||
</form>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
@@ -36,6 +36,22 @@
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if tenchat_connected %}
|
||||
<div class="card" style="margin-top:15px;">
|
||||
<h2>TenChat</h2>
|
||||
<p>Опубликовать этот пост в ваш профиль TenChat (@{{ tenchat_username }}).</p>
|
||||
<form method="post" action="/posts/{{ post.id }}/publish/tenchat" class="form" style="margin:0;">
|
||||
<button type="submit">📤 Опубликовать в TenChat</button>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card" style="margin-top:15px;">
|
||||
<h2>TenChat</h2>
|
||||
<p>Подключите TenChat, чтобы публиковать посты в профиль.</p>
|
||||
<a href="/tenchat" class="copy-btn">🔑 Настроить TenChat</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="post-actions">
|
||||
<a href="/posts/{{ post.id }}" class="copy-btn">← Назад к посту</a>
|
||||
<a href="/channels" class="copy-btn">⚙ Управление каналами</a>
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
{% extends "base.html" %}
|
||||
{% block title %}TenChat — Личный кабинет{% endblock %}
|
||||
{% block content %}
|
||||
<h1>Интеграция с TenChat</h1>
|
||||
<p class="form-hint">Подключите свой аккаунт TenChat, чтобы публиковать посты автоматически. Refresh token хранится только у вас и используется для публикации от вашего имени.</p>
|
||||
|
||||
{% if creds %}
|
||||
<div class="card">
|
||||
<p style="margin:0">
|
||||
<strong>Подключено:</strong> @{{ creds.tenchat_username }}
|
||||
| <span class="form-hint">подключено {{ creds.tenchat_connected_at }}</span>
|
||||
{% if creds.tenchat_last_refresh %}
|
||||
| <span class="form-hint">обновлено {{ creds.tenchat_last_refresh }}</span>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="card">
|
||||
<p style="margin:0">⚠️ TenChat не подключён. Введите refresh token ниже, чтобы публиковать посты.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<form method="post" action="/tenchat" class="form">
|
||||
<h2>🔑 Refresh token TenChat</h2>
|
||||
|
||||
<label>Username в TenChat
|
||||
<input type="text" name="tenchat_username"
|
||||
value="{{ form.tenchat_username if form else (creds.tenchat_username if creds else '') }}"
|
||||
placeholder="например, kavalar" required>
|
||||
</label>
|
||||
|
||||
<label>Ключ TCRF (refresh token)
|
||||
<input type="password" name="tenchat_tcrf"
|
||||
value="{{ form.tenchat_tcrf if form else '' }}"
|
||||
placeholder="вставьте значение куки TCRF" required>
|
||||
</label>
|
||||
|
||||
<button type="submit">💾 Сохранить ключи</button>
|
||||
</form>
|
||||
|
||||
{% if creds %}
|
||||
<form method="post" action="/tenchat/disconnect" class="form" style="margin-top:15px"
|
||||
onsubmit="return confirm('Отключить TenChat? Ключи будут удалены.');">
|
||||
<button type="submit" style="background: var(--danger, #dc3545);">🔌 Отключить TenChat</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
|
||||
<div class="card" style="margin-top:20px; background: var(--bg-secondary);">
|
||||
<h2>📖 Как получить refresh token</h2>
|
||||
<ol style="margin:5px 0 0 18px; font-size: 14px; line-height: 1.7;">
|
||||
<li>Откройте <a href="https://tenchat.ru" target="_blank" rel="noopener">tenchat.ru</a> и войдите в свой аккаунт.</li>
|
||||
<li>Нажмите <strong>F12</strong> (или <strong>Ctrl+Shift+I</strong>), чтобы открыть инструменты разработчика.</li>
|
||||
<li>Перейдите на вкладку <strong>Application</strong> (Приложение).</li>
|
||||
<li>В левом меню выберите <strong>Cookies</strong> → <strong>https://tenchat.ru</strong>.</li>
|
||||
<li>Найдите куку <strong>TCRF</strong> и скопируйте её значение.</li>
|
||||
<li>Вставьте значение в поле выше и нажмите «Сохранить ключи».</li>
|
||||
</ol>
|
||||
<p class="form-hint" style="margin-top:10px">
|
||||
⚠️ Refresh token живёт ~14 дней и автоматически обновляется при каждой публикации.
|
||||
Если публикация перестала работать — обновите ключ заново.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:20px"><a href="/profile/edit">← Назад в профиль</a> · <a href="/dashboard">← На главную</a></p>
|
||||
{% endblock %}
|
||||
Reference in new issue
Block a user