Files
account-manager/scripts/account.py
chendelian 01d9c82f80
All checks were successful
技能自动化发布 / release (push) Successful in 14s
chore: auto release commit (2026-03-31 11:50:55)
2026-03-31 11:50:56 +08:00

281 lines
8.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sys
import json
import os
import sqlite3
import subprocess
from datetime import datetime
# Windows GBK 编码兼容修复
if sys.platform == "win32":
import io
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8', errors='replace')
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding='utf-8', errors='replace')
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
SKILL_SLUG = "account-manager"
LEGACY_ACCOUNTS_FILE = os.path.join(BASE_DIR, "accounts.json")
PLATFORM_URLS = {
# 自媒体/图文平台
"sohu": "https://mp.sohu.com",
"zhihu": "https://www.zhihu.com",
"wechat": "https://mp.weixin.qq.com",
# 大模型平台
"kimi": "https://kimi.moonshot.cn",
"deepseek": "https://chat.deepseek.com",
"doubao": "https://www.doubao.com",
"qianwen": "https://tongyi.aliyun.com",
"yiyan": "https://yiyan.baidu.com",
"yuanbao": "https://yuanbao.tencent.com"
}
def get_data_root():
return (os.getenv("JIANGCHANG_DATA_ROOT") or os.path.join(os.path.expanduser("~"), ".jiangchang-data")).strip()
def get_user_id():
uid = (os.getenv("JIANGCHANG_USER_ID") or os.getenv("OPENCLAW_USER_ID") or "").strip()
return uid or "_anon"
def get_skill_data_dir():
path = os.path.join(get_data_root(), get_user_id(), SKILL_SLUG)
os.makedirs(path, exist_ok=True)
return path
def get_db_path():
return os.path.join(get_skill_data_dir(), "account-manager.db")
def get_default_profile_dir(account_id):
path = os.path.join(get_skill_data_dir(), "profiles", account_id)
os.makedirs(path, exist_ok=True)
return path
def get_conn():
return sqlite3.connect(get_db_path())
def init_db():
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS accounts (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
platform TEXT NOT NULL,
phone TEXT,
profile_dir TEXT,
extra_json TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
)
"""
)
conn.commit()
finally:
conn.close()
def migrate_legacy_json_if_needed():
init_db()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute("SELECT COUNT(1) FROM accounts")
count = cur.fetchone()[0]
if count > 0:
return
finally:
conn.close()
if not os.path.exists(LEGACY_ACCOUNTS_FILE):
return
try:
with open(LEGACY_ACCOUNTS_FILE, "r", encoding="utf-8") as f:
raw = json.load(f)
except Exception:
return
now = datetime.now().isoformat(timespec="seconds")
rows = []
for platform, accounts in (raw or {}).items():
for acc in accounts:
acc_id = str(acc.get("id") or "").strip()
if not acc_id:
continue
profile_dir = (acc.get("profile_dir") or "").strip() or get_default_profile_dir(acc_id)
base = {
"id": acc_id,
"name": str(acc.get("name") or acc_id),
"platform": str(acc.get("platform") or platform),
"phone": str(acc.get("phone") or ""),
"profile_dir": profile_dir,
}
extra = {k: v for k, v in acc.items() if k not in base}
rows.append(
(
base["id"],
base["name"],
base["platform"],
base["phone"],
base["profile_dir"],
json.dumps(extra, ensure_ascii=False) if extra else None,
now,
now,
)
)
if not rows:
return
conn = get_conn()
try:
cur = conn.cursor()
cur.executemany(
"""
INSERT OR REPLACE INTO accounts
(id, name, platform, phone, profile_dir, extra_json, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""",
rows,
)
conn.commit()
finally:
conn.close()
def get_account_by_id(account_id):
migrate_legacy_json_if_needed()
conn = get_conn()
try:
cur = conn.cursor()
cur.execute(
"SELECT id, name, platform, phone, profile_dir, extra_json FROM accounts WHERE id = ?",
(account_id,),
)
row = cur.fetchone()
if not row:
return None
acc = {
"id": row[0],
"name": row[1],
"platform": row[2],
"phone": row[3] or "",
"profile_dir": row[4] or get_default_profile_dir(row[0]),
}
if row[5]:
try:
extra = json.loads(row[5])
if isinstance(extra, dict):
acc.update(extra)
except Exception:
pass
return acc
finally:
conn.close()
def cmd_list(platform="all"):
"""
修改后的 list 功能能直观打出带着【手机号】的详细台账供AI查询匹配
支持查全部(all),或指定(sohu)
"""
migrate_legacy_json_if_needed()
conn = get_conn()
found = False
try:
cur = conn.cursor()
if platform == "all":
cur.execute("SELECT id, name, phone, platform FROM accounts ORDER BY platform, id")
else:
cur.execute(
"SELECT id, name, phone, platform FROM accounts WHERE platform = ? ORDER BY id",
(platform,),
)
rows = cur.fetchall()
finally:
conn.close()
found = False
for row in rows:
phone = row[2] or "未绑定手机"
print(f"账号ID:{row[0]} | 名称:{row[1]} | 手机号:{phone} | 平台:{row[3]}")
found = True
if not found:
print(f"ERROR:NO_ACCOUNTS_FOUND")
def cmd_get(account_id):
acc = get_account_by_id(account_id)
if acc:
print(json.dumps(acc, ensure_ascii=False))
return
print("ERROR:ACCOUNT_NOT_FOUND")
def cmd_login(account_id):
"""首次登录打开浏览器用户手动登录登录态自动保存到Profile目录"""
target = get_account_by_id(account_id)
if not target:
print("ERROR:ACCOUNT_NOT_FOUND")
return
profile_dir = target["profile_dir"]
os.makedirs(profile_dir, exist_ok=True)
url = PLATFORM_URLS.get(target["platform"], "https://www.google.com")
print(f"正在为账号 [{target['name']}] 打开浏览器...")
print(f"请在浏览器中完成登录,登录后直接关闭浏览器窗口即可。")
print(f"登录态将自动保存到:{profile_dir}")
# 用Playwright打开持久化浏览器等待用户手动登录
login_script = f"""
import asyncio
from playwright.async_api import async_playwright
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch_persistent_context(
user_data_dir=r"{profile_dir}",
headless=False,
channel="chrome", # 【修改处】也加上这行
no_viewport=True, # 确保浏览器内容视口也最大化
args=["--start-maximized"]
)
page = browser.pages[0] if browser.pages else await browser.new_page()
await page.goto("{url}")
print("浏览器已打开,请完成登录后关闭窗口...")
# 移除自动超时,无限等待直到用户扫码完手动关闭
await browser.wait_for_event("close", timeout=0)
print("登录态已保存!")
asyncio.run(main())
"""
import tempfile
with tempfile.NamedTemporaryFile(mode='w', suffix='.py',
delete=False, encoding='utf-8') as f:
f.write(login_script)
tmp_path = f.name
os.system(f'python "{tmp_path}"')
os.unlink(tmp_path)
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法python account.py list [platform] | get <id> | login <id>")
sys.exit(1)
cmd = sys.argv[1]
if cmd == "list":
# 默认不传 platform 时打出 all
cmd_list(sys.argv[2] if len(sys.argv) >= 3 else "all")
elif cmd == "get" and len(sys.argv) >= 3:
cmd_get(sys.argv[2])
elif cmd == "login" and len(sys.argv) >= 3:
cmd_login(sys.argv[2])
else:
print("参数错误")
sys.exit(1)