Arena(arena.ai / lmarena.ai)里,模型不一定都挂在前台可选列表。想点名测 Kimi 相关前端检查点,装下面这套油猴脚本,再在搜索里选模型开聊就行。
先说实测里比较稳的几点:
- 前端观感经常能给惊喜
- 复杂业务逻辑、后端别指望一次做稳
- 提示词越短越好;写太长反而容易限制发挥
- 脚本有时刷不出模型,刷新或换时段重试
kivine和列表里第三个kimi-2.7-code不是同一个检测点,别混着比
安装
- 浏览器装 Tampermonkey(或兼容扩展)
- 油猴里新建脚本
- 把文末完整脚本粘进去保存
脚本匹配:
https://arena.ai/*https://*.arena.ai/*https://lmarena.ai/*https://*.lmarena.ai/*
怎么测
打开 Arena,搜索框输入模型名,选中后直接聊,让它生成页面 / 小游戏 / 可视化。
优先试:
kivine- 第三个
kimi-2.7-code
列表没刷出来就刷新;高峰期慢是正常的。
控制台可以看注入状态:
window.__arenaInjectorStatus
提示词怎么写
只记一条:短、具体、能一眼验收。
可以这样写:
做一个单关卡愤怒的小鸟,单 HTML,完成度尽量高。
生成一个 3D 城市交通模拟器:十字路口、红绿灯、高架桥、电车,日本街景车流。
写一个最难看的门户网站,要丑得有风格。
别一上来塞整本 PRD。复杂题拆两轮:先要可玩/可看,再补系统细节。
脚本干了什么
脚本名大致是 Arena Injector V5.5 Selectable Only。它主要做这些事:
- 扫 Arena 返回的模型载荷(RSC / fetch / XHR)
- 对已经
userSelectable: true的模型,补前端展示字段(比如provider、organization) - 处理同名模型去重时的遮挡(Text 和 Code 同名时,尽量别把 Code/WebDev 挡掉)
- 继续 hook Next.js 的
__next_f分块、嵌套 JSON、后续 SPA 导航
注意:
- 页面一改,脚本可能挂,得换版本
- 这是前端注入,不是官方功能
- 只建议自己做对比实验
已有样例
同一题材、不同检查点,先建立预期:
看材质、构图、UI 完成度、技能演出就够了。别当论文评测。
题库
游戏
请做一个像素骑士冒险放置游戏:3 个关卡,3 个豪华技能演出,6 个一般技能演出,6 个普通 buff,1 个 debuff;随机自动战斗,横版卷轴;带角色系统和装备系统。
制作一个单关卡的愤怒的小鸟,单 HTML,完成度尽量高。
做网页游戏:玩法以无人深空为主,画风以我的世界方块风为主;飞机与星球建模、UI 要酷;进入星球要有过渡动画;音效和交互尽量完整。
3D / 城市
生成一个 3D 城市交通模拟器,日本典型交通场景与车流,含十字路口、红绿灯、高架桥、电车。
工程前端
2.5D 化学工作流前端:React Flow 管 DAG,共享 WebGL Canvas 渲染悬浮分子;支持同 (x,y) 不同 z 层任务;Zustand 管状态;SSE/WebSocket 刷任务进度。
这类题更适合看架构表达和组件分层,别默认后端也能一次做稳。
教学可视化
做一个单页交互式网页,讲清格子玻尔兹曼方法(LBM)基本原理:D2Q9、碰撞、迁移、宏观量恢复;中文讲解,公式可渲染,带动画和自测题。
反例
反向思维:写一个最难看的门户网站。
制作一个比这个更糟糕的网站:https://www.richify.net/zh-hans/
想比得更公平
固定这几条就行:
- 同一提示词分别打
kivine和kimi-2.7-code - 同一浏览器、同一时段,少掺排队噪音
- 只评三件事:首屏观感、交互能不能点、代码能不能本地独立打开
- 复杂题拆两轮,别一次 dump 完整方案
- 要验收标准时,可以先让别的模型出 rubric,再回 Arena 看成品
能力边界
目前体感大概是:
- 前端好看:经常有
- 复杂状态机、联机、数值闭环:一般
- 真实后端工程题:别默认能扛
- 长提示词:容易限制发挥,短题更舒服
故障排查
| 现象 | 先试什么 |
|---|---|
| 搜不到目标模型 | 刷新;确认脚本已启用;看 window.__arenaInjectorStatus 有没有 patch |
| 偶尔刷不出来 | 换时段重试 |
| 生成很慢 | 高峰排队正常;先用更短提示词验证通路 |
| 同名模型点错 | 分清 Text / Code,确认 publicName |
| 脚本装了没效果 | 确认域名匹配 arena.ai / lmarena.ai,扩展对该站有权限 |
完整脚本
油猴新建脚本后,整段复制粘贴即可。Arena 前端一改,可能要换新版本。
// ==UserScript==
// @name (仙之人兮列如麻) V5.5 Selectable Only
// @namespace http://tampermonkey.net/
// @version 0.10.0
// @description Reveals Arena userSelectable:true models and resolves duplicate model IDs safely
// @author skuhrasr0plus1
// @match https://arena.ai/*
// @match https://*.arena.ai/*
// @match https://lmarena.ai/*
// @match https://*.lmarena.ai/*
// @grant none
// @run-at document-start
// @inject-into page
// @sandbox raw
// ==/UserScript==
(function () {
'use strict';
const W = window;
const INSTALL_KEY = Symbol.for('arena.provider.injector.v5.5.installed');
const PUSH_ACCESSOR_MARK = Symbol.for('arena.provider.injector.push.accessor');
const PUSH_WRAPPER_MARK = Symbol.for('arena.provider.injector.push.wrapper');
const WATCH_MARK = Symbol.for('arena.provider.injector.nextf.watcher');
const FETCH_MARK = Symbol.for('arena.provider.injector.fetch');
const PARSE_MARK = Symbol.for('arena.provider.injector.parse');
const XHR_MARK = Symbol.for('arena.provider.injector.xhr');
if (W[INSTALL_KEY]) {
console.warn('[Arena Injector V5.5] 已运行,阻止重复安装。');
return;
}
W[INSTALL_KEY] = true;
const CONFIG = {
provider: 'google',
organization: 'google',
// 不翻转 userSelectable:false;只补齐服务端已允许的模型。
forceSelectablePublicNames: new Set(),
// Arena 同时提供同名的 Text 与 Code Kivine。跳过 Text ID,
// 避免前端按 publicName 去重时遮蔽 Code/WebDev 版本。
excludedModelIds: new Set([
'019f62a7-eda4-7659-9436-a4bd76fbc383'
]),
debug: true,
hookFetch: true,
hookJSONParse: true,
hookXHR: true,
recoveryInterval: 1000
};
const status = {
version: '5.5',
startedAt: new Date().toISOString(),
watcherInstallations: 0,
nextArraysHooked: 0,
nextPushCalls: 0,
pushReplacements: 0,
textPayloadsScanned: 0,
textPayloadsPatched: 0,
textModelsPatched: 0,
objectModelsPatched: 0,
fieldsAdded: 0,
selectableForced: 0,
fetchSeen: 0,
fetchPatched: 0,
jsonParseCalls: 0,
xhrPatched: 0,
lastPatchAt: null,
lastError: null
};
try {
Object.defineProperty(W, '__arenaInjectorStatus', {
value: status,
configurable: true
});
} catch {
W.__arenaInjectorStatus = status;
}
const log = (...args) => {
if (CONFIG.debug) {
console.log('[Arena Injector V5.5]', ...args);
}
};
const reportError = (area, error) => {
status.lastError = {
area,
message: String(error?.message || error),
time: new Date().toISOString()
};
console.warn(\`[Arena Injector V5.5] ${area}:\`, error);
};
const own = (object, key) =>
Object.prototype.hasOwnProperty.call(object, key);
const touchPatchTime = () => {
status.lastPatchAt = new Date().toISOString();
};
/*
* level 0:普通 JSON
* {"publicName":"..."}
*
* level 1:嵌套在字符串中的一次转义 JSON
* {\"publicName\":\"...\"}
*/
function isStructuralQuote(text, index, level) {
let slashCount = 0;
for (let i = index - 1; i >= 0 && text[i] === '\\'; i--) {
slashCount++;
}
if (level === 0) {
return slashCount % 2 === 0;
}
return slashCount % 4 === 1;
}
function skipWhitespace(text, position) {
while (
position < text.length &&
(
text[position] === ' ' ||
text[position] === '\t' ||
text[position] === '\r' ||
text[position] === '\n'
)
) {
position++;
}
return position;
}
function encodePair(key, value, level) {
const raw = JSON.stringify(key) + ':' + JSON.stringify(value);
if (level === 0) {
return raw;
}
return raw
.replace(/\\/g, '\\\\')
.replace(/"/g, '\\"');
}
/*
* 使用对象边界而不是“向后看 300 字符”。
* provider/organization 只在同一个完整对象中检查。
*/
function collectLevelInsertions(text, level) {
const objects = [];
const stack = [];
const tokens = [];
let inString = false;
let opening = null;
for (let i = 0; i < text.length; i++) {
const character = text[i];
if (
character === '"' &&
isStructuralQuote(text, i, level)
) {
if (!inString) {
inString = true;
opening = {
quote: i,
start: level === 1 ? i - 1 : i,
parent: stack.length
? stack[stack.length - 1]
: null
};
} else {
tokens.push({
start: opening.start,
end: i + 1,
content: text.slice(
opening.quote + 1,
level === 1 ? i - 1 : i
),
parent: opening.parent
});
inString = false;
opening = null;
}
continue;
}
if (inString) {
continue;
}
if (character === '{') {
const object = {
start: i,
end: -1,
keys: new Set(),
id: null,
publicName: null,
publicNameValueEnd: -1,
userSelectableValue: null,
userSelectableFalseStart: -1
};
objects.push(object);
stack.push(object);
} else if (character === '}' && stack.length) {
const object = stack.pop();
object.end = i;
}
}
const tokenByStart = new Map();
for (const token of tokens) {
tokenByStart.set(token.start, token);
}
for (const token of tokens) {
const object = token.parent;
// 不处理跨分块后尚未闭合的对象。
if (!object || object.end < 0) {
continue;
}
let position = skipWhitespace(text, token.end);
if (text[position] !== ':') {
continue;
}
const key = token.content;
if (
key !== 'id' &&
key !== 'publicName' &&
key !== 'provider' &&
key !== 'organization' &&
key !== 'userSelectable'
) {
continue;
}
object.keys.add(key);
if (key === 'id' && object.id === null) {
position = skipWhitespace(text, position + 1);
const valueToken = tokenByStart.get(position);
if (
valueToken &&
valueToken.parent === object
) {
object.id = valueToken.content;
}
} else if (
key === 'publicName' &&
object.publicNameValueEnd < 0
) {
position = skipWhitespace(text, position + 1);
const valueToken = tokenByStart.get(position);
if (
valueToken &&
valueToken.parent === object
) {
object.publicName = valueToken.content;
object.publicNameValueEnd = valueToken.end;
}
} else if (
key === 'userSelectable' &&
object.userSelectableValue === null
) {
position = skipWhitespace(text, position + 1);
const literal = text.slice(position, position + 5);
const valueLength = literal.startsWith('true')
? 4
: literal === 'false'
? 5
: 0;
const afterValue = text[position + valueLength];
if (
valueLength > 0 &&
(
afterValue === undefined ||
afterValue === ',' ||
afterValue === '}' ||
afterValue === ' ' ||
afterValue === '\t' ||
afterValue === '\r' ||
afterValue === '\n'
)
) {
object.userSelectableValue =
valueLength === 4;
if (valueLength === 5) {
object.userSelectableFalseStart =
position;
}
}
}
}
const insertions = [];
for (const object of objects) {
if (
object.end < 0 ||
object.publicNameValueEnd < 0 ||
object.userSelectableValue !== true ||
CONFIG.excludedModelIds.has(object.id)
) {
continue;
}
const fields = [];
if (!object.keys.has('organization')) {
fields.push(
encodePair(
'organization',
CONFIG.organization,
level
)
);
}
if (!object.keys.has('provider')) {
fields.push(
encodePair(
'provider',
CONFIG.provider,
level
)
);
}
if (fields.length) {
insertions.push({
position: object.publicNameValueEnd,
value: ',' + fields.join(','),
fieldCount: fields.length,
selectableCount: 0
});
}
if (
CONFIG.forceSelectablePublicNames.has(
object.publicName
) &&
object.userSelectableFalseStart >= 0
) {
insertions.push({
position: object.userSelectableFalseStart,
end: object.userSelectableFalseStart + 5,
// 保持与 false 相同长度,便于跨 RSC chunk 原位替换。
value: 'true ',
fieldCount: 0,
selectableCount: 1
});
}
}
return insertions;
}
function collectAllInsertions(text) {
if (
typeof text !== 'string' ||
!text.includes('publicName')
) {
return [];
}
const candidates = [
...collectLevelInsertions(text, 0),
...collectLevelInsertions(text, 1)
];
const result = [];
const seen = new Set();
for (const insertion of candidates) {
const key = [
insertion.position,
insertion.end ?? insertion.position,
insertion.value
].join('\u0000');
if (!seen.has(key)) {
seen.add(key);
result.push(insertion);
}
}
return result;
}
function recordTextInsertions(insertions) {
if (!insertions.length) {
return;
}
status.textModelsPatched += insertions.length;
status.fieldsAdded += insertions.reduce(
(sum, item) => sum + item.fieldCount,
0
);
status.selectableForced += insertions.reduce(
(sum, item) => sum + (item.selectableCount || 0),
0
);
touchPatchTime();
}
function applyInsertions(text, insertions) {
let output = text;
const ordered = [...insertions].sort(
(a, b) => b.position - a.position
);
for (const insertion of ordered) {
const end = insertion.end ?? insertion.position;
output =
output.slice(0, insertion.position) +
insertion.value +
output.slice(end);
}
return output;
}
function patchText(text) {
if (
typeof text !== 'string' ||
!text.includes('publicName')
) {
return text;
}
status.textPayloadsScanned++;
const insertions = collectAllInsertions(text);
if (!insertions.length) {
return text;
}
const output = applyInsertions(text, insertions);
status.textPayloadsPatched++;
recordTextInsertions(insertions);
log(
\`文本载荷修改 ${insertions.length} 处,\` +
\`增加 ${insertions.reduce(
(sum, item) => sum + item.fieldCount,
0
)} 个字段,强制开放 ${insertions.reduce(
(sum, item) =>
sum + (item.selectableCount || 0),
0
)} 个目标模型。\`
);
return output;
}
/*
* 处理跨 __next_f chunk 的对象。
* 不重新切割原有字符,只把新增字段放回对应原始 chunk,
* 避免破坏 Unicode 代理对以及 RSC 分块顺序。
*/
function patchFlightGroup(entries) {
if (!entries.length) {
return;
}
const chunks = entries.map(entry => entry[1]);
const combined = chunks.join('');
if (!combined.includes('publicName')) {
return;
}
status.textPayloadsScanned++;
const insertions = collectAllInsertions(combined);
if (!insertions.length) {
return;
}
const starts = [];
const ends = [];
const buckets = chunks.map(() => []);
let offset = 0;
for (let i = 0; i < chunks.length; i++) {
starts[i] = offset;
offset += chunks[i].length;
ends[i] = offset;
}
for (const insertion of insertions) {
const insertionEnd =
insertion.end ?? insertion.position;
let index = 0;
if (insertionEnd > insertion.position) {
/*
* 长度保持不变的替换可能刚好跨 chunk。
* 按原始 chunk 边界拆开,避免残留 false 的尾部。
*/
let segmentStart = insertion.position;
while (segmentStart < insertionEnd) {
index = 0;
while (
index < ends.length - 1 &&
ends[index] <= segmentStart
) {
index++;
}
const segmentEnd = Math.min(
insertionEnd,
ends[index]
);
const valueOffset =
segmentStart - insertion.position;
const segmentLength =
segmentEnd - segmentStart;
buckets[index].push({
...insertion,
position: segmentStart - starts[index],
end: segmentEnd - starts[index],
value: insertion.value.slice(
valueOffset,
valueOffset + segmentLength
)
});
segmentStart = segmentEnd;
}
continue;
}
while (
index < ends.length - 1 &&
ends[index] < insertion.position
) {
index++;
}
buckets[index].push({
...insertion,
position: insertion.position - starts[index]
});
}
for (let i = 0; i < chunks.length; i++) {
if (!buckets[i].length) {
continue;
}
entries[i][1] = applyInsertions(
chunks[i],
buckets[i]
);
}
status.textPayloadsPatched++;
recordTextInsertions(insertions);
log(
\`跨 RSC chunk 修改 ${insertions.length} 处。\`
);
}
function patchFlightEntries(nextArray) {
let group = [];
const flush = () => {
if (group.length) {
patchFlightGroup(group);
group = [];
}
};
for (const entry of nextArray) {
if (
Array.isArray(entry) &&
entry[0] === 1 &&
typeof entry[1] === 'string'
) {
group.push(entry);
} else {
flush();
}
}
flush();
}
function isPlainContainer(value) {
if (!value || typeof value !== 'object') {
return false;
}
if (Array.isArray(value)) {
return true;
}
const prototype = Object.getPrototypeOf(value);
return (
prototype === Object.prototype ||
prototype === null
);
}
function setMissingField(object, key, value) {
if (own(object, key)) {
return false;
}
try {
if (!Reflect.set(object, key, value)) {
return false;
}
return own(object, key);
} catch {
return false;
}
}
function replaceOwnField(
object,
key,
expectedValue,
replacementValue
) {
if (!own(object, key)) {
return false;
}
try {
if (object[key] !== expectedValue) {
return false;
}
if (!Reflect.set(object, key, replacementValue)) {
return false;
}
return object[key] === replacementValue;
} catch {
return false;
}
}
/*
* JSON.parse、XHR responseType=json 等已经转成对象的数据,
* 直接递归补字段。
*/
function patchAny(value, seen = new WeakSet()) {
if (typeof value === 'string') {
return patchText(value);
}
if (!isPlainContainer(value) || seen.has(value)) {
return value;
}
seen.add(value);
if (!Array.isArray(value)) {
let id;
let publicName;
let userSelectable;
try {
id = value.id;
publicName = value.publicName;
userSelectable = value.userSelectable;
} catch {
id = undefined;
publicName = undefined;
userSelectable = undefined;
}
if (
own(value, 'publicName') &&
typeof publicName === 'string' &&
own(value, 'userSelectable') &&
userSelectable === true &&
!CONFIG.excludedModelIds.has(id)
) {
let added = 0;
let selectableForced = 0;
if (
CONFIG.forceSelectablePublicNames.has(
publicName
) &&
replaceOwnField(
value,
'userSelectable',
false,
true
)
) {
selectableForced++;
}
if (
setMissingField(
value,
'organization',
CONFIG.organization
)
) {
added++;
}
if (
setMissingField(
value,
'provider',
CONFIG.provider
)
) {
added++;
}
if (added || selectableForced) {
status.objectModelsPatched++;
status.fieldsAdded += added;
status.selectableForced +=
selectableForced;
touchPatchTime();
}
}
}
if (Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
const patched = patchAny(value[i], seen);
if (patched !== value[i]) {
try {
value[i] = patched;
} catch {
// 忽略只读数组。
}
}
}
} else {
let keys;
try {
keys = Object.keys(value);
} catch {
return value;
}
for (const key of keys) {
let child;
try {
child = value[key];
} catch {
continue;
}
const patched = patchAny(child, seen);
if (patched !== child) {
try {
value[key] = patched;
} catch {
// 忽略只读属性。
}
}
}
}
return value;
}
/*
* __next_f 处理。
*/
const nextStates = new WeakMap();
function processExisting(nextArray) {
if (!Array.isArray(nextArray)) {
return;
}
// 优先联合处理,解决模型对象跨 chunk。
patchFlightEntries(nextArray);
let state = nextStates.get(nextArray);
if (!state) {
state = {
snapshot: [],
delegate: null,
wrapper: null
};
nextStates.set(nextArray, state);
}
for (let i = 0; i < nextArray.length; i++) {
if (state.snapshot[i] === nextArray[i]) {
continue;
}
const patched = patchAny(nextArray[i]);
if (patched !== nextArray[i]) {
try {
nextArray[i] = patched;
} catch {
// 忽略只读项。
}
}
state.snapshot[i] = nextArray[i];
}
state.snapshot.length = nextArray.length;
}
/*
* 使用 push 访问器,而不是单纯 arr.push = wrapper。
* Next.js 后续执行 arr.push = nextServerDataCallback 时,
* setter 会立即捕获并继续包装,不再出现 hook 被覆盖的窗口。
*/
function ensurePushHook(nextArray) {
if (
!Array.isArray(nextArray) ||
typeof nextArray.push !== 'function'
) {
return;
}
const currentDescriptor =
Object.getOwnPropertyDescriptor(nextArray, 'push');
if (
currentDescriptor?.get?.[PUSH_ACCESSOR_MARK]
) {
return;
}
let state = nextStates.get(nextArray);
if (!state) {
state = {
snapshot: [],
delegate: null,
wrapper: null
};
nextStates.set(nextArray, state);
}
const currentPush = nextArray.push;
if (
currentPush !== state.wrapper &&
typeof currentPush === 'function'
) {
state.delegate = currentPush;
}
if (!state.wrapper) {
state.wrapper = function (...args) {
status.nextPushCalls++;
for (let i = 0; i < args.length; i++) {
args[i] = patchAny(args[i]);
}
const delegate = state.delegate;
if (typeof delegate !== 'function') {
throw new TypeError(
'__next_f.push delegate is not a function'
);
}
const result = Reflect.apply(
delegate,
this,
args
);
if (this === nextArray) {
// native push 阶段可在追加后联合检查所有分块。
processExisting(nextArray);
}
return result;
};
Object.defineProperty(
state.wrapper,
PUSH_WRAPPER_MARK,
{ value: true }
);
}
const getter = function () {
return state.wrapper;
};
const setter = function (newPush) {
if (
newPush !== state.wrapper &&
typeof newPush === 'function'
) {
state.delegate = newPush;
status.pushReplacements++;
log('捕获到 Next.js 替换 __next_f.push。');
} else if (newPush !== state.wrapper) {
state.delegate = newPush;
}
};
Object.defineProperty(
getter,
PUSH_ACCESSOR_MARK,
{ value: true }
);
try {
Object.defineProperty(nextArray, 'push', {
get: getter,
set: setter,
configurable: true,
enumerable: false
});
status.nextArraysHooked++;
log('__next_f.push 已安装持久访问器。');
} catch (error) {
reportError('安装 push 访问器失败,使用普通包装', error);
try {
nextArray.push = state.wrapper;
} catch (fallbackError) {
reportError('普通 push 包装也失败', fallbackError);
}
}
}
function attachNextArray(value) {
if (!Array.isArray(value)) {
return;
}
processExisting(value);
ensurePushHook(value);
}
function installNextWatcher() {
let descriptor;
try {
descriptor =
Object.getOwnPropertyDescriptor(W, '__next_f');
} catch (error) {
reportError('读取 __next_f descriptor 失败', error);
return;
}
if (descriptor?.get?.[WATCH_MARK]) {
try {
attachNextArray(W.__next_f);
} catch {
// getter 自身异常时保持原行为。
}
return;
}
let currentValue;
try {
currentValue = descriptor?.get
? Reflect.apply(descriptor.get, W, [])
: descriptor?.value;
} catch {
currentValue = undefined;
}
attachNextArray(currentValue);
if (descriptor && !descriptor.configurable) {
log('__next_f 不可配置,仅启用直接 hook/轮询恢复。');
return;
}
const originalGetter = descriptor?.get;
const originalSetter = descriptor?.set;
let localValue = currentValue;
const getter = function () {
const value = originalGetter
? Reflect.apply(originalGetter, this, [])
: localValue;
attachNextArray(value);
return value;
};
const setter = function (value) {
if (originalSetter) {
Reflect.apply(originalSetter, this, [value]);
} else {
localValue = value;
}
const actualValue = originalGetter
? Reflect.apply(originalGetter, this, [])
: localValue;
attachNextArray(actualValue);
};
Object.defineProperty(getter, WATCH_MARK, {
value: true
});
try {
Object.defineProperty(W, '__next_f', {
get: getter,
set: setter,
configurable: true,
enumerable: descriptor
? descriptor.enumerable
: true
});
status.watcherInstallations++;
log('__next_f 全局 watcher 已安装。');
} catch (error) {
reportError('安装 __next_f watcher 失败', error);
}
}
/*
* JSON.parse fallback。
*/
function installJSONParseHook() {
if (!CONFIG.hookJSONParse) {
return;
}
const nativeParse = W.JSON?.parse;
if (
typeof nativeParse !== 'function' ||
nativeParse[PARSE_MARK]
) {
return;
}
const wrappedParse = function (...args) {
status.jsonParseCalls++;
const result = Reflect.apply(
nativeParse,
this,
args
);
return patchAny(result);
};
Object.defineProperty(wrappedParse, PARSE_MARK, {
value: true
});
try {
W.JSON.parse = wrappedParse;
log('JSON.parse fallback 已安装。');
} catch (error) {
reportError('安装 JSON.parse hook 失败', error);
}
}
/*
* SPA/RSC fetch。
* Next.js 客户端导航不会继续使用初始 __next_f,
* 因此必须处理 text/x-component 和 JSON 响应。
*/
function shouldInspectResponse(response, input) {
if (
!response ||
!response.body ||
response.type === 'opaque' ||
response.status === 204 ||
response.status === 304
) {
return false;
}
const contentType = (
response.headers.get('content-type') || ''
).toLowerCase();
if (
contentType.includes('text/event-stream') ||
contentType.includes('javascript') ||
contentType.includes('text/css') ||
contentType.startsWith('image/') ||
contentType.startsWith('audio/') ||
contentType.startsWith('video/') ||
contentType.includes('font/')
) {
return false;
}
let url = response.url || '';
if (!url) {
if (typeof input === 'string') {
url = input;
} else if (input?.url) {
url = input.url;
}
}
return (
contentType.includes('text/x-component') ||
contentType.includes('application/json') ||
contentType.includes('application/graphql') ||
/(?:[?&]_rsc=|\/api\/|graphql|models?)/i.test(url)
);
}
function rebuildResponse(response, body) {
const headers = new W.Headers(response.headers);
// 原响应的压缩及长度头不再适用于新正文。
headers.delete('content-length');
headers.delete('content-encoding');
headers.delete('transfer-encoding');
headers.delete('etag');
const replacement = new W.Response(body, {
status: response.status,
statusText: response.statusText,
headers
});
for (const property of [
'url',
'redirected',
'type'
]) {
try {
Object.defineProperty(replacement, property, {
value: response[property],
configurable: true
});
} catch {
// 非关键元数据,忽略。
}
}
return replacement;
}
function installFetchHook() {
if (!CONFIG.hookFetch) {
return;
}
const nativeFetch = W.fetch;
if (
typeof nativeFetch !== 'function' ||
nativeFetch[FETCH_MARK]
) {
return;
}
const wrappedFetch = async function (...args) {
status.fetchSeen++;
const response = await Reflect.apply(
nativeFetch,
this,
args
);
if (!shouldInspectResponse(response, args[0])) {
return response;
}
try {
const clone = response.clone();
const originalText = await clone.text();
if (!originalText.includes('publicName')) {
return response;
}
const patchedText = patchText(originalText);
if (patchedText === originalText) {
return response;
}
status.fetchPatched++;
log('已修补 SPA/fetch 响应:', response.url);
return rebuildResponse(
response,
patchedText
);
} catch (error) {
reportError('处理 fetch 响应失败', error);
return response;
}
};
Object.defineProperty(wrappedFetch, FETCH_MARK, {
value: true
});
try {
W.fetch = wrappedFetch;
log('fetch/RSC hook 已安装。');
} catch (error) {
reportError('安装 fetch hook 失败', error);
}
}
/*
* XHR fallback。
*/
function installXHRHook() {
if (!CONFIG.hookXHR || !W.XMLHttpRequest) {
return;
}
const prototype = W.XMLHttpRequest.prototype;
const textCache = new WeakMap();
function patchCachedText(xhr, raw) {
const cached = textCache.get(xhr);
if (cached?.raw === raw) {
return cached.patched;
}
const patched = patchText(raw);
textCache.set(xhr, {
raw,
patched
});
if (patched !== raw) {
status.xhrPatched++;
}
return patched;
}
function wrapGetter(name) {
const descriptor =
Object.getOwnPropertyDescriptor(
prototype,
name
);
if (
!descriptor?.get ||
!descriptor.configurable ||
descriptor.get[XHR_MARK]
) {
return;
}
const nativeGetter = descriptor.get;
const getter = function () {
const value = Reflect.apply(
nativeGetter,
this,
[]
);
if (typeof value === 'string') {
return patchCachedText(this, value);
}
if (isPlainContainer(value)) {
return patchAny(value);
}
return value;
};
Object.defineProperty(getter, XHR_MARK, {
value: true
});
Object.defineProperty(prototype, name, {
...descriptor,
get: getter
});
}
try {
wrapGetter('responseText');
wrapGetter('response');
log('XHR fallback 已安装。');
} catch (error) {
reportError('安装 XHR hook 失败', error);
}
}
/*
* 安装顺序:先抢占 __next_f,再安装其他数据通道。
*/
installNextWatcher();
installJSONParseHook();
installFetchHook();
installXHRHook();
/*
* 恢复机制:
* 应对页面通过 Object.defineProperty 覆盖 watcher/push。
*/
W.setInterval(() => {
try {
installNextWatcher();
attachNextArray(W.__next_f);
} catch (error) {
reportError('周期恢复检查失败', error);
}
}, CONFIG.recoveryInterval);
log('启动完成。调试状态:window.__arenaInjectorStatus');
})();
本稿 published: false,审完再上线。
