From f5d20116934ba5d615974d5d67b7a8e6a58ae44a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=A2=AB=E9=81=97=E5=BF=98=E7=9A=84=E8=AE=B0=E5=BF=86?= <32097237+YIWANG-sketch@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:30:49 +0800 Subject: [PATCH] Update sessionManager.mjs --- sessionManager.mjs | 323 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 296 insertions(+), 27 deletions(-) diff --git a/sessionManager.mjs b/sessionManager.mjs index e16ca0c..fdc00a3 100644 --- a/sessionManager.mjs +++ b/sessionManager.mjs @@ -1,20 +1,58 @@ +import fs from 'fs'; +import path from 'path'; import { Mutex } from 'async-mutex'; +import { detectBrowser } from './utils/browserDetector.mjs'; +import { createDirectoryIfNotExists } from './utils.mjs'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +const isHeadless = process.env.HEADLESS_BROWSER === 'true' && process.env.USE_MANUAL_LOGIN !== 'true'; +let puppeteerModule; +let connect; +if (isHeadless === false) { + puppeteerModule = await import('puppeteer-real-browser'); + connect = puppeteerModule.connect; +} else { + puppeteerModule = await import('puppeteer-core'); +} + +// 会话自动释放时间(秒) +const SESSION_LOCK_TIMEOUT = parseInt(process.env.SESSION_LOCK_TIMEOUT || '0', 10); + +// 存储已达请求上限的账号(格式: "timestamp | username") +const cooldownFilePath = path.join(__dirname, 'cooldownAccounts.log'); + +// 冷却时长(默认24小时) +const COOLDOWN_DURATION = 24 * 60 * 60 * 1000; class SessionManager { constructor(provider) { - this.sessions = provider.sessions; this.provider = provider; this.isCustomModeEnabled = process.env.USE_CUSTOM_MODE === 'true'; this.isRotationEnabled = process.env.ENABLE_MODE_ROTATION === 'true'; + this.isHeadless = isHeadless; // 是否隐藏浏览器 this.currentIndex = 0; - // 缓存用户名列表 + this.usernameList = []; // 缓存用户名列表 + this.browserInstances = []; // 浏览器实例数组 + this.browserMutex = new Mutex(); // 浏览器互斥锁 + this.browserIndex = 0; + this.sessionAutoUnlockTimers = {}; // 自动解锁计时器 + this.cooldownList = this.loadCooldownList(); // 加载并清理 cooldown 文件 + this.cleanupCooldownList(); + } + + setSessions(sessions) { + this.sessions = sessions; this.usernameList = Object.keys(this.sessions); + + // 为每个 session 初始化相关属性 for (const username in this.sessions) { const session = this.sessions[username]; - session.locked = false; - session.requestCount = 0; - session.valid = true; - session.mutex = new Mutex(); // 创建互斥锁 + session.locked = false; // 标记会话是否被锁定 + session.requestCount = 0; // 请求计数 + session.valid = true; // 标记会话是否有效 + session.mutex = new Mutex(); // 创建互斥锁 if (session.currentMode === undefined) { session.currentMode = this.isCustomModeEnabled ? 'custom' : 'default'; } @@ -24,33 +62,201 @@ class SessionManager { custom: true, }; } - session.rotationEnabled = true; - session.switchCounter = 0; - session.requestsInCurrentMode = 0; - session.lastDefaultThreshold = 0; + session.rotationEnabled = true; // 是否启用模式轮换 + session.switchCounter = 0; // 模式切换计数器 + session.requestsInCurrentMode = 0; // 当前模式下的请求次数 + session.lastDefaultThreshold = 0; // 上次默认模式阈值 session.switchThreshold = this.provider.getRandomSwitchThreshold(session); + + // 记录请求次数 + session.youTotalRequests = 0; } } - // 获取所有可用且未锁定会话 - async getAvailableSessions() { - const allSessionsLocked = this.usernameList.every( - (username) => this.sessions[username].locked - ); - if (allSessionsLocked) { - console.warn('所有会话都已被锁定,等待释放...'); - throw new Error('所有会话都已被锁定'); + loadCooldownList() { + try { + if (!fs.existsSync(cooldownFilePath)) { + fs.writeFileSync(cooldownFilePath, '', 'utf8'); + return []; + } + const lines = fs.readFileSync(cooldownFilePath, 'utf8') + .split('\n') + .map(line => line.trim()) + .filter(line => line.length > 0); + + const arr = []; + for (const line of lines) { + const parts = line.split('|').map(x => x.trim()); + if (parts.length === 2) { + const timestamp = parseInt(parts[0], 10); + const name = parts[1]; + if (!isNaN(timestamp) && name) { + arr.push({ time: timestamp, username: name }); + } + } + } + return arr; + } catch (err) { + console.error(`读取 ${cooldownFilePath} 出错:`, err); + return []; + } + } + + saveCooldownList() { + try { + const lines = this.cooldownList.map(item => `${item.time} | ${item.username}`); + fs.writeFileSync(cooldownFilePath, lines.join('\n') + '\n', 'utf8'); + } catch (err) { + console.error(`写入 ${cooldownFilePath} 出错:`, err); + } + } + + // 清理过期(超过指定冷却时长) + cleanupCooldownList() { + const now = Date.now(); + let changed = false; + this.cooldownList = this.cooldownList.filter(item => { + const expired = (now - item.time) >= COOLDOWN_DURATION; + if (expired) changed = true; + return !expired; + }); + if (changed) { + this.saveCooldownList(); + } + } + + recordLimitedAccount(username) { + const now = Date.now(); + const already = this.cooldownList.find(x => x.username === username); + if (!already) { + this.cooldownList.push({ time: now, username }); + this.saveCooldownList(); + console.log(`写入冷却列表:${new Date(now).toLocaleString()} | ${username}`); + } + } + + // 是否在冷却期(24小时内) + isInCooldown(username) { + this.cleanupCooldownList(); + return this.cooldownList.some(item => item.username === username); + } + + // 批量初始化浏览器实例 + async initBrowserInstancesInBatch() { + const browserCount = parseInt(process.env.BROWSER_INSTANCE_COUNT) || 1; + // 可以是 'chrome', 'edge', 或 'auto' + const browserPath = detectBrowser('auto'); + const sharedProfilePath = path.join(__dirname, 'browser_profiles'); + createDirectoryIfNotExists(sharedProfilePath); + + const tasks = []; + for (let i = 0; i < browserCount; i++) { + const browserId = `browser_${i}`; + const userDataDir = path.join(sharedProfilePath, browserId); + createDirectoryIfNotExists(userDataDir); + + tasks.push(this.launchSingleBrowser(browserId, userDataDir, browserPath)); } - // 轮询选择下一个可用 + // 并行执行 + const results = await Promise.all(tasks); + for (const instanceInfo of results) { + this.browserInstances.push(instanceInfo); + console.log(`创建浏览器实例: ${instanceInfo.id}`); + } + } + + async launchSingleBrowser(browserId, userDataDir, browserPath) { + let browser, page; + if (isHeadless === false) { + // 使用 puppeteer-real-browser + const response = await connect({ + headless: 'auto', + turnstile: true, + customConfig: { + userDataDir: userDataDir, + executablePath: browserPath, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--remote-debugging-address=::', + ], + }, + }); + browser = response.browser; + page = response.page; + } else { + // 使用 puppeteer-core + browser = await puppeteerModule.launch({ + headless: this.isHeadless, + executablePath: browserPath, + userDataDir: userDataDir, + args: [ + '--no-sandbox', + '--disable-setuid-sandbox', + '--disable-gpu', + '--disable-dev-shm-usage', + '--remote-debugging-port=0', + ], + }); + page = await browser.newPage(); + } + + return { + id: browserId, + browser: browser, + page: page, + locked: false, + }; + } + + async getAvailableBrowser() { + return await this.browserMutex.runExclusive(async () => { + const totalBrowsers = this.browserInstances.length; + + for (let i = 0; i < totalBrowsers; i++) { + const index = (this.browserIndex + i) % totalBrowsers; + const browserInstance = this.browserInstances[index]; + + if (!browserInstance.locked) { + browserInstance.locked = true; + this.browserIndex = (index + 1) % totalBrowsers; + return browserInstance; + } + } + throw new Error('当前负载已饱和,请稍后再试'); + }); + } + + async releaseBrowser(browserId) { + await this.browserMutex.runExclusive(async () => { + const browserInstance = this.browserInstances.find(b => b.id === browserId); + if (browserInstance) { + browserInstance.locked = false; + } + }); + } + + async getAvailableSessions() { + const allSessionsLocked = this.usernameList.every(username => this.sessions[username].locked); + if (allSessionsLocked) { + throw new Error('所有会话处于饱和状态,请稍后再试'); + } + + // 轮询 const totalSessions = this.usernameList.length; for (let i = 0; i < totalSessions; i++) { const index = (this.currentIndex + i) % totalSessions; const username = this.usernameList[index]; const session = this.sessions[username]; - // 检查是否有效且未锁定 + // 如果没被锁 并且 session.valid if (session.valid && !session.locked) { + if (this.provider.enableRequestLimit && this.isInCooldown(username)) { + // console.log(`账号 ${username} 处于 24 小时冷却中,跳过`); + continue; + } + const result = await session.mutex.runExclusive(async () => { // 再次检查锁定状态 if (session.locked) { @@ -61,8 +267,26 @@ class SessionManager { session.locked = true; session.requestCount++; this.currentIndex = (index + 1) % totalSessions; - return { selectedUsername: username, modeSwitched: false }; - } else if (this.isCustomModeEnabled && this.isRotationEnabled && this.provider && typeof this.provider.switchMode === 'function') { + + // 获取可用浏览器实例 + const browserInstance = await this.getAvailableBrowser(); + + // 启动计时器 + if (SESSION_LOCK_TIMEOUT > 0) { + this.startAutoUnlockTimer(username, browserInstance.id); + } + + return { + selectedUsername: username, + modeSwitched: false, + browserInstance: browserInstance + }; + } else if ( + this.isCustomModeEnabled && + this.isRotationEnabled && + this.provider && + typeof this.provider.switchMode === 'function' + ) { console.warn(`尝试为账号 ${username} 切换模式...`); this.provider.switchMode(session); session.rotationEnabled = false; @@ -71,7 +295,19 @@ class SessionManager { session.locked = true; session.requestCount++; this.currentIndex = (index + 1) % totalSessions; - return { selectedUsername: username, modeSwitched: true }; + + // 获取可用浏览器实例 + const browserInstance = await this.getAvailableBrowser(); + + if (SESSION_LOCK_TIMEOUT > 0) { + this.startAutoUnlockTimer(username, browserInstance.id); + } + + return { + selectedUsername: username, + modeSwitched: true, + browserInstance: browserInstance + }; } } return null; @@ -83,21 +319,54 @@ class SessionManager { } } } - - console.warn('没有可用的会话'); throw new Error('没有可用的会话'); } - // 释放账号锁定 - async releaseSession(username) { + startAutoUnlockTimer(username, browserId) { + // 清除可能残留计时器 + if (this.sessionAutoUnlockTimers[username]) { + clearTimeout(this.sessionAutoUnlockTimers[username]); + } + const lockDurationMs = SESSION_LOCK_TIMEOUT * 1000; + + this.sessionAutoUnlockTimers[username] = setTimeout(async () => { + const session = this.sessions[username]; + if (session && session.locked) { + console.warn( + `会话 "${username}" 已自动解锁` + ); + + await session.mutex.runExclusive(async () => { + session.locked = false; + }); + + } + }, lockDurationMs); + } + + async releaseSession(username, browserId) { const session = this.sessions[username]; if (session) { await session.mutex.runExclusive(() => { session.locked = false; }); } + // 存在相应计时器清除 + if (this.sessionAutoUnlockTimers[username]) { + clearTimeout(this.sessionAutoUnlockTimers[username]); + delete this.sessionAutoUnlockTimers[username]; + } + + if (browserId) { + await this.releaseBrowser(browserId); + } } + // 返回会话 + // getBrowserInstances() { + // return this.browserInstances; + // } + // 策略 async getSessionByStrategy(strategy = 'round_robin') { if (strategy === 'round_robin') {