@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.cjs
CHANGED
|
@@ -70,12 +70,23 @@ var createActorInfo = (info) => {
|
|
|
70
70
|
const normalizeShareIdentities = (value) => {
|
|
71
71
|
if (!Array.isArray(value)) return [];
|
|
72
72
|
const unique = /* @__PURE__ */ new Set();
|
|
73
|
+
const normalized = [];
|
|
73
74
|
for (const raw of value) {
|
|
75
|
+
if (raw instanceof RegExp) {
|
|
76
|
+
const key2 = `regex:${raw.source}/${raw.flags}`;
|
|
77
|
+
if (unique.has(key2)) continue;
|
|
78
|
+
unique.add(key2);
|
|
79
|
+
normalized.push(raw);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
74
82
|
const identity = String(raw || "").trim();
|
|
75
83
|
if (!identity) continue;
|
|
76
|
-
|
|
84
|
+
const key = `string:${identity}`;
|
|
85
|
+
if (unique.has(key)) continue;
|
|
86
|
+
unique.add(key);
|
|
87
|
+
normalized.push(identity);
|
|
77
88
|
}
|
|
78
|
-
return
|
|
89
|
+
return normalized;
|
|
79
90
|
};
|
|
80
91
|
const buildLandingUrl = ({ protocol: protocol2, domain: domain2, path: path2 }) => {
|
|
81
92
|
const safeProtocol = String(protocol2).trim();
|
|
@@ -111,42 +122,42 @@ var ActorInfo = {
|
|
|
111
122
|
name: "\u8C46\u5305",
|
|
112
123
|
domain: "www.doubao.com",
|
|
113
124
|
path: "/",
|
|
114
|
-
shareIdentities: [
|
|
125
|
+
shareIdentities: [/\/thread\/[^/?#]+(?:[/?#]|$)/i]
|
|
115
126
|
}),
|
|
116
127
|
deepseek: createActorInfo({
|
|
117
128
|
key: "deepseek",
|
|
118
129
|
name: "DeepSeek",
|
|
119
130
|
domain: "chat.deepseek.com",
|
|
120
131
|
path: "/",
|
|
121
|
-
shareIdentities: [
|
|
132
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
122
133
|
}),
|
|
123
134
|
erine: createActorInfo({
|
|
124
135
|
key: "erine",
|
|
125
136
|
name: "\u6587\u5FC3\u4E00\u8A00",
|
|
126
137
|
domain: "yiyan.baidu.com",
|
|
127
138
|
path: "/",
|
|
128
|
-
shareIdentities: [
|
|
139
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
129
140
|
}),
|
|
130
141
|
yuanbao: createActorInfo({
|
|
131
142
|
key: "yuanbao",
|
|
132
143
|
name: "\u5143\u5B9D",
|
|
133
144
|
domain: "yuanbao.tencent.com",
|
|
134
145
|
path: "/chat/",
|
|
135
|
-
shareIdentities: [
|
|
146
|
+
shareIdentities: [/\/s\/[^/?#]+(?:[/?#]|$)/i]
|
|
136
147
|
}),
|
|
137
148
|
kimi: createActorInfo({
|
|
138
149
|
key: "kimi",
|
|
139
150
|
name: "Kimi",
|
|
140
151
|
domain: "www.kimi.com",
|
|
141
152
|
path: "/",
|
|
142
|
-
shareIdentities: [
|
|
153
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
143
154
|
}),
|
|
144
155
|
qwen: createActorInfo({
|
|
145
156
|
key: "qwen",
|
|
146
157
|
name: "\u901A\u4E49\u5343\u95EE",
|
|
147
158
|
domain: "www.qianwen.com",
|
|
148
159
|
path: "/chat",
|
|
149
|
-
shareIdentities: [
|
|
160
|
+
shareIdentities: [/\/share\/[^/?#]+(?:[/?#]|$)/i]
|
|
150
161
|
})
|
|
151
162
|
};
|
|
152
163
|
|
|
@@ -510,21 +521,36 @@ var Utils = {
|
|
|
510
521
|
* 从字符串中提取 URL 链接
|
|
511
522
|
* @param {string} text
|
|
512
523
|
* @param {Object} [options]
|
|
513
|
-
* @param {string[]} [options.identities] -
|
|
524
|
+
* @param {(string | RegExp)[]} [options.identities] - 关键路径标识,支持字符串 includes 或正则
|
|
514
525
|
* @returns {string[]}
|
|
515
526
|
*/
|
|
516
527
|
parseLinks(text, options = {}) {
|
|
517
528
|
const raw = String(text || "");
|
|
518
529
|
if (!raw) return [];
|
|
519
530
|
const opts = options && typeof options === "object" ? options : {};
|
|
520
|
-
const identities = Array.isArray(opts.identities) ? opts.identities
|
|
531
|
+
const identities = Array.isArray(opts.identities) ? opts.identities : [];
|
|
532
|
+
const matchers = identities.map((item) => {
|
|
533
|
+
if (item instanceof RegExp) {
|
|
534
|
+
return { type: "regex", value: item };
|
|
535
|
+
}
|
|
536
|
+
const value = String(item || "").trim();
|
|
537
|
+
if (!value) return null;
|
|
538
|
+
return { type: "string", value };
|
|
539
|
+
}).filter(Boolean);
|
|
521
540
|
const matched = raw.match(/https?:\/\/[\w\-._~:/?#[\]@!$&'()*+,;=%]+/g) || [];
|
|
522
541
|
const unique = /* @__PURE__ */ new Set();
|
|
523
542
|
for (const item of matched) {
|
|
524
543
|
const link = String(item || "").trim().replace(/["'“”‘’>\].,,。;;!!??]+$/, "");
|
|
525
544
|
if (!link || !/^https?:\/\//i.test(link)) continue;
|
|
526
|
-
if (
|
|
527
|
-
|
|
545
|
+
if (matchers.length > 0) {
|
|
546
|
+
const ok = matchers.some((matcher) => {
|
|
547
|
+
if (matcher.type === "string") {
|
|
548
|
+
return link.includes(matcher.value);
|
|
549
|
+
}
|
|
550
|
+
matcher.value.lastIndex = 0;
|
|
551
|
+
return matcher.value.test(link);
|
|
552
|
+
});
|
|
553
|
+
if (!ok) continue;
|
|
528
554
|
}
|
|
529
555
|
unique.add(link);
|
|
530
556
|
}
|
|
@@ -3034,10 +3060,10 @@ var Monitor = {
|
|
|
3034
3060
|
*
|
|
3035
3061
|
* @param {import('playwright').Page} page
|
|
3036
3062
|
* @param {Object} [options]
|
|
3037
|
-
* @param {string[]} [options.identities]
|
|
3063
|
+
* @param {(string | RegExp)[]} [options.identities]
|
|
3038
3064
|
* @param {string | string[]} [options.selectors]
|
|
3039
3065
|
* @param {'added' | 'changed' | 'all'} [options.mode]
|
|
3040
|
-
* @param {(text: string, options?: { identities?: string[] }) => string[]} [options.parseLinks]
|
|
3066
|
+
* @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
|
|
3041
3067
|
* @param {(payload: { link: string; rawDom: string; mutationCount: number; html: string; text: string; mutationNodes: Array<{ html: string; text: string; mutationType: string }> }) => void} [options.onMatch]
|
|
3042
3068
|
* @returns {Promise<{ stop: () => Promise<{ totalMutations: number }> }>}
|
|
3043
3069
|
*/
|
|
@@ -3082,55 +3108,67 @@ ${text}`;
|
|
|
3082
3108
|
// src/share.js
|
|
3083
3109
|
var DEFAULT_TIMEOUT_AFTER_ACTION_MS = 10 * 1e3;
|
|
3084
3110
|
var DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN = 500;
|
|
3111
|
+
var createRuntimeKey = (prefix) => `__${prefix}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 10)}`;
|
|
3085
3112
|
var Share = {
|
|
3086
3113
|
/**
|
|
3087
|
-
*
|
|
3114
|
+
* 捕获分享链接(剪切板 + DOM + 接口 三通道)
|
|
3115
|
+
* 优先级:clipboard > dom > response
|
|
3088
3116
|
*
|
|
3089
3117
|
* @param {import('playwright').Page} page
|
|
3090
3118
|
* @param {Object} [options]
|
|
3091
|
-
* @param {string[]} [options.identities]
|
|
3119
|
+
* @param {(string | RegExp)[]} [options.identities]
|
|
3092
3120
|
* @param {number} [options.timeoutAfterActionMs]
|
|
3093
3121
|
* @param {number} [options.payloadSnapshotMaxLen]
|
|
3094
|
-
* @param {boolean} [options.
|
|
3122
|
+
* @param {boolean} [options.enableClipboard=true]
|
|
3095
3123
|
* @param {boolean} [options.enableDom=true]
|
|
3124
|
+
* @param {boolean} [options.enableResponse=true]
|
|
3096
3125
|
* @param {string | string[]} [options.domSelectors='html']
|
|
3097
3126
|
* @param {'added' | 'changed' | 'all'} [options.domMode='added']
|
|
3127
|
+
* @param {number} [options.clipboardPollIntervalMs=220]
|
|
3098
3128
|
* @param {(response: import('playwright').Response) => boolean | Promise<boolean>} [options.responseFilter]
|
|
3099
|
-
* @param {(text: string, options?: { identities?: string[] }) => string[]} [options.parseLinks]
|
|
3129
|
+
* @param {(text: string, options?: { identities?: (string | RegExp)[] }) => string[]} [options.parseLinks]
|
|
3100
3130
|
* @param {() => Promise<void>} [options.performActions]
|
|
3101
|
-
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: '
|
|
3131
|
+
* @returns {Promise<{ link: string | null; payloadText: string; payloadSnapshot: string; source: 'clipboard' | 'dom' | 'response' | 'none' }>}
|
|
3102
3132
|
*/
|
|
3103
3133
|
async captureLink(page, options = {}) {
|
|
3104
3134
|
const identities = Array.isArray(options.identities) ? options.identities : [];
|
|
3105
3135
|
const timeoutAfterActionMs = options.timeoutAfterActionMs ?? DEFAULT_TIMEOUT_AFTER_ACTION_MS;
|
|
3106
3136
|
const payloadSnapshotMaxLen = options.payloadSnapshotMaxLen ?? DEFAULT_PAYLOAD_SNAPSHOT_MAX_LEN;
|
|
3107
|
-
const
|
|
3137
|
+
const enableClipboard = options.enableClipboard !== false;
|
|
3108
3138
|
const enableDom = options.enableDom !== false;
|
|
3139
|
+
const enableResponse = options.enableResponse !== false;
|
|
3109
3140
|
const domSelectors = options.domSelectors ?? "html";
|
|
3110
|
-
const domMode = options.domMode ?? Mutation.Mode.
|
|
3141
|
+
const domMode = options.domMode ?? Mutation.Mode.Added;
|
|
3142
|
+
const clipboardPollIntervalMs = Math.max(80, options.clipboardPollIntervalMs ?? 220);
|
|
3111
3143
|
const responseFilter = typeof options.responseFilter === "function" ? options.responseFilter : null;
|
|
3112
3144
|
const parseLinks = typeof options.parseLinks === "function" ? options.parseLinks : Utils.parseLinks;
|
|
3113
3145
|
const performActions = typeof options.performActions === "function" ? options.performActions : async () => {
|
|
3114
3146
|
};
|
|
3115
|
-
|
|
3116
|
-
|
|
3147
|
+
const clipboardKeys = {
|
|
3148
|
+
state: createRuntimeKey("pk_clip_state"),
|
|
3149
|
+
hookReady: createRuntimeKey("pk_clip_hook_ready"),
|
|
3150
|
+
copyListener: createRuntimeKey("pk_clip_copy_listener"),
|
|
3151
|
+
execWrapped: createRuntimeKey("pk_clip_exec_wrapped"),
|
|
3152
|
+
writeWrapped: createRuntimeKey("pk_clip_write_wrapped")
|
|
3153
|
+
};
|
|
3154
|
+
if (!enableClipboard && !enableResponse && !enableDom) {
|
|
3155
|
+
throw new Error("Share.captureLink requires at least one channel: clipboard/dom/response");
|
|
3117
3156
|
}
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
const
|
|
3124
|
-
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
payloadText = String(payload || "");
|
|
3131
|
-
if (resolveMatched) resolveMatched(candidate);
|
|
3157
|
+
const candidates = {
|
|
3158
|
+
clipboard: null,
|
|
3159
|
+
dom: null,
|
|
3160
|
+
response: null
|
|
3161
|
+
};
|
|
3162
|
+
const setCandidate = (sourceKey, candidateLink, payload) => {
|
|
3163
|
+
if (!candidateLink) return false;
|
|
3164
|
+
if (candidates[sourceKey]?.link) return false;
|
|
3165
|
+
candidates[sourceKey] = {
|
|
3166
|
+
link: candidateLink,
|
|
3167
|
+
payloadText: String(payload || "")
|
|
3168
|
+
};
|
|
3132
3169
|
return true;
|
|
3133
3170
|
};
|
|
3171
|
+
let domMonitor = null;
|
|
3134
3172
|
const stopDomMonitor = async () => {
|
|
3135
3173
|
if (!domMonitor) return;
|
|
3136
3174
|
const monitor = domMonitor;
|
|
@@ -3141,7 +3179,7 @@ var Share = {
|
|
|
3141
3179
|
}
|
|
3142
3180
|
};
|
|
3143
3181
|
const onResponse = async (response) => {
|
|
3144
|
-
if (link) return;
|
|
3182
|
+
if (candidates.response?.link) return;
|
|
3145
3183
|
try {
|
|
3146
3184
|
if (responseFilter) {
|
|
3147
3185
|
const accepted = await responseFilter(response);
|
|
@@ -3151,13 +3189,105 @@ var Share = {
|
|
|
3151
3189
|
if (!text) return;
|
|
3152
3190
|
const [candidate] = parseLinks(text, { identities }) || [];
|
|
3153
3191
|
if (!candidate) return;
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3192
|
+
setCandidate("response", candidate, text);
|
|
3193
|
+
} catch {
|
|
3194
|
+
}
|
|
3195
|
+
};
|
|
3196
|
+
const ensureClipboardHooks = async () => {
|
|
3197
|
+
if (!enableClipboard) return;
|
|
3198
|
+
try {
|
|
3199
|
+
await page.evaluate(({ keys }) => {
|
|
3200
|
+
if (document[keys.hookReady]) return;
|
|
3201
|
+
const state = document[keys.state] || { captured: "" };
|
|
3202
|
+
document[keys.state] = state;
|
|
3203
|
+
const normalize = (value) => String(value || "").trim();
|
|
3204
|
+
const setClipboard = (value) => {
|
|
3205
|
+
const safe = normalize(value);
|
|
3206
|
+
if (!safe) return;
|
|
3207
|
+
state.captured = safe;
|
|
3208
|
+
};
|
|
3209
|
+
try {
|
|
3210
|
+
if (navigator.clipboard && typeof navigator.clipboard.writeText === "function") {
|
|
3211
|
+
const clipboard = navigator.clipboard;
|
|
3212
|
+
if (!clipboard[keys.writeWrapped]) {
|
|
3213
|
+
const originalWriteText = clipboard.writeText.bind(clipboard);
|
|
3214
|
+
clipboard.writeText = async (text) => {
|
|
3215
|
+
setClipboard(text);
|
|
3216
|
+
return await originalWriteText(text);
|
|
3217
|
+
};
|
|
3218
|
+
clipboard[keys.writeWrapped] = true;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
} catch {
|
|
3222
|
+
}
|
|
3223
|
+
try {
|
|
3224
|
+
if (!document[keys.copyListener]) {
|
|
3225
|
+
document.addEventListener("copy", (event) => {
|
|
3226
|
+
try {
|
|
3227
|
+
const copied = event?.clipboardData?.getData?.("text/plain") || "";
|
|
3228
|
+
setClipboard(copied);
|
|
3229
|
+
} catch {
|
|
3230
|
+
}
|
|
3231
|
+
}, true);
|
|
3232
|
+
document[keys.copyListener] = true;
|
|
3233
|
+
}
|
|
3234
|
+
} catch {
|
|
3235
|
+
}
|
|
3236
|
+
try {
|
|
3237
|
+
if (typeof document.execCommand === "function" && !document[keys.execWrapped]) {
|
|
3238
|
+
const originalExecCommand = document.execCommand.bind(document);
|
|
3239
|
+
document.execCommand = function(command, ...args) {
|
|
3240
|
+
try {
|
|
3241
|
+
if (String(command || "").toLowerCase() === "copy") {
|
|
3242
|
+
const selected = globalThis.getSelection?.().toString?.() || "";
|
|
3243
|
+
setClipboard(selected);
|
|
3244
|
+
}
|
|
3245
|
+
} catch {
|
|
3246
|
+
}
|
|
3247
|
+
return originalExecCommand(command, ...args);
|
|
3248
|
+
};
|
|
3249
|
+
document[keys.execWrapped] = true;
|
|
3250
|
+
}
|
|
3251
|
+
} catch {
|
|
3252
|
+
}
|
|
3253
|
+
document[keys.hookReady] = true;
|
|
3254
|
+
}, { keys: clipboardKeys });
|
|
3255
|
+
} catch {
|
|
3256
|
+
}
|
|
3257
|
+
};
|
|
3258
|
+
const probeClipboard = async () => {
|
|
3259
|
+
if (!enableClipboard) return null;
|
|
3260
|
+
try {
|
|
3261
|
+
const data = await page.evaluate(async ({ keys }) => {
|
|
3262
|
+
const capturedText = String(document[keys.state]?.captured || "").trim();
|
|
3263
|
+
let directText = "";
|
|
3264
|
+
try {
|
|
3265
|
+
if (navigator.clipboard && typeof navigator.clipboard.readText === "function") {
|
|
3266
|
+
directText = String(await navigator.clipboard.readText() || "").trim();
|
|
3267
|
+
}
|
|
3268
|
+
} catch {
|
|
3269
|
+
}
|
|
3270
|
+
return { capturedText, directText };
|
|
3271
|
+
}, { keys: clipboardKeys });
|
|
3272
|
+
const [capturedLink] = parseLinks(data?.capturedText || "", { identities }) || [];
|
|
3273
|
+
if (capturedLink) {
|
|
3274
|
+
return {
|
|
3275
|
+
link: capturedLink,
|
|
3276
|
+
payloadText: data.capturedText || ""
|
|
3277
|
+
};
|
|
3278
|
+
}
|
|
3279
|
+
const [directLink] = parseLinks(data?.directText || "", { identities }) || [];
|
|
3280
|
+
if (directLink) {
|
|
3281
|
+
return {
|
|
3282
|
+
link: directLink,
|
|
3283
|
+
payloadText: data.directText || ""
|
|
3284
|
+
};
|
|
3285
|
+
}
|
|
3158
3286
|
} catch {
|
|
3159
3287
|
}
|
|
3288
|
+
return null;
|
|
3160
3289
|
};
|
|
3290
|
+
await ensureClipboardHooks();
|
|
3161
3291
|
if (enableDom) {
|
|
3162
3292
|
domMonitor = await Monitor.useShareLinkMonitor(page, {
|
|
3163
3293
|
identities,
|
|
@@ -3165,11 +3295,7 @@ var Share = {
|
|
|
3165
3295
|
mode: domMode,
|
|
3166
3296
|
parseLinks,
|
|
3167
3297
|
onMatch: ({ link: domLink, rawDom }) => {
|
|
3168
|
-
|
|
3169
|
-
if (!matched) return;
|
|
3170
|
-
if (enableResponse) {
|
|
3171
|
-
page.off("response", onResponse);
|
|
3172
|
-
}
|
|
3298
|
+
setCandidate("dom", domLink, rawDom);
|
|
3173
3299
|
}
|
|
3174
3300
|
});
|
|
3175
3301
|
}
|
|
@@ -3178,18 +3304,43 @@ var Share = {
|
|
|
3178
3304
|
}
|
|
3179
3305
|
try {
|
|
3180
3306
|
await performActions();
|
|
3181
|
-
|
|
3182
|
-
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3307
|
+
const deadline = Date.now() + Math.max(0, timeoutAfterActionMs);
|
|
3308
|
+
const nonClipboardGraceMs = Math.max(120, Math.min(500, clipboardPollIntervalMs * 2));
|
|
3309
|
+
let nonClipboardSeenAt = null;
|
|
3310
|
+
while (true) {
|
|
3311
|
+
if (enableClipboard) {
|
|
3312
|
+
const clipboardResult = await probeClipboard();
|
|
3313
|
+
if (clipboardResult?.link) {
|
|
3314
|
+
setCandidate("clipboard", clipboardResult.link, clipboardResult.payloadText);
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3317
|
+
if (candidates.clipboard?.link) {
|
|
3318
|
+
break;
|
|
3319
|
+
}
|
|
3320
|
+
if (candidates.dom?.link || candidates.response?.link) {
|
|
3321
|
+
if (!nonClipboardSeenAt) {
|
|
3322
|
+
nonClipboardSeenAt = Date.now();
|
|
3323
|
+
} else if (Date.now() - nonClipboardSeenAt >= nonClipboardGraceMs) {
|
|
3324
|
+
break;
|
|
3325
|
+
}
|
|
3326
|
+
}
|
|
3327
|
+
if (Date.now() >= deadline) {
|
|
3328
|
+
break;
|
|
3329
|
+
}
|
|
3330
|
+
const remaining = deadline - Date.now();
|
|
3331
|
+
await new Promise((resolve) => {
|
|
3332
|
+
setTimeout(resolve, Math.max(0, Math.min(clipboardPollIntervalMs, remaining)));
|
|
3333
|
+
});
|
|
3186
3334
|
}
|
|
3335
|
+
const finalMatch = candidates.clipboard || candidates.dom || candidates.response || null;
|
|
3336
|
+
const finalSource = candidates.clipboard ? "clipboard" : candidates.dom ? "dom" : candidates.response ? "response" : "none";
|
|
3337
|
+
const payloadText = finalMatch?.payloadText || "";
|
|
3187
3338
|
const payloadSnapshot = payloadText ? payloadText.replace(/\s+/g, " ").trim().slice(0, payloadSnapshotMaxLen) : "";
|
|
3188
3339
|
return {
|
|
3189
|
-
link,
|
|
3340
|
+
link: finalMatch?.link || null,
|
|
3190
3341
|
payloadText,
|
|
3191
3342
|
payloadSnapshot,
|
|
3192
|
-
source
|
|
3343
|
+
source: finalSource
|
|
3193
3344
|
};
|
|
3194
3345
|
} finally {
|
|
3195
3346
|
if (enableResponse) {
|