myagent-ai 1.30.1 → 1.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/aiskills/browser_stealth.py +153 -13
- package/aiskills/registry.py +3 -0
- package/aiskills/site-operations/SKILL.md +150 -0
- package/core/browser_profile.py +50 -53
- package/core/site_registry.py +773 -0
- package/main.py +6 -1
- package/package.json +1 -1
- package/scripts/cli.py +179 -2
|
@@ -669,15 +669,15 @@ class StealthBrowserStartSkill(Skill):
|
|
|
669
669
|
name = "stealth_browser_start"
|
|
670
670
|
description = (
|
|
671
671
|
"启动反检测浏览器 — 使用 DrissionPage 启动带有反检测能力的 Chromium 浏览器,"
|
|
672
|
-
"
|
|
673
|
-
"
|
|
672
|
+
"适合需要登录的网站操作。每个网站使用独立 Profile,登录状态自动保持。"
|
|
673
|
+
"使用 site-manage list 查看所有可用网站。"
|
|
674
674
|
)
|
|
675
675
|
category = "browser_stealth"
|
|
676
676
|
parameters = [
|
|
677
677
|
SkillParameter(
|
|
678
678
|
name="profile",
|
|
679
679
|
type="string",
|
|
680
|
-
description="Profile
|
|
680
|
+
description="Profile 名称(使用 site-manage list 查看可用网站)",
|
|
681
681
|
required=False,
|
|
682
682
|
default="default",
|
|
683
683
|
),
|
|
@@ -692,7 +692,8 @@ class StealthBrowserStartSkill(Skill):
|
|
|
692
692
|
dangerous = False
|
|
693
693
|
|
|
694
694
|
async def execute(self, profile: str = "default", headless: bool = False, **kw) -> SkillResult:
|
|
695
|
-
|
|
695
|
+
# Bug Fix: get_stealth_browser 是同步函数,不能用 await
|
|
696
|
+
browser = get_stealth_browser(profile_name=profile, headless=headless)
|
|
696
697
|
return await browser.start()
|
|
697
698
|
|
|
698
699
|
|
|
@@ -708,7 +709,7 @@ class StealthBrowserNavigateSkill(Skill):
|
|
|
708
709
|
]
|
|
709
710
|
|
|
710
711
|
async def execute(self, url: str, profile: str = "default", **kw) -> SkillResult:
|
|
711
|
-
browser =
|
|
712
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
712
713
|
if not browser._started:
|
|
713
714
|
r = await browser.start()
|
|
714
715
|
if not r.success:
|
|
@@ -728,7 +729,7 @@ class StealthBrowserClickSkill(Skill):
|
|
|
728
729
|
]
|
|
729
730
|
|
|
730
731
|
async def execute(self, selector: str, profile: str = "default", **kw) -> SkillResult:
|
|
731
|
-
browser =
|
|
732
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
732
733
|
return await browser.click(selector)
|
|
733
734
|
|
|
734
735
|
|
|
@@ -748,7 +749,7 @@ class StealthBrowserFillSkill(Skill):
|
|
|
748
749
|
async def execute(
|
|
749
750
|
self, selector: str, value: str, clear: bool = True, profile: str = "default", **kw
|
|
750
751
|
) -> SkillResult:
|
|
751
|
-
browser =
|
|
752
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
752
753
|
return await browser.fill(selector, value, clear=clear)
|
|
753
754
|
|
|
754
755
|
|
|
@@ -764,7 +765,7 @@ class StealthBrowserScreenshotSkill(Skill):
|
|
|
764
765
|
]
|
|
765
766
|
|
|
766
767
|
async def execute(self, save_path: str = "", profile: str = "default", **kw) -> SkillResult:
|
|
767
|
-
browser =
|
|
768
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
768
769
|
return await browser.screenshot(save_path=save_path)
|
|
769
770
|
|
|
770
771
|
|
|
@@ -780,7 +781,7 @@ class StealthBrowserEvalSkill(Skill):
|
|
|
780
781
|
]
|
|
781
782
|
|
|
782
783
|
async def execute(self, script: str, profile: str = "default", **kw) -> SkillResult:
|
|
783
|
-
browser =
|
|
784
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
784
785
|
return await browser.evaluate(script)
|
|
785
786
|
|
|
786
787
|
|
|
@@ -795,7 +796,7 @@ class StealthBrowserGetContentSkill(Skill):
|
|
|
795
796
|
]
|
|
796
797
|
|
|
797
798
|
async def execute(self, profile: str = "default", **kw) -> SkillResult:
|
|
798
|
-
browser =
|
|
799
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
799
800
|
return await browser.get_content()
|
|
800
801
|
|
|
801
802
|
|
|
@@ -821,7 +822,7 @@ class StealthBrowserCookieSkill(Skill):
|
|
|
821
822
|
]
|
|
822
823
|
|
|
823
824
|
async def execute(self, action: str, profile: str = "default", **kw) -> SkillResult:
|
|
824
|
-
browser =
|
|
825
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
825
826
|
if action == "save":
|
|
826
827
|
return await browser.save_cookies()
|
|
827
828
|
elif action == "load":
|
|
@@ -881,7 +882,7 @@ class StealthBrowserWaitManualSkill(Skill):
|
|
|
881
882
|
async def execute(
|
|
882
883
|
self, reason: str = "验证码", timeout: int = 300, profile: str = "default", **kw
|
|
883
884
|
) -> SkillResult:
|
|
884
|
-
browser =
|
|
885
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
885
886
|
return await browser.wait_for_manual(reason=reason, timeout=float(timeout))
|
|
886
887
|
|
|
887
888
|
|
|
@@ -900,10 +901,149 @@ class StealthBrowserWaitSkill(Skill):
|
|
|
900
901
|
async def execute(
|
|
901
902
|
self, selector: str, timeout: int = 10, profile: str = "default", **kw
|
|
902
903
|
) -> SkillResult:
|
|
903
|
-
browser =
|
|
904
|
+
browser = get_stealth_browser(profile_name=profile)
|
|
904
905
|
return await browser.wait_for(selector, timeout=float(timeout))
|
|
905
906
|
|
|
906
907
|
|
|
908
|
+
# ── Site Registry 管理技能 (v1.31.0) ────────────────────
|
|
909
|
+
|
|
910
|
+
|
|
911
|
+
class SiteManageListSkill(Skill):
|
|
912
|
+
"""列出所有已注册网站"""
|
|
913
|
+
|
|
914
|
+
name = "site_manage_list"
|
|
915
|
+
description = "列出所有已注册网站 — 显示网站名称、分类、登录 URL 和 Profile 状态"
|
|
916
|
+
category = "browser_stealth"
|
|
917
|
+
parameters = [
|
|
918
|
+
SkillParameter(
|
|
919
|
+
name="category",
|
|
920
|
+
type="string",
|
|
921
|
+
description="按分类筛选(空=全部)",
|
|
922
|
+
required=False,
|
|
923
|
+
default="",
|
|
924
|
+
),
|
|
925
|
+
]
|
|
926
|
+
|
|
927
|
+
async def execute(self, category: str = "", **kw) -> SkillResult:
|
|
928
|
+
from core.site_registry import get_site_registry
|
|
929
|
+
reg = get_site_registry()
|
|
930
|
+
sites = reg.list_sites(category=category)
|
|
931
|
+
summary = reg.to_summary()
|
|
932
|
+
|
|
933
|
+
return SkillResult(
|
|
934
|
+
success=True,
|
|
935
|
+
message=f"共 {len(sites)} 个网站" + (f" (分类: {category})" if category else ""),
|
|
936
|
+
data={"sites": sites, "summary": {k: v for k, v in summary.items() if k != "sites"}},
|
|
937
|
+
output=json.dumps(sites, ensure_ascii=False, indent=2),
|
|
938
|
+
)
|
|
939
|
+
|
|
940
|
+
|
|
941
|
+
class SiteManageShowSkill(Skill):
|
|
942
|
+
"""显示网站详细配置"""
|
|
943
|
+
|
|
944
|
+
name = "site_manage_show"
|
|
945
|
+
description = "显示网站详细配置 — 查看网站的登录 URL、可用页面和操作提示"
|
|
946
|
+
category = "browser_stealth"
|
|
947
|
+
parameters = [
|
|
948
|
+
SkillParameter(name="name", type="string", description="网站名称", required=True),
|
|
949
|
+
]
|
|
950
|
+
|
|
951
|
+
async def execute(self, name: str, **kw) -> SkillResult:
|
|
952
|
+
from core.site_registry import get_site_registry
|
|
953
|
+
reg = get_site_registry()
|
|
954
|
+
site = reg.get_site(name)
|
|
955
|
+
if not site:
|
|
956
|
+
return SkillResult(success=False, error=f"网站不存在: {name}")
|
|
957
|
+
|
|
958
|
+
return SkillResult(
|
|
959
|
+
success=True,
|
|
960
|
+
message=f"网站: {site.get('display_name', name)}",
|
|
961
|
+
data=site,
|
|
962
|
+
output=json.dumps(site, ensure_ascii=False, indent=2),
|
|
963
|
+
)
|
|
964
|
+
|
|
965
|
+
|
|
966
|
+
class SiteManageAddSkill(Skill):
|
|
967
|
+
"""添加自定义网站"""
|
|
968
|
+
|
|
969
|
+
name = "site_manage_add"
|
|
970
|
+
description = (
|
|
971
|
+
"添加自定义网站 — 为新网站创建配置,后续可用 stealth-open 登录。"
|
|
972
|
+
"需提供 name(英文标识)、login_url(登录页)和 display_name(显示名称)。"
|
|
973
|
+
)
|
|
974
|
+
category = "browser_stealth"
|
|
975
|
+
parameters = [
|
|
976
|
+
SkillParameter(name="name", type="string", description="网站名称(英文,如 my_site)", required=True),
|
|
977
|
+
SkillParameter(name="display_name", type="string", description="显示名称", required=False, default=""),
|
|
978
|
+
SkillParameter(name="login_url", type="string", description="登录页 URL", required=False, default=""),
|
|
979
|
+
SkillParameter(name="home_url", type="string", description="首页 URL", required=False, default=""),
|
|
980
|
+
SkillParameter(name="category", type="string", description="分类", required=False, default="custom"),
|
|
981
|
+
]
|
|
982
|
+
|
|
983
|
+
async def execute(
|
|
984
|
+
self, name: str, display_name: str = "", login_url: str = "",
|
|
985
|
+
home_url: str = "", category: str = "custom", **kw
|
|
986
|
+
) -> SkillResult:
|
|
987
|
+
from core.site_registry import get_site_registry
|
|
988
|
+
reg = get_site_registry()
|
|
989
|
+
try:
|
|
990
|
+
site = reg.add_site({
|
|
991
|
+
"name": name,
|
|
992
|
+
"display_name": display_name or name,
|
|
993
|
+
"login_url": login_url,
|
|
994
|
+
"home_url": home_url,
|
|
995
|
+
"category": category,
|
|
996
|
+
})
|
|
997
|
+
return SkillResult(
|
|
998
|
+
success=True,
|
|
999
|
+
message=f"已添加网站: {name}",
|
|
1000
|
+
data=site,
|
|
1001
|
+
)
|
|
1002
|
+
except ValueError as e:
|
|
1003
|
+
return SkillResult(success=False, error=str(e))
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
class SiteManageRemoveSkill(Skill):
|
|
1007
|
+
"""删除自定义网站"""
|
|
1008
|
+
|
|
1009
|
+
name = "site_manage_remove"
|
|
1010
|
+
description = "删除自定义网站(内置网站不可删除)"
|
|
1011
|
+
category = "browser_stealth"
|
|
1012
|
+
parameters = [
|
|
1013
|
+
SkillParameter(name="name", type="string", description="网站名称", required=True),
|
|
1014
|
+
]
|
|
1015
|
+
|
|
1016
|
+
async def execute(self, name: str, **kw) -> SkillResult:
|
|
1017
|
+
from core.site_registry import get_site_registry
|
|
1018
|
+
reg = get_site_registry()
|
|
1019
|
+
ok = reg.remove_site(name)
|
|
1020
|
+
if ok:
|
|
1021
|
+
return SkillResult(success=True, message=f"已删除网站: {name}")
|
|
1022
|
+
return SkillResult(
|
|
1023
|
+
success=False,
|
|
1024
|
+
error=f"删除失败: {name}(内置网站不可删除,或网站不存在)",
|
|
1025
|
+
)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
class SiteManageInitSkill(Skill):
|
|
1029
|
+
"""批量初始化网站 Profile"""
|
|
1030
|
+
|
|
1031
|
+
name = "site_manage_init"
|
|
1032
|
+
description = "批量初始化所有网站的浏览器 Profile(仅创建目录,不自动登录)"
|
|
1033
|
+
category = "browser_stealth"
|
|
1034
|
+
parameters = []
|
|
1035
|
+
|
|
1036
|
+
async def execute(self, **kw) -> SkillResult:
|
|
1037
|
+
from core.site_registry import get_site_registry
|
|
1038
|
+
reg = get_site_registry()
|
|
1039
|
+
initialized = reg.init_profiles()
|
|
1040
|
+
return SkillResult(
|
|
1041
|
+
success=True,
|
|
1042
|
+
message=f"已初始化 {len(initialized)} 个网站的 Profile",
|
|
1043
|
+
data={"initialized": initialized, "count": len(initialized)},
|
|
1044
|
+
)
|
|
1045
|
+
|
|
1046
|
+
|
|
907
1047
|
# ── Profile 管理技能 ──────────────────────────────────────
|
|
908
1048
|
|
|
909
1049
|
|
package/aiskills/registry.py
CHANGED
|
@@ -206,6 +206,9 @@ CLI_COMMANDS: List[Dict[str, Any]] = [
|
|
|
206
206
|
"description": "创建浏览器 Profile"},
|
|
207
207
|
{"name": "profile-delete", "category": "stealth", "cli": "myagent-ai profile-delete <name>",
|
|
208
208
|
"description": "删除浏览器 Profile"},
|
|
209
|
+
# ── Site 管理 (v1.31.0) ──
|
|
210
|
+
{"name": "site-manage", "category": "stealth", "cli": "myagent-ai site-manage <list|show|add|remove|init> [args]",
|
|
211
|
+
"description": "网站注册管理 — 列出/查看/添加/删除/初始化网站配置(支持 20+ 内置网站和动态扩展)"},
|
|
209
212
|
# ── GUI 桌面 ──
|
|
210
213
|
{"name": "screenshot", "category": "gui", "cli": "myagent-ai screenshot [region] [-m monitor]",
|
|
211
214
|
"description": "屏幕截图(仅 Windows/macOS)"},
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: site-operations
|
|
3
|
+
description: |
|
|
4
|
+
统一网站操作指南 — 使用反检测浏览器(DrissionPage)登录和操作任何网站。
|
|
5
|
+
覆盖邮箱、社交媒体、云服务、AI 对话平台等 20+ 网站,支持动态扩展。
|
|
6
|
+
metadata:
|
|
7
|
+
category: site_operation
|
|
8
|
+
version: "1.31.0"
|
|
9
|
+
requires: ["stealth-browser"]
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# 统一网站操作指南
|
|
13
|
+
|
|
14
|
+
## 概述
|
|
15
|
+
|
|
16
|
+
本指南使用反检测浏览器(stealth browser)登录和操作各种网站。
|
|
17
|
+
所有网站共享同一套命令接口(stealth-*),只是 Profile 名称和 URL 不同。
|
|
18
|
+
|
|
19
|
+
## 支持的网站
|
|
20
|
+
|
|
21
|
+
使用 `myagent-ai site-manage list` 查看所有可用网站。
|
|
22
|
+
|
|
23
|
+
### 分类一览
|
|
24
|
+
|
|
25
|
+
| 分类 | 网站 | Profile 名称 |
|
|
26
|
+
|------|------|-------------|
|
|
27
|
+
| 邮箱 | Gmail, QQ邮箱, 139邮箱 | gmail, qq_mail, mail139 |
|
|
28
|
+
| 社交 | X.com, 微博, 微信网页版, 微信公众号 | x_com, weibo, wechat_web, wechat_mp |
|
|
29
|
+
| 云服务 | 百度网盘 | baidu_netdisk |
|
|
30
|
+
| AI 对话 | Gemini, DeepSeek, z.ai, 智谱清言, 豆包, Kimi, 腾讯元宝, 通义千问 | gemini, deepseek, z_ai, zhipu, doubao, kimi, tencent_yuanbao, qianwen |
|
|
31
|
+
| 开发 | GitHub, Google 账号 | github, google |
|
|
32
|
+
| 内容 | 抖音, 哔哩哔哩 | douyin, bilibili |
|
|
33
|
+
|
|
34
|
+
## 通用操作流程
|
|
35
|
+
|
|
36
|
+
### 1. 首次登录(通用模板)
|
|
37
|
+
|
|
38
|
+
任何网站的首次登录流程都遵循以下模板:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# 第1步:查看网站配置
|
|
42
|
+
myagent-ai site-manage show <profile_name>
|
|
43
|
+
|
|
44
|
+
# 第2步:启动浏览器(有头模式,方便观察和手动操作)
|
|
45
|
+
myagent-ai stealth-open <profile_name> --no-headless
|
|
46
|
+
|
|
47
|
+
# 第3步:导航到登录页
|
|
48
|
+
myagent-ai stealth-navigate <login_url> -p <profile_name>
|
|
49
|
+
|
|
50
|
+
# 第4步:尝试自动填写(如果页面允许)
|
|
51
|
+
myagent-ai stealth-screenshot -p <profile_name>
|
|
52
|
+
myagent-ai stealth-fill '<用户名选择器>' '用户名' -p <profile_name>
|
|
53
|
+
myagent-ai stealth-click '<下一步按钮选择器>' -p <profile_name>
|
|
54
|
+
myagent-ai stealth-wait '<密码输入框选择器>' -p <profile_name>
|
|
55
|
+
myagent-ai stealth-fill '<密码选择器>' '密码' -p <profile_name>
|
|
56
|
+
myagent-ai stealth-click '<登录按钮选择器>' -p <profile_name>
|
|
57
|
+
|
|
58
|
+
# 第5步:如果出现验证码/2FA,等待用户手动操作
|
|
59
|
+
myagent-ai stealth-wait-manual "验证码或安全验证" -t 300 -p <profile_name>
|
|
60
|
+
|
|
61
|
+
# 第6步:确认登录成功并保存 Cookie
|
|
62
|
+
myagent-ai stealth-cookies save -p <profile_name>
|
|
63
|
+
myagent-ai stealth-close <profile_name>
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 2. 后续操作(已登录)
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# 启动浏览器(自动恢复登录状态)
|
|
70
|
+
myagent-ai stealth-open <profile_name> --no-headless
|
|
71
|
+
|
|
72
|
+
# 导航到目标页面
|
|
73
|
+
myagent-ai stealth-navigate <目标URL> -p <profile_name>
|
|
74
|
+
|
|
75
|
+
# 等待页面加载
|
|
76
|
+
myagent-ai stealth-wait '<关键元素选择器>' -t 15 -p <profile_name>
|
|
77
|
+
|
|
78
|
+
# 截图确认
|
|
79
|
+
myagent-ai stealth-screenshot -p <profile_name>
|
|
80
|
+
|
|
81
|
+
# 获取页面内容
|
|
82
|
+
myagent-ai stealth-content -p <profile_name>
|
|
83
|
+
|
|
84
|
+
# 点击操作
|
|
85
|
+
myagent-ai stealth-click '<选择器>' -p <profile_name>
|
|
86
|
+
|
|
87
|
+
# 填写输入
|
|
88
|
+
myagent-ai stealth-fill '<选择器>' '内容' -p <profile_name>
|
|
89
|
+
|
|
90
|
+
# 执行 JavaScript(高级操作)
|
|
91
|
+
myagent-ai stealth-eval 'document.title' -p <profile_name>
|
|
92
|
+
|
|
93
|
+
# 完成后关闭
|
|
94
|
+
myagent-ai stealth-close <profile_name>
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
## 查看网站详情
|
|
98
|
+
|
|
99
|
+
使用 `site-manage show` 查看具体网站的登录 URL、可用页面和操作提示:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# 查看 Gmail 的配置
|
|
103
|
+
myagent-ai site-manage show gmail
|
|
104
|
+
|
|
105
|
+
# 查看 DeepSeek 的配置
|
|
106
|
+
myagent-ai site-manage show deepseek
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 动态管理网站
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
# 列出所有网站
|
|
113
|
+
myagent-ai site-manage list
|
|
114
|
+
|
|
115
|
+
# 按分类查看
|
|
116
|
+
myagent-ai site-manage list --category ai_chat
|
|
117
|
+
|
|
118
|
+
# 添加自定义网站
|
|
119
|
+
myagent-ai site-manage add my_site --display "我的网站" --login-url "https://example.com/login" --home-url "https://example.com/dashboard"
|
|
120
|
+
|
|
121
|
+
# 更新网站配置
|
|
122
|
+
myagent-ai site-manage update my_site --display "新名称"
|
|
123
|
+
|
|
124
|
+
# 删除自定义网站(内置网站不可删除)
|
|
125
|
+
myagent-ai site-manage remove my_site
|
|
126
|
+
|
|
127
|
+
# 批量初始化所有网站的 Profile
|
|
128
|
+
myagent-ai site-manage init
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## 选择器技巧
|
|
132
|
+
|
|
133
|
+
- CSS 选择器: `input[type="email"]`, `button.submit`, `div#main`
|
|
134
|
+
- 文本匹配: `button:contains("登录")`, `a:contains("下一步")`
|
|
135
|
+
- 属性匹配: `input[name="password"]`, `[data-testid="login-btn"]`
|
|
136
|
+
- XPath: `//div[@class="login-form"]//input`
|
|
137
|
+
|
|
138
|
+
不确定选择器时,先用 `stealth-screenshot` 截图查看页面结构,再用 `stealth-eval` 测试选择器:
|
|
139
|
+
```bash
|
|
140
|
+
myagent-ai stealth-eval "document.querySelectorAll('button').forEach((b,i) => console.log(i, b.textContent))" -p <profile>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## 重要提示
|
|
144
|
+
|
|
145
|
+
1. **反检测模式**:stealth browser 使用 DrissionPage,自动隐藏 webdriver 标志
|
|
146
|
+
2. **Profile 隔离**:每个网站独立 Profile,Cookie/Session 互不干扰
|
|
147
|
+
3. **验证码处理**:遇到验证码时使用 `stealth-wait-manual`,通过 VNC/Web 手动处理
|
|
148
|
+
4. **操作间隔**:建议操作间等待 2-3 秒,避免被网站检测为自动化
|
|
149
|
+
5. **Cookie 持久化**:登录后执行 `stealth-cookies save`,下次自动恢复
|
|
150
|
+
6. **有头模式**:登录场景建议用 `--no-headless`,方便观察和手动干预
|
package/core/browser_profile.py
CHANGED
|
@@ -41,54 +41,48 @@ from core.logger import get_logger
|
|
|
41
41
|
|
|
42
42
|
logger = get_logger("myagent.core.browser_profile")
|
|
43
43
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
"
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
"github": {
|
|
87
|
-
"display_name": "GitHub",
|
|
88
|
-
"login_url": "https://github.com/login",
|
|
89
|
-
"detect_url": "https://github.com/",
|
|
90
|
-
},
|
|
91
|
-
}
|
|
44
|
+
|
|
45
|
+
# ════════════════════════════════════════════════════════════
|
|
46
|
+
# 预置 Profile(兼容性保留 — 优先从 site_registry 加载)
|
|
47
|
+
# ════════════════════════════════════════════════════════════
|
|
48
|
+
# v1.31.0: PRESET_PROFILES 不再硬编码,改为从 core.site_registry 动态获取。
|
|
49
|
+
# 此处保留为空 dict,由 get_preset_profiles() 延迟加载。
|
|
50
|
+
|
|
51
|
+
_PRESET_PROFILES_CACHE: Optional[Dict[str, Dict[str, str]]] = None
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_preset_profiles() -> Dict[str, Dict[str, str]]:
|
|
55
|
+
"""获取预置 Profile 配置(从 SiteRegistry 延迟加载)。
|
|
56
|
+
|
|
57
|
+
返回 {name: {display_name, login_url, detect_url}} 格式,
|
|
58
|
+
保持与旧 PRESET_PROFILES 完全兼容。
|
|
59
|
+
"""
|
|
60
|
+
global _PRESET_PROFILES_CACHE
|
|
61
|
+
if _PRESET_PROFILES_CACHE is not None:
|
|
62
|
+
return _PRESET_PROFILES_CACHE
|
|
63
|
+
|
|
64
|
+
try:
|
|
65
|
+
from core.site_registry import get_site_registry
|
|
66
|
+
reg = get_site_registry()
|
|
67
|
+
sites = reg.list_sites()
|
|
68
|
+
_PRESET_PROFILES_CACHE = {}
|
|
69
|
+
for s in sites:
|
|
70
|
+
_PRESET_PROFILES_CACHE[s["name"]] = {
|
|
71
|
+
"display_name": s["display_name"],
|
|
72
|
+
"login_url": s.get("login_url", ""),
|
|
73
|
+
"detect_url": s.get("detect_url", s.get("home_url", "")),
|
|
74
|
+
}
|
|
75
|
+
logger.debug(f"从 SiteRegistry 加载了 {len(_PRESET_PROFILES_CACHE)} 个预置 Profile")
|
|
76
|
+
return _PRESET_PROFILES_CACHE
|
|
77
|
+
except Exception as e:
|
|
78
|
+
logger.warning(f"从 SiteRegistry 加载失败,使用空预置: {e}")
|
|
79
|
+
_PRESET_PROFILES_CACHE = {}
|
|
80
|
+
return _PRESET_PROFILES_CACHE
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
# 向后兼容别名
|
|
84
|
+
PRESET_PROFILES = {} # 由 get_preset_profiles() 延迟填充,直接引用会得到空 dict
|
|
85
|
+
# 注意:BrowserProfile.__init__ 已改用 get_preset_profiles(),不再直接读 PRESET_PROFILES
|
|
92
86
|
|
|
93
87
|
|
|
94
88
|
class BrowserProfile:
|
|
@@ -112,8 +106,9 @@ class BrowserProfile:
|
|
|
112
106
|
self.cookies_file = self.profile_dir / "cookies.json"
|
|
113
107
|
self.config_file = self.profile_dir / "config.json"
|
|
114
108
|
|
|
115
|
-
#
|
|
116
|
-
|
|
109
|
+
# 预置配置(从 SiteRegistry 延迟加载)
|
|
110
|
+
_presets = get_preset_profiles()
|
|
111
|
+
preset = _presets.get(name, {})
|
|
117
112
|
self.display_name = (config or preset).get("display_name", name)
|
|
118
113
|
self.login_url = (config or preset).get("login_url", "")
|
|
119
114
|
self.detect_url = (config or preset).get("detect_url", "")
|
|
@@ -394,18 +389,20 @@ class BrowserProfileManager:
|
|
|
394
389
|
return profile.delete_profile()
|
|
395
390
|
|
|
396
391
|
def get_preset_names(self) -> List[str]:
|
|
397
|
-
"""获取所有预置 Profile
|
|
398
|
-
return list(
|
|
392
|
+
"""获取所有预置 Profile 名称列表(从 SiteRegistry 动态获取)"""
|
|
393
|
+
return list(get_preset_profiles().keys())
|
|
399
394
|
|
|
400
395
|
def init_presets(self) -> List[str]:
|
|
401
396
|
"""
|
|
402
397
|
初始化所有预置 Profile(仅创建目录结构,不自动登录)。
|
|
398
|
+
预置列表从 SiteRegistry 动态获取。
|
|
403
399
|
|
|
404
400
|
Returns:
|
|
405
401
|
已初始化的 Profile 名称列表
|
|
406
402
|
"""
|
|
403
|
+
_presets = get_preset_profiles()
|
|
407
404
|
initialized = []
|
|
408
|
-
for name, config in
|
|
405
|
+
for name, config in _presets.items():
|
|
409
406
|
profile = self.get_profile(name)
|
|
410
407
|
if not profile.is_initialized():
|
|
411
408
|
profile.ensure_dirs()
|