Spaces:
Paused
Paused
| import http from 'http'; | |
| import puppeteer from 'puppeteer'; | |
| import crypto from 'crypto'; | |
| /* | |
| Package Information (原 package.json 内容): | |
| - Name: mindvideo-hf-deploy | |
| - Version: 1.0.0 | |
| - Description: MindVideo 多账号机器人 - Hugging Face 部署版 | |
| - Main: worker.js | |
| - Type: module | |
| - Dependencies: puppeteer ^23.0.0, @puppeteer/browsers ^2.0.0 | |
| - Node Engine: >=18.0.0 | |
| System Dependencies (原 requirements.txt 内容): | |
| - @supabase/supabase-js>=2.38.0 (Python package, 仅作说明) | |
| - node-fetch>=3.3.2 (Python package, 仅作说明) | |
| - System dependencies: chromium, libgtk-3-0, libx11-xcb1, libxcomposite1, etc. (通过 Dockerfile 安装) | |
| */ | |
| // 配置信息 - 基于 HF 部署版本优化 | |
| // Hugging Face Spaces 配置: 端口 7860,监听所有接口 (原 config.capnp 内容) | |
| const CONFIG = { | |
| PORT: process.env.PORT || 7860, | |
| // Supabase 配置 | |
| SUPABASE_URL: process.env.SUPABASE_URL, | |
| SUPABASE_KEY: process.env.SUPABASE_KEY, | |
| // MindVideo 配置 | |
| MINDVIDEO_LOGIN_URL: "https://www.mindvideo.ai/zh/auth/signin/", | |
| MINDVIDEO_CHECKIN_API: "https://api.mindvideo.ai/api/checkin", | |
| SIGN_APP_KEY: "s#c_120*AB", | |
| USER_AGENT: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" | |
| }; | |
| // 全局变量 | |
| let browser = null; | |
| let debugInfo = { capturedRequests: [] }; | |
| // Supabase 存储服务 | |
| class SupabaseService { | |
| constructor() { | |
| this.url = CONFIG.SUPABASE_URL; | |
| this.key = CONFIG.SUPABASE_KEY; | |
| this.enabled = !!(this.url && this.key); | |
| } | |
| async request(method, endpoint, data = null) { | |
| if (!this.enabled) return null; | |
| try { | |
| const options = { | |
| method, | |
| headers: { | |
| 'Authorization': `Bearer ${this.key}`, | |
| 'Content-Type': 'application/json', | |
| 'apikey': this.key | |
| } | |
| }; | |
| if (data) { | |
| options.body = JSON.stringify(data); | |
| } | |
| const response = await fetch(`${this.url}/rest/v1/${endpoint}`, options); | |
| if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`); | |
| return await response.json(); | |
| } catch (error) { | |
| console.error('❌ Supabase 请求失败:', error.message); | |
| return null; | |
| } | |
| } | |
| async insert(table, data) { | |
| return await this.request('POST', table, data); | |
| } | |
| async select(table, columns = '*', filters = {}) { | |
| const queryString = Object.entries(filters) | |
| .map(([key, value]) => `${key}=eq.${encodeURIComponent(value)}`) | |
| .join('&'); | |
| const endpoint = `${table}?select=${columns}${queryString ? '&' + queryString : ''}`; | |
| return await this.request('GET', endpoint); | |
| } | |
| async update(table, data, filters) { | |
| const queryString = Object.entries(filters) | |
| .map(([key, value]) => `${key}=eq.${encodeURIComponent(value)}`) | |
| .join('&'); | |
| const endpoint = `${table}?${queryString}`; | |
| return await this.request('PATCH', endpoint, data); | |
| } | |
| } | |
| // 临时邮箱服务类 - 负责与linshiyou.com网站交互 | |
| class LinshiyouEmailService { | |
| constructor() { | |
| this.baseUrl = 'https://www.linshiyou.com'; | |
| this.userAgent = CONFIG.USER_AGENT; | |
| this.browser = null; | |
| this.page = null; | |
| this.timeout = 30000; | |
| } | |
| // 初始化浏览器 | |
| async initBrowser() { | |
| if (!this.browser) { | |
| // Hugging Face 环境优化的浏览器配置 | |
| const isHfEnvironment = process.env.SPACE_ID !== undefined || process.env.HF_SPACE !== undefined; | |
| this.browser = await puppeteer.launch({ | |
| headless: 'new', | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-accelerated-2d-canvas', | |
| '--no-first-run', | |
| '--no-zygote', | |
| ...(isHfEnvironment ? [] : ['--single-process']), // HF环境避免单进程模式 | |
| '--disable-gpu', | |
| '--disable-background-timer-throttling', | |
| '--disable-backgrounding-occluded-windows', | |
| '--disable-renderer-backgrounding', | |
| '--disable-features=TranslateUI', | |
| '--disable-extensions', | |
| '--disable-plugins', | |
| '--disable-default-apps', | |
| '--disable-sync', | |
| '--metrics-recording-only', | |
| '--no-report-upload', | |
| '--disable-logging', | |
| '--silent', | |
| '--log-level=3' | |
| ].concat(isHfEnvironment ? [ | |
| // HF 环境特定优化 | |
| '--memory-pressure-off', | |
| '--max_old_space_size=256', | |
| '--optimize-for-size' | |
| ] : []) | |
| }); | |
| this.page = await this.browser.newPage(); | |
| await this.page.setUserAgent(this.userAgent); | |
| await this.page.setViewport({ width: 1366, height: 768 }); | |
| } | |
| return this.browser; | |
| } | |
| // 创建临时邮箱 | |
| async createTempEmail() { | |
| try { | |
| console.log('📧 开始创建临时邮箱...'); | |
| await this.initBrowser(); | |
| // 访问linshiyou.com主页 | |
| await this.page.goto(this.baseUrl, { waitUntil: 'networkidle2', timeout: this.timeout }); | |
| // 等待页面加载完成,尝试多种可能的邮箱输入框选择器 | |
| let emailSelector = null; | |
| const possibleSelectors = ['#email', '#email-input', 'input[type="email"]', 'input[name="email"]']; | |
| for (const selector of possibleSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 5000 }); | |
| emailSelector = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| if (!emailSelector) { | |
| // 如果找不到输入框,尝试直接使用生成的邮箱地址 | |
| const randomPrefix = this.generateRandomString(8); | |
| const emailAddress = `${randomPrefix}@linshiyou.com`; | |
| console.log(`✅ 生成临时邮箱地址: ${emailAddress}`); | |
| return { | |
| email: emailAddress, | |
| created_at: new Date().toISOString(), | |
| expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() | |
| }; | |
| } | |
| // 清空输入框并输入生成的邮箱地址 | |
| await this.page.evaluate((selector) => { | |
| const element = document.querySelector(selector); | |
| if (element) element.value = ''; | |
| }, emailSelector); | |
| const randomPrefix = this.generateRandomString(8); | |
| const emailAddress = `${randomPrefix}@linshiyou.com`; | |
| await this.page.type(emailSelector, emailAddress); | |
| // 查找并点击创建按钮 | |
| let createButton = null; | |
| const possibleButtonSelectors = ['#create-email', '#submit', '#create', 'button[type="submit"]', 'button:contains("创建")', 'button:contains("Create")']; | |
| for (const selector of possibleButtonSelectors) { | |
| try { | |
| if (selector.includes(':contains(')) { | |
| // 处理包含文本的选择器 | |
| const buttonText = selector.match(/:contains\("([^"]+)"\)/)[1]; | |
| createButton = await this.page.evaluateHandle((text) => { | |
| const buttons = Array.from(document.querySelectorAll('button')); | |
| return buttons.find(btn => btn.textContent.includes(text)); | |
| }, buttonText); | |
| } else { | |
| await this.page.waitForSelector(selector, { timeout: 5000 }); | |
| createButton = await this.page.$(selector); | |
| } | |
| if (createButton) { | |
| await createButton.click(); | |
| break; | |
| } | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 等待邮箱创建完成 | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| console.log(`✅ 临时邮箱创建成功: ${emailAddress}`); | |
| return { | |
| email: emailAddress, | |
| created_at: new Date().toISOString(), | |
| expires_at: new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString() | |
| }; | |
| } catch (error) { | |
| console.error(`❌ 创建临时邮箱失败: ${error.message}`); | |
| throw new Error(`创建临时邮箱失败: ${error.message}`); | |
| } | |
| } | |
| // 获取收件箱 | |
| async getInbox(emailAddress) { | |
| try { | |
| console.log(`📬 检查邮箱收件箱: ${emailAddress}`); | |
| await this.initBrowser(); | |
| // 尝试访问收件箱页面 | |
| let inboxUrl = `${this.baseUrl}/inbox/${emailAddress}`; | |
| try { | |
| await this.page.goto(inboxUrl, { waitUntil: 'networkidle2', timeout: this.timeout }); | |
| } catch (e) { | |
| // 如果直接访问失败,尝试从主页导航 | |
| await this.page.goto(this.baseUrl, { waitUntil: 'networkidle2', timeout: this.timeout }); | |
| // 查找收件箱链接或按钮 | |
| const inboxButton = await this.page.evaluateHandle(() => { | |
| const links = Array.from(document.querySelectorAll('a')); | |
| return links.find(link => | |
| link.textContent.includes('收件箱') || | |
| link.textContent.includes('inbox') || | |
| link.href.includes('inbox') | |
| ); | |
| }); | |
| if (inboxButton) { | |
| await inboxButton.click(); | |
| await this.page.waitForNavigation({ waitUntil: 'networkidle2', timeout: this.timeout }); | |
| } | |
| } | |
| // 等待邮件列表加载 | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| // 尝试提取邮件列表 | |
| const emails = await this.page.evaluate(() => { | |
| const emailElements = document.querySelectorAll('.email-item, .mail-item, .message-item, tr.email'); | |
| return Array.from(emailElements).map(el => { | |
| const subjectElement = el.querySelector('.subject, .title, .message-subject'); | |
| const senderElement = el.querySelector('.sender, .from, .message-from'); | |
| const timeElement = el.querySelector('.time, .date, .message-time'); | |
| return { | |
| subject: subjectElement ? subjectElement.textContent.trim() : '', | |
| sender: senderElement ? senderElement.textContent.trim() : '', | |
| time: timeElement ? timeElement.textContent.trim() : '', | |
| id: el.getAttribute('data-email-id') || el.getAttribute('id') || '' | |
| }; | |
| }).filter(email => email.subject || email.sender); | |
| }); | |
| console.log(`📬 找到 ${emails.length} 封邮件`); | |
| return emails; | |
| } catch (error) { | |
| console.error(`❌ 获取收件箱失败: ${error.message}`); | |
| // 返回空数组而不是抛出错误,让调用方处理 | |
| return []; | |
| } | |
| } | |
| // 获取最新邮件 | |
| async getLatestEmail(emailAddress) { | |
| try { | |
| console.log(`📨 获取最新邮件: ${emailAddress}`); | |
| const emails = await this.getInbox(emailAddress); | |
| if (emails.length === 0) { | |
| console.log('📭 收件箱为空'); | |
| return null; | |
| } | |
| const latestEmail = emails[0]; | |
| console.log(`📨 最新邮件: ${latestEmail.subject}`); | |
| // 尝试点击邮件查看内容 | |
| try { | |
| if (latestEmail.id) { | |
| const emailElement = await this.page.$(`[data-email-id="${latestEmail.id}"], #${latestEmail.id}`); | |
| if (emailElement) { | |
| await emailElement.click(); | |
| await this.page.waitForSelector('.email-content, .mail-content, .message-content', { timeout: 5000 }); | |
| } | |
| } | |
| } catch (e) { | |
| // 如果点击失败,继续使用已有信息 | |
| console.log('⚠️ 无法点击邮件,使用基本信息'); | |
| } | |
| // 等待邮件内容加载 | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| // 提取邮件内容 | |
| let content = ''; | |
| try { | |
| content = await this.page.$eval('.email-content, .mail-content, .message-content, .content', el => el.textContent); | |
| } catch (e) { | |
| // 如果找不到内容区域,尝试获取整个页面文本 | |
| content = await this.page.evaluate(() => document.body.textContent); | |
| } | |
| const result = { | |
| ...latestEmail, | |
| content: content | |
| }; | |
| console.log(`📨 邮件内容提取成功,长度: ${content.length} 字符`); | |
| return result; | |
| } catch (error) { | |
| console.error(`❌ 获取最新邮件失败: ${error.message}`); | |
| return null; | |
| } | |
| } | |
| // 提取验证码 | |
| async extractVerificationCode(emailContent) { | |
| console.log('🔍 开始提取验证码...'); | |
| // 匹配验证码的正则表达式 | |
| const patterns = [ | |
| /\b(\d{6})\b/g, | |
| /验证码[::\s]*(\d{6})/, | |
| /code[::\s]*(\d{6})/i, | |
| /verification[::\s]*(\d{6})/i, | |
| /验证[::\s]*(\d{6})/, | |
| /码[::\s]*(\d{6})/ | |
| ]; | |
| for (const pattern of patterns) { | |
| const matches = emailContent.match(pattern); | |
| if (matches && matches.length > 0) { | |
| // 返回最后一个匹配的6位数字 | |
| const code = matches[matches.length - 1].match(/\d{6}/)[0]; | |
| console.log(`✅ 验证码提取成功: ${code}`); | |
| return code; | |
| } | |
| } | |
| // 如果正则匹配失败,尝试查找所有6位数字 | |
| const allNumbers = emailContent.match(/\b\d{6}\b/g); | |
| if (allNumbers && allNumbers.length > 0) { | |
| const code = allNumbers[allNumbers.length - 1]; | |
| console.log(`✅ 验证码提取成功(备选方法): ${code}`); | |
| return code; | |
| } | |
| console.error('❌ 无法从邮件内容中提取验证码'); | |
| throw new Error('无法从邮件内容中提取验证码'); | |
| } | |
| // 等待验证邮件 | |
| async waitForVerificationEmail(emailAddress, maxWaitTime = 300000) { | |
| console.log(`⏳ 等待验证邮件,最长等待: ${maxWaitTime/1000}秒`); | |
| const startTime = Date.now(); | |
| const checkInterval = 5000; // 5秒检查一次 | |
| let attempts = 0; | |
| while (Date.now() - startTime < maxWaitTime) { | |
| attempts++; | |
| console.log(`📧 检查邮件 (${attempts}次)...`); | |
| try { | |
| const latestEmail = await this.getLatestEmail(emailAddress); | |
| if (latestEmail && latestEmail.content) { | |
| try { | |
| const verificationCode = await this.extractVerificationCode(latestEmail.content); | |
| return { | |
| code: verificationCode, | |
| email: latestEmail, | |
| received_at: new Date().toISOString(), | |
| attempts: attempts | |
| }; | |
| } catch (extractError) { | |
| console.log(`⚠️ 邮件已收到但验证码提取失败: ${extractError.message}`); | |
| } | |
| } | |
| } catch (error) { | |
| console.log(`⚠️ 检查邮件时出错: ${error.message}`); | |
| } | |
| await new Promise(resolve => setTimeout(resolve, checkInterval)); | |
| } | |
| console.error(`❌ 等待验证邮件超时,已尝试 ${attempts} 次`); | |
| throw new Error(`等待验证邮件超时,已尝试 ${attempts} 次`); | |
| } | |
| // 生成随机字符串 | |
| generateRandomString(length) { | |
| const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; | |
| let result = ''; | |
| for (let i = 0; i < length; i++) { | |
| result += chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return result; | |
| } | |
| // 关闭浏览器 | |
| async closeBrowser() { | |
| if (this.page) { | |
| await this.page.close(); | |
| this.page = null; | |
| } | |
| if (this.browser) { | |
| await this.browser.close(); | |
| this.browser = null; | |
| } | |
| } | |
| } | |
| // MindVideo账号注册服务类 | |
| class MindVideoRegistrationService { | |
| constructor() { | |
| this.registrationUrl = 'https://www.mindvideo.ai/zh/auth/signup/'; | |
| this.browser = null; | |
| this.page = null; | |
| this.emailService = new LinshiyouEmailService(); | |
| this.timeout = 30000; | |
| } | |
| // 初始化浏览器 | |
| async initBrowser() { | |
| if (!this.browser) { | |
| // Hugging Face 环境优化的浏览器配置 | |
| const isHfEnvironment = process.env.SPACE_ID !== undefined || process.env.HF_SPACE !== undefined; | |
| this.browser = await puppeteer.launch({ | |
| headless: 'new', | |
| args: [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-accelerated-2d-canvas', | |
| '--no-first-run', | |
| '--no-zygote', | |
| ...(isHfEnvironment ? [] : ['--single-process']), // HF环境避免单进程模式 | |
| '--disable-gpu', | |
| '--disable-background-timer-throttling', | |
| '--disable-backgrounding-occluded-windows', | |
| '--disable-renderer-backgrounding', | |
| '--disable-features=TranslateUI', | |
| '--disable-extensions', | |
| '--disable-plugins', | |
| '--disable-default-apps', | |
| '--disable-sync', | |
| '--metrics-recording-only', | |
| '--no-report-upload', | |
| '--disable-logging', | |
| '--silent', | |
| '--log-level=3' | |
| ].concat(isHfEnvironment ? [ | |
| // HF 环境特定优化 | |
| '--memory-pressure-off', | |
| '--max_old_space_size=256', | |
| '--optimize-for-size' | |
| ] : []) | |
| }); | |
| this.page = await this.browser.newPage(); | |
| await this.page.setUserAgent(CONFIG.USER_AGENT); | |
| await this.page.setViewport({ width: 1366, height: 768 }); | |
| } | |
| return this.browser; | |
| } | |
| // 注册单个账号 | |
| async registerAccount(email, password) { | |
| try { | |
| console.log(`🚀 开始注册账号: ${email}`); | |
| await this.initBrowser(); | |
| // 访问注册页面 | |
| await this.page.goto(this.registrationUrl, { waitUntil: 'networkidle2', timeout: this.timeout }); | |
| console.log('📱 已访问注册页面'); | |
| // 等待页面加载 | |
| await new Promise(resolve => setTimeout(resolve, 3000)); | |
| // 查找并填写注册表单 | |
| const formFields = await this.findRegistrationFormFields(); | |
| if (!formFields.email || !formFields.password) { | |
| throw new Error('未找到注册表单字段'); | |
| } | |
| // 清空并填写邮箱 | |
| await this.page.evaluate((selector) => { | |
| const element = document.querySelector(selector); | |
| if (element) element.value = ''; | |
| }, formFields.email); | |
| await this.page.type(formFields.email, email); | |
| // 清空并填写密码 | |
| await this.page.evaluate((selector) => { | |
| const element = document.querySelector(selector); | |
| if (element) element.value = ''; | |
| }, formFields.password); | |
| await this.page.type(formFields.password, password); | |
| // 如果有确认密码字段 | |
| if (formFields.confirmPassword) { | |
| await this.page.evaluate((selector) => { | |
| const element = document.querySelector(selector); | |
| if (element) element.value = ''; | |
| }, formFields.confirmPassword); | |
| await this.page.type(formFields.confirmPassword, password); | |
| } | |
| // 处理条款同意(如果存在) | |
| if (formFields.agreeCheckbox) { | |
| const isChecked = await this.page.evaluate((selector) => { | |
| const element = document.querySelector(selector); | |
| return element ? element.checked : false; | |
| }, formFields.agreeCheckbox); | |
| if (!isChecked) { | |
| await this.page.click(formFields.agreeCheckbox); | |
| console.log('✅ 已同意服务条款'); | |
| } | |
| } | |
| // 提交注册表单 | |
| console.log('📤 提交注册表单...'); | |
| await this.submitRegistrationForm(formFields.submitButton); | |
| // 等待页面响应 | |
| await new Promise(resolve => setTimeout(resolve, 3000)); | |
| // 检查是否需要邮箱验证 | |
| const needsVerification = await this.checkEmailVerificationRequired(); | |
| if (needsVerification) { | |
| console.log('📧 需要进行邮箱验证'); | |
| // 处理邮箱验证 | |
| const verificationResult = await this.verifyEmail(email); | |
| console.log('✅ 邮箱验证完成'); | |
| } else { | |
| console.log('⚠️ 注册可能不需要邮箱验证,或已自动完成'); | |
| } | |
| // 提取用户Token | |
| const token = await this.extractToken(); | |
| if (token) { | |
| console.log(`✅ 账号注册成功,Token已获取`); | |
| return { | |
| success: true, | |
| email: email, | |
| password: password, | |
| token: token, | |
| created_at: new Date().toISOString() | |
| }; | |
| } else { | |
| console.log('⚠️ 注册可能成功但未获取到Token'); | |
| return { | |
| success: true, | |
| email: email, | |
| password: password, | |
| token: null, | |
| created_at: new Date().toISOString(), | |
| warning: '注册成功但未获取到Token' | |
| }; | |
| } | |
| } catch (error) { | |
| console.error(`❌ 注册账号失败: ${error.message}`); | |
| return { | |
| success: false, | |
| email: email, | |
| error: error.message, | |
| created_at: new Date().toISOString() | |
| }; | |
| } | |
| } | |
| // 查找注册表单字段 | |
| async findRegistrationFormFields() { | |
| const fields = { | |
| email: null, | |
| password: null, | |
| confirmPassword: null, | |
| agreeCheckbox: null, | |
| submitButton: null | |
| }; | |
| // 查找邮箱字段 | |
| const emailSelectors = [ | |
| '#email', '#email-input', 'input[type="email"]', 'input[name="email"]', | |
| '[placeholder*="邮箱"]', '[placeholder*="email"]' | |
| ]; | |
| for (const selector of emailSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 2000 }); | |
| fields.email = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 查找密码字段 | |
| const passwordSelectors = [ | |
| '#password', '#password-input', 'input[type="password"]', 'input[name="password"]', | |
| '[placeholder*="密码"]', '[placeholder*="password"]' | |
| ]; | |
| for (const selector of passwordSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 2000 }); | |
| fields.password = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 查找确认密码字段 | |
| const confirmPasswordSelectors = [ | |
| '#confirm-password', '#confirmPassword', '#password-confirm', | |
| 'input[name="confirm_password"]', '[placeholder*="确认密码"]', '[placeholder*="confirm"]' | |
| ]; | |
| for (const selector of confirmPasswordSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 1000 }); | |
| fields.confirmPassword = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 查找同意条款复选框 | |
| const checkboxSelectors = [ | |
| '#agree', '#terms', '#agree-terms', '#terms-checkbox', | |
| 'input[type="checkbox"][name*="agree"]', 'input[type="checkbox"][name*="terms"]' | |
| ]; | |
| for (const selector of checkboxSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 1000 }); | |
| fields.agreeCheckbox = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 查找提交按钮 | |
| const submitSelectors = [ | |
| '#register', '#register-btn', '#signup', '#signup-btn', | |
| 'button[type="submit"]', 'input[type="submit"]', | |
| 'button:contains("注册")', 'button:contains("注册")', 'button:contains("Sign Up")' | |
| ]; | |
| for (const selector of submitSelectors) { | |
| try { | |
| if (selector.includes(':contains(')) { | |
| // 处理包含文本的选择器 | |
| const buttonText = selector.match(/:contains\("([^"]+)"\)/)[1]; | |
| const button = await this.page.evaluateHandle((text) => { | |
| const buttons = Array.from(document.querySelectorAll('button, input[type="submit"]')); | |
| return buttons.find(btn => btn.textContent.includes(text) || btn.value.includes(text)); | |
| }, buttonText); | |
| if (button) { | |
| fields.submitButton = button; | |
| break; | |
| } | |
| } else { | |
| await this.page.waitForSelector(selector, { timeout: 2000 }); | |
| fields.submitButton = selector; | |
| break; | |
| } | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| console.log('📋 找到的表单字段:', fields); | |
| return fields; | |
| } | |
| // 提交注册表单 | |
| async submitRegistrationForm(submitButton) { | |
| try { | |
| if (typeof submitButton === 'string') { | |
| await this.page.click(submitButton); | |
| } else { | |
| // 如果是ElementHandle | |
| await submitButton.click(); | |
| } | |
| console.log('📤 注册表单已提交'); | |
| } catch (error) { | |
| // 尝试使用JavaScript提交 | |
| await this.page.evaluate(() => { | |
| const forms = document.querySelectorAll('form'); | |
| if (forms.length > 0) { | |
| forms[forms.length - 1].submit(); | |
| } | |
| }); | |
| console.log('📤 使用JavaScript提交注册表单'); | |
| } | |
| } | |
| // 检查是否需要邮箱验证 | |
| async checkEmailVerificationRequired() { | |
| try { | |
| // 检查页面是否包含验证相关的元素 | |
| const verificationSelectors = [ | |
| '#verification-code', '#code', '#verification', | |
| 'input[name="verification_code"]', 'input[name="code"]', | |
| '[placeholder*="验证码"]', '[placeholder*="verification"]', | |
| '[placeholder*="code"]' | |
| ]; | |
| for (const selector of verificationSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 2000 }); | |
| return true; // 找到验证码输入框,需要验证 | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 检查页面文本内容 | |
| const pageText = await this.page.evaluate(() => document.body.textContent.toLowerCase()); | |
| const verificationKeywords = ['验证码', 'verification code', '邮箱验证', 'email verification', '请查收邮件']; | |
| return verificationKeywords.some(keyword => pageText.includes(keyword)); | |
| } catch (error) { | |
| console.log('⚠️ 检查验证要求时出错:', error.message); | |
| return false; | |
| } | |
| } | |
| // 处理邮箱验证 | |
| async verifyEmail(emailAddress) { | |
| try { | |
| console.log(`📧 开始邮箱验证流程: ${emailAddress}`); | |
| // 获取验证码 | |
| const verificationData = await this.emailService.waitForVerificationEmail( | |
| emailAddress, | |
| 120000 // 2分钟超时 | |
| ); | |
| console.log(`📨 收到验证码: ${verificationData.code}`); | |
| // 查找验证码输入框 | |
| const codeSelectors = [ | |
| '#verification-code', '#code', '#verification', '#verify-code', | |
| 'input[name="verification_code"]', 'input[name="code"]', | |
| '[placeholder*="验证码"]', '[placeholder*="verification"]', '[placeholder*="code"]' | |
| ]; | |
| let codeInputSelector = null; | |
| for (const selector of codeSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 5000 }); | |
| codeInputSelector = selector; | |
| break; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| if (!codeInputSelector) { | |
| throw new Error('未找到验证码输入框'); | |
| } | |
| // 输入验证码 | |
| await this.page.type(codeInputSelector, verificationData.code); | |
| console.log('🔢 验证码已输入'); | |
| // 查找并点击验证按钮 | |
| const verifyButtonSelectors = [ | |
| '#verify-btn', '#verify', '#submit-btn', '#submit', | |
| 'button[type="submit"]', 'button:contains("验证")', 'button:contains("Verify")', | |
| 'button:contains("确认")', 'button:contains("Submit")' | |
| ]; | |
| let verifyButton = null; | |
| for (const selector of verifyButtonSelectors) { | |
| try { | |
| if (selector.includes(':contains(')) { | |
| const buttonText = selector.match(/:contains\("([^"]+)"\)/)[1]; | |
| verifyButton = await this.page.evaluateHandle((text) => { | |
| const buttons = Array.from(document.querySelectorAll('button, input[type="submit"]')); | |
| return buttons.find(btn => btn.textContent.includes(text) || btn.value.includes(text)); | |
| }, buttonText); | |
| } else { | |
| await this.page.waitForSelector(selector, { timeout: 3000 }); | |
| verifyButton = await this.page.$(selector); | |
| } | |
| if (verifyButton) { | |
| await verifyButton.click(); | |
| break; | |
| } | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| if (!verifyButton) { | |
| // 尝试按回车键 | |
| await this.page.keyboard.press('Enter'); | |
| console.log('⌨️ 使用回车键提交验证码'); | |
| } else { | |
| console.log('🔘 已点击验证按钮'); | |
| } | |
| // 等待验证结果 | |
| await new Promise(resolve => setTimeout(resolve, 3000)); | |
| // 检查验证是否成功 | |
| const success = await this.checkVerificationSuccess(); | |
| if (success) { | |
| return { | |
| success: true, | |
| verification_code: verificationData.code, | |
| verified_at: new Date().toISOString() | |
| }; | |
| } else { | |
| throw new Error('邮箱验证失败'); | |
| } | |
| } catch (error) { | |
| throw new Error(`邮箱验证失败: ${error.message}`); | |
| } | |
| } | |
| // 检查验证是否成功 | |
| async checkVerificationSuccess() { | |
| try { | |
| // 检查是否有成功消息 | |
| const successSelectors = [ | |
| '#success-message', '#success', '.success', '.alert-success', | |
| '[class*="success"]', '[id*="success"]' | |
| ]; | |
| for (const selector of successSelectors) { | |
| try { | |
| await this.page.waitForSelector(selector, { timeout: 2000 }); | |
| console.log('✅ 找到验证成功标识'); | |
| return true; | |
| } catch (e) { | |
| continue; | |
| } | |
| } | |
| // 检查页面文本 | |
| const pageText = await this.page.evaluate(() => document.body.textContent.toLowerCase()); | |
| const successKeywords = ['验证成功', 'verification successful', '注册成功', 'registration successful', '验证完成']; | |
| const hasSuccessKeyword = successKeywords.some(keyword => pageText.includes(keyword)); | |
| // 检查是否被重定向到其他页面(通常是主页) | |
| const currentUrl = this.page.url(); | |
| const isRedirected = !currentUrl.includes('signup') && !currentUrl.includes('verify'); | |
| return hasSuccessKeyword || isRedirected; | |
| } catch (error) { | |
| console.log('⚠️ 检查验证结果时出错:', error.message); | |
| return false; | |
| } | |
| } | |
| // 提取用户Token | |
| async extractToken() { | |
| try { | |
| console.log('🔑 开始提取用户Token...'); | |
| // 等待页面稳定 | |
| await new Promise(resolve => setTimeout(resolve, 2000)); | |
| // 尝试多种方式提取Token | |
| // 1. 从localStorage提取 | |
| let token = await this.page.evaluate(() => { | |
| const keys = ['token', 'auth_token', 'jwt_token', 'access_token', 'mindvideo_token']; | |
| for (const key of keys) { | |
| const value = localStorage.getItem(key); | |
| if (value && value.startsWith('eyJ')) { // JWT通常以eyJ开头 | |
| return value; | |
| } | |
| } | |
| return null; | |
| }); | |
| if (token) { | |
| console.log('🔑 从localStorage提取到Token'); | |
| return token; | |
| } | |
| // 2. 从cookie提取 | |
| const cookies = await this.page.cookies(); | |
| const cookieToken = cookies.find(c => | |
| (c.name.includes('token') || c.name.includes('auth')) && | |
| c.value.startsWith('eyJ') | |
| ); | |
| if (cookieToken) { | |
| console.log('🔑 从Cookie提取到Token'); | |
| return cookieToken.value; | |
| } | |
| // 3. 从页面脚本或全局变量提取 | |
| token = await this.page.evaluate(() => { | |
| // 检查常见的全局变量 | |
| if (window.token) return window.token; | |
| if (window.authToken) return window.authToken; | |
| if (window.user && window.user.token) return window.user.token; | |
| // 检查script标签中的token | |
| const scripts = Array.from(document.querySelectorAll('script')); | |
| for (const script of scripts) { | |
| const text = script.textContent; | |
| const tokenMatch = text.match(/(?:token|auth[_-]?token)\s*[:=]\s*['"`]([^'"`]+)['"`]/i); | |
| if (tokenMatch && tokenMatch[1].startsWith('eyJ')) { | |
| return tokenMatch[1]; | |
| } | |
| } | |
| return null; | |
| }); | |
| if (token) { | |
| console.log('🔑 从页面脚本提取到Token'); | |
| return token; | |
| } | |
| // 4. 从API响应中提取(如果有请求拦截) | |
| try { | |
| const responses = await this.page.evaluate(() => { | |
| if (window.performance && window.performance.getEntriesByType) { | |
| const entries = window.performance.getEntriesByType('navigation'); | |
| return entries.map(entry => entry.name); | |
| } | |
| return []; | |
| }); | |
| console.log('🔍 检查API响应记录...'); | |
| } catch (e) { | |
| console.log('⚠️ 无法检查API响应记录'); | |
| } | |
| console.log('⚠️ 未能提取到用户Token'); | |
| return null; | |
| } catch (error) { | |
| console.error(`❌ 提取Token失败: ${error.message}`); | |
| return null; | |
| } | |
| } | |
| // 生成随机密码 | |
| generatePassword(prefix = 'Pwd', length = 12) { | |
| const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%'; | |
| let password = prefix; | |
| for (let i = prefix.length; i < length; i++) { | |
| password += chars.charAt(Math.floor(Math.random() * chars.length)); | |
| } | |
| return password; | |
| } | |
| // 关闭浏览器 | |
| async closeBrowser() { | |
| await this.emailService.closeBrowser(); | |
| if (this.page) { | |
| await this.page.close(); | |
| this.page = null; | |
| } | |
| if (this.browser) { | |
| await this.browser.close(); | |
| this.browser = null; | |
| } | |
| } | |
| } | |
| // 注册控制器类 - 负责协调整个注册流程 | |
| class RegistrationController { | |
| constructor() { | |
| this.registrationService = new MindVideoRegistrationService(); | |
| this.storageService = new StorageService(); | |
| this.maxConcurrent = 3; // 最大并发数 | |
| this.activeTasks = new Map(); // 活跃任务管理 | |
| } | |
| // 批量注册账号 | |
| async batchRegisterAccounts(count, options = {}) { | |
| try { | |
| console.log(`🚀 启动批量注册任务,账号数量: ${count}`); | |
| const taskId = this.generateTaskId(); | |
| const { | |
| passwordPrefix = 'Pwd', | |
| passwordLength = 12 | |
| } = options; | |
| // 创建任务记录 | |
| await this.createTaskRecord(taskId, count, options); | |
| // 启动批量注册任务(异步) | |
| this.runBatchRegistration(taskId, count, passwordPrefix, passwordLength); | |
| return { | |
| success: true, | |
| task_id: taskId, | |
| message: `已启动${count}个账号的批量注册任务`, | |
| estimated_time: Math.ceil(count / this.maxConcurrent) * 3 + '分钟' | |
| }; | |
| } catch (error) { | |
| console.error(`❌ 启动批量注册任务失败: ${error.message}`); | |
| return { | |
| success: false, | |
| error: error.message | |
| }; | |
| } | |
| } | |
| // 运行批量注册 | |
| async runBatchRegistration(taskId, count, passwordPrefix, passwordLength) { | |
| console.log(`🔄 开始执行批量注册任务: ${taskId}`); | |
| const results = []; | |
| const concurrent = Math.min(this.maxConcurrent, count); | |
| try { | |
| // 初始化注册服务 | |
| await this.registrationService.initBrowser(); | |
| // 分批处理 | |
| for (let i = 0; i < count; i += concurrent) { | |
| const batch = []; | |
| const batchEnd = Math.min(i + concurrent, count); | |
| console.log(`📦 处理批次 ${Math.floor(i/concurrent) + 1}: 账号 ${i+1}-${batchEnd}`); | |
| // 创建并发任务 | |
| for (let j = i; j < batchEnd; j++) { | |
| const accountIndex = j + 1; | |
| const password = this.registrationService.generatePassword(passwordPrefix, passwordLength); | |
| batch.push(this.registerSingleAccount(taskId, accountIndex, password)); | |
| } | |
| // 等待当前批次完成 | |
| const batchResults = await Promise.allSettled(batch); | |
| // 处理批次结果 | |
| for (let k = 0; k < batchResults.length; k++) { | |
| const result = batchResults[k]; | |
| const accountIndex = i + k + 1; | |
| if (result.status === 'fulfilled') { | |
| results.push(result.value); | |
| if (result.value.status === 'success') { | |
| await this.updateTaskProgress(taskId, 'completed'); | |
| } else { | |
| await this.updateTaskProgress(taskId, 'failed'); | |
| } | |
| } else { | |
| results.push({ | |
| task_id: taskId, | |
| account_index: accountIndex, | |
| status: 'failed', | |
| error_message: result.reason.message, | |
| created_at: new Date().toISOString() | |
| }); | |
| await this.updateTaskProgress(taskId, 'failed'); | |
| } | |
| } | |
| // 更新任务状态 | |
| await this.updateTaskStatus(taskId, results); | |
| // 批次间延迟 | |
| if (batchEnd < count) { | |
| console.log('⏳ 批次间延迟 3 秒...'); | |
| await new Promise(resolve => setTimeout(resolve, 3000)); | |
| } | |
| } | |
| // 标记任务完成 | |
| await this.completeTask(taskId, results); | |
| console.log(`✅ 批量注册任务完成: ${taskId}`); | |
| } catch (error) { | |
| console.error(`❌ 批量注册任务失败: ${error.message}`); | |
| await this.failTask(taskId, error.message); | |
| } finally { | |
| await this.registrationService.closeBrowser(); | |
| } | |
| } | |
| // 单个账号注册 | |
| async registerSingleAccount(taskId, accountIndex, password) { | |
| try { | |
| console.log(`🔐 注册单个账号 [${accountIndex}]`); | |
| // 创建临时邮箱 | |
| const tempEmail = await this.registrationService.emailService.createTempEmail(); | |
| console.log(`📧 创建临时邮箱成功: ${tempEmail.email}`); | |
| // 执行注册流程 | |
| const registrationResult = await this.registrationService.registerAccount( | |
| tempEmail.email, | |
| password | |
| ); | |
| const result = { | |
| task_id: taskId, | |
| account_index: accountIndex, | |
| email: tempEmail.email, | |
| password: password, // 这里应该加密存储 | |
| status: registrationResult.success ? 'success' : 'failed', | |
| token: registrationResult.token || null, | |
| error_message: registrationResult.error || null, | |
| created_at: new Date().toISOString() | |
| }; | |
| // 保存到数据库 | |
| if (registrationResult.success) { | |
| await this.storageService.saveAccount(result); | |
| console.log(`💾 账号 ${accountIndex} 已保存到数据库`); | |
| } | |
| return result; | |
| } catch (error) { | |
| console.error(`❌ 注册账号 ${accountIndex} 失败: ${error.message}`); | |
| return { | |
| task_id: taskId, | |
| account_index: accountIndex, | |
| status: 'failed', | |
| error_message: error.message, | |
| created_at: new Date().toISOString() | |
| }; | |
| } | |
| } | |
| // 获取注册进度 | |
| async getRegistrationProgress(taskId) { | |
| try { | |
| console.log(`📊 查询注册进度: ${taskId}`); | |
| const task = await this.storageService.getTask(taskId); | |
| if (!task) { | |
| return { | |
| success: false, | |
| error: '任务不存在' | |
| }; | |
| } | |
| const results = await this.storageService.getTaskResults(taskId); | |
| const progress = { | |
| total: task.total_count, | |
| completed: task.completed_count, | |
| failed: task.failed_count, | |
| cancelled: task.cancelled_count || 0, | |
| percentage: Math.round((task.completed_count / task.total_count) * 100) | |
| }; | |
| return { | |
| success: true, | |
| task_id: taskId, | |
| status: task.status, | |
| progress: progress, | |
| results: results, | |
| created_at: task.created_at, | |
| updated_at: task.updated_at, | |
| completed_at: task.completed_at | |
| }; | |
| } catch (error) { | |
| console.error(`❌ 查询注册进度失败: ${error.message}`); | |
| return { | |
| success: false, | |
| error: error.message | |
| }; | |
| } | |
| } | |
| // 取消注册任务 | |
| async cancelRegistrationTask(taskId) { | |
| try { | |
| console.log(`🛑 取消注册任务: ${taskId}`); | |
| // 更新任务状态 | |
| const updated = await this.storageService.updateTaskStatus(taskId, 'cancelled'); | |
| if (!updated) { | |
| return { | |
| success: false, | |
| error: '任务不存在或无法取消' | |
| }; | |
| } | |
| // 从活跃任务中移除 | |
| this.activeTasks.delete(taskId); | |
| return { | |
| success: true, | |
| message: '任务已取消', | |
| cancelled_at: new Date().toISOString() | |
| }; | |
| } catch (error) { | |
| console.error(`❌ 取消任务失败: ${error.message}`); | |
| return { | |
| success: false, | |
| error: error.message | |
| }; | |
| } | |
| } | |
| // 获取所有注册任务 | |
| async getAllRegistrationTasks() { | |
| try { | |
| console.log('📋 获取所有注册任务'); | |
| const tasks = await this.storageService.getAllTasks(); | |
| return { | |
| success: true, | |
| data: tasks, | |
| total: tasks.length | |
| }; | |
| } catch (error) { | |
| console.error(`❌ 获取任务列表失败: ${error.message}`); | |
| return { | |
| success: false, | |
| error: error.message | |
| }; | |
| } | |
| } | |
| // 生成任务ID | |
| generateTaskId() { | |
| return `reg_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; | |
| } | |
| // 创建任务记录 | |
| async createTaskRecord(taskId, totalCount, options = {}) { | |
| const taskData = { | |
| task_id: taskId, | |
| status: 'running', | |
| total_count: totalCount, | |
| completed_count: 0, | |
| failed_count: 0, | |
| cancelled_count: 0, | |
| options: JSON.stringify(options), | |
| created_at: new Date().toISOString(), | |
| updated_at: new Date().toISOString() | |
| }; | |
| await this.storageService.saveTask(taskData); | |
| this.activeTasks.set(taskId, taskData); | |
| console.log(`📝 创建任务记录: ${taskId}`); | |
| } | |
| // 更新任务进度 | |
| async updateTaskProgress(taskId, type) { | |
| try { | |
| if (type === 'completed') { | |
| await this.storageService.incrementCompletedCount(taskId); | |
| } else if (type === 'failed') { | |
| await this.storageService.incrementFailedCount(taskId); | |
| } else if (type === 'cancelled') { | |
| await this.storageService.incrementCancelledCount(taskId); | |
| } | |
| } catch (error) { | |
| console.error(`❌ 更新任务进度失败: ${error.message}`); | |
| } | |
| } | |
| // 更新任务状态 | |
| async updateTaskStatus(taskId, results) { | |
| try { | |
| const successCount = results.filter(r => r.status === 'success').length; | |
| const failedCount = results.filter(r => r.status === 'failed').length; | |
| await this.storageService.updateTaskProgress(taskId, { | |
| completed_count: successCount, | |
| failed_count: failedCount, | |
| updated_at: new Date().toISOString() | |
| }); | |
| // 更新内存中的任务状态 | |
| if (this.activeTasks.has(taskId)) { | |
| const task = this.activeTasks.get(taskId); | |
| task.completed_count = successCount; | |
| task.failed_count = failedCount; | |
| task.updated_at = new Date().toISOString(); | |
| } | |
| } catch (error) { | |
| console.error(`❌ 更新任务状态失败: ${error.message}`); | |
| } | |
| } | |
| // 完成任务 | |
| async completeTask(taskId, results) { | |
| try { | |
| await this.storageService.updateTaskStatus(taskId, 'completed'); | |
| // 保存所有结果 | |
| for (const result of results) { | |
| await this.storageService.saveRegistrationResult(result); | |
| } | |
| this.activeTasks.delete(taskId); | |
| console.log(`✅ 任务 ${taskId} 已完成`); | |
| } catch (error) { | |
| console.error(`❌ 完成任务失败: ${error.message}`); | |
| } | |
| } | |
| // 任务失败 | |
| async failTask(taskId, errorMessage) { | |
| try { | |
| await this.storageService.updateTaskStatus(taskId, 'failed'); | |
| await this.storageService.updateTaskError(taskId, errorMessage); | |
| this.activeTasks.delete(taskId); | |
| console.log(`❌ 任务 ${taskId} 已失败: ${errorMessage}`); | |
| } catch (error) { | |
| console.error(`❌ 标记任务失败失败: ${error.message}`); | |
| } | |
| } | |
| // 获取活跃任务列表 | |
| getActiveTasks() { | |
| return Array.from(this.activeTasks.values()); | |
| } | |
| // 清理过期任务 | |
| async cleanupExpiredTasks() { | |
| try { | |
| console.log('🧹 清理过期任务...'); | |
| const expiredTasks = await this.storageService.getExpiredTasks(24); // 24小时前的任务 | |
| for (const task of expiredTasks) { | |
| await this.storageService.deleteTask(task.task_id); | |
| this.activeTasks.delete(task.task_id); | |
| } | |
| console.log(`🧹 清理了 ${expiredTasks.length} 个过期任务`); | |
| } catch (error) { | |
| console.error(`❌ 清理过期任务失败: ${error.message}`); | |
| } | |
| } | |
| } | |
| const supabase = new SupabaseService(); | |
| // 通用存储服务(使用 Supabase) | |
| class StorageService { | |
| async saveAccount(account) { | |
| return await supabase.insert('accounts', account); | |
| } | |
| async updateAccount(email, data) { | |
| return await supabase.update('accounts', data, { email }); | |
| } | |
| async getAccounts() { | |
| return await supabase.select('accounts', '*'); | |
| } | |
| async saveTask(task) { | |
| return await supabase.insert('registration_tasks', task); | |
| } | |
| async updateTask(taskId, data) { | |
| return await supabase.update('registration_tasks', data, { task_id: taskId }); | |
| } | |
| // ========== 注册任务相关方法 ========== | |
| // 获取单个任务 | |
| async getTask(taskId) { | |
| const tasks = await supabase.select('registration_tasks', '*', { task_id: taskId }); | |
| return tasks.length > 0 ? tasks[0] : null; | |
| } | |
| // 获取所有任务 | |
| async getAllTasks() { | |
| return await supabase.select('registration_tasks', '*', {}, 'created_at.desc'); | |
| } | |
| // 更新任务状态 | |
| async updateTaskStatus(taskId, status) { | |
| const updateData = { | |
| status, | |
| updated_at: new Date().toISOString() | |
| }; | |
| if (status === 'completed') { | |
| updateData.completed_at = new Date().toISOString(); | |
| } | |
| return await supabase.update('registration_tasks', updateData, { task_id: taskId }); | |
| } | |
| // 更新任务进度 | |
| async updateTaskProgress(taskId, progressData) { | |
| const updateData = { | |
| ...progressData, | |
| updated_at: new Date().toISOString() | |
| }; | |
| return await supabase.update('registration_tasks', updateData, { task_id: taskId }); | |
| } | |
| // 更新任务错误信息 | |
| async updateTaskError(taskId, errorMessage) { | |
| const updateData = { | |
| error_message: errorMessage, | |
| status: 'failed', | |
| updated_at: new Date().toISOString() | |
| }; | |
| return await supabase.update('registration_tasks', updateData, { task_id: taskId }); | |
| } | |
| // 增加完成计数 | |
| async incrementCompletedCount(taskId) { | |
| return await supabase.rpc('increment_completed_count', { task_id: taskId }); | |
| } | |
| // 增加失败计数 | |
| async incrementFailedCount(taskId) { | |
| return await supabase.rpc('increment_failed_count', { task_id: taskId }); | |
| } | |
| // 增加取消计数 | |
| async incrementCancelledCount(taskId) { | |
| return await supabase.rpc('increment_cancelled_count', { task_id: taskId }); | |
| } | |
| // 保存注册结果 | |
| async saveRegistrationResult(result) { | |
| return await supabase.insert('registration_results', result); | |
| } | |
| // 获取任务结果 | |
| async getTaskResults(taskId) { | |
| return await supabase.select('registration_results', '*', { task_id: taskId }, 'account_index.asc'); | |
| } | |
| // 获取过期任务 | |
| async getExpiredTasks(hoursOld = 24) { | |
| const cutoffTime = new Date(Date.now() - hoursOld * 60 * 60 * 1000).toISOString(); | |
| return await supabase.select('registration_tasks', '*', { | |
| created_at: `lt.${cutoffTime}`, | |
| status: 'in.completed' // 不是完成状态的任务 | |
| }, 'created_at.asc'); | |
| } | |
| // 删除任务 | |
| async deleteTask(taskId) { | |
| // 需要先删除相关的结果记录 | |
| await supabase.request('DELETE', `registration_results?task_id=eq.${taskId}`); | |
| return await supabase.request('DELETE', `registration_tasks?task_id=eq.${taskId}`); | |
| } | |
| // 获取统计信息 | |
| async getRegistrationStats() { | |
| const stats = await supabase.rpc('get_registration_stats'); | |
| return stats; | |
| } | |
| } | |
| const storage = new StorageService(); | |
| const registrationController = new RegistrationController(); | |
| async function initBrowser() { | |
| if (!browser) { | |
| console.log('🚀 启动浏览器...'); | |
| // Hugging Face 环境优化的浏览器配置 | |
| const isHfEnvironment = process.env.SPACE_ID !== undefined || process.env.HF_SPACE !== undefined; | |
| const launchArgs = [ | |
| '--no-sandbox', | |
| '--disable-setuid-sandbox', | |
| '--disable-dev-shm-usage', | |
| '--disable-gpu', | |
| '--disable-accelerated-2d-canvas', | |
| '--no-first-run', | |
| '--no-zygote', | |
| '--disable-background-timer-throttling', | |
| '--disable-backgrounding-occluded-windows', | |
| '--disable-renderer-backgrounding', | |
| '--disable-features=TranslateUI', | |
| '--disable-extensions', | |
| '--disable-plugins', | |
| '--disable-default-apps', | |
| '--disable-sync', | |
| '--metrics-recording-only', | |
| '--no-report-upload', | |
| '--disable-logging', | |
| '--silent', | |
| '--log-level=3' | |
| ]; | |
| // HF 环境特定优化 | |
| if (isHfEnvironment) { | |
| launchArgs.push( | |
| '--memory-pressure-off', | |
| '--max_old_space_size=256', | |
| '--optimize-for-size' | |
| ); | |
| console.log('🤖 检测到 Hugging Face 环境,应用优化配置'); | |
| } else { | |
| launchArgs.push('--single-process'); | |
| } | |
| browser = await puppeteer.launch({ | |
| headless: 'new', | |
| args: launchArgs | |
| }); | |
| } | |
| return browser; | |
| } | |
| function md5(message) { | |
| return crypto.createHash('md5').update(message).digest('hex'); | |
| } | |
| async function generateSign() { | |
| const nonce = crypto.randomUUID().replace(/-/g, '').substring(0, 16); | |
| const timestamp = Date.now(); | |
| const signStr = `nonce=${nonce}×tamp=${timestamp}&app_key=${CONFIG.SIGN_APP_KEY}`; | |
| const sign = md5(signStr); | |
| return JSON.stringify({ nonce, timestamp, sign }); | |
| } | |
| async function performCheckIn(token, index = 1, email = null) { | |
| console.log(`📅 [账号 ${index}] ${email ? `(${email}) ` : ''}尝试签到...`); | |
| try { | |
| const signHeader = await generateSign(); | |
| const response = await fetch(CONFIG.MINDVIDEO_CHECKIN_API, { | |
| method: 'POST', | |
| headers: { | |
| "accept": "application/json, text/plain, */*", | |
| "authorization": `Bearer ${token}`, | |
| "content-type": "application/json", | |
| "i-lang": "zh-CN", | |
| "i-version": "1.0.8", | |
| "i-sign": signHeader, | |
| "origin": "https://www.mindvideo.ai", | |
| "referer": "https://www.mindvideo.ai/", | |
| "user-agent": CONFIG.USER_AGENT | |
| } | |
| }); | |
| const text = await response.text(); | |
| let data; | |
| try { | |
| data = JSON.parse(text); | |
| } catch (e) { | |
| console.error(`❌ [账号 ${index}] 签到响应非 JSON:`, text.substring(0, 100)); | |
| return { success: false, message: "无效的 JSON 响应" }; | |
| } | |
| if (data.code === 0) { | |
| console.log(`✅ [账号 ${index}] 签到成功! 获得积分: ${data.data?.credits}, 连续天数: ${data.data?.current_day}`); | |
| return { | |
| success: true, | |
| credits: data.data?.credits, | |
| total: data.data?.total_credits, | |
| day: data.data?.current_day | |
| }; | |
| } else { | |
| console.warn(`⚠️ [账号 ${index}] 签到失败: ${data.message}`); | |
| return { success: false, message: data.message }; | |
| } | |
| } catch (error) { | |
| console.error(`❌ [账号 ${index}] 签到过程出错:`, error.message); | |
| return { success: false, error: error.message }; | |
| } | |
| } | |
| async function getUserInfo(token, index = 1) { | |
| console.log(`🔍 [账号 ${index}] 查询用户信息...`); | |
| try { | |
| const signHeader = await generateSign(); | |
| const response = await fetch('https://api.mindvideo.ai/api/user/info', { | |
| method: 'GET', | |
| headers: { | |
| "accept": "application/json, text/plain, */*", | |
| "authorization": `Bearer ${token}`, | |
| "i-lang": "zh-CN", | |
| "i-version": "1.0.8", | |
| "i-sign": signHeader, | |
| "origin": "https://www.mindvideo.ai", | |
| "referer": "https://www.mindvideo.ai/", | |
| "user-agent": CONFIG.USER_AGENT | |
| } | |
| }); | |
| const text = await response.text(); | |
| let data; | |
| try { | |
| data = JSON.parse(text); | |
| } catch (e) { | |
| console.warn(`⚠️ [账号 ${index}] 用户信息响应非 JSON`); | |
| return null; | |
| } | |
| if (data.code === 0 && data.data) { | |
| console.log(`✅ [账号 ${index}] 积分: ${data.data.credits || 0}`); | |
| return { | |
| credits: data.data.credits || 0, | |
| email: data.data.email || null, | |
| nickname: data.data.nickname || null, | |
| avatar: data.data.avatar || null | |
| }; | |
| } else { | |
| console.warn(`⚠️ [账号 ${index}] 无法获取用户信息`); | |
| return null; | |
| } | |
| } catch (error) { | |
| console.warn(`⚠️ [账号 ${index}] 查询用户信息失败: ${error.message}`); | |
| return null; | |
| } | |
| } | |
| async function loginSingleAccount(browser, username, password, index) { | |
| console.log(`🔄 [账号 ${index}] 开始登录流程: ${username}`); | |
| const page = await browser.newPage(); | |
| try { | |
| await page.setUserAgent(CONFIG.USER_AGENT); | |
| // 在登录前清除所有 Cookie 和存储 | |
| console.log(`[账号 ${index}] 清除浏览器缓存和 Cookie...`); | |
| const client = await page.target().createCDPSession(); | |
| await client.send('Network.clearBrowserCookies'); | |
| await client.send('Network.clearBrowserCache'); | |
| await page.evaluateOnNewDocument(() => { | |
| localStorage.clear(); | |
| sessionStorage.clear(); | |
| }); | |
| await page.goto(CONFIG.MINDVIDEO_LOGIN_URL, { waitUntil: 'networkidle2', timeout: 60000 }); | |
| console.log(`[账号 ${index}] 输入账号密码...`); | |
| await page.waitForSelector('#email', { timeout: 10000 }); | |
| await page.evaluate(() => { | |
| if (document.querySelector('#email')) document.querySelector('#email').value = ''; | |
| if (document.querySelector('#password')) document.querySelector('#password').value = ''; | |
| }); | |
| await page.type('#email', username); | |
| await page.type('#password', password); | |
| console.log(`[账号 ${index}] 点击登录...`); | |
| const loginBtn = await page.$('button[type="submit"]'); | |
| if (loginBtn) { | |
| await loginBtn.click(); | |
| } else { | |
| const buttons = await page.$$('button'); | |
| let clicked = false; | |
| for (const btn of buttons) { | |
| const text = await page.evaluate(el => el.innerText, btn); | |
| if (text.replace(/\s/g, '').includes('登录')) { | |
| await btn.click(); | |
| clicked = true; | |
| break; | |
| } | |
| } | |
| if (!clicked) throw new Error("未找到登录按钮"); | |
| } | |
| await page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 60000 }); | |
| console.log(`[账号 ${index}] 提取 Token...`); | |
| const cookies = await page.cookies(); | |
| const cookieToken = cookies.find(c => c.name === 'token'); | |
| const lsToken = await page.evaluate(() => localStorage.getItem('token')); | |
| const finalToken = (cookieToken ? cookieToken.value : null) || lsToken; | |
| if (finalToken) { | |
| console.log(`✅ [账号 ${index}] Token 获取成功!`); | |
| console.log(` Token 预览: ${finalToken.substring(0, 30)}...`); | |
| // 解析 JWT Token 获取用户信息 | |
| let expireTime = null; | |
| let userId = null; | |
| let tokenEmail = null; | |
| let isExpired = false; | |
| try { | |
| const payload = JSON.parse(Buffer.from(finalToken.split('.')[1], 'base64').toString()); | |
| expireTime = payload.exp ? new Date(payload.exp * 1000).toISOString() : null; | |
| userId = payload.uid || payload.sub || null; | |
| tokenEmail = payload.email || null; | |
| if (payload.exp) { | |
| const expireDate = new Date(payload.exp * 1000); | |
| const now = new Date(); | |
| isExpired = expireDate < now; | |
| } | |
| } catch (e) { | |
| console.warn(`⚠️ 无法解析 Token 信息: ${e.message}`); | |
| } | |
| // 保存账号信息到数据库 | |
| const accountData = { | |
| email: username, | |
| password: password, | |
| token: finalToken, | |
| user_id: userId, | |
| token_email: tokenEmail, | |
| created_at: new Date().toISOString(), | |
| last_login_at: new Date().toISOString(), | |
| expire_at: expireTime, | |
| is_expired: isExpired, | |
| status: isExpired ? "expired" : "active" | |
| }; | |
| await storage.saveAccount(accountData); | |
| // 清除会话 | |
| try { | |
| await page.evaluate(() => { | |
| localStorage.clear(); | |
| sessionStorage.clear(); | |
| }); | |
| await client.send('Network.clearBrowserCookies'); | |
| console.log(`[账号 ${index}] 已清除登录会话`); | |
| } catch (e) { | |
| console.warn(`[账号 ${index}] 清除会话失败: ${e.message}`); | |
| } | |
| await client.detach(); | |
| return finalToken; | |
| } else { | |
| console.warn(`⚠️ [账号 ${index}] 未找到 Token Cookie`); | |
| await client.detach(); | |
| return null; | |
| } | |
| } catch (error) { | |
| console.error(`❌ [账号 ${index}] 登录失败:`, error.message); | |
| return null; | |
| } finally { | |
| await page.close(); | |
| } | |
| } | |
| async function processAllAccounts() { | |
| console.log("🚀 开始多账号处理流程..."); | |
| // 从环境变量获取账号信息 | |
| const accountsRaw = process.env.MINDVIDEO_ACCOUNTS; | |
| if (!accountsRaw) { | |
| console.error("❌ 未找到账号配置"); | |
| return { success: false, message: "未找到 MINDVIDEO_ACCOUNTS 配置" }; | |
| } | |
| const accounts = accountsRaw.split(/[\n;]/) | |
| .map(line => line.trim()) | |
| .filter(line => line && line.includes(':')) | |
| .map(line => { | |
| const parts = line.split(':'); | |
| const user = parts[0].trim(); | |
| const pass = parts.slice(1).join(':').trim(); | |
| return { user, pass }; | |
| }); | |
| if (accounts.length === 0) { | |
| console.error("❌ 账号格式错误或为空"); | |
| return { success: false, message: "账号格式错误,请使用 '账号:密码' 格式,每行一个" }; | |
| } | |
| console.log(`📋 发现 ${accounts.length} 个账号,准备执行...`); | |
| const browser = await initBrowser(); | |
| const results = []; | |
| for (let i = 0; i < accounts.length; i++) { | |
| const { user, pass } = accounts[i]; | |
| const token = await loginSingleAccount(browser, user, pass, i + 1); | |
| if (token) { | |
| const checkinRes = await performCheckIn(token, i + 1, user); | |
| results.push({ | |
| user, | |
| success: true, | |
| checkin: checkinRes | |
| }); | |
| } else { | |
| results.push({ user, success: false, message: "登录失败" }); | |
| } | |
| if (i < accounts.length - 1) { | |
| await new Promise(r => setTimeout(r, 2000)); | |
| } | |
| } | |
| return { | |
| success: true, | |
| processed: accounts.length, | |
| valid: results.filter(r => r.success).length, | |
| details: results | |
| }; | |
| } | |
| // 获取统计信息 | |
| async function getStats() { | |
| const accounts = await storage.getAccounts(); | |
| let stats = { | |
| status: "ok", | |
| storage_type: "Supabase", | |
| account_count: 0, | |
| active_count: 0, | |
| expired_count: 0, | |
| server_time: new Date().toISOString(), | |
| accounts: [] | |
| }; | |
| if (accounts && Array.isArray(accounts)) { | |
| stats.account_count = accounts.length; | |
| stats.active_count = accounts.filter(acc => !acc.is_expired).length; | |
| stats.expired_count = accounts.filter(acc => acc.is_expired).length; | |
| stats.accounts = accounts.map(acc => ({ | |
| email: acc.email, | |
| user_id: acc.user_id, | |
| created_at: acc.created_at, | |
| last_login_at: acc.last_login_at, | |
| expire_at: acc.expire_at, | |
| status: acc.status, | |
| token_preview: acc.token ? acc.token.substring(0, 20) + "..." : null | |
| })); | |
| } | |
| return stats; | |
| } | |
| // Web 服务器 | |
| const server = http.createServer(async (req, res) => { | |
| const url = new URL(req.url, `http://${req.headers.host}`); | |
| if (url.pathname === '/') { | |
| res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); | |
| res.end(`<!DOCTYPE html><html><head><meta charset="UTF-8"><title>MindVideo 多账号机器人</title><style>*{margin:0;padding:0;box-sizing:border-box}body{font-family:'Segoe UI',Tahoma,Geneva,Verdana,sans-serif;background:linear-gradient(135deg,#667eea 0%,#764ba2 100%);min-height:100vh;padding:20px}.container{max-width:800px;margin:0 auto;background:white;border-radius:16px;box-shadow:0 20px 60px rgba(0,0,0,0.3);padding:40px}h1{color:#333;margin-bottom:10px;font-size:28px}.status{color:#10b981;font-weight:600;margin-bottom:30px}.btn-group{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:15px;margin-bottom:30px}button{padding:14px 24px;border:none;border-radius:8px;font-size:15px;font-weight:600;cursor:pointer;transition:all 0.3s;color:white}button:hover{transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,0.15)}button:active{transform:translateY(0)}button:disabled{opacity:0.6;cursor:not-allowed;transform:none}.btn-primary{background:linear-gradient(135deg,#667eea 0%,#764ba2 100%)}.btn-success{background:linear-gradient(135deg,#10b981 0%,#059669 100%)}.btn-info{background:linear-gradient(135deg,#3b82f6 0%,#2563eb 100%)}.btn-warning{background:linear-gradient(135deg,#f59e0b 0%,#d97706 100%)}pre{background:#f8f9fa;padding:20px;border-radius:8px;border:1px solid #e9ecef;max-height:400px;overflow-y:auto;font-size:13px;line-height:1.6}.info-box{background:#f0f9ff;padding:15px;border-radius:8px;margin-bottom:20px;border-left:4px solid #3b82f6}.info-box h3{margin:0 0 10px 0;color:#1e40af}table{font-size:14px}table th{background:#f8f9fa;font-weight:600;color:#374151}table td{vertical-align:top}#registrationPanel,#tasksPanel{background:#f9fafb;padding:20px;border-radius:8px;margin-top:20px;border:1px solid #e5e7eb}#registrationPanel h3,#tasksPanel h3{margin-top:0;color:#1f2937}#registrationProgress{background:white;padding:20px;border-radius:8px;border:1px solid #e5e7eb}#registrationProgress h4{margin-top:0;color:#374151}</style></head><body><div class="container"><h1>🤖 MindVideo 多账号机器人</h1><p class="status">● 运行中</p><div class="info-box"><strong>⏰ 定时任务:</strong><br>🔄 Token 刷新: 已取消<br>📅 每日签到: 已取消</div><div class="btn-group"><button class="btn-primary" onclick="processAll()">🔄 完整流程</button><button class="btn-info" onclick="getStats()">📊 查看状态</button></div><div class="btn-group"><button class="btn-primary" onclick="showRegistrationPanel()">🆕 账号注册</button><button class="btn-info" onclick="getRegistrationTasks()">📋 注册任务</button><button class="btn-warning" onclick="getRegistrationStats()">📈 注册统计</button><button class="btn-success" onclick="cleanupTasks()">🧹 清理任务</button></div><div id="registrationPanel" style="display:none"><div class="info-box"><h3>🆕 MindVideo 账号自动注册</h3><p>通过临时邮箱自动注册 MindVideo 账号,支持批量处理和进度跟踪。</p></div><div style="display:grid;grid-template-columns:1fr 1fr;gap:15px;margin-bottom:20px"><div><label style="display:block;margin-bottom:5px;font-weight:600">注册数量:</label><input type="number" id="regCount" value="3" min="1" max="20" style="width:100%;padding:10px;border:1px solid #ddd;border-radius:6px"></div><div><label style="display:block;margin-bottom:5px;font-weight:600">密码前缀:</label><input type="text" id="passwordPrefix" value="Pwd" style="width:100%;padding:10px;border:1px solid #ddd;border-radius:6px"></div><div><label style="display:block;margin-bottom:5px;font-weight:600">密码长度:</label><input type="number" id="passwordLength" value="12" min="8" max="20" style="width:100%;padding:10px;border:1px solid #ddd;border-radius:6px"></div><div><label style="display:block;margin-bottom:5px;font-weight:600">任务ID:</label><input type="text" id="taskId" placeholder="自动生成" readonly style="width:100%;padding:10px;border:1px solid #ddd;border-radius:6px;background:#f8f9fa"></div></div><div style="margin-bottom:20px"><button class="btn-primary" onclick="startRegistration()" style="margin-right:10px">🚀 开始注册</button><button class="btn-warning" onclick="cancelRegistration()">🛑 取消任务</button></div><div id="registrationProgress" style="display:none"><h4>📊 注册进度</h4><div style="background:#f0f0f0;border-radius:8px;padding:2px;margin:10px 0"><div id="progressBar" style="background:linear-gradient(90deg,#667eea,#764ba2);height:20px;border-radius:6px;width:0%;transition:width 0.3s"></div></div><div id="progressText" style="font-size:14px;color:#666">准备中...</div></div></div><div id="tasksPanel" style="display:none"><h3>📋 注册任务列表</h3><div id="tasksList"></div></div><pre id="output">点击上方按钮执行操作...</pre></div><script>const output=document.getElementById('output');async function processAll(){output.innerText="正在执行完整流程(登录 + 签到),请耐心等待...";disableButtons(true);try{const res=await fetch('/trigger');const data=await res.json();output.innerText=JSON.stringify(data,null,2)}catch(e){output.innerText="错误: "+e.message}disableButtons(false)}async function getStats(){output.innerText="正在查询状态...";try{const res=await fetch('/stats');const data=await res.json();output.innerText=JSON.stringify(data,null,2)}catch(e){output.innerText="错误: "+e.message}}function disableButtons(disabled){document.querySelectorAll('button').forEach(btn=>btn.disabled=disabled)}let currentTaskId=null;let progressInterval=null;function showRegistrationPanel(){document.getElementById('registrationPanel').style.display='block';document.getElementById('tasksPanel').style.display='none'}function showTasksPanel(){document.getElementById('registrationPanel').style.display='none';document.getElementById('tasksPanel').style.display='block'}async function startRegistration(){const count=parseInt(document.getElementById('regCount').value);const passwordPrefix=document.getElementById('passwordPrefix').value;const passwordLength=parseInt(document.getElementById('passwordLength').value);if(count<1||count>20){alert('注册数量必须在1-20之间');return}output.innerText='正在启动'+count+'个账号的批量注册...';disableButtons(true);try{const res=await fetch('/api/register',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({count:count,password_prefix:passwordPrefix,password_length:passwordLength})});const data=await res.json();if(data.success){currentTaskId=data.task_id;document.getElementById('taskId').value=data.task_id;output.innerText='注册任务已启动!\n任务ID: '+data.task_id+'\n预计时间: '+data.estimated_time+'\n\n正在监控进度...';document.getElementById('registrationProgress').style.display='block';startProgressMonitoring()}else{output.innerText='启动注册失败: '+data.error;disableButtons(false)}}catch(error){output.innerText='启动注册出错: '+error.message;disableButtons(false)}}async function startProgressMonitoring(){if(!currentTaskId)return;progressInterval=setInterval(async()=>{try{const res=await fetch('/api/registration/progress/'+currentTaskId);const data=await res.json();if(data.success){updateProgress(data);if(data.status==='completed'||data.status==='failed'){clearInterval(progressInterval);disableButtons(false);let resultText='\n🎉 注册任务'+(data.status==='completed'?'完成':'失败')+'!\n';resultText+='总计: '+data.progress.total+' | 成功: '+data.progress.completed+' | 失败: '+data.progress.failed+'\n\n';resultText+='📋 详细结果:\n';data.results.forEach(result=>{const status=result.status==='success'?'✅':'❌';resultText+=status+' 账号'+result.account_index+': '+result.email;if(result.error_message){resultText+=' - '+result.error_message}resultText+='\n'});output.innerText+=resultText}}}catch(error){console.error('获取进度失败:',error)}},2000)}function updateProgress(data){const percentage=data.progress.percentage||0;const total=data.progress.total;const completed=data.progress.completed;const failed=data.progress.failed;document.getElementById('progressBar').style.width=percentage+'%';let progressText='进度: '+(completed+failed)+'/'+total+' ('+percentage+'%)';if(completed>0)progressText+=' | ✅ 成功: '+completed;if(failed>0)progressText+=' | ❌ 失败: '+failed;progressText+=' | 状态: '+getStatusText(data.status);document.getElementById('progressText').innerText=progressText}function getStatusText(status){const statusMap={'running':'运行中','completed':'已完成','failed':'失败','cancelled':'已取消'};return statusMap[status]||status}async function cancelRegistration(){if(!currentTaskId){alert('没有正在运行的注册任务');return}if(!confirm('确定要取消任务 '+currentTaskId+' 吗?')){return}try{const res=await fetch('/api/registration/cancel/'+currentTaskId,{method:'DELETE'});const data=await res.json();if(data.success){clearInterval(progressInterval);output.innerText+='\n\n🛑 任务已取消\n取消时间: '+data.cancelled_at;disableButtons(false);currentTaskId=null}else{alert('取消任务失败: '+data.error)}}catch(error){alert('取消任务出错: '+error.message)}}async function getRegistrationTasks(){output.innerText='正在获取注册任务列表...';showTasksPanel();try{const res=await fetch('/api/registration/tasks');const data=await res.json();if(data.success){displayTasks(data.data);output.innerText='找到 '+data.total+' 个注册任务'}else{output.innerText='获取任务列表失败: '+data.error}}catch(error){output.innerText='获取任务列表出错: '+error.message}}function displayTasks(tasks){const tasksList=document.getElementById('tasksList');if(tasks.length===0){tasksList.innerHTML='<p style="text-align:center;color:#666">暂无注册任务</p>';return}let html='<div style="overflow-x:auto"><table style="width:100%;border-collapse:collapse">';html+='<thead><tr style="background:#f8f9fa">';html+='<th style="padding:10px;border:1px solid #ddd">任务ID</th>';html+='<th style="padding:10px;border:1px solid #ddd">状态</th>';html+='<th style="padding:10px;border:1px solid #ddd">总数</th>';html+='<th style="padding:10px;border:1px solid #ddd">成功</th>';html+='<th style="padding:10px;border:1px solid #ddd">失败</th>';html+='<th style="padding:10px;border:1px solid #ddd">创建时间</th>';html+='<th style="padding:10px;border:1px solid #ddd">操作</th>';html+='</tr></thead><tbody>';tasks.forEach(task=>{const statusClass=task.status==='completed'?'color:#10b981;':task.status==='failed'?'color:#ef4444;':task.status==='running'?'color:#f59e0b;':'color:#6b7280;';html+='<tr>';html+='<td style="padding:10px;border:1px solid #ddd;font-family:monospace">'+task.task_id+'</td>';html+='<td style="padding:10px;border:1px solid #ddd;'+statusClass+';font-weight:600">'+getStatusText(task.status)+'</td>';html+='<td style="padding:10px;border:1px solid #ddd;text-align:center">'+task.total_count+'</td>';html+='<td style="padding:10px;border:1px solid #ddd;text-align:center;color:#10b981">'+task.completed_count+'</td>';html+='<td style="padding:10px;border:1px solid #ddd;text-align:center;color:#ef4444">'+task.failed_count+'</td>';html+='<td style="padding:10px;border:1px solid #ddd">'+new Date(task.created_at).toLocaleString()+'</td>';html+='<td style="padding:10px;border:1px solid #ddd">';html+='<button onclick="viewTaskResults(\''+task.task_id+'\')" style="margin-right:5px;padding:5px 10px;border:1px solid #ddd;border-radius:4px;cursor:pointer">查看</button>';if(task.status==='running'){html+='<button onclick="cancelTask(\''+task.task_id+'\')" style="padding:5px 10px;border:1px solid #ef4444;border-radius:4px;cursor:pointer;color:#ef4444">取消</button>'}html+='</td></tr>'});html+='</tbody></table></div>';tasksList.innerHTML=html}async function viewTaskResults(taskId){try{const res=await fetch('/api/registration/progress/'+taskId);const data=await res.json();if(data.success){let resultText='\n📋 任务详情 - '+taskId+'\n';resultText+='状态: '+getStatusText(data.status)+'\n';resultText+='进度: '+data.progress.completed+'/'+data.progress.total+'\n\n';resultText+='📧 账号列表:\n';data.results.forEach(result=>{const status=result.status==='success'?'✅':'❌';resultText+=status+' '+result.email+'\n';if(result.token){resultText+=' Token: '+result.token.substring(0,30)+'...\n'}if(result.error_message){resultText+=' 错误: '+result.error_message+'\n'}});output.innerText=resultText}else{alert('获取任务结果失败: '+data.error)}}catch(error){alert('获取任务结果出错: '+error.message)}}async function cancelTask(taskId){if(!confirm('确定要取消任务 '+taskId+' 吗?')){return}try{const res=await fetch('/api/registration/cancel/'+taskId,{method:'DELETE'});const data=await res.json();if(data.success){alert('任务已取消');getRegistrationTasks()}else{alert('取消任务失败: '+data.error)}}catch(error){alert('取消任务出错: '+error.message)}}async function getRegistrationStats(){output.innerText='正在获取注册统计信息...';try{const res=await fetch('/api/registration/stats');const data=await res.json();if(data.success){const stats=data.data;let statsText='📈 MindVideo 注册统计\n\n';statsText+='📋 任务统计:\n';statsText+=' 总任务数: '+stats.tasks.total+'\n';statsText+=' 已完成: '+stats.tasks.completed+'\n';statsText+=' 失败: '+stats.tasks.failed+'\n';statsText+=' 运行中: '+stats.tasks.running+'\n';statsText+=' 成功率: '+stats.tasks.success_rate+'\n\n';statsText+='👤 账号统计:\n';statsText+=' 总账号数: '+stats.accounts.total+'\n';statsText+=' 成功注册: '+stats.accounts.successful+'\n';statsText+=' 注册失败: '+stats.accounts.failed+'\n';statsText+=' 成功率: '+stats.accounts.success_rate+'\n';output.innerText=statsText}else{output.innerText='获取统计信息失败: '+data.error}}catch(error){output.innerText='获取统计信息出错: '+error.message}}async function cleanupTasks(){if(!confirm('确定要清理过期任务吗?这将删除24小时前的未完成任务。')){return}output.innerText='正在清理过期任务...';try{const res=await fetch('/api/registration/cleanup',{method:'POST'});const data=await res.json();if(data.success){output.innerText='✅ '+data.message;getRegistrationTasks()}else{output.innerText='清理任务失败: '+data.error}}catch(error){output.innerText='清理任务出错: '+error.message}}window.addEventListener('load',getStats);</script></body></html>`); | |
| } else if (url.pathname === '/trigger') { | |
| const result = await processAllAccounts(); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } else if (url.pathname === '/stats') { | |
| const result = await getStats(); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } else if (url.pathname === '/api/register' && req.method === 'POST') { | |
| // 启动批量注册 | |
| try { | |
| let body = ''; | |
| req.on('data', chunk => body += chunk.toString()); | |
| req.on('end', async () => { | |
| try { | |
| const params = JSON.parse(body); | |
| const result = await registrationController.batchRegisterAccounts( | |
| params.count || 1, | |
| { | |
| passwordPrefix: params.password_prefix || 'Pwd', | |
| passwordLength: params.password_length || 12 | |
| } | |
| ); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| }); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else if (url.pathname.startsWith('/api/registration/progress/') && req.method === 'GET') { | |
| // 查询注册进度 | |
| try { | |
| const taskId = url.pathname.split('/').pop(); | |
| const result = await registrationController.getRegistrationProgress(taskId); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else if (url.pathname.startsWith('/api/registration/cancel/') && req.method === 'DELETE') { | |
| // 取消注册任务 | |
| try { | |
| const taskId = url.pathname.split('/').pop(); | |
| const result = await registrationController.cancelRegistrationTask(taskId); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else if (url.pathname === '/api/registration/tasks' && req.method === 'GET') { | |
| // 获取所有注册任务 | |
| try { | |
| const result = await registrationController.getAllRegistrationTasks(); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify(result)); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else if (url.pathname === '/api/registration/stats' && req.method === 'GET') { | |
| // 获取注册统计信息 | |
| try { | |
| const stats = await storage.getRegistrationStats(); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: true, data: stats })); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else if (url.pathname === '/api/registration/cleanup' && req.method === 'POST') { | |
| // 清理过期任务 | |
| try { | |
| await registrationController.cleanupExpiredTasks(); | |
| res.writeHead(200, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: true, message: '过期任务清理完成' })); | |
| } catch (error) { | |
| res.writeHead(400, { 'Content-Type': 'application/json' }); | |
| res.end(JSON.stringify({ success: false, error: error.message })); | |
| } | |
| } else { | |
| res.writeHead(404); | |
| res.end('Not Found'); | |
| } | |
| }); | |
| server.listen(CONFIG.PORT, () => { | |
| console.log(`✅ 服务器运行在端口 ${CONFIG.PORT}`); | |
| console.log(`📱 访问 http://localhost:${CONFIG.PORT} 查看控制面板`); | |
| if (supabase.enabled) { | |
| console.log(`💾 存储方式: Supabase 数据库`); | |
| } | |
| }); |