@web-auto/webauto 0.1.4 → 0.1.6
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/apps/desktop-console/default-settings.json +2 -2
- package/apps/desktop-console/dist/main/index.mjs +915 -85
- package/apps/desktop-console/dist/main/preload.mjs +7 -0
- package/apps/desktop-console/dist/renderer/index.html +622 -50
- package/apps/desktop-console/dist/renderer/index.js +2415 -470
- package/apps/desktop-console/dist/renderer/run.mts +6 -5
- package/apps/desktop-console/entry/ui-cli.mjs +672 -0
- package/apps/desktop-console/entry/ui-console.mjs +416 -29
- package/apps/webauto/entry/account.mjs +89 -53
- package/apps/webauto/entry/browser-status.mjs +7 -10
- package/apps/webauto/entry/lib/account-detect.mjs +254 -28
- package/apps/webauto/entry/lib/account-store.mjs +219 -30
- package/apps/webauto/entry/lib/bus-publish.mjs +63 -0
- package/apps/webauto/entry/lib/camo-cli.mjs +93 -0
- package/apps/webauto/entry/lib/profilepool.mjs +14 -5
- package/apps/webauto/entry/lib/quota-status.mjs +23 -0
- package/apps/webauto/entry/lib/schedule-store.mjs +1068 -0
- package/apps/webauto/entry/profilepool.mjs +106 -17
- package/apps/webauto/entry/schedule.mjs +612 -0
- package/apps/webauto/entry/weibo-unified.mjs +134 -0
- package/apps/webauto/entry/xhs-install.mjs +236 -29
- package/apps/webauto/entry/xhs-status.mjs +5 -2
- package/apps/webauto/entry/xhs-unified.mjs +631 -98
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/comment_item/container.json +40 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_expand_button/container.json +38 -0
- package/apps/webauto/resources/container-library/weibo/weibo_detail_page/reply_list/container.json +37 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/container.json +8 -3
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/login_anchor/container.json +30 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_bar/container.json +47 -0
- package/apps/webauto/resources/container-library/weibo/weibo_search_page/search_button/container.json +39 -0
- package/bin/camoufox-cli.mjs +61 -0
- package/bin/webauto.mjs +301 -54
- package/dist/modules/camo-backend/src/index.js +49 -1
- package/dist/modules/camo-backend/src/internal/BrowserSession.js +572 -3
- package/dist/modules/camo-backend/src/internal/SessionManager.js +13 -1
- package/dist/modules/camo-backend/src/internal/storage-paths.js +6 -0
- package/dist/modules/collection-manager/bloom-filter.js +91 -0
- package/dist/modules/collection-manager/date-utils.js +275 -0
- package/dist/modules/collection-manager/index.js +258 -0
- package/dist/modules/collection-manager/storage.js +195 -0
- package/dist/modules/collection-manager/types.js +47 -0
- package/dist/modules/logging/src/index.js +1 -1
- package/dist/modules/process-registry/index.js +230 -0
- package/dist/modules/rate-limiter/index.js +242 -0
- package/dist/modules/workflow/blocks/ExecuteWeiboSearchBlock.js +128 -0
- package/dist/modules/workflow/blocks/PersistXhsNoteBlock.js +7 -3
- package/dist/modules/workflow/blocks/RenderMarkdown.js +4 -1
- package/dist/modules/workflow/blocks/WeiboCollectCommentsBlock.js +282 -0
- package/dist/modules/workflow/blocks/WeiboCollectFromLinksBlock.js +283 -0
- package/dist/modules/workflow/blocks/WeiboCollectSearchLinksBlock.js +208 -0
- package/dist/modules/workflow/blocks/WeiboCollectTimelineListBlock.js +128 -0
- package/dist/modules/workflow/blocks/WeiboCollectUserPostsListBlock.js +127 -0
- package/dist/modules/workflow/blocks/helpers/downloadPaths.js +21 -0
- package/dist/modules/workflow/config/workflowRegistry.js +2 -0
- package/dist/modules/workflow/definitions/weibo-search-workflow-v1.js +47 -0
- package/dist/modules/workflow/src/runner.js +6 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +4 -0
- package/dist/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +2 -2
- package/dist/modules/xiaohongshu/app/src/blocks/helpers/sharding.js +123 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/container-registry/src/index.js +184 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.d.ts +31 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/AnchorVerificationBlock.js +71 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/DetectPageStateBlock.js +259 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.d.ts +28 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/ErrorRecoveryBlock.js +319 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/WaitSearchPermitBlock.js +162 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.d.ts +36 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/containerAnchors.js +301 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/operationLogger.js +195 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.d.ts +25 -0
- package/dist/modules/xiaohongshu/app/src/workflow/blocks/helpers/searchPageState.js +164 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.d.ts +66 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/MatchCommentsBlock.js +139 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.d.ts +16 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1EnsureServicesBlock.js +36 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.d.ts +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1MonitorCookieBlock.js +213 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.d.ts +18 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.js +121 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2CollectLinksBlock.js +1249 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.d.ts +17 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase2SearchBlock.js +703 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.d.ts +15 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseDetailBlock.js +41 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.d.ts +26 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CloseTabsBlock.js +44 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.d.ts +29 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34CollectCommentsBlock.js +150 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.d.ts +38 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ExtractDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.d.ts +30 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenDetailBlock.js +102 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34OpenTabsBlock.js +109 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.d.ts +32 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.js +117 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.d.ts +35 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ProcessSingleNoteBlock.js +114 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.d.ts +34 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase34ValidateLinksBlock.js +90 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.d.ts +111 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase3InteractBlock.js +1009 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.d.ts +20 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/Phase4MultiTabHarvestBlock.js +233 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.d.ts +48 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/ReplyInteractBlock.js +291 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.d.ts +23 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/XhsDiscoverFallbackBlock.js +240 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.d.ts +55 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatchDsl.js +126 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.d.ts +21 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/commentMatcher.js +99 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.d.ts +5 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/evidence.js +27 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.d.ts +37 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/sharding.js +165 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.d.ts +33 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/blocks/helpers/xhsComments.js +270 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.d.ts +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/index.js +9 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.d.ts +50 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/checkpoints.js +222 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.d.ts +10 -0
- package/dist/modules/xiaohongshu/app/src/xiaohongshu/app/src/utils/controllerAction.js +43 -0
- package/dist/services/shared/serviceProcessLogger.js +1 -1
- package/dist/services/unified-api/server.js +105 -11
- package/modules/camo-backend/src/index.ts +46 -1
- package/modules/camo-backend/src/internal/BrowserSession.ts +619 -3
- package/modules/camo-backend/src/internal/SessionManager.ts +12 -1
- package/modules/camo-backend/src/internal/storage-paths.ts +5 -0
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/comments.mjs +38 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/interaction.mjs +47 -2
- package/modules/camo-runtime/src/autoscript/action-providers/xhs/search.mjs +94 -11
- package/modules/camo-runtime/src/autoscript/action-providers/xhs.mjs +208 -2
- package/modules/camo-runtime/src/autoscript/runtime.mjs +7 -1
- package/modules/camo-runtime/src/autoscript/xhs-unified-template.mjs +76 -43
- package/modules/camo-runtime/src/container/runtime-core/operations/index.mjs +75 -1
- package/modules/camo-runtime/src/container/runtime-core/operations/selector-scripts.mjs +71 -4
- package/modules/camo-runtime/src/container/runtime-core/operations/tab-pool.mjs +183 -27
- package/modules/collection-manager/bloom-filter.ts +112 -0
- package/modules/collection-manager/date-utils.ts +316 -0
- package/modules/collection-manager/index.ts +309 -0
- package/modules/collection-manager/package.json +10 -0
- package/modules/collection-manager/storage.ts +174 -0
- package/modules/collection-manager/types.ts +156 -0
- package/modules/logging/src/index.ts +1 -1
- package/modules/process-registry/index.ts +284 -0
- package/modules/rate-limiter/index.ts +322 -0
- package/modules/state/src/paths.ts +9 -1
- package/modules/task-scheduler/index.ts +293 -0
- package/modules/workflow/blocks/ExecuteWeiboSearchBlock.ts +167 -0
- package/modules/workflow/blocks/PersistXhsNoteBlock.ts +7 -3
- package/modules/workflow/blocks/RenderMarkdown.ts +4 -1
- package/modules/workflow/blocks/WeiboCollectCommentsBlock.ts +339 -0
- package/modules/workflow/blocks/WeiboCollectFromLinksBlock.ts +338 -0
- package/modules/workflow/blocks/helpers/downloadPaths.ts +16 -0
- package/modules/workflow/config/workflowRegistry.ts +2 -0
- package/modules/workflow/definitions/weibo-search-workflow-v1.ts +47 -0
- package/modules/workflow/src/runner.ts +6 -0
- package/modules/xiaohongshu/app/src/blocks/Phase1StartProfileBlock.ts +1 -1
- package/modules/xiaohongshu/app/src/blocks/Phase34PersistDetailBlock.ts +4 -0
- package/modules/xiaohongshu/app/src/blocks/Phase3InteractBlock.ts +2 -3
- package/modules/xiaohongshu/app/src/blocks/helpers/sharding.ts +152 -0
- package/package.json +13 -4
- package/scripts/postinstall-resources.mjs +62 -0
- package/scripts/test/run-coverage.mjs +76 -0
- package/scripts/weibo/search.ts +49 -0
- package/services/shared/serviceProcessLogger.ts +1 -1
- package/services/unified-api/server.ts +98 -12
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RateLimiter - 统一流控配额系统
|
|
3
|
+
*
|
|
4
|
+
* 功能:
|
|
5
|
+
* - 管理 search/like/comment 等操作的配额
|
|
6
|
+
* - 支持多维度限制(关键字、profile、全局)
|
|
7
|
+
* - 可配置规则,支持持久化
|
|
8
|
+
*
|
|
9
|
+
* 使用方式:
|
|
10
|
+
* const limiter = RateLimiter.getInstance();
|
|
11
|
+
* const result = await limiter.acquire('search', { keyword: '春晚' });
|
|
12
|
+
* if (!result.granted) {
|
|
13
|
+
* console.log(`被拒绝: ${result.reason}, 需等待 ${result.waitMs}ms`);
|
|
14
|
+
* return;
|
|
15
|
+
* }
|
|
16
|
+
* // 执行搜索...
|
|
17
|
+
* await limiter.record('search', { keyword: '春晚' });
|
|
18
|
+
*/
|
|
19
|
+
import os from 'node:os';
|
|
20
|
+
import path from 'node:path';
|
|
21
|
+
import { promises as fs } from 'node:fs';
|
|
22
|
+
// 默认配置
|
|
23
|
+
const DEFAULT_CONFIG = {
|
|
24
|
+
search: {
|
|
25
|
+
rules: [
|
|
26
|
+
{ scope: 'keyword', windowMs: 600000, max: 3, key: '同一关键字10分钟内最多3次' },
|
|
27
|
+
{ scope: 'global', windowMs: 60000, max: 10, key: '全局搜索1分钟内最多10次' },
|
|
28
|
+
],
|
|
29
|
+
},
|
|
30
|
+
like: {
|
|
31
|
+
rules: [
|
|
32
|
+
{ scope: 'profile', windowMs: 60000, max: 6, key: '同一账号1分钟内最多点赞6次' },
|
|
33
|
+
{ scope: 'profile', windowMs: 3600000, max: 100, key: '同一账号1小时内最多点赞100次' },
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
comment: {
|
|
37
|
+
rules: [
|
|
38
|
+
{ scope: 'profile', windowMs: 60000, max: 1, key: '同一账号1分钟内最多评论1次' },
|
|
39
|
+
{ scope: 'profile', windowMs: 3600000, max: 30, key: '同一账号1小时内最多评论30次' },
|
|
40
|
+
],
|
|
41
|
+
},
|
|
42
|
+
follow: {
|
|
43
|
+
rules: [
|
|
44
|
+
{ scope: 'profile', windowMs: 60000, max: 2, key: '同一账号1分钟内最多关注2次' },
|
|
45
|
+
{ scope: 'profile', windowMs: 3600000, max: 20, key: '同一账号1小时内最多关注20次' },
|
|
46
|
+
],
|
|
47
|
+
},
|
|
48
|
+
repost: {
|
|
49
|
+
rules: [
|
|
50
|
+
{ scope: 'profile', windowMs: 60000, max: 1, key: '同一账号1分钟内最多转发1次' },
|
|
51
|
+
{ scope: 'profile', windowMs: 3600000, max: 10, key: '同一账号1小时内最多转发10次' },
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
function resolveConfigPath() {
|
|
56
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
57
|
+
return path.join(home, '.webauto', 'config', 'rate-limits.json');
|
|
58
|
+
}
|
|
59
|
+
function resolveHistoryPath() {
|
|
60
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
61
|
+
return path.join(home, '.webauto', 'rate-limiter-history.json');
|
|
62
|
+
}
|
|
63
|
+
export class RateLimiter {
|
|
64
|
+
static instance = null;
|
|
65
|
+
config;
|
|
66
|
+
history = new Map();
|
|
67
|
+
configPath;
|
|
68
|
+
historyPath;
|
|
69
|
+
initialized = false;
|
|
70
|
+
constructor() {
|
|
71
|
+
this.config = { ...DEFAULT_CONFIG };
|
|
72
|
+
this.configPath = resolveConfigPath();
|
|
73
|
+
this.historyPath = resolveHistoryPath();
|
|
74
|
+
}
|
|
75
|
+
static getInstance() {
|
|
76
|
+
if (!RateLimiter.instance) {
|
|
77
|
+
RateLimiter.instance = new RateLimiter();
|
|
78
|
+
}
|
|
79
|
+
return RateLimiter.instance;
|
|
80
|
+
}
|
|
81
|
+
// 初始化:加载配置和历史
|
|
82
|
+
async init() {
|
|
83
|
+
if (this.initialized)
|
|
84
|
+
return;
|
|
85
|
+
// 加载用户配置(覆盖默认值)
|
|
86
|
+
try {
|
|
87
|
+
const content = await fs.readFile(this.configPath, 'utf-8');
|
|
88
|
+
const userConfig = JSON.parse(content);
|
|
89
|
+
this.config = { ...DEFAULT_CONFIG, ...userConfig };
|
|
90
|
+
console.log('[RateLimiter] Loaded user config from', this.configPath);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
// 使用默认配置
|
|
94
|
+
}
|
|
95
|
+
// 加载历史记录
|
|
96
|
+
try {
|
|
97
|
+
const content = await fs.readFile(this.historyPath, 'utf-8');
|
|
98
|
+
const data = JSON.parse(content);
|
|
99
|
+
for (const [key, entries] of Object.entries(data)) {
|
|
100
|
+
this.history.set(key, entries);
|
|
101
|
+
}
|
|
102
|
+
// 清理过期历史
|
|
103
|
+
this.cleanExpiredHistory();
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// 无历史记录
|
|
107
|
+
}
|
|
108
|
+
this.initialized = true;
|
|
109
|
+
}
|
|
110
|
+
// 申请配额
|
|
111
|
+
async acquire(type, params) {
|
|
112
|
+
await this.init();
|
|
113
|
+
const typeConfig = this.config[type];
|
|
114
|
+
if (!typeConfig) {
|
|
115
|
+
return { granted: true }; // 无配置的类型默认允许
|
|
116
|
+
}
|
|
117
|
+
const now = Date.now();
|
|
118
|
+
for (const rule of typeConfig.rules) {
|
|
119
|
+
const key = this.buildKey(type, rule.scope, params);
|
|
120
|
+
const entries = this.history.get(key) || [];
|
|
121
|
+
// 统计窗口内的次数
|
|
122
|
+
const windowStart = now - rule.windowMs;
|
|
123
|
+
const count = entries.filter(e => e.ts > windowStart).length;
|
|
124
|
+
if (count >= rule.max) {
|
|
125
|
+
// 找到最早的过期时间,计算等待时间
|
|
126
|
+
const oldestInWindow = entries.find(e => e.ts > windowStart);
|
|
127
|
+
const waitMs = oldestInWindow ? (oldestInWindow.ts + rule.windowMs - now) : rule.windowMs;
|
|
128
|
+
return {
|
|
129
|
+
granted: false,
|
|
130
|
+
reason: rule.key || `配额已用尽: ${rule.scope} ${rule.windowMs}ms 内最多 ${rule.max} 次`,
|
|
131
|
+
waitMs: Math.max(0, waitMs),
|
|
132
|
+
ruleKey: `${type}:${rule.scope}:${rule.windowMs}`,
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return { granted: true };
|
|
137
|
+
}
|
|
138
|
+
// 记录执行
|
|
139
|
+
async record(type, params) {
|
|
140
|
+
await this.init();
|
|
141
|
+
const typeConfig = this.config[type];
|
|
142
|
+
if (!typeConfig)
|
|
143
|
+
return;
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
// 为所有相关维度记录
|
|
146
|
+
for (const rule of typeConfig.rules) {
|
|
147
|
+
const key = this.buildKey(type, rule.scope, params);
|
|
148
|
+
const entries = this.history.get(key) || [];
|
|
149
|
+
entries.push({ ts: now, key });
|
|
150
|
+
this.history.set(key, entries);
|
|
151
|
+
}
|
|
152
|
+
// 持久化历史(异步,不阻塞)
|
|
153
|
+
this.persistHistory().catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
// 构建维度键
|
|
156
|
+
buildKey(type, scope, params) {
|
|
157
|
+
switch (scope) {
|
|
158
|
+
case 'keyword':
|
|
159
|
+
return `${type}:keyword:${params.keyword || '*'}`;
|
|
160
|
+
case 'profile':
|
|
161
|
+
return `${type}:profile:${params.profileId || '*'}`;
|
|
162
|
+
case 'global':
|
|
163
|
+
return `${type}:global`;
|
|
164
|
+
default:
|
|
165
|
+
return `${type}:unknown`;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
// 清理过期历史
|
|
169
|
+
cleanExpiredHistory() {
|
|
170
|
+
const now = Date.now();
|
|
171
|
+
const maxWindow = 3600000; // 1小时
|
|
172
|
+
for (const [key, entries] of this.history) {
|
|
173
|
+
const filtered = entries.filter(e => now - e.ts < maxWindow);
|
|
174
|
+
if (filtered.length === 0) {
|
|
175
|
+
this.history.delete(key);
|
|
176
|
+
}
|
|
177
|
+
else if (filtered.length !== entries.length) {
|
|
178
|
+
this.history.set(key, filtered);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
// 持久化历史
|
|
183
|
+
async persistHistory() {
|
|
184
|
+
try {
|
|
185
|
+
const dir = path.dirname(this.historyPath);
|
|
186
|
+
await fs.mkdir(dir, { recursive: true });
|
|
187
|
+
const data = {};
|
|
188
|
+
for (const [key, entries] of this.history) {
|
|
189
|
+
data[key] = entries;
|
|
190
|
+
}
|
|
191
|
+
await fs.writeFile(this.historyPath, JSON.stringify(data), 'utf-8');
|
|
192
|
+
}
|
|
193
|
+
catch (err) {
|
|
194
|
+
console.error('[RateLimiter] Failed to persist history:', err);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
// 获取当前配置
|
|
198
|
+
getConfig() {
|
|
199
|
+
return { ...this.config };
|
|
200
|
+
}
|
|
201
|
+
// 更新配置
|
|
202
|
+
async updateConfig(newConfig) {
|
|
203
|
+
this.config = { ...this.config, ...newConfig };
|
|
204
|
+
try {
|
|
205
|
+
const dir = path.dirname(this.configPath);
|
|
206
|
+
await fs.mkdir(dir, { recursive: true });
|
|
207
|
+
await fs.writeFile(this.configPath, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
208
|
+
console.log('[RateLimiter] Config saved to', this.configPath);
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
console.error('[RateLimiter] Failed to save config:', err);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
// 获取状态(用于调试/UI)
|
|
215
|
+
getStatus() {
|
|
216
|
+
const result = [];
|
|
217
|
+
const now = Date.now();
|
|
218
|
+
for (const [type, typeConfig] of Object.entries(this.config)) {
|
|
219
|
+
for (const rule of typeConfig.rules) {
|
|
220
|
+
const key = `${type}:${rule.scope}`;
|
|
221
|
+
let totalCount = 0;
|
|
222
|
+
for (const [k, entries] of this.history) {
|
|
223
|
+
if (k.startsWith(key)) {
|
|
224
|
+
const windowStart = now - rule.windowMs;
|
|
225
|
+
totalCount += entries.filter(e => e.ts > windowStart).length;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
result.push({
|
|
229
|
+
type: type,
|
|
230
|
+
scope: rule.scope,
|
|
231
|
+
count: totalCount,
|
|
232
|
+
max: rule.max,
|
|
233
|
+
windowMs: rule.windowMs,
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return result;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// 单例导出
|
|
241
|
+
export const rateLimiter = RateLimiter.getInstance();
|
|
242
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Block: ExecuteWeiboSearchBlock
|
|
3
|
+
*
|
|
4
|
+
* 微博搜索执行:直接导航到搜索结果页
|
|
5
|
+
*/
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { promises as fs } from 'node:fs';
|
|
9
|
+
function sanitizeFilenamePart(value) {
|
|
10
|
+
return String(value || '')
|
|
11
|
+
.trim()
|
|
12
|
+
.replace(/[\\/:"*?<>|]+/g, '_')
|
|
13
|
+
.replace(/\s+/g, '_')
|
|
14
|
+
.slice(0, 80);
|
|
15
|
+
}
|
|
16
|
+
function resolveDownloadRoot() {
|
|
17
|
+
const custom = process.env.WEBAUTO_DOWNLOAD_ROOT || process.env.WEBAUTO_DOWNLOAD_DIR;
|
|
18
|
+
if (custom && custom.trim())
|
|
19
|
+
return custom;
|
|
20
|
+
const home = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
21
|
+
return path.join(home, '.webauto', 'download');
|
|
22
|
+
}
|
|
23
|
+
function isDebugArtifactsEnabled() {
|
|
24
|
+
return (process.env.WEBAUTO_DEBUG === '1' ||
|
|
25
|
+
process.env.WEBAUTO_DEBUG_ARTIFACTS === '1');
|
|
26
|
+
}
|
|
27
|
+
export async function execute(input) {
|
|
28
|
+
const { sessionId, keyword, env = 'debug', serviceUrl = 'http://127.0.0.1:7704', } = input;
|
|
29
|
+
const profile = sessionId;
|
|
30
|
+
const controllerUrl = `${serviceUrl}/command`;
|
|
31
|
+
const steps = [];
|
|
32
|
+
const debugEnabled = isDebugArtifactsEnabled();
|
|
33
|
+
async function controllerAction(action, args = {}) {
|
|
34
|
+
const res = await fetch(controllerUrl, {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
headers: { 'Content-Type': 'application/json' },
|
|
37
|
+
body: JSON.stringify({ action, args: { profileId: profile, ...args } }),
|
|
38
|
+
signal: AbortSignal.timeout ? AbortSignal.timeout(30000) : undefined,
|
|
39
|
+
});
|
|
40
|
+
const raw = await res.text();
|
|
41
|
+
if (!res.ok)
|
|
42
|
+
throw new Error(`HTTP ${res.status}: ${raw}`);
|
|
43
|
+
try {
|
|
44
|
+
return JSON.parse(raw);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return { raw };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
async function getCurrentUrl() {
|
|
51
|
+
const res = await controllerAction('evaluate', { script: 'window.location.href' });
|
|
52
|
+
return res?.body?.result ?? res?.result ?? '';
|
|
53
|
+
}
|
|
54
|
+
async function saveDebugScreenshot(kind, meta = {}) {
|
|
55
|
+
if (!debugEnabled)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
const keywordDir = path.join(resolveDownloadRoot(), 'weibo', env, sanitizeFilenamePart(keyword));
|
|
59
|
+
const debugDir = path.join(keywordDir, '_debug', 'search');
|
|
60
|
+
await fs.mkdir(debugDir, { recursive: true });
|
|
61
|
+
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
62
|
+
const pngPath = path.join(debugDir, `${ts}-${sanitizeFilenamePart(kind)}.png`);
|
|
63
|
+
const shot = await controllerAction('screenshot');
|
|
64
|
+
const b64 = shot?.body?.data ?? shot?.data;
|
|
65
|
+
if (typeof b64 === 'string' && b64.length > 10) {
|
|
66
|
+
await fs.writeFile(pngPath, Buffer.from(b64, 'base64'));
|
|
67
|
+
}
|
|
68
|
+
console.log(`[ExecuteWeiboSearch][debug] saved ${kind}: ${pngPath}`);
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
console.warn(`[ExecuteWeiboSearch][debug] save failed (${kind}): ${e?.message || String(e)}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function pushStep(step) {
|
|
75
|
+
steps.push(step);
|
|
76
|
+
console.log('[ExecuteWeiboSearch][step]', step.id, step.status, step.error || '');
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
// 直接导航到搜索结果页
|
|
80
|
+
const currentUrl = await getCurrentUrl();
|
|
81
|
+
if (!currentUrl.includes('s.weibo.com/weibo')) {
|
|
82
|
+
pushStep({ id: 'goto_search_page', status: 'running', meta: { from: currentUrl, keyword } });
|
|
83
|
+
const searchUrl = `https://s.weibo.com/weibo?q=${encodeURIComponent(keyword)}`;
|
|
84
|
+
console.log('[ExecuteWeiboSearch] navigating to:', searchUrl);
|
|
85
|
+
await controllerAction('goto', { url: searchUrl });
|
|
86
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
87
|
+
pushStep({ id: 'goto_search_page', status: 'success' });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
pushStep({ id: 'goto_search_page', status: 'skipped', meta: { reason: 'already_on_search_page' } });
|
|
91
|
+
}
|
|
92
|
+
// 检查是否成功加载搜索页
|
|
93
|
+
const finalUrl = await getCurrentUrl();
|
|
94
|
+
console.log('[ExecuteWeiboSearch] current URL:', finalUrl);
|
|
95
|
+
// 检查是否有搜索结果卡片
|
|
96
|
+
const cardsScript = `document.querySelectorAll('.card-wrap').length`;
|
|
97
|
+
const cardsRes = await controllerAction('evaluate', { script: cardsScript });
|
|
98
|
+
const cardsCount = cardsRes?.result ?? 0;
|
|
99
|
+
if (cardsCount > 0) {
|
|
100
|
+
console.log('[ExecuteWeiboSearch] search results loaded:', cardsCount, 'cards');
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
searchExecuted: true,
|
|
104
|
+
url: finalUrl,
|
|
105
|
+
steps,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
// 没有找到搜索结果卡片
|
|
109
|
+
console.log('[ExecuteWeiboSearch] no search results found');
|
|
110
|
+
await saveDebugScreenshot('no_search_results', { keyword, url: finalUrl });
|
|
111
|
+
return {
|
|
112
|
+
success: true, // 仍然返回成功,因为已经导航到搜索页
|
|
113
|
+
searchExecuted: true,
|
|
114
|
+
url: finalUrl,
|
|
115
|
+
steps,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return {
|
|
120
|
+
success: false,
|
|
121
|
+
searchExecuted: false,
|
|
122
|
+
url: '',
|
|
123
|
+
steps,
|
|
124
|
+
error: `ExecuteWeiboSearch failed: ${error.message}`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=ExecuteWeiboSearchBlock.js.map
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
11
|
import { promises as fs } from 'node:fs';
|
|
12
|
+
import { getCurrentTimestamp } from '../../collection-manager/date-utils.js';
|
|
12
13
|
function sanitizeForPath(name) {
|
|
13
14
|
if (!name)
|
|
14
15
|
return '';
|
|
@@ -116,6 +117,7 @@ export async function execute(input) {
|
|
|
116
117
|
const detailPath = path.join(postDir, 'content.md');
|
|
117
118
|
const commentsPath = path.join(postDir, 'comments.md');
|
|
118
119
|
const commentsDonePath = path.join(postDir, 'comments.done.json');
|
|
120
|
+
const persistTs = getCurrentTimestamp();
|
|
119
121
|
// 1) 详情(正文/图片)
|
|
120
122
|
if (wantDetail) {
|
|
121
123
|
const existingDetail = await fs.stat(detailPath).catch(() => null);
|
|
@@ -160,7 +162,8 @@ export async function execute(input) {
|
|
|
160
162
|
lines.push(`- 链接: ${detailUrl}`);
|
|
161
163
|
if (author)
|
|
162
164
|
lines.push(`- 作者: ${author}`);
|
|
163
|
-
lines.push(`- 采集时间: ${
|
|
165
|
+
lines.push(`- 采集时间: ${persistTs.collectedAt}`);
|
|
166
|
+
lines.push(`- 采集时间(本地): ${persistTs.collectedAtLocal}`);
|
|
164
167
|
lines.push('');
|
|
165
168
|
lines.push('## 正文');
|
|
166
169
|
lines.push('');
|
|
@@ -207,7 +210,8 @@ export async function execute(input) {
|
|
|
207
210
|
lines.push(`- Search URL: ${searchUrl}`);
|
|
208
211
|
if (detailUrl)
|
|
209
212
|
lines.push(`- 链接: ${detailUrl}`);
|
|
210
|
-
lines.push(`- 采集时间: ${
|
|
213
|
+
lines.push(`- 采集时间: ${persistTs.collectedAt}`);
|
|
214
|
+
lines.push(`- 采集时间(本地): ${persistTs.collectedAtLocal}`);
|
|
211
215
|
lines.push(`- 评论统计: 抓取=${comments.length}, header=${headerTotal !== null ? headerTotal : '未知'}(reachedEnd=${commentsResult?.reachedEnd ? '是' : '否'}, empty=${commentsResult?.emptyState ? '是' : '否'}${headerPositive
|
|
212
216
|
? `, coverage=${Math.round((coverageRatio || 0) * 100)}% (need>=${coverageNeed}, ok=${coverageOk ? '是' : '否'})`
|
|
213
217
|
: ''})`);
|
|
@@ -246,7 +250,7 @@ export async function execute(input) {
|
|
|
246
250
|
stoppedByMaxComments: Boolean(commentsResult?.stoppedByMaxComments),
|
|
247
251
|
totalComments: comments.length,
|
|
248
252
|
headerTotal,
|
|
249
|
-
ts:
|
|
253
|
+
ts: persistTs.collectedAt,
|
|
250
254
|
}, null, 2), 'utf-8');
|
|
251
255
|
}
|
|
252
256
|
else {
|
|
@@ -20,7 +20,9 @@ export async function execute(input) {
|
|
|
20
20
|
}
|
|
21
21
|
const lines = [];
|
|
22
22
|
lines.push(`# 微博采集结果`);
|
|
23
|
-
|
|
23
|
+
const ts = getCurrentTimestamp();
|
|
24
|
+
lines.push(`采集时间: ${ts.collectedAt}`);
|
|
25
|
+
lines.push(`采集时间(本地): ${ts.collectedAtLocal}`);
|
|
24
26
|
lines.push(`总计: ${posts.length} 条`);
|
|
25
27
|
lines.push('');
|
|
26
28
|
posts.forEach((post, index) => {
|
|
@@ -45,4 +47,5 @@ export async function execute(input) {
|
|
|
45
47
|
count: posts.length
|
|
46
48
|
};
|
|
47
49
|
}
|
|
50
|
+
import { getCurrentTimestamp } from '../../collection-manager/date-utils.js';
|
|
48
51
|
//# sourceMappingURL=RenderMarkdown.js.map
|