Refactor workflow auto-run to node graph

This commit is contained in:
QLHazyCoder
2026-05-15 17:41:35 +08:00
parent f6f804f1a2
commit 81fc40706a
76 changed files with 4028 additions and 1453 deletions
+34 -10
View File
@@ -20,7 +20,7 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1'
// Listen for commands from Background
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (
message.type === 'EXECUTE_STEP'
message.type === 'EXECUTE_NODE'
|| message.type === 'FILL_CODE'
|| message.type === 'STEP8_FIND_AND_CLICK'
|| message.type === 'STEP8_GET_STATE'
@@ -46,6 +46,7 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1'
sendResponse({ ok: true, ...(result || {}) });
}).catch(err => {
const reportedStep = Number(message.payload?.visibleStep) || message.step;
const reportedNodeId = resolveCommandNodeId(message);
if (isStopError(err)) {
if (reportedStep) {
log(`步骤 ${reportedStep || 8}:已被用户停止。`, 'warn');
@@ -61,7 +62,7 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1'
}
if (reportedStep) {
reportError(reportedStep, err.message);
reportError(reportedNodeId || reportedStep, err.message);
}
sendResponse({ error: err.message });
});
@@ -72,17 +73,40 @@ if (document.documentElement.getAttribute(SIGNUP_PAGE_LISTENER_SENTINEL) !== '1'
console.log('[MultiPage:signup-page] 消息监听已存在,跳过重复注册');
}
const SIGNUP_PAGE_NODE_HANDLERS = Object.freeze({
'submit-signup-email': (payload) => step2_clickRegister(payload),
'fill-password': (payload) => step3_fillEmailPassword(payload),
'fill-profile': (payload) => step5_fillNameBirthday(payload),
'oauth-login': (payload) => step6_login(payload),
'confirm-oauth': (_payload) => step8_findAndClick(),
});
function resolveCommandNodeId(message = {}) {
const directNodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
if (directNodeId) {
return directNodeId;
}
const visibleStep = Number(message.payload?.visibleStep || message.step) || 0;
if (visibleStep === 4) return 'fetch-signup-code';
if (visibleStep === 8 || visibleStep === 11) return 'fetch-login-code';
if (visibleStep === 9 || visibleStep === 12) return 'confirm-oauth';
if (visibleStep === 7 || visibleStep === 10) return 'oauth-login';
if (visibleStep === 5) return 'fill-profile';
if (visibleStep === 3) return 'fill-password';
if (visibleStep === 2) return 'submit-signup-email';
return '';
}
async function handleCommand(message) {
switch (message.type) {
case 'EXECUTE_STEP':
switch (message.step) {
case 2: return await step2_clickRegister(message.payload);
case 3: return await step3_fillEmailPassword(message.payload);
case 5: return await step5_fillNameBirthday(message.payload);
case 7: return await step6_login(message.payload);
case 9: return await step8_findAndClick();
default: throw new Error(`signup-page.js 不处理步骤 ${message.step}`);
case 'EXECUTE_NODE': {
const nodeId = String(message.nodeId || message.payload?.nodeId || '').trim();
const handler = SIGNUP_PAGE_NODE_HANDLERS[nodeId];
if (!handler) {
throw new Error(`signup-page.js 不处理节点 ${nodeId}`);
}
return await handler(message.payload || {});
}
case 'FILL_CODE':
// Step 4 = signup code, Step 7 = login code (same handler)
return await fillVerificationCode(message.step, message.payload);
+18 -7
View File
@@ -14,23 +14,23 @@ if (document.documentElement.getAttribute(SUB2API_PANEL_LISTENER_SENTINEL) !== '
document.documentElement.setAttribute(SUB2API_PANEL_LISTENER_SENTINEL, '1');
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'EXECUTE_STEP' || message.type === 'REQUEST_OAUTH_URL') {
if (message.type === 'EXECUTE_NODE' || message.type === 'REQUEST_OAUTH_URL') {
resetStopState();
const handler = message.type === 'REQUEST_OAUTH_URL'
? requestOAuthUrl(message.payload)
: handleStep(message.step, message.payload);
: handleNode(message.nodeId || message.payload?.nodeId, message.payload);
handler.then((result) => {
sendResponse({ ok: true, ...(result || {}) });
}).catch((err) => {
if (isStopError(err)) {
if (message.step) {
log('已被用户停止。', 'warn', { step: message.step });
if (message.payload?.visibleStep || message.step) {
log('已被用户停止。', 'warn', { step: message.payload?.visibleStep || message.step });
}
sendResponse({ stopped: true, error: err.message });
return;
}
if (message.step) {
reportError(message.step, err.message);
if (message.nodeId || message.payload?.nodeId) {
reportError(message.nodeId || message.payload?.nodeId, err.message);
}
sendResponse({ error: err.message });
});
@@ -76,6 +76,16 @@ async function handleStep(step, payload = {}) {
}
}
async function handleNode(nodeId, payload = {}) {
const normalizedNodeId = String(nodeId || '').trim();
switch (normalizedNodeId) {
case 'platform-verify':
return step9_submitOpenAiCallback(payload);
default:
throw new Error(`sub2api-panel.js 不处理节点 ${normalizedNodeId}`);
}
}
async function requestOAuthUrl(payload = {}) {
return step1_generateOpenAiAuthUrl(payload, { report: false });
}
@@ -665,9 +675,10 @@ async function step9_submitOpenAiCallback(payload = {}) {
const verifiedStatus = `SUB2API 已创建账号 #${createdAccount?.id || 'unknown'}`;
log(verifiedStatus, 'ok', { step: visibleStep, stepKey: 'platform-verify' });
reportComplete(visibleStep, {
reportComplete('platform-verify', {
localhostUrl: callback.url,
verifiedStatus,
visibleStep,
});
openAccountsPageSoon(origin);
}
+117 -20
View File
@@ -274,6 +274,35 @@ function normalizeLogStep(value) {
return step > 0 ? step : null;
}
const DEFAULT_OPENAI_NODE_BY_STEP = Object.freeze({
1: 'open-chatgpt',
2: 'submit-signup-email',
3: 'fill-password',
4: 'fetch-signup-code',
5: 'fill-profile',
6: 'wait-registration-success',
7: 'oauth-login',
8: 'fetch-login-code',
9: 'confirm-oauth',
10: 'platform-verify',
11: 'fetch-login-code',
12: 'confirm-oauth',
13: 'platform-verify',
});
function resolveReportNodeId(stepOrNodeId, data = {}) {
const explicitNodeId = String(data?.nodeId || data?.nodeKey || '').trim();
if (explicitNodeId) {
return explicitNodeId;
}
const directNodeId = String(stepOrNodeId || '').trim();
if (directNodeId && !/^\d+$/.test(directNodeId)) {
return directNodeId;
}
const step = normalizeLogStep(stepOrNodeId || data?.step || data?.visibleStep);
return step ? DEFAULT_OPENAI_NODE_BY_STEP[step] || '' : '';
}
/**
* Send a log message to Side Panel via Background.
* @param {string} message
@@ -323,30 +352,65 @@ function reportReady() {
}
/**
* Report step completion.
* @param {number} step
* @param {Object} data - Step output data
* Report node completion.
* @param {string|number} stepOrNodeId
* @param {Object} data - Node output data
*/
function reportComplete(step, data = {}) {
console.log(LOG_PREFIX, `步骤 ${step} 已完成`, data);
log('已成功完成', 'ok', { step });
function reportComplete(stepOrNodeId, data = {}) {
const nodeId = resolveReportNodeId(stepOrNodeId, data);
const step = normalizeLogStep(stepOrNodeId || data?.step || data?.visibleStep);
console.log(LOG_PREFIX, `节点 ${nodeId || stepOrNodeId} 已完成`, data);
log('已成功完成', 'ok', { step, stepKey: nodeId });
const message = {
type: 'STEP_COMPLETE',
type: 'NODE_COMPLETE',
source: getRuntimeScriptSource(),
step,
payload: data,
nodeId,
payload: {
...(data || {}),
...(nodeId ? { nodeId } : {}),
...(step ? { step } : {}),
},
error: null,
};
Promise.resolve(chrome.runtime.sendMessage(message))
.then((response) => {
console.log(LOG_PREFIX, `STEP_COMPLETE sent successfully for step ${step}`, {
console.log(LOG_PREFIX, `NODE_COMPLETE sent successfully for node ${nodeId || stepOrNodeId}`, {
response,
url: location.href,
payloadKeys: Object.keys(data || {}),
});
})
.catch((err) => {
console.error(LOG_PREFIX, `STEP_COMPLETE send failed for step ${step}`, err?.message || err, {
console.error(LOG_PREFIX, `NODE_COMPLETE send failed for node ${nodeId || stepOrNodeId}`, err?.message || err, {
url: location.href,
payloadKeys: Object.keys(data || {}),
});
});
}
function reportNodeComplete(nodeId, data = {}) {
const normalizedNodeId = String(nodeId || '').trim();
console.log(LOG_PREFIX, `节点 ${normalizedNodeId} 已完成`, data);
const message = {
type: 'NODE_COMPLETE',
source: getRuntimeScriptSource(),
nodeId: normalizedNodeId,
payload: {
...(data || {}),
nodeId: normalizedNodeId,
},
error: null,
};
Promise.resolve(chrome.runtime.sendMessage(message))
.then((response) => {
console.log(LOG_PREFIX, `NODE_COMPLETE sent successfully for node ${normalizedNodeId}`, {
response,
url: location.href,
payloadKeys: Object.keys(data || {}),
});
})
.catch((err) => {
console.error(LOG_PREFIX, `NODE_COMPLETE send failed for node ${normalizedNodeId}`, err?.message || err, {
url: location.href,
payloadKeys: Object.keys(data || {}),
});
@@ -354,29 +418,62 @@ function reportComplete(step, data = {}) {
}
/**
* Report step error.
* @param {number} step
* Report node error.
* @param {string|number} stepOrNodeId
* @param {string} errorMessage
*/
function reportError(step, errorMessage) {
console.error(LOG_PREFIX, `步骤 ${step} 失败: ${errorMessage}`);
function reportError(stepOrNodeId, errorMessage) {
const nodeId = resolveReportNodeId(stepOrNodeId);
const step = normalizeLogStep(stepOrNodeId);
console.error(LOG_PREFIX, `节点 ${nodeId || stepOrNodeId} 失败: ${errorMessage}`);
const message = {
type: 'STEP_ERROR',
type: 'NODE_ERROR',
source: getRuntimeScriptSource(),
step,
payload: {},
nodeId,
payload: {
...(nodeId ? { nodeId } : {}),
...(step ? { step } : {}),
},
error: errorMessage,
};
Promise.resolve(chrome.runtime.sendMessage(message))
.then((response) => {
console.log(LOG_PREFIX, `STEP_ERROR sent successfully for step ${step}`, {
console.log(LOG_PREFIX, `NODE_ERROR sent successfully for node ${nodeId || stepOrNodeId}`, {
response,
url: location.href,
errorMessage,
});
})
.catch((err) => {
console.error(LOG_PREFIX, `STEP_ERROR send failed for step ${step}`, err?.message || err, {
console.error(LOG_PREFIX, `NODE_ERROR send failed for node ${nodeId || stepOrNodeId}`, err?.message || err, {
url: location.href,
errorMessage,
});
});
}
function reportNodeError(nodeId, errorMessage) {
const normalizedNodeId = String(nodeId || '').trim();
console.error(LOG_PREFIX, `节点 ${normalizedNodeId} 失败: ${errorMessage}`);
const message = {
type: 'NODE_ERROR',
source: getRuntimeScriptSource(),
nodeId: normalizedNodeId,
payload: {
nodeId: normalizedNodeId,
},
error: errorMessage,
};
Promise.resolve(chrome.runtime.sendMessage(message))
.then((response) => {
console.log(LOG_PREFIX, `NODE_ERROR sent successfully for node ${normalizedNodeId}`, {
response,
url: location.href,
errorMessage,
});
})
.catch((err) => {
console.error(LOG_PREFIX, `NODE_ERROR send failed for node ${normalizedNodeId}`, err?.message || err, {
url: location.href,
errorMessage,
});
+19 -9
View File
@@ -1,4 +1,4 @@
// content/vps-panel.js — Content script for CPA panel (steps 7, 10 / OAuth URL request)
// content/vps-panel.js — Content script for CPA panel (OAuth URL request / platform verification node)
// Injected on: CPA panel (user-configured URL)
//
// Actual DOM structure (after login click):
@@ -39,12 +39,12 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1')
// Listen for commands from Background
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
if (message.type === 'EXECUTE_STEP' || message.type === 'REQUEST_OAUTH_URL') {
if (message.type === 'EXECUTE_NODE' || message.type === 'REQUEST_OAUTH_URL') {
resetStopState();
const startedAt = Date.now();
const actionLabel = message.type === 'REQUEST_OAUTH_URL'
? 'REQUEST_OAUTH_URL'
: `EXECUTE_STEP received for step ${message.step}`;
: `EXECUTE_NODE received for node ${message.nodeId || message.payload?.nodeId || ''}`;
console.log(LOG_PREFIX, actionLabel, {
url: location.href,
payloadKeys: Object.keys(message.payload || {}),
@@ -52,7 +52,7 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1')
});
const handler = message.type === 'REQUEST_OAUTH_URL'
? requestOAuthUrl(message.payload)
: handleStep(message.step, message.payload);
: handleNode(message.nodeId || message.payload?.nodeId, message.payload);
handler.then((result) => {
console.log(LOG_PREFIX, `${actionLabel} resolved after ${Date.now() - startedAt}ms`, {
url: location.href,
@@ -65,14 +65,14 @@ if (document.documentElement.getAttribute(VPS_PANEL_LISTENER_SENTINEL) !== '1')
snapshot: getVpsPanelSnapshot(),
});
if (isStopError(err)) {
if (message.step) {
log('已被用户停止。', 'warn', { step: message.step });
if (message.payload?.visibleStep || message.step) {
log('已被用户停止。', 'warn', { step: message.payload?.visibleStep || message.step });
}
sendResponse({ stopped: true, error: err.message });
return;
}
if (message.step) {
reportError(message.step, err.message);
if (message.nodeId || message.payload?.nodeId) {
reportError(message.nodeId || message.payload?.nodeId, err.message);
}
sendResponse({ error: err.message });
});
@@ -95,6 +95,16 @@ async function handleStep(step, payload) {
}
}
async function handleNode(nodeId, payload = {}) {
const normalizedNodeId = String(nodeId || '').trim();
switch (normalizedNodeId) {
case 'platform-verify':
return await step9_vpsVerify(payload);
default:
throw new Error(`vps-panel.js 不处理节点 ${normalizedNodeId}`);
}
}
function isVisibleElement(el) {
if (!el) return false;
const style = window.getComputedStyle(el);
@@ -1078,5 +1088,5 @@ async function step9_vpsVerify(payload) {
const verifiedStatus = await waitForExactSuccessBadge(STEP9_SUCCESS_BADGE_TIMEOUT_MS, visibleStep);
log(verifiedStatus, 'ok', { step: visibleStep, stepKey: 'platform-verify' });
reportComplete(visibleStep, { localhostUrl, verifiedStatus });
reportComplete('platform-verify', { localhostUrl, verifiedStatus, visibleStep });
}