fix: make instagram auth visible and manual
This commit is contained in:
+1
-1
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user