JiMengIntl / scripts /logout-sessions.py
github-actions[bot]
Deploy from GitHub Actions
8e02bdb
#!/usr/bin/env python3
"""
即梦 AI (jimeng.jianying.com) 历史 Session 强制退出工具
==========================================================
功能:
通过 Playwright headless 浏览器模拟登录,点击"设置 → 退出"按钮,
批量使指定的历史 sessionid 在服务器端失效。
适用场景:
当 sessionid 泄露或安全审计时,强制注销不再使用的历史 cookie,
防止被未授权方继续调用 API。
使用方法:
python3 scripts/logout-sessions.py <sessionid1> [sessionid2] ...
或在脚本末尾的 SESSION_IDS 列表中填写需要退出的 sessionid,
然后直接运行:
python3 scripts/logout-sessions.py
依赖安装:
pip install playwright
playwright install chromium
注意:
- 每个 sessionid 独立启动浏览器实例执行退出,互不影响
- 已失效的 sessionid 会自动跳过
- 退出操作不可逆,请确认 sessionid 列表无误后再执行
"""
import sys
import time
import argparse
# -------------------------------------------------------
# 在此填写需要强制退出的历史 sessionid 列表
# 也可通过命令行参数传入(见使用方法)
# -------------------------------------------------------
SESSION_IDS = [
# 示例(已失效,仅作格式参考):
# "aabbddddddddddddddd",
]
def logout_session(session_id: str) -> str:
"""
对单个 sessionid 执行退出操作。
返回值:
"success" - 退出成功
"already_invalid"- sessionid 已失效,无需处理
"error_no_button"- 找不到退出按钮
"unknown" - 退出状态不确定
"error" - 发生异常
"""
from playwright.sync_api import sync_playwright
with sync_playwright() as p:
browser = p.chromium.launch(
headless=True,
args=[
"--no-sandbox",
"--disable-setuid-sandbox",
"--disable-dev-shm-usage",
],
)
context = browser.new_context(
viewport={"width": 1920, "height": 1080},
user_agent=(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
"AppleWebKit/537.36 (KHTML, like Gecko) "
"Chrome/135.0.0.0 Safari/537.36"
),
)
domain = ".jianying.com"
context.add_cookies(
[
{"name": "_tea_web_id", "value": "7619975442964235802", "domain": domain, "path": "/"},
{"name": "is_staff_user", "value": "false", "domain": domain, "path": "/"},
{"name": "store-region", "value": "cn-gd", "domain": domain, "path": "/"},
{"name": "uid_tt", "value": session_id, "domain": domain, "path": "/"},
{"name": "uid_tt_ss", "value": session_id, "domain": domain, "path": "/"},
{"name": "sid_tt", "value": session_id, "domain": domain, "path": "/"},
{"name": "sessionid", "value": session_id, "domain": domain, "path": "/"},
{"name": "sessionid_ss", "value": session_id, "domain": domain, "path": "/"},
]
)
page = context.new_page()
try:
# 导航到即梦主页,等待页面完全加载
page.goto(
"https://jimeng.jianying.com",
timeout=30000,
wait_until="networkidle",
)
time.sleep(3)
# 检查是否已登录(通过全局变量 window.__isLogined)
is_logged_in = page.evaluate("() => window.__isLogined === true")
if not is_logged_in:
return "already_invalid"
# 步骤 1:点击左侧底部设置按钮(#SiderMenuSetting)
# 该按钮点击后会弹出下拉菜单,菜单中包含"退出"选项
setting_el = page.wait_for_selector("#SiderMenuSetting", timeout=8000)
setting_el.click()
time.sleep(2)
# 步骤 2:查找弹出菜单中的"退出"按钮并点击
# 优先使用文字 selector,备选使用 class 包含文字方式
exit_btn = page.query_selector("text=退出")
if not exit_btn:
exit_btn = page.query_selector('.lv-dropdown-menu-item:has-text("退出")')
if not exit_btn:
return "error_no_button"
# 监听退出请求(可用于日志记录)
logout_requests: list[str] = []
def on_request(req):
if any(k in req.url for k in ["logout", "sign_out", "revoke", "signout"]):
logout_requests.append(req.url)
page.on("request", on_request)
# 执行点击
exit_btn.click()
time.sleep(4)
# 验证退出结果
after_state = page.evaluate("() => window.__isLogined")
current_url = page.url
if after_state is False or "login" in current_url:
return "success"
# 再次确认:检查页面是否有登录入口文字
page_content = page.content()
if "登录" in page_content[:1000]:
return "success"
return "unknown"
except Exception as e:
print(f" [异常] {e}")
return "error"
finally:
browser.close()
def main():
parser = argparse.ArgumentParser(
description="即梦 AI 历史 Session 强制退出工具",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument(
"sessions",
nargs="*",
help="需要退出的 sessionid(可传多个,空格分隔)",
)
args = parser.parse_args()
# 命令行参数优先,否则使用脚本顶部的 SESSION_IDS 列表
targets = args.sessions if args.sessions else SESSION_IDS
if not targets:
parser.print_help()
print(
"\n❌ 错误:未指定任何 sessionid。\n"
" 请通过命令行传入或在脚本顶部的 SESSION_IDS 列表中填写。"
)
sys.exit(1)
# 去重并过滤空值
targets = [s.strip() for s in targets if s.strip()]
targets = list(dict.fromkeys(targets)) # 保序去重
print(f"\n即梦 AI 历史 Session 强制退出工具")
print(f"{'=' * 60}")
print(f"共 {len(targets)} 个 sessionid 待处理\n")
STATUS_ICONS = {
"success": "✅ 退出成功(服务器端已失效)",
"already_invalid":"⬜ 已失效,无需处理",
"error_no_button":"❌ 找不到退出按钮(页面结构可能已变更)",
"unknown": "⚠️ 退出状态不确定,请手动验证",
"error": "❌ 发生异常,请查看上方错误信息",
}
results: dict[str, str] = {}
for idx, sid in enumerate(targets, 1):
print(f"[{idx}/{len(targets)}] 处理: {sid}")
result = logout_session(sid)
results[sid] = result
icon = STATUS_ICONS.get(result, result)
print(f" → {icon}\n")
time.sleep(1) # 各次请求间隔,避免触发频率限制
# 汇总报告
print(f"\n{'=' * 60}")
print("退出结果汇总:")
print(f"{'=' * 60}")
for sid, result in results.items():
icon = STATUS_ICONS.get(result, result)
print(f" {sid}{icon}")
success_count = sum(1 for r in results.values() if r == "success")
invalid_count = sum(1 for r in results.values() if r == "already_invalid")
fail_count = len(results) - success_count - invalid_count
print(f"\n ✅ 成功退出:{success_count} 个")
print(f" ⬜ 已失效: {invalid_count} 个(无需处理)")
if fail_count > 0:
print(f" ❌ 处理失败:{fail_count} 个(请手动处理)")
print()
if __name__ == "__main__":
main()