fix: make instagram auth visible and manual

This commit is contained in:
Your Name
2026-08-05 21:53:03 +05:00
parent 3b2e5a3205
commit 603eb88937
4 changed files with 91 additions and 27 deletions
+1 -1
View File
@@ -10,4 +10,4 @@ Pillow==11.3.0
python-multipart==0.0.20
uvicorn[standard]==0.35.0
yt-dlp==2026.6.9
instagrapi==2.1.2
instagrapi==2.6.9
+35 -4
View File
@@ -37,7 +37,7 @@ from .workers.tg_poster import TelegramPoster
from .workers.tg_reactor import TelegramReactor
from .workers.vk_poster import VKPoster
from .workers.vk_storage_uploader import TelegramStorageUploader
from .workers.insta_parser import InstaParserWorker, instagram_login
from .workers.insta_parser import InstaParserWorker, InstagramCodeRequired, instagram_login
COOKIE_NAME = "vk_parser_admin"
VK_OAUTH_VERIFIER_COOKIE = "vk_oauth_verifier"
@@ -3623,6 +3623,8 @@ async def workers(request: Request):
vk_schedule=await vk_poster_schedule_rows(),
category_titles=CATEGORY_TITLES,
prompt_hints=PROMPT_HINTS,
instagram_auth_status=setting_values.get("insta_auth_status") or "",
instagram_cooldown_until=setting_values.get("insta_cooldown_until") or "",
),
)
@@ -3952,7 +3954,7 @@ async def worker_toggle(request: Request, worker_name: str, csrf_token: str = Fo
@app.post("/instagram-auth/login")
async def instagram_auth_login(request: Request, csrf_token: str = Form(...)):
async def instagram_auth_login(request: Request, csrf_token: str = Form(...), verification_code: str = Form("")):
user = await get_current_user(request)
if not user:
return redirect("/login")
@@ -3966,7 +3968,7 @@ async def instagram_auth_login(request: Request, csrf_token: str = Form(...)):
status_text = "missing login or password"
else:
try:
await asyncio.to_thread(instagram_login, login, password, proxy, session_path)
await asyncio.to_thread(instagram_login, login, password, proxy, session_path, verification_code.strip())
status_text = f"ok: session saved to {session_path or 'insta_session.json'} at {datetime.now(timezone.utc).isoformat()}"
await pool.execute(
"""
@@ -3978,8 +3980,14 @@ async def instagram_auth_login(request: Request, csrf_token: str = Form(...)):
updated_at=NOW()
"""
)
except InstagramCodeRequired as exc:
status_text = f"code_required: {exc.choice}"
except Exception as exc:
status_text = f"failed: {str(exc)[:1000]}"
exc_name = exc.__class__.__name__
if exc_name in {"TwoFactorRequired", "ChallengeRequired"}:
status_text = f"code_required: {exc_name}"
else:
status_text = f"failed: {exc_name}: {str(exc)[:1000]}"
await pool.execute(
"""
INSERT INTO app_settings(key, value_json, value_type, title, description, category, updated_by)
@@ -3996,6 +4004,29 @@ async def instagram_auth_login(request: Request, csrf_token: str = Form(...)):
return redirect("/workers")
@app.post("/instagram-auth/clear-cooldown")
async def instagram_auth_clear_cooldown(request: Request, csrf_token: str = Form(...)):
user = await get_current_user(request)
if not user:
return redirect("/login")
require_csrf(user, csrf_token)
pool = await get_pool()
await pool.execute(
"""
INSERT INTO app_settings(key, value_json, value_type, title, description, category, updated_by)
VALUES('insta_cooldown_until', '""'::jsonb, 'str', 'Cooldown until', '', 'Instagram Parser', $1)
ON CONFLICT (key) DO UPDATE
SET value_json='""'::jsonb,
description='manual reset',
updated_by=$1,
updated_at=NOW()
""",
user["id"],
)
await audit(user["id"], "instagram.cooldown_reset", "setting", None)
return redirect("/workers")
@app.post("/settings/save")
async def settings_save(request: Request, csrf_token: str = Form(...), key: str = Form(...), value: str = Form(...)):
user = await get_current_user(request)
+39 -20
View File
@@ -8,25 +8,6 @@
<div class="text-app-textMuted text-sm">Управление фоновыми процессами, категориями, расписанием и настройками AI.</div>
</div>
<div class="card mb-8">
<div class="p-4 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div>
<div class="font-bold text-white flex items-center gap-2">
<i data-lucide="instagram" class="w-5 h-5 text-pink-400"></i>
Instagram
</div>
<div class="text-xs text-app-textMuted mt-1">Session-файл: настройка <span class="font-mono">insta_session_path</span></div>
</div>
<form method="post" action="/instagram-auth/login" class="m-0">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<button class="btn btn-primary btn-sm" type="submit">
<i data-lucide="key-round" class="w-4 h-4"></i>
Войти и сохранить session
</button>
</form>
</div>
</div>
<!-- Workers Panel -->
<div class="card overflow-hidden mb-8">
<div class="overflow-x-auto">
@@ -313,12 +294,50 @@
<div class="flex flex-col gap-6">
{% for category, rows in settings|groupby("category") %}
<details class="card group/details" data-workers-details="settings:{{ category }}">
<details class="card group/details" data-workers-details="settings:{{ category }}" {% if category == "Instagram Parser" %}open{% endif %}>
<summary class="p-4 flex items-center justify-between cursor-pointer select-none hover:bg-app-surfaceHover transition-colors border-b border-app-border list-none">
<div class="text-lg font-bold text-white">{{ category_titles.get(category, category) }}</div>
<i data-lucide="chevron-down" class="w-5 h-5 text-app-textMuted transition-transform group-open/details:rotate-180"></i>
</summary>
<div class="p-6 bg-app-bg/30">
{% if category == "Instagram Parser" %}
<div class="mb-8 p-5 bg-app-surface border border-app-border rounded-xl flex flex-col gap-4">
<div class="flex flex-col lg:flex-row lg:items-start lg:justify-between gap-4">
<div class="min-w-0">
<div class="font-bold text-white flex items-center gap-2">
<i data-lucide="instagram" class="w-5 h-5 text-pink-400"></i>
Авторизация Instagram
</div>
<div class="text-xs text-app-textMuted mt-2 break-words">
Статус: <span class="font-mono text-app-textMain">{{ instagram_auth_status or "—" }}</span>
</div>
{% if instagram_cooldown_until %}
<div class="text-xs text-app-warning mt-2 break-words">
Cooldown: <span class="font-mono">{{ instagram_cooldown_until }}</span>
</div>
{% endif %}
</div>
<form method="post" action="/instagram-auth/clear-cooldown" class="m-0">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<button class="btn btn-surface btn-sm" type="submit">
<i data-lucide="timer-reset" class="w-4 h-4"></i>
Сбросить cooldown
</button>
</form>
</div>
<form method="post" action="/instagram-auth/login" class="grid grid-cols-1 md:grid-cols-[1fr_auto] gap-3 items-end">
<input type="hidden" name="csrf_token" value="{{ user.csrf_token }}">
<div>
<label class="block text-[10px] uppercase font-bold text-app-textMuted mb-1">Код из SMS/email/2FA, если Instagram его просит</label>
<input name="verification_code" class="input w-full font-mono" autocomplete="one-time-code" placeholder="Оставь пустым для первой попытки">
</div>
<button class="btn btn-primary" type="submit">
<i data-lucide="key-round" class="w-4 h-4"></i>
Войти
</button>
</form>
</div>
{% endif %}
<div class="flex flex-col gap-8">
{% for s in rows %}
+16 -2
View File
@@ -113,6 +113,12 @@ class InstagramAuthRequired(Exception):
pass
class InstagramCodeRequired(Exception):
def __init__(self, choice: Any) -> None:
self.choice = choice
super().__init__(f"Instagram requested verification code: {choice}")
def load_instagrapi():
from instagrapi import Client
import instagrapi.exceptions as exc
@@ -126,14 +132,22 @@ def load_instagrapi():
}
def instagram_login(login: str, password: str, proxy: str, session_path: str) -> None:
def instagram_login(login: str, password: str, proxy: str, session_path: str, verification_code: str = "") -> None:
Client, _ = load_instagrapi()
client = Client()
if proxy:
client.set_proxy(proxy)
if verification_code:
client.challenge_code_handler = lambda username, choice: verification_code
else:
def challenge_code_handler(username: str, choice: Any) -> str:
raise InstagramCodeRequired(choice)
client.challenge_code_handler = challenge_code_handler
path = Path(session_path or "insta_session.json")
path.parent.mkdir(parents=True, exist_ok=True)
client.login(login, password)
login_kwargs = {"verification_code": verification_code} if verification_code else {}
client.login(login, password, **login_kwargs)
client.dump_settings(path)