@skrillex1224/playwright-toolkit 2.1.113 → 2.1.114
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/dist/browser.js +19 -8
- package/dist/browser.js.map +3 -3
- package/dist/index.cjs +205 -54
- package/dist/index.cjs.map +3 -3
- package/dist/index.js +205 -54
- package/dist/index.js.map +3 -3
- package/index.d.ts +8 -6
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -43,12 +43,23 @@ var createActorInfo = (info) => {
|
|
|
43
43
|
const normalizeShareIdentities = (value) => {
|
|
44
44
|
if (!Array.isArray(value)) return [];
|
|
45
45
|
const unique = /* @__PURE__ */ new Set();
|
|
46
|
+
const normalized = [];
|
|
46
47
|
for (const raw of value) {
|
|
48
|
+
if (raw instanceof RegExp) {
|
|
49
|
+
const key2 = `regex:${raw.source}/${raw.flags}`;
|
|
50
|
+
if (unique.has(key2)) continue;
|
|
51
|
+
unique.add(key2);
|
|
52
|
+
normalized.push(raw);
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
47
55
|
const identity = String(raw || "").trim();
|
|
48
56
|
if (!identity) continue;
|
|
49
|
-
|
|
57
|
+
const key = `string:${identity}`;
|
|
58
|
+
if (unique.has(key)) continue;
|
|
59
|
+
unique.add(key);
|
|
60
|
+
normalized.push(identity);
|
|
50
61
|
}
|
|
51
|
-
return
|
|
62
|
+
return normalized;
|
|
52
63
|
};
|
|
53
64
|
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path2 }) => {
|
|
54
65
|
const safeProtocol = String(protocol2).trim();
|
|
@@ -84,42 +95,42 @@ var ActorInfo = {
|
|
|
84
95
|
name: "\u8C46\u5305",
|
|
85
96
|
domain: "www.doubao.com",
|
|
86
97
|
path: "/",
|
|
87
|
-
shareIdentities: [
|
|
98
|
+
shareIdentities: [/\/thread\/[^/?#]+(?:[/?#]|$)/i]
|
|
88
99
|
}),
|
|
89
100
|
deepseek: createActorInfo({
|
|
90
101
|
key: "deepseek",
|
|
91
102
|
name: "DeepSeek",
|
|
92
103
|
domain: "chat.deepseek.com",
|
|
93
104
|
path: "/",
|
|
94
|
-
shareIdentities: [
|
|
105
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
95
106
|
}),
|
|
96
107
|
erine: createActorInfo({
|
|
97
108
|
key: "erine",
|
|
98
109
|
name: "\u6587\u5FC3\u4E00\u8A00",
|
|
99
110
|
domain: "yiyan.baidu.com",
|
|
100
111
|
path: "/",
|
|
101
|
-
shareIdentities: [
|
|
112
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
102
113
|
}),
|
|
103
114
|
yuanbao: createActorInfo({
|
|
104
115
|
key: "yuanbao",
|
|
105
116
|
name: "\u5143\u5B9D",
|
|
106
117
|
domain: "yuanbao.tencent.com",
|
|
107
118
|
path: "/chat/",
|
|
108
|
-
shareIdentities: [
|
|
119
|
+
shareIdentities: [/\/s\/[^/?#]+(?:[/?#]|$)/i]
|
|
109
120
|
}),
|
|
110
121
|
kimi: createActorInfo({
|
|
111
122
|
key: "kimi",
|
|
112
123
|
name: "Kimi",
|
|
113
124
|
domain: "www.kimi.com",
|
|
114
125
|
path: "/",
|
|
115
|
-
shareIdentities: [
|
|
126
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
116
127
|
}),
|
|
117
128
|
qwen: createActorInfo({
|
|
118
129
|
key: "qwen",
|
|
119
130
|
name: "\u901A\u4E49\u5343\u95EE",
|
|
120
131
|
domain: "www.qianwen.com",
|
|
121
132
|
path: "/chat",
|
|
122
|
-
shareIdentities: [
|
|
133
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
123
134
|
})
|
|
124
135
|
};
|
|
125
136
|
|
|
@@ -483,21 +494,36 @@ var Utils = {
|
|
|
483
494
|
* 从字符串中提取 URL 链接
|
|
484
495
|
* @param {string} text
|
|
485
496
|
* @param {Object} [options]
|
|
486
|
-
* @param {string[]} [options.identities] -
|
|
497
|
+
* @param {(string | RegExp)[]} [options.identities] - 关键路径标识,支持字符串 includes 或正则
|
|
487
498
|
* @returns {string[]}
|
|
488
499
|
*/
|
|
489
500
|
parseLinks(text, options = {}) {
|
|
490
501
|
const raw = String(text || "");
|
|
491
502
|
if (!raw) return [];
|
|
492
503
|
const opts = options && typeof options === "object" ? options : {};
|
|
493
|
-
const identities = Array.isArray(opts.identities) ? opts.identities
|
|
504
|
+
const identities = Array.isArray(opts.identities) ? opts.identities : [];
|
|
505
|
+
const matchers = identities.map((item) => {
|
|
506
|
+
if (item instanceof RegExp) {
|
|
507
|
+
return { type: "regex", value: item };
|
|
508
|
+
}
|
|
509
|
+
const value = String(item || "").trim();
|
|
510
|
+
if (!value) return null;
|
|
511
|
+
return { type: "string", value };
|
|
512
|
+
}).filter(Boolean);
|
|
494
513
|
const matched = raw.match(/https?:\/\/[\w\-._~:/?#[\]@!$&'()*+,;=%]+/g) || [];
|
|
495
514
|
const unique = /* @__PURE__ */ new Set();
|
|
496
515
|
for (const item of matched) {
|
|
497
516
|
const link = String(item || "").trim().replace(/["'“”‘’>\].,,。;;!!??]+$/, "");
|
|
498
517
|
if (!link || !/^https?:\/\//i.test(link)) continue;
|
|
499
|
-
if (
|
|
500
|
-
|
|
518
|
+
if (matchers.length > 0) {
|
|
519
|
+
const ok = matchers.some((matcher) => {
|
|
520
|
+
if (matcher.type === "string") {
|
|
521
|
+
return link.includes(matcher.value);
|
|
522
|
+
}
|
|
523
|
+
matcher.value.lastIndex = 0;
|
|
524
|
+
return matcher.value.test(link);
|
|
525
|
+
});
|
|
526
|
+
if (!ok) continue;
|
|
501
527
|
}
|
|
502
528
|
unique.add(link);
|
|
503
529
|
}
|
|
@@ -3007,10 +3033,10 @@ var Monitor = {
|
|
|
3007
3033
|
*
|
|
3008
3034
|
* @param {import('playwright').Page} page
|
|
3009
3035
|
* @param {Object} [options]
|
|
3010
|
-
* @param {string[]} [options.identities]
|
|
3036
|
+
* @param {(string | RegExp)[]} [options.identities]
|
|
3011
3037
|
* @param {string | string[]} [options.selectors]
|
|
3012
3038
|
* @param {'added' | 'changed' | 'all'} [options.mode]
|
|
3013
|
-
* @param {(text: string, options?: { identities?: string[] }) => string[]} [options.parseLinks]
|
|
3039
|
+
* @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
|
|
3014
3040
|
* @param {(payload: { link: string; rawDom: string; mutationCount: number; html: string; text: string; mutationNodes: Array<{ html: string; text: string; mutationType: string }> }) => void} [options.onMatch]
|
|
3015
3041
|
* @returns {Promise<{ stop: () => Promise<{ totalMutations: number }> }>}
|
|
3016
3042
|
*/
|
|
@@ -3055,55 +3081,67 @@ ${text}`;
|
|
|
3055
3081
|
// src/share.js
|
|
3056
3082
|
var DEFAULT_TIMEOUT_AFTER_ACTION_MS = 10 * 1e3;
|
|
3057
3083
|
var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
|
|
3084
|
+
var createRuntimeKey = (prefix) => `__${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3058
3085
|
var Share = {
|
|
3059
3086
|
/**
|
|
3060
|
-
*
|
|
3087
|
+
* 捕获分享链接(剪切板 + DOM + 接口 三通道)
|
|
3088
|
+
* 优先级:clipboard > dom > response
|
|
3061
3089
|
*
|
|
3062
3090
|
* @param {import('playwright').Page} page
|
|
3063
3091
|
* @param {Object} [options]
|
|
3064
|
-
* @param {string[]} [options.identities]
|
|
3092
|
+
* @param {(string | RegExp)[]} [options.identities]
|
|
3065
3093
|
* @param {number} [options.timeoutAfterActionMs]
|
|
3066
3094
|
* @param {number} [options.payloadSnapshotMaxLen]
|
|
3067
|
-
* @param {boolean} [options.
|
|
3095
|
+
* @param {boolean} [options.enableClipboard=true]
|
|
3068
3096
|
* @param {boolean} [options.enableDom=true]
|
|
3097
|
+
* @param {boolean} [options.enableResponse=true]
|
|
3069
3098
|
* @param {string | string[]} [options.domSelectors='html']
|
|
3070
3099
|
* @param {'added' | 'changed' | 'all'} [options.domMode='added']
|
|
3100
|
+
* @param {number} [options.clipboardPollIntervalMs=220]
|
|
3071
3101
|
* @param {(response: import('playwright').Response) => boolean | Promise<boolean>} [options.responseFilter]
|
|
3072
|
-
* @param {(text: string, options?: { identities?: string[] }) => string[]} [options.parseLinks]
|
|
3102
|
+
* @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
|
|
3073
3103
|
* @param {() => Promise<void>} [options.performActions]
|
|
3074
|
-
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: '
|
|
3104
|
+
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'clipboard' | 'dom' | 'response' | 'none' }>}
|
|
3075
3105
|
*/
|
|
3076
3106
|
async captureLink(page, options = {}) {
|
|
3077
3107
|
const identities = Array.isArray(options.identities) ? options.identities : [];
|
|
3078
3108
|
const timeoutAfterActionMs = options.timeoutAfterActionMs ?? DEFAULT_TIMEOUT_AFTER_ACTION_MS;
|
|
3079
3109
|
const payloadSnapshotMaxLen = options.payloadSnapshotMaxLen ?? DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN;
|
|
3080
|
-
const
|
|
3110
|
+
const enableClipboard = options.enableClipboard !== false;
|
|
3081
3111
|
const enableDom = options.enableDom !== false;
|
|
3112
|
+
const enableResponse = options.enableResponse !== false;
|
|
3082
3113
|
const domSelectors = options.domSelectors ?? "html";
|
|
3083
|
-
const domMode = options.domMode ?? Mutation.Mode.
|
|
3114
|
+
const domMode = options.domMode ?? Mutation.Mode.Added;
|
|
3115
|
+
const clipboardPollIntervalMs = Math.max(80, options.clipboardPollIntervalMs ?? 220);
|
|
3084
3116
|
const responseFilter = typeof options.responseFilter === "function" ? options.responseFilter : null;
|
|
3085
3117
|
const parseLinks = typeof options.parseLinks === "function" ? options.parseLinks : Utils.parseLinks;
|
|
3086
3118
|
const performActions = typeof options.performActions === "function" ? options.performActions : async () => {
|
|
3087
3119
|
};
|
|
3088
|
-
|
|
3089
|
-
|
|
3120
|
+
const clipboardKeys = {
|
|
3121
|
+
state: createRuntimeKey("pk_clip_state"),
|
|
3122
|
+
hookReady: createRuntimeKey("pk_clip_hook_ready"),
|
|
3123
|
+
copyListener: createRuntimeKey("pk_clip_copy_listener"),
|
|
3124
|
+
execWrapped: createRuntimeKey("pk_clip_exec_wrapped"),
|
|
3125
|
+
writeWrapped: createRuntimeKey("pk_clip_write_wrapped")
|
|
3126
|
+
};
|
|
3127
|
+
if (!enableClipboard && !enableResponse && !enableDom) {
|
|
3128
|
+
throw new Error("Share.captureLink requires at least one channel: clipboard/dom/response");
|
|
3090
3129
|
}
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
const
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
payloadText = String(payload || "");
|
|
3104
|
-
if (resolveMatched) resolveMatched(candidate);
|
|
3130
|
+
const candidates = {
|
|
3131
|
+
clipboard: null,
|
|
3132
|
+
dom: null,
|
|
3133
|
+
response: null
|
|
3134
|
+
};
|
|
3135
|
+
const setCandidate = (sourceKey, candidateLink, payload) => {
|
|
3136
|
+
if (!candidateLink) return false;
|
|
3137
|
+
if (candidates[sourceKey]?.link) return false;
|
|
3138
|
+
candidates[sourceKey] = {
|
|
3139
|
+
link: candidateLink,
|
|
3140
|
+
payloadText: String(payload || "")
|
|
3141
|
+
};
|
|
3105
3142
|
return true;
|
|
3106
3143
|
};
|
|
3144
|
+
let domMonitor = null;
|
|
3107
3145
|
const stopDomMonitor = async () => {
|
|
3108
3146
|
if (!domMonitor) return;
|
|
3109
3147
|
const monitor = domMonitor;
|
|
@@ -3114,7 +3152,7 @@ var Share = {
|
|
|
3114
3152
|
}
|
|
3115
3153
|
};
|
|
3116
3154
|
const onResponse = async (response) => {
|
|
3117
|
-
if (link) return;
|
|
3155
|
+
if (candidates.response?.link) return;
|
|
3118
3156
|
try {
|
|
3119
3157
|
if (responseFilter) {
|
|
3120
3158
|
const accepted = await responseFilter(response);
|
|
@@ -3124,13 +3162,105 @@ var Share = {
|
|
|
3124
3162
|
if (!text) return;
|
|
3125
3163
|
const [candidate] = parseLinks(text, { identities }) || [];
|
|
3126
3164
|
if (!candidate) return;
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3165
|
+
setCandidate("response", candidate, text);
|
|
3166
|
+
} catch {
|
|
3167
|
+
}
|
|
3168
|
+
};
|
|
3169
|
+
const ensureClipboardHooks = async () => {
|
|
3170
|
+
if (!enableClipboard) return;
|
|
3171
|
+
try {
|
|
3172
|
+
await page.evaluate(({ keys }) => {
|
|
3173
|
+
if (document[keys.hookReady]) return;
|
|
3174
|
+
const state = document[keys.state] || { captured: "" };
|
|
3175
|
+
document[keys.state] = state;
|
|
3176
|
+
const normalize = (value) => String(value || "").trim();
|
|
3177
|
+
const setClipboard = (value) => {
|
|
3178
|
+
const safe = normalize(value);
|
|
3179
|
+
if (!safe) return;
|
|
3180
|
+
state.captured = safe;
|
|
3181
|
+
};
|
|
3182
|
+
try {
|
|
3183
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
3184
|
+
const clipboard = navigator.clipboard;
|
|
3185
|
+
if (!clipboard[keys.writeWrapped]) {
|
|
3186
|
+
const originalWriteText = clipboard.writeText.bind(clipboard);
|
|
3187
|
+
clipboard.writeText = async (text) => {
|
|
3188
|
+
setClipboard(text);
|
|
3189
|
+
return await originalWriteText(text);
|
|
3190
|
+
};
|
|
3191
|
+
clipboard[keys.writeWrapped] = true;
|
|
3192
|
+
}
|
|
3193
|
+
}
|
|
3194
|
+
} catch {
|
|
3195
|
+
}
|
|
3196
|
+
try {
|
|
3197
|
+
if (!document[keys.copyListener]) {
|
|
3198
|
+
document.addEventListener("copy", (event) => {
|
|
3199
|
+
try {
|
|
3200
|
+
const copied = event?.clipboardData?.getData?.("text/plain") || "";
|
|
3201
|
+
setClipboard(copied);
|
|
3202
|
+
} catch {
|
|
3203
|
+
}
|
|
3204
|
+
}, true);
|
|
3205
|
+
document[keys.copyListener] = true;
|
|
3206
|
+
}
|
|
3207
|
+
} catch {
|
|
3208
|
+
}
|
|
3209
|
+
try {
|
|
3210
|
+
if (typeof document.execCommand === "function" && !document[keys.execWrapped]) {
|
|
3211
|
+
const originalExecCommand = document.execCommand.bind(document);
|
|
3212
|
+
document.execCommand = function(command, ...args) {
|
|
3213
|
+
try {
|
|
3214
|
+
if (String(command || "").toLowerCase() === "copy") {
|
|
3215
|
+
const selected = globalThis.getSelection?.().toString?.() || "";
|
|
3216
|
+
setClipboard(selected);
|
|
3217
|
+
}
|
|
3218
|
+
} catch {
|
|
3219
|
+
}
|
|
3220
|
+
return originalExecCommand(command, ...args);
|
|
3221
|
+
};
|
|
3222
|
+
document[keys.execWrapped] = true;
|
|
3223
|
+
}
|
|
3224
|
+
} catch {
|
|
3225
|
+
}
|
|
3226
|
+
document[keys.hookReady] = true;
|
|
3227
|
+
}, { keys: clipboardKeys });
|
|
3228
|
+
} catch {
|
|
3229
|
+
}
|
|
3230
|
+
};
|
|
3231
|
+
const probeClipboard = async () => {
|
|
3232
|
+
if (!enableClipboard) return null;
|
|
3233
|
+
try {
|
|
3234
|
+
const data = await page.evaluate(async ({ keys }) => {
|
|
3235
|
+
const capturedText = String(document[keys.state]?.captured || "").trim();
|
|
3236
|
+
let directText = "";
|
|
3237
|
+
try {
|
|
3238
|
+
if (navigator.clipboard && typeof navigator.clipboard.readText === "function") {
|
|
3239
|
+
directText = String(await navigator.clipboard.readText() || "").trim();
|
|
3240
|
+
}
|
|
3241
|
+
} catch {
|
|
3242
|
+
}
|
|
3243
|
+
return { capturedText, directText };
|
|
3244
|
+
}, { keys: clipboardKeys });
|
|
3245
|
+
const [capturedLink] = parseLinks(data?.capturedText || "", { identities }) || [];
|
|
3246
|
+
if (capturedLink) {
|
|
3247
|
+
return {
|
|
3248
|
+
link: capturedLink,
|
|
3249
|
+
payloadText: data.capturedText || ""
|
|
3250
|
+
};
|
|
3251
|
+
}
|
|
3252
|
+
const [directLink] = parseLinks(data?.directText || "", { identities }) || [];
|
|
3253
|
+
if (directLink) {
|
|
3254
|
+
return {
|
|
3255
|
+
link: directLink,
|
|
3256
|
+
payloadText: data.directText || ""
|
|
3257
|
+
};
|
|
3258
|
+
}
|
|
3131
3259
|
} catch {
|
|
3132
3260
|
}
|
|
3261
|
+
return null;
|
|
3133
3262
|
};
|
|
3263
|
+
await ensureClipboardHooks();
|
|
3134
3264
|
if (enableDom) {
|
|
3135
3265
|
domMonitor = await Monitor.useShareLinkMonitor(page, {
|
|
3136
3266
|
identities,
|
|
@@ -3138,11 +3268,7 @@ var Share = {
|
|
|
3138
3268
|
mode: domMode,
|
|
3139
3269
|
parseLinks,
|
|
3140
3270
|
onMatch: ({ link: domLink, rawDom }) => {
|
|
3141
|
-
|
|
3142
|
-
if (!matched) return;
|
|
3143
|
-
if (enableResponse) {
|
|
3144
|
-
page.off("response", onResponse);
|
|
3145
|
-
}
|
|
3271
|
+
setCandidate("dom", domLink, rawDom);
|
|
3146
3272
|
}
|
|
3147
3273
|
});
|
|
3148
3274
|
}
|
|
@@ -3151,18 +3277,43 @@ var Share = {
|
|
|
3151
3277
|
}
|
|
3152
3278
|
try {
|
|
3153
3279
|
await performActions();
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3280
|
+
const deadline = Date.now() + Math.max(0, timeoutAfterActionMs);
|
|
3281
|
+
const nonClipboardGraceMs = Math.max(120, Math.min(500, clipboardPollIntervalMs * 2));
|
|
3282
|
+
let nonClipboardSeenAt = null;
|
|
3283
|
+
while (true) {
|
|
3284
|
+
if (enableClipboard) {
|
|
3285
|
+
const clipboardResult = await probeClipboard();
|
|
3286
|
+
if (clipboardResult?.link) {
|
|
3287
|
+
setCandidate("clipboard", clipboardResult.link, clipboardResult.payloadText);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
if (candidates.clipboard?.link) {
|
|
3291
|
+
break;
|
|
3292
|
+
}
|
|
3293
|
+
if (candidates.dom?.link || candidates.response?.link) {
|
|
3294
|
+
if (!nonClipboardSeenAt) {
|
|
3295
|
+
nonClipboardSeenAt = Date.now();
|
|
3296
|
+
} else if (Date.now() - nonClipboardSeenAt >= nonClipboardGraceMs) {
|
|
3297
|
+
break;
|
|
3298
|
+
}
|
|
3299
|
+
}
|
|
3300
|
+
if (Date.now() >= deadline) {
|
|
3301
|
+
break;
|
|
3302
|
+
}
|
|
3303
|
+
const remaining = deadline - Date.now();
|
|
3304
|
+
await new Promise((resolve) => {
|
|
3305
|
+
setTimeout(resolve, Math.max(0, Math.min(clipboardPollIntervalMs, remaining)));
|
|
3306
|
+
});
|
|
3159
3307
|
}
|
|
3308
|
+
const finalMatch = candidates.clipboard || candidates.dom || candidates.response || null;
|
|
3309
|
+
const finalSource = candidates.clipboard ? "clipboard" : candidates.dom ? "dom" : candidates.response ? "response" : "none";
|
|
3310
|
+
const payloadText = finalMatch?.payloadText || "";
|
|
3160
3311
|
const payloadSnapshot = payloadText ? payloadText.replace(/\s+/g, " ").trim().slice(0, payloadSnapshotMaxLen) : "";
|
|
3161
3312
|
return {
|
|
3162
|
-
link,
|
|
3313
|
+
link: finalMatch?.link || null,
|
|
3163
3314
|
payloadText,
|
|
3164
3315
|
payloadSnapshot,
|
|
3165
|
-
source
|
|
3316
|
+
source: finalSource
|
|
3166
3317
|
};
|
|
3167
3318
|
} finally {
|
|
3168
3319
|
if (enableResponse) {
|