背景
第一批申请的额度,一直无法开机,世界第一公司居然这么垃圾…
描述
自动开机脚本,成功自动停止,防频繁触发500
使用方式
浏览器console或dev tool代码块或油猴自己扩展
注意
替换fallbackSimulationId参数为自己的容器id
window.__simStateLoop?.stop?.();
window.__simStateLoop = (() => {
const fallbackSimulationId = "xxx-xxx-xxx-xxx";
const simulationId =
window.location.pathname.match(/\/(?:simulation|simulations)\/([0-9a-f-]+)/i)?.[1] ??
fallbackSimulationId;
const referrer = window.location.href;
const apiPrefix = "/api/v1";
const defaultRetryMs = 60000;
const serverErrorRetryMs = 30000;
const capacityRetryMs = 60000;
const successRetryMs = 15000;
const loadedStates = new Set(["LOADING", "LOADED", "RUNNING", "STARTING"]);
const requestCandidates = [
{
name: "v1.control.load",
method: "POST",
url: new URL(
`${apiPrefix}/simulation/${simulationId}/control/`,
window.location.origin
).toString(),
body: JSON.stringify({ action: "load" })
},
{
name: "v1.autoprovision",
method: "POST",
url: new URL(
`${apiPrefix}/simulation/autoprovision/?simulation_id=${encodeURIComponent(
simulationId
)}`,
window.location.origin
).toString(),
body: JSON.stringify({ include_services: true })
},
{
name: "v1.control.load.noTrailingSlash",
method: "POST",
url: new URL(
`${apiPrefix}/simulation/${simulationId}/control`,
window.location.origin
).toString(),
body: JSON.stringify({ action: "load" })
},
{
name: "v2.simulation.get",
method: "GET",
url: new URL(`${apiPrefix}/v2/simulations/${simulationId}/`, window.location.origin).toString(),
body: undefined
},
{
name: "v2.jobs.list",
method: "GET",
url: new URL(
`${apiPrefix}/v2/jobs/?simulation_id=${encodeURIComponent(simulationId)}`,
window.location.origin
).toString(),
body: undefined
},
{
name: "v2.nodes.list",
method: "GET",
url: new URL(
`${apiPrefix}/v2/simulations/?id=${encodeURIComponent(simulationId)}`,
window.location.origin
).toString(),
body: undefined
}
];
let stopped = false;
let preferredCandidateIndex = 0;
let lastObservedState = null;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const parseResponse = (text) => {
try {
return JSON.parse(text);
} catch {
return text;
}
};
const formatLogValue = (value) =>
typeof value === "string" ? value.slice(0, 500) : value;
const withJitter = (ms) => ms + Math.floor(Math.random() * 5000);
const logHeaders = (headers) => {
const entries = {};
for (const [key, value] of headers.entries()) {
entries[key] = value;
}
return entries;
};
const stopLoop = (reason) => {
stopped = true;
console.log(`simStateLoop stopped: ${reason}`);
};
const extractState = (candidateName, data) => {
if (!data || typeof data !== "object") {
return null;
}
if (typeof data.state === "string") {
return data.state;
}
if (
candidateName === "v2.simulation.get" &&
typeof data.state === "string"
) {
return data.state;
}
if (
candidateName === "v2.nodes.list" &&
Array.isArray(data.results) &&
data.results.length > 0 &&
typeof data.results[0]?.state === "string"
) {
return data.results[0].state;
}
return null;
};
const run = async () => {
while (!stopped) {
let waitMs = defaultRetryMs;
try {
const candidateOrder = requestCandidates.map((candidate, index) => ({
candidate,
index
}));
candidateOrder.sort((left, right) => {
if (left.index === preferredCandidateIndex) {
return -1;
}
if (right.index === preferredCandidateIndex) {
return 1;
}
return left.index - right.index;
});
let matchedCandidate = false;
for (const { candidate, index } of candidateOrder) {
const requestInit = {
method: candidate.method,
credentials: "include",
referrer,
headers: {
accept: "application/json, text/plain, */*"
}
};
if (candidate.body !== undefined) {
requestInit.headers["content-type"] = "application/json;charset=UTF-8";
requestInit.body = candidate.body;
}
const response = await fetch(candidate.url, requestInit);
const text = await response.text();
const data = parseResponse(text);
const prefix = `[${new Date().toLocaleTimeString()}] ${candidate.name}`;
console.log(`${prefix} status:`, response.status, response.statusText);
console.log(`${prefix} headers:`, logHeaders(response.headers));
if (data && typeof data === "object") {
console.log(`${prefix} response:`, data);
} else {
console.log(`${prefix} response:`, formatLogValue(data));
}
const state = extractState(candidate.name, data);
if (state && state !== lastObservedState) {
lastObservedState = state;
console.log(`${prefix} observed state:`, state);
}
if (state && loadedStates.has(state)) {
stopLoop(`simulation state is ${state}`);
matchedCandidate = true;
break;
}
if (response.ok) {
preferredCandidateIndex = index;
matchedCandidate = true;
waitMs = successRetryMs;
console.log(`${prefix} next retry in ${Math.round(waitMs / 1000)}s`);
break;
}
if (
response.status === 400 &&
data &&
typeof data === "object" &&
typeof data.message === "string" &&
data.message.includes("out of capacity")
) {
preferredCandidateIndex = index;
matchedCandidate = true;
waitMs = capacityRetryMs;
console.warn(
`${prefix} capacity limited, retry in ${Math.round(waitMs / 1000)}s`
);
break;
}
if (response.status >= 500) {
preferredCandidateIndex = index;
matchedCandidate = true;
waitMs = serverErrorRetryMs;
console.warn(
`${prefix} server error, retry in ${Math.round(waitMs / 1000)}s`
);
break;
}
if (response.status === 404 || response.status === 405) {
console.warn(
`${prefix} endpoint unavailable:`,
response.status,
response.statusText
);
continue;
}
preferredCandidateIndex = index;
matchedCandidate = true;
console.error(
`${prefix} HTTP error:`,
response.status,
response.statusText,
data
);
break;
}
if (!matchedCandidate) {
console.error("No compatible simulation start endpoint was found.");
}
} catch (error) {
console.error("request failed:", error);
waitMs = serverErrorRetryMs;
}
const finalWaitMs = withJitter(waitMs);
if (!stopped) {
console.log(`next loop in ${Math.round(finalWaitMs / 1000)}s`);
await sleep(finalWaitMs);
}
}
};
run();
return {
stop() {
stopLoop("manual stop");
}
};
})();
备注: 如有问题,请及时反馈,目前自测无问题
1 个帖子 - 1 位参与者