Upload unified-server.js
Browse files- unified-server.js +109 -29
unified-server.js
CHANGED
|
@@ -219,13 +219,13 @@ class BrowserManager {
|
|
| 219 |
this.page = await this.context.newPage();
|
| 220 |
this.logger.info(`[Browser] 正在加载账户 ${authIndex} 并访问目标网页...`);
|
| 221 |
const targetUrl = 'https://aistudio.google.com/u/0/apps/bundled/blank?showPreview=true&showCode=true&showAssistant=true';
|
| 222 |
-
await this.page.goto(targetUrl, { timeout:
|
| 223 |
this.logger.info('[Browser] 网页加载完成,正在注入客户端脚本...');
|
| 224 |
|
| 225 |
const editorContainerLocator = this.page.locator('div.monaco-editor').first();
|
| 226 |
|
| 227 |
-
this.logger.info('[Browser] 等待编辑器出现,最长
|
| 228 |
-
await editorContainerLocator.waitFor({ state: 'visible', timeout:
|
| 229 |
this.logger.info('[Browser] 编辑器已出现,准备粘贴脚本。');
|
| 230 |
|
| 231 |
// --- START: 新增的点击逻辑 ---
|
|
@@ -243,7 +243,7 @@ class BrowserManager {
|
|
| 243 |
}
|
| 244 |
// --- END: 新增的点击逻辑 ---
|
| 245 |
|
| 246 |
-
await editorContainerLocator.click({ timeout:
|
| 247 |
await this.page.evaluate(text => navigator.clipboard.writeText(text), buildScriptContent);
|
| 248 |
const isMac = os.platform() === 'darwin';
|
| 249 |
const pasteKey = isMac ? 'Meta+V' : 'Control+V';
|
|
@@ -302,7 +302,7 @@ class LoggingService {
|
|
| 302 |
}
|
| 303 |
|
| 304 |
class MessageQueue extends EventEmitter {
|
| 305 |
-
constructor(timeoutMs =
|
| 306 |
super();
|
| 307 |
this.messages = [];
|
| 308 |
this.waitingResolvers = [];
|
|
@@ -469,7 +469,8 @@ class RequestHandler {
|
|
| 469 |
if (nextAuthIndex === null) {
|
| 470 |
this.logger.error('🔴 [Auth] 无法切换账号,因为没有可用的认证源!');
|
| 471 |
this.isAuthSwitching = false;
|
| 472 |
-
|
|
|
|
| 473 |
}
|
| 474 |
|
| 475 |
this.logger.info('==================================================');
|
|
@@ -497,12 +498,45 @@ class RequestHandler {
|
|
| 497 |
}
|
| 498 |
}
|
| 499 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
async _handleRequestFailureAndSwitch(errorDetails, res) {
|
| 501 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 502 |
|
| 503 |
if (isImmediateSwitch) {
|
| 504 |
-
this.logger.warn(`🔴 [Auth] 收到状态码 ${
|
| 505 |
-
if (res) this._sendErrorChunkToClient(res, `收到状态码 ${
|
| 506 |
try {
|
| 507 |
await this._switchToNextAuth();
|
| 508 |
if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
|
|
@@ -512,23 +546,24 @@ class RequestHandler {
|
|
| 512 |
}
|
| 513 |
return; // End here after immediate switch attempt
|
| 514 |
}
|
| 515 |
-
|
|
|
|
| 516 |
if (this.config.failureThreshold > 0) {
|
| 517 |
-
|
| 518 |
-
|
| 519 |
-
|
| 520 |
-
|
| 521 |
-
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
|
|
|
| 528 |
}
|
| 529 |
-
}
|
| 530 |
} else {
|
| 531 |
-
|
| 532 |
}
|
| 533 |
}
|
| 534 |
|
|
@@ -595,8 +630,13 @@ class RequestHandler {
|
|
| 595 |
this._forwardRequest(proxyRequest);
|
| 596 |
lastMessage = await messageQueue.dequeue();
|
| 597 |
if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
|
| 598 |
-
|
| 599 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
this._sendErrorChunkToClient(res, errorText);
|
| 601 |
if (attempt < this.maxRetries) {
|
| 602 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
|
@@ -606,9 +646,14 @@ class RequestHandler {
|
|
| 606 |
}
|
| 607 |
break;
|
| 608 |
}
|
|
|
|
| 609 |
if (lastMessage.event_type === 'error' || requestFailed) {
|
| 610 |
-
|
|
|
|
|
|
|
| 611 |
}
|
|
|
|
|
|
|
| 612 |
if (this.failureCount > 0) {
|
| 613 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
| 614 |
}
|
|
@@ -633,8 +678,13 @@ class RequestHandler {
|
|
| 633 |
this._forwardRequest(proxyRequest);
|
| 634 |
headerMessage = await messageQueue.dequeue();
|
| 635 |
if (headerMessage.event_type === 'error' && headerMessage.status >= 400 && headerMessage.status <= 599) {
|
| 636 |
-
|
| 637 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 638 |
if (attempt < this.maxRetries) {
|
| 639 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
| 640 |
continue;
|
|
@@ -644,7 +694,11 @@ class RequestHandler {
|
|
| 644 |
break;
|
| 645 |
}
|
| 646 |
if (headerMessage.event_type === 'error' || requestFailed) {
|
| 647 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 648 |
}
|
| 649 |
if (this.failureCount > 0) {
|
| 650 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
|
@@ -941,6 +995,32 @@ class ProxyServerSystem extends EventEmitter {
|
|
| 941 |
});
|
| 942 |
});
|
| 943 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 944 |
app.use(this._createAuthMiddleware());
|
| 945 |
|
| 946 |
app.all(/(.*)/, (req, res) => {
|
|
|
|
| 219 |
this.page = await this.context.newPage();
|
| 220 |
this.logger.info(`[Browser] 正在加载账户 ${authIndex} 并访问目标网页...`);
|
| 221 |
const targetUrl = 'https://aistudio.google.com/u/0/apps/bundled/blank?showPreview=true&showCode=true&showAssistant=true';
|
| 222 |
+
await this.page.goto(targetUrl, { timeout: 120000, waitUntil: 'networkidle' });
|
| 223 |
this.logger.info('[Browser] 网页加载完成,正在注入客户端脚本...');
|
| 224 |
|
| 225 |
const editorContainerLocator = this.page.locator('div.monaco-editor').first();
|
| 226 |
|
| 227 |
+
this.logger.info('[Browser] 等待编辑器出现,最长120秒...');
|
| 228 |
+
await editorContainerLocator.waitFor({ state: 'visible', timeout: 120000 });
|
| 229 |
this.logger.info('[Browser] 编辑器已出现,准备粘贴脚本。');
|
| 230 |
|
| 231 |
// --- START: 新增的点击逻辑 ---
|
|
|
|
| 243 |
}
|
| 244 |
// --- END: 新增的点击逻辑 ---
|
| 245 |
|
| 246 |
+
await editorContainerLocator.click({ timeout: 120000 });
|
| 247 |
await this.page.evaluate(text => navigator.clipboard.writeText(text), buildScriptContent);
|
| 248 |
const isMac = os.platform() === 'darwin';
|
| 249 |
const pasteKey = isMac ? 'Meta+V' : 'Control+V';
|
|
|
|
| 302 |
}
|
| 303 |
|
| 304 |
class MessageQueue extends EventEmitter {
|
| 305 |
+
constructor(timeoutMs = 1200000) {
|
| 306 |
super();
|
| 307 |
this.messages = [];
|
| 308 |
this.waitingResolvers = [];
|
|
|
|
| 469 |
if (nextAuthIndex === null) {
|
| 470 |
this.logger.error('🔴 [Auth] 无法切换账号,因为没有可用的认证源!');
|
| 471 |
this.isAuthSwitching = false;
|
| 472 |
+
// 抛出错误以便调用者可以捕获它
|
| 473 |
+
throw new Error('No available authentication sources to switch to.');
|
| 474 |
}
|
| 475 |
|
| 476 |
this.logger.info('==================================================');
|
|
|
|
| 498 |
}
|
| 499 |
}
|
| 500 |
|
| 501 |
+
// NEW: Error parsing and correction utility
|
| 502 |
+
_parseAndCorrectErrorDetails(errorDetails) {
|
| 503 |
+
// 创建一个副本以避免修改原始对象
|
| 504 |
+
const correctedDetails = { ...errorDetails };
|
| 505 |
+
this.logger.debug(`[ErrorParser] 原始错误详情: status=${correctedDetails.status}, message="${correctedDetails.message}"`);
|
| 506 |
+
|
| 507 |
+
// 只有在错误消息存在时才尝试解析
|
| 508 |
+
if (correctedDetails.message && typeof correctedDetails.message === 'string') {
|
| 509 |
+
// 正则表达式匹配 "HTTP xxx" 或 "status code xxx" 等模式
|
| 510 |
+
const regex = /(?:HTTP|status code)\s+(\d{3})/;
|
| 511 |
+
const match = correctedDetails.message.match(regex);
|
| 512 |
+
|
| 513 |
+
if (match && match[1]) {
|
| 514 |
+
const parsedStatus = parseInt(match[1], 10);
|
| 515 |
+
// 确保解析出的状态码是有效的 HTTP 错误码
|
| 516 |
+
if (parsedStatus >= 400 && parsedStatus <= 599) {
|
| 517 |
+
if (correctedDetails.status !== parsedStatus) {
|
| 518 |
+
this.logger.warn(`[ErrorParser] 修正了错误状态码!原始: ${correctedDetails.status}, 从消息中解析得到: ${parsedStatus}`);
|
| 519 |
+
correctedDetails.status = parsedStatus; // 使用解析出的更准确的状态码
|
| 520 |
+
} else {
|
| 521 |
+
this.logger.debug(`[ErrorParser] 解析的状态码 (${parsedStatus}) 与原始状态码一致,无需修正。`);
|
| 522 |
+
}
|
| 523 |
+
}
|
| 524 |
+
}
|
| 525 |
+
}
|
| 526 |
+
return correctedDetails;
|
| 527 |
+
}
|
| 528 |
+
|
| 529 |
async _handleRequestFailureAndSwitch(errorDetails, res) {
|
| 530 |
+
// --- START: MODIFICATION ---
|
| 531 |
+
const correctedErrorDetails = this._parseAndCorrectErrorDetails(errorDetails);
|
| 532 |
+
// --- END: MODIFICATION ---
|
| 533 |
+
|
| 534 |
+
// 使用修正后的错误详情进行判断
|
| 535 |
+
const isImmediateSwitch = this.config.immediateSwitchStatusCodes.includes(correctedErrorDetails.status);
|
| 536 |
|
| 537 |
if (isImmediateSwitch) {
|
| 538 |
+
this.logger.warn(`🔴 [Auth] 收到状态码 ${correctedErrorDetails.status} (已修正),触发立即切换账号...`);
|
| 539 |
+
if (res) this._sendErrorChunkToClient(res, `收到状态码 ${correctedErrorDetails.status},正在尝试切换账号...`);
|
| 540 |
try {
|
| 541 |
await this._switchToNextAuth();
|
| 542 |
if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
|
|
|
|
| 546 |
}
|
| 547 |
return; // End here after immediate switch attempt
|
| 548 |
}
|
| 549 |
+
|
| 550 |
+
// 使用 correctedErrorDetails.status
|
| 551 |
if (this.config.failureThreshold > 0) {
|
| 552 |
+
this.failureCount++;
|
| 553 |
+
this.logger.warn(`⚠️ [Auth] 请求失败 - 失败计数: ${this.failureCount}/${this.config.failureThreshold} (当前账号索引: ${this.currentAuthIndex}, 状态码: ${correctedErrorDetails.status})`);
|
| 554 |
+
if (this.failureCount >= this.config.failureThreshold) {
|
| 555 |
+
this.logger.warn(`🔴 [Auth] 达到失败阈值!准备切换账号...`);
|
| 556 |
+
if (res) this._sendErrorChunkToClient(res, `连续失败${this.failureCount}次,正在尝试切换账号...`);
|
| 557 |
+
try {
|
| 558 |
+
await this._switchToNextAuth();
|
| 559 |
+
if (res) this._sendErrorChunkToClient(res, `已切换到账号索引 ${this.currentAuthIndex},请重试`);
|
| 560 |
+
} catch (switchError) {
|
| 561 |
+
this.logger.error(`🔴 [Auth] 账号切换失败: ${switchError.message}`);
|
| 562 |
+
if (res) this._sendErrorChunkToClient(res, `切换账号失败: ${switchError.message}`);
|
| 563 |
+
}
|
| 564 |
}
|
|
|
|
| 565 |
} else {
|
| 566 |
+
this.logger.warn(`[Auth] 请求失败 (状态码: ${correctedErrorDetails.status})。基于计数的自动切换已禁用 (failureThreshold=0)`);
|
| 567 |
}
|
| 568 |
}
|
| 569 |
|
|
|
|
| 630 |
this._forwardRequest(proxyRequest);
|
| 631 |
lastMessage = await messageQueue.dequeue();
|
| 632 |
if (lastMessage.event_type === 'error' && lastMessage.status >= 400 && lastMessage.status <= 599) {
|
| 633 |
+
|
| 634 |
+
// --- START: MODIFICATION ---
|
| 635 |
+
const correctedMessage = this._parseAndCorrectErrorDetails(lastMessage);
|
| 636 |
+
await this._handleRequestFailureAndSwitch(correctedMessage, res);
|
| 637 |
+
const errorText = `收到 ${correctedMessage.status} 错误。${attempt < this.maxRetries ? `将在 ${this.retryDelay / 1000}秒后重试...` : '已达到最大重试次数。'}`;
|
| 638 |
+
// --- END: MODIFICATION ---
|
| 639 |
+
|
| 640 |
this._sendErrorChunkToClient(res, errorText);
|
| 641 |
if (attempt < this.maxRetries) {
|
| 642 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
|
|
|
| 646 |
}
|
| 647 |
break;
|
| 648 |
}
|
| 649 |
+
// --- START: MODIFICATION ---
|
| 650 |
if (lastMessage.event_type === 'error' || requestFailed) {
|
| 651 |
+
const finalError = this._parseAndCorrectErrorDetails(lastMessage);
|
| 652 |
+
// 抛出错误,以便被外层 catch 块捕获,并使用修正后的信息
|
| 653 |
+
throw new Error(`请求失败 (状态码: ${finalError.status}): ${finalError.message}`);
|
| 654 |
}
|
| 655 |
+
// --- END: MODIFICATION ---
|
| 656 |
+
|
| 657 |
if (this.failureCount > 0) {
|
| 658 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
| 659 |
}
|
|
|
|
| 678 |
this._forwardRequest(proxyRequest);
|
| 679 |
headerMessage = await messageQueue.dequeue();
|
| 680 |
if (headerMessage.event_type === 'error' && headerMessage.status >= 400 && headerMessage.status <= 599) {
|
| 681 |
+
|
| 682 |
+
// --- START: MODIFICATION ---
|
| 683 |
+
const correctedMessage = this._parseAndCorrectErrorDetails(headerMessage);
|
| 684 |
+
await this._handleRequestFailureAndSwitch(correctedMessage, null); // res is not available
|
| 685 |
+
this.logger.warn(`[Request] 收到 ${correctedMessage.status} 错误,将在 ${this.retryDelay / 1000}秒后重试...`);
|
| 686 |
+
// --- END: MODIFICATION ---
|
| 687 |
+
|
| 688 |
if (attempt < this.maxRetries) {
|
| 689 |
await new Promise(resolve => setTimeout(resolve, this.retryDelay));
|
| 690 |
continue;
|
|
|
|
| 694 |
break;
|
| 695 |
}
|
| 696 |
if (headerMessage.event_type === 'error' || requestFailed) {
|
| 697 |
+
// --- START: MODIFICATION ---
|
| 698 |
+
const finalError = this._parseAndCorrectErrorDetails(headerMessage);
|
| 699 |
+
// 使用修正后的状态码和消息返回给客户端
|
| 700 |
+
return this._sendErrorResponse(res, finalError.status, finalError.message);
|
| 701 |
+
// --- END: MODIFICATION ---
|
| 702 |
}
|
| 703 |
if (this.failureCount > 0) {
|
| 704 |
this.logger.info(`✅ [Auth] 请求成功 - 失败计数已从 ${this.failureCount} 重置为 0`);
|
|
|
|
| 995 |
});
|
| 996 |
});
|
| 997 |
|
| 998 |
+
// --- 新增的 /switch 端点 ---
|
| 999 |
+
app.get('/switch', async (req, res) => {
|
| 1000 |
+
this.logger.info('[Admin] 接到 /switch 请求,手动触发账号切换。');
|
| 1001 |
+
|
| 1002 |
+
if (this.requestHandler.isAuthSwitching) {
|
| 1003 |
+
const msg = '账号切换已在进行中,请稍后。';
|
| 1004 |
+
this.logger.warn(`[Admin] /switch 请求被拒绝: ${msg}`);
|
| 1005 |
+
return res.status(429).send(msg);
|
| 1006 |
+
}
|
| 1007 |
+
|
| 1008 |
+
const oldIndex = this.requestHandler.currentAuthIndex;
|
| 1009 |
+
|
| 1010 |
+
try {
|
| 1011 |
+
await this.requestHandler._switchToNextAuth();
|
| 1012 |
+
const newIndex = this.requestHandler.currentAuthIndex;
|
| 1013 |
+
|
| 1014 |
+
const message = `成功将账号从索引 ${oldIndex} 切换到 ${newIndex}。`;
|
| 1015 |
+
this.logger.info(`[Admin] 手动切换成功。 ${message}`);
|
| 1016 |
+
res.status(200).send(message);
|
| 1017 |
+
} catch (error) {
|
| 1018 |
+
const errorMessage = `切换账号失败: ${error.message}`;
|
| 1019 |
+
this.logger.error(`[Admin] 手动切换失败。错误: ${errorMessage}`);
|
| 1020 |
+
res.status(500).send(errorMessage);
|
| 1021 |
+
}
|
| 1022 |
+
});
|
| 1023 |
+
|
| 1024 |
app.use(this._createAuthMiddleware());
|
| 1025 |
|
| 1026 |
app.all(/(.*)/, (req, res) => {
|