// 默认自定义模型配置(OpenAI 兼容接口) const DEFAULT_AI = { apiBaseUrl: 'https://ai.88531.cn/v1', model: 'mimo-v2.6-flash' }; // 弹窗界面功能 class AutoCommentManager { constructor() { this.init(); } async init() { await this.loadClickMode(); // 加载点击模式设置 await this.loadDelayConfig(); // 加载评论间隔设置 await this.loadRegion(); // 加载按地区搜索设置 await this.loadApiKey(); // 加载API密钥 await this.loadModelConfig(); // 加载自定义模型配置 await this.loadCommentMode(); // 加载评论方式(固定评论池 / 自定义文字 / AI) await this.loadAiPrompt(); // 加载AI提示词 await this.loadUserComments(); // 加载自定义文字库 this.bindEvents(); this.updateAiStatus(); this.loadCommentLogs(); // 加载评论记录 this.initAuth(); // 授权状态 / 公告 / 更新提示 // 小红书页面记录新评论后自动刷新列表 chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace === 'local' && changes.commentLogs) { this.renderCommentLogs(changes.commentLogs.newValue || []); } }); } bindEvents() { // 搜索功能 document.getElementById('search-btn').addEventListener('click', () => { this.searchXiaohongshu(); }); document.getElementById('keyword-input').addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.searchXiaohongshu(); } }); // 按地区搜索 const regionSelect = document.getElementById('region-select'); if (regionSelect) { regionSelect.addEventListener('change', (e) => { this.onRegionChange(e.target.value); }); } const regionCustom = document.getElementById('region-custom'); if (regionCustom) { regionCustom.addEventListener('input', (e) => { this.saveRegion({ regionCustom: e.target.value }); }); regionCustom.addEventListener('keypress', (e) => { if (e.key === 'Enter') { this.searchXiaohongshu(); } }); } // 自动点击功能 document.getElementById('auto-click-btn').addEventListener('click', () => { this.triggerAutoClick(); }); // 测试元素检测功能 document.getElementById('test-detection-btn').addEventListener('click', () => { this.testElementDetection(); }); // 点击模式选择器 document.getElementById('expand-content').addEventListener('change', (e) => { this.saveClickMode(e.target.checked); }); // 评论间隔:输入即保存 ['delay-min', 'delay-max'].forEach((id) => { const input = document.getElementById(id); if (input) { input.addEventListener('input', () => { this.saveDelayConfig(); }); } }); // 评论方式选择(固定评论池 / 自定义文字 / 图片 / AI 按笔记生成) document.querySelectorAll('input[name="comment-mode"]').forEach((radio) => { radio.addEventListener('change', (e) => { this.saveCommentMode(e.target.value); }); }); // 自定义文字库:输入即保存 const userCommentsArea = document.getElementById('user-comments'); if (userCommentsArea) { userCommentsArea.addEventListener('input', () => { this.saveUserComments(); }); } const saveUserCommentsBtn = document.getElementById('save-user-comments-btn'); if (saveUserCommentsBtn) { saveUserCommentsBtn.addEventListener('click', () => { this.saveUserComments(true); }); } // 评论记录:手动刷新 const refreshLogBtn = document.getElementById('refresh-log-btn'); if (refreshLogBtn) { refreshLogBtn.addEventListener('click', () => { this.loadCommentLogs(); }); } // 授权:卡密激活 const activateBtn = document.getElementById('activate-btn'); if (activateBtn) { activateBtn.addEventListener('click', () => { this.activateCard(); }); } const cardInput = document.getElementById('card-key-input'); if (cardInput) { cardInput.addEventListener('keypress', (e) => { if (e.key === 'Enter') this.activateCard(); }); } // 授权:购买(展开支付方式选择) const buyBtn = document.getElementById('buy-btn'); if (buyBtn) { buyBtn.addEventListener('click', () => { const row = document.getElementById('pay-select-row'); if (row) row.hidden = !row.hidden; }); } const payCancelBtn = document.getElementById('pay-cancel-btn'); if (payCancelBtn) { payCancelBtn.addEventListener('click', () => { const row = document.getElementById('pay-select-row'); if (row) row.hidden = true; }); } document.querySelectorAll('.pay-btn[data-pay]').forEach((btn) => { btn.addEventListener('click', () => { this.startBuyFlow(btn.dataset.pay); }); }); // API密钥配置 document.getElementById('api-key').addEventListener('input', (e) => { this.saveApiKey(e.target.value); this.updateAiStatus(); }); // 自定义模型:API地址与模型名,输入即保存 const baseUrlInput = document.getElementById('api-base-url'); if (baseUrlInput) { baseUrlInput.addEventListener('input', (e) => { this.saveModelConfig({ apiBaseUrl: e.target.value }); }); baseUrlInput.addEventListener('change', () => this.updateAiStatus()); } const modelInput = document.getElementById('model-name'); if (modelInput) { modelInput.addEventListener('input', (e) => { this.saveModelConfig({ modelName: e.target.value }); }); modelInput.addEventListener('change', () => this.updateAiStatus()); } // 测试AI按钮 document.getElementById('test-ai-btn').addEventListener('click', () => { this.testAiGeneration(); }); // AI提示词保存 const savePromptBtn = document.getElementById('save-prompt-btn'); if (savePromptBtn) { savePromptBtn.addEventListener('click', () => { this.saveAiPrompt(); }); } // AI提示词恢复默认 const resetPromptBtn = document.getElementById('reset-prompt-btn'); if (resetPromptBtn) { resetPromptBtn.addEventListener('click', () => { this.resetAiPromptToDefault(); }); } } async searchXiaohongshu() { if (!(await this.isFeatureAllowed())) return; const keyword = document.getElementById('keyword-input').value.trim(); if (!keyword) { alert('请输入搜索关键词'); return; } // 按地区搜索:地区拼进关键词(如「咖啡 上海」) const region = this.getSearchRegion(); const finalKeyword = region ? `${keyword} ${region}` : keyword; // 构建小红书搜索URL const encodedKeyword = encodeURIComponent(finalKeyword); const searchUrl = `https://www.xiaohongshu.com/search_result?keyword=${encodedKeyword}`; console.log('准备打开搜索页面:', searchUrl); // 在新标签页打开搜索结果 chrome.tabs.create({ url: searchUrl }, (tab) => { console.log('新标签页已创建:', tab.id); if (chrome.runtime.lastError) { console.error('创建标签页失败:', chrome.runtime.lastError); return; } // 等待页面加载完成后发送自动点击消息 chrome.tabs.onUpdated.addListener(function listener(tabId, changeInfo) { console.log('标签页更新:', tabId, changeInfo); if (tabId === tab.id && changeInfo.status === 'complete') { console.log('页面加载完成,准备发送自动点击消息'); // 页面加载完成,延迟一下确保DOM完全渲染 setTimeout(() => { console.log('发送自动点击消息到标签页:', tab.id); chrome.tabs.sendMessage(tab.id, { action: 'autoClickNotes', keyword: finalKeyword }, (response) => { if (chrome.runtime.lastError) { console.error('发送消息失败:', chrome.runtime.lastError); } else { console.log('消息发送成功,响应:', response); } }); }, 3000); // 增加延迟时间 // 移除监听器 chrome.tabs.onUpdated.removeListener(listener); } }); }); // 清空输入框 document.getElementById('keyword-input').value = ''; // 侧边栏常驻不关闭,改为在面板内显示状态 this.showActionStatus(true, region ? `✓ 已按地区「${region}」打开搜索页,稍后自动开始评论流程` : '✓ 已打开搜索页,稍后自动开始评论流程'); } // 获取当前选择的地区(空字符串 = 不限) getSearchRegion() { const select = document.getElementById('region-select'); if (!select) return ''; if (select.value === '__custom__') { return (document.getElementById('region-custom')?.value || '').trim(); } return select.value; } // 地区下拉切换(含自定义输入框显隐) onRegionChange(value) { const customInput = document.getElementById('region-custom'); const isCustom = value === '__custom__'; if (customInput) { customInput.hidden = !isCustom; if (isCustom) customInput.focus(); } this.saveRegion({ regionSelectVal: value }); } // 保存地区设置 async saveRegion(changes) { try { await chrome.storage.local.set(changes); console.log('保存地区设置:', Object.keys(changes).join(', ')); } catch (error) { console.error('保存地区设置失败:', error); } } // 加载地区设置 async loadRegion() { try { const result = await chrome.storage.local.get(['regionSelectVal', 'regionCustom']); const select = document.getElementById('region-select'); const customInput = document.getElementById('region-custom'); if (!select) return; const saved = result.regionSelectVal || ''; const hasOption = Array.from(select.options).some((o) => o.value === saved); const value = hasOption ? saved : ''; select.value = value; const isCustom = value === '__custom__'; if (customInput) { customInput.value = result.regionCustom || ''; customInput.hidden = !isCustom; } console.log('加载地区设置:', value, result.regionCustom || ''); } catch (error) { console.error('加载地区设置失败:', error); } } // 面板内联状态提示(替代弹窗关闭/alert) showActionStatus(ok, text) { const el = document.getElementById('action-status'); if (!el) return; el.textContent = text; el.className = 'action-status ' + (ok ? 'ok' : 'fail'); } // 手动触发自动点击功能 async triggerAutoClick() { try { if (!(await this.isFeatureAllowed())) return; // 获取当前活动标签页 const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab) { alert('无法获取当前标签页'); return; } // 检查是否在小红书页面 if (!tab.url || !tab.url.includes('xiaohongshu.com')) { alert('请在小红书页面上使用此功能'); return; } // 发送自动点击消息 await chrome.tabs.sendMessage(tab.id, { action: 'autoClickNotes' }); this.showActionStatus(true, '✓ 已开始自动点击笔记'); } catch (error) { console.error('触发自动点击失败:', error); this.showActionStatus(false, '✗ 触发失败: ' + error.message); } } // 测试元素检测功能 async testElementDetection() { try { // 获取当前活动标签页 const [tab] = await chrome.tabs.query({ active: true, currentWindow: true }); if (!tab) { alert('无法获取当前标签页'); return; } // 检查是否在小红书页面 if (!tab.url || !tab.url.includes('xiaohongshu.com')) { alert('请在小红书页面上使用此功能'); return; } console.log('发送测试元素检测消息到标签页:', tab.id); // 发送测试消息 const response = await chrome.tabs.sendMessage(tab.id, { action: 'testElementDetection' }); if (response && response.success) { let message = `元素检测结果:\n\n主要选择器: ${response.primarySelector}\n主要选择器找到: ${response.primaryElementsCount} 个元素\n\n总元素: ${response.totalElements}\n可见元素: ${response.visibleElements}`; // 添加第一个元素的详细分析 if (response.firstElementAnalysis) { const analysis = response.firstElementAnalysis; message += `\n\n第一个元素分析:\n- 标签: ${analysis.tagName}.${analysis.className}`; message += `\n- 笔记索引: ${analysis.noteIndex || '未知'}`; message += `\n- 选择器: ${analysis.selector || '未知'}`; // 显示目标链接检测结果 if (analysis.targetLink) { if (analysis.targetLink.found) { message += `\n- 目标链接: ✅ 已找到`; message += `\n - 链接: ${analysis.targetLink.href}`; message += `\n - 文本: ${analysis.targetLink.text}`; message += `\n - 类名: ${analysis.targetLink.className}`; } else { message += `\n- 目标链接: ❌ ${analysis.targetLink.message}`; } } if (analysis.links && analysis.links.length > 0) { message += `\n- 所有链接 (${analysis.links.length}个):`; analysis.links.forEach((link, index) => { message += `\n ${index + 1}. ${link.text} (${link.href})`; }); } if (analysis.buttons && analysis.buttons.length > 0) { message += `\n- 按钮 (${analysis.buttons.length}个):`; analysis.buttons.forEach((btn, index) => { message += `\n ${index + 1}. ${btn.text} (${btn.className})`; }); } if (analysis.clickableDivs && analysis.clickableDivs.length > 0) { message += `\n- 可点击div (${analysis.clickableDivs.length}个):`; analysis.clickableDivs.forEach((div, index) => { message += `\n ${index + 1}. ${div.text} (${div.className})`; }); } } message += `\n\n前5个元素详情:\n${response.elementDetails.map(el => `- ${el.tagName}.${el.className} (链接:${el.hasLink}, 图片:${el.hasImage}, 文本长度:${el.textLength})` ).join('\n')}`; alert(message); } else { alert('测试失败,请检查控制台'); } } catch (error) { console.error('测试元素检测失败:', error); alert('测试失败: ' + error.message); } } // 保存用户选择的点击模式 async saveClickMode(shouldExpand) { try { await chrome.storage.local.set({ expandContent: shouldExpand }); console.log('保存点击模式:', shouldExpand); } catch (error) { console.error('保存点击模式失败:', error); } } // 加载用户选择的点击模式 async loadClickMode() { try { const result = await chrome.storage.local.get(['expandContent']); const shouldExpand = result.expandContent !== false; // 默认展开内容 document.getElementById('expand-content').checked = shouldExpand; console.log('加载点击模式:', shouldExpand); } catch (error) { console.error('加载点击模式失败:', error); } } // 加载评论间隔(分钟,默认 1~2 分钟随机) async loadDelayConfig() { try { const result = await chrome.storage.local.get(['commentDelayMin', 'commentDelayMax']); const min = Number(result.commentDelayMin); const max = Number(result.commentDelayMax); const minInput = document.getElementById('delay-min'); const maxInput = document.getElementById('delay-max'); if (minInput) minInput.value = Number.isFinite(min) && min >= 0 ? min : 1; if (maxInput) maxInput.value = Number.isFinite(max) && max >= 0 ? max : 2; console.log('加载评论间隔:', minInput?.value, '~', maxInput?.value, '分钟'); } catch (error) { console.error('加载评论间隔失败:', error); } } // 保存评论间隔 async saveDelayConfig() { try { const minInput = document.getElementById('delay-min'); const maxInput = document.getElementById('delay-max'); let min = Number(minInput?.value); let max = Number(maxInput?.value); if (!Number.isFinite(min) || min < 0) min = 1; if (!Number.isFinite(max) || max < 0) max = 2; if (max < min) { // 自动纠正顺序 [min, max] = [max, min]; if (minInput) minInput.value = min; if (maxInput) maxInput.value = max; } await chrome.storage.local.set({ commentDelayMin: min, commentDelayMax: max }); console.log('保存评论间隔:', min, '~', max, '分钟'); } catch (error) { console.error('保存评论间隔失败:', error); } } // 保存API密钥 async saveApiKey(apiKey) { try { await chrome.storage.local.set({ apiKey: apiKey }); console.log('保存API密钥:', apiKey); } catch (error) { console.error('保存API密钥失败:', error); } } // 加载API密钥 async loadApiKey() { try { const result = await chrome.storage.local.get(['apiKey']); const apiKey = result.apiKey || ''; // 默认API密钥 document.getElementById('api-key').value = apiKey; console.log('加载API密钥:', apiKey); } catch (error) { console.error('加载API密钥失败:', error); } } // 加载评论方式(custom / text / ai) async loadCommentMode() { try { const result = await chrome.storage.local.get(['commentMode']); const validModes = ['custom', 'text', 'ai']; const mode = validModes.includes(result.commentMode) ? result.commentMode : 'custom'; if (result.commentMode && result.commentMode !== mode) { // 旧版本残留的 image 模式已移除,回退到固定评论池并写回 await chrome.storage.local.set({ commentMode: mode }); } const radio = document.getElementById(`mode-${mode}`); if (radio) radio.checked = true; this.updateModePanels(mode); await this.updateModeHint(mode); } catch (error) { console.error('加载评论方式失败:', error); } } // 保存评论方式(写入后已打开的小红书页面会立即切换) async saveCommentMode(mode) { try { await chrome.storage.local.set({ commentMode: mode }); console.log('保存评论方式:', mode); this.updateModePanels(mode); await this.updateModeHint(mode); } catch (error) { console.error('保存评论方式失败:', error); } } // 根据评论方式显示/隐藏对应的配置面板 updateModePanels(mode) { const textPanel = document.getElementById('text-comment-panel'); if (textPanel) textPanel.style.display = mode === 'text' ? 'block' : 'none'; } // 加载自定义文字库 async loadUserComments() { try { const result = await chrome.storage.local.get(['userComments']); const lines = result.userComments || []; const textarea = document.getElementById('user-comments'); if (textarea) textarea.value = lines.join('\n'); this.updateUserCommentsCount(lines.length); console.log('加载自定义文字库:', lines.length, '条'); } catch (error) { console.error('加载自定义文字库失败:', error); } } // 保存自定义文字库(一行一条) async saveUserComments(showFeedback = false) { try { const textarea = document.getElementById('user-comments'); const lines = (textarea?.value || '') .split('\n') .map((line) => line.trim()) .filter((line) => line.length > 0); await chrome.storage.local.set({ userComments: lines }); this.updateUserCommentsCount(lines.length); console.log('保存自定义文字库:', lines.length, '条'); if (showFeedback) { this.showActionStatus(true, `✓ 文字库已保存,共 ${lines.length} 条`); } } catch (error) { console.error('保存自定义文字库失败:', error); if (showFeedback) { this.showActionStatus(false, '✗ 文字库保存失败: ' + error.message); } } } // 更新文字库条数显示 updateUserCommentsCount(count) { const el = document.getElementById('user-comments-count'); if (el) el.textContent = count > 0 ? `共 ${count} 条` : '暂无内容'; } // ===== 授权系统(CardAuth) ===== // 功能门槛:授权 / 试用期 / 到期降级(每日 3 条)都可开始;降级额度用完才拦 async isFeatureAllowed() { try { const status = await chrome.runtime.sendMessage({ action: 'authStatus' }); const s = status && status.status ? status.status : null; if (!s) return true; // 拿不到状态时放行,不锁死 if (s.state === 'authorized' || s.state === 'trial') return true; if (s.state === 'grace') { if (s.dayLeft > 0) { this.showActionStatus(true, `体验模式:今日还剩 ${s.dayLeft}/${s.graceDaily} 条,用完明天恢复`); return true; } this.showActionStatus(false, `✗ 今日 ${s.graceDaily} 条体验额度已用完(明天恢复),购买授权解锁无限量`); return false; } return true; } catch (error) { // 后台不可达时不阻断功能 console.error('授权状态检查失败:', error); return true; } } // 初始化授权区:状态 / 公告 async initAuth() { await this.renderAuthStatus(); await this.loadAnnouncements(); // 优惠码复制 const copyBtn = document.getElementById('coupon-copy-btn'); if (copyBtn) { copyBtn.addEventListener('click', async () => { const codeEl = document.getElementById('coupon-code'); const code = (codeEl ? codeEl.textContent : '').trim(); if (!code) return; try { await navigator.clipboard.writeText(code); } catch (error) { const ta = document.createElement('textarea'); ta.value = code; document.body.appendChild(ta); ta.select(); document.execCommand('copy'); ta.remove(); } this.showActionStatus(true, `✓ 优惠码 ${code} 已复制,点「购买授权」下单自动抵扣`); }); } // 后台复验或购买激活后自动刷新 chrome.storage.onChanged.addListener((changes, namespace) => { if (namespace !== 'local') return; if (changes.licenseState || changes.trialUsage) this.renderAuthStatus(); if (changes.announcements) this.renderAnnouncements((changes.announcements.newValue || {}).list || []); }); } // 渲染授权状态徽章 async renderAuthStatus() { const badge = document.getElementById('auth-badge'); const detail = document.getElementById('auth-detail'); if (!badge || !detail) return; try { const status = await chrome.runtime.sendMessage({ action: 'authStatus' }); const s = status && status.status ? status.status : { state: 'locked' }; badge.classList.remove('authorized', 'trial', 'locked', 'grace'); this.stopTrialCountdown(); if (s.state === 'authorized') { badge.classList.add('authorized'); badge.textContent = '已授权'; const license = s.license || {}; if (license.isPermanent) { detail.textContent = '永久授权 · 卡密 ' + this.maskCardKey(license.cardKey); } else if (license.expireTime) { const days = license.remainingDays != null ? ` · 剩余 ${license.remainingDays} 天` : ''; detail.textContent = `到期 ${license.expireTime}${days}`; } else { detail.textContent = '授权有效'; } this.hideCouponBar(); } else if (s.state === 'trial') { badge.classList.add('trial'); badge.textContent = '试用中'; this.hideCouponBar(); this.startTrialCountdown(s.trialEndAt || (Date.now() + 3600000), s); } else if (s.state === 'grace') { badge.classList.add('grace'); badge.textContent = '体验模式'; detail.textContent = `试用已到期 · 今日剩 ${s.dayLeft}/${s.graceDaily} 条(每天恢复)` + (s.promo && s.promo.active ? ` · 限时优惠码 ${s.promo.code}` : ''); this.renderCouponBar(s.promo); } else { badge.classList.add('locked'); badge.textContent = '未授权'; detail.textContent = '授权状态异常,请重新打开侧边栏'; this.hideCouponBar(); } } catch (error) { console.error('获取授权状态失败:', error); badge.classList.add('locked'); badge.textContent = '未知'; detail.textContent = '授权状态获取失败,请重新打开侧边栏'; } } // 试用倒计时(精确到秒,每秒刷新) startTrialCountdown(endAt, status) { this.stopTrialCountdown(); const quotaLeft = status && status.quotaLeft != null ? status.quotaLeft : null; const tick = () => { const remain = endAt - Date.now(); const detail = document.getElementById('auth-detail'); if (remain <= 0) { this.stopTrialCountdown(); this.renderAuthStatus(); // 到期后刷新为降级体验状态 return; } if (detail) { const quotaText = quotaLeft != null ? ` · 额度剩 ${quotaLeft} 条` : ''; detail.textContent = `免费试用剩余 ${this.formatRemain(remain)}${quotaText} · 到期后每天仍可体验 3 条`; } }; tick(); this.trialTimer = setInterval(tick, 1000); } stopTrialCountdown() { if (this.trialTimer) { clearInterval(this.trialTimer); this.trialTimer = null; } } // ===== 到期 24 小时限时优惠条 ===== renderCouponBar(promo) { const bar = document.getElementById('coupon-bar'); if (!bar) return; if (!promo || !promo.active) { this.hideCouponBar(); return; } const codeEl = document.getElementById('coupon-code'); const pctEl = document.getElementById('coupon-percent'); if (codeEl) codeEl.textContent = promo.code; if (pctEl) pctEl.textContent = `${100 - promo.percent} 折(立减 ${promo.percent}%)`; bar.hidden = false; this.promoFetchedAt = Date.now(); this.promoLeftMs = promo.promoLeftMs; this.stopCouponTimer(); const tick = () => { const left = this.promoLeftMs - (Date.now() - this.promoFetchedAt); const leftEl = document.getElementById('coupon-left'); if (left <= 0) { this.hideCouponBar(); this.renderAuthStatus(); return; } if (leftEl) leftEl.textContent = this.formatRemain(left); }; tick(); this.couponTimer = setInterval(tick, 1000); } hideCouponBar() { const bar = document.getElementById('coupon-bar'); if (bar) bar.hidden = true; this.stopCouponTimer(); } stopCouponTimer() { if (this.couponTimer) { clearInterval(this.couponTimer); this.couponTimer = null; } } // 毫秒 → HH:MM:SS formatRemain(ms) { const totalSec = Math.max(0, Math.floor(ms / 1000)); const pad = (n) => String(n).padStart(2, '0'); return `${pad(Math.floor(totalSec / 3600))}:${pad(Math.floor((totalSec % 3600) / 60))}:${pad(totalSec % 60)}`; } maskCardKey(key) { if (!key) return ''; const k = String(key); return k.length > 10 ? k.slice(0, 8) + '****' + k.slice(-4) : k; } // 手动输入卡密激活 async activateCard() { const input = document.getElementById('card-key-input'); const btn = document.getElementById('activate-btn'); const cardKey = (input?.value || '').trim(); if (!cardKey) { this.showOrderStatus(false, '请输入卡密'); return; } if (btn) { btn.disabled = true; btn.textContent = '激活中…'; } this.showOrderStatus(true, '⏳ 正在验证卡密…'); try { const res = await chrome.runtime.sendMessage({ action: 'authActivate', cardKey }); if (res && res.ok && res.licenseState && res.licenseState.valid) { input.value = ''; this.showOrderStatus(true, `✓ 激活成功!${res.licenseState.isPermanent ? '永久授权' : '到期 ' + res.licenseState.expireTime}`); await this.renderAuthStatus(); } else { const msg = (res && res.licenseState && res.licenseState.message) || (res && res.message) || '激活失败'; this.showOrderStatus(false, '✗ ' + msg); } } catch (error) { this.showOrderStatus(false, '✗ 激活失败: ' + error.message); } finally { if (btn) { btn.disabled = false; btn.textContent = '激活'; } } } // 购买流程:下单(带机器码)→ 打开支付页 → 轮询订单 → 自动发卡激活 async startBuyFlow(payType) { const payRow = document.getElementById('pay-select-row'); if (payRow) payRow.hidden = true; this.showOrderStatus(true, '⏳ 正在创建订单…'); try { const res = await chrome.runtime.sendMessage({ action: 'authCreateOrder', payType }); if (!res || !res.ok) { this.showOrderStatus(false, '✗ 创建订单失败: ' + ((res && res.message) || '未知错误')); return; } const order = res.order; const priceText = order.couponCode ? `¥${order.amount},原价 ¥${order.originalPrice},已用优惠码 ${order.couponCode}` : `¥${order.amount}`; this.showOrderStatus(true, `订单 ${order.orderNo}(${priceText})已创建,请在新页面完成支付,支付成功后自动激活…`); if (res.payUrl) { chrome.tabs.create({ url: res.payUrl }); } this.startOrderPolling(order.orderNo); } catch (error) { this.showOrderStatus(false, '✗ 购买失败: ' + error.message); } } // 轮询订单:支付+自动发卡后自动激活 startOrderPolling(orderNo) { if (this.orderPollTimer) { clearInterval(this.orderPollTimer); this.orderPollTimer = null; } let attempts = 0; const maxAttempts = 120; // 5秒 × 120 = 最多10分钟 this.orderPollTimer = setInterval(async () => { attempts++; if (attempts > maxAttempts) { clearInterval(this.orderPollTimer); this.orderPollTimer = null; this.showOrderStatus(false, `✗ 等待支付超时。订单号 ${orderNo},支付后可凭订单号到购买中心查询,或用收到的卡密在此激活`); return; } try { const res = await chrome.runtime.sendMessage({ action: 'authQueryOrder', orderNo }); if (!res || !res.ok) return; if (res.activated) { clearInterval(this.orderPollTimer); this.orderPollTimer = null; this.showOrderStatus(true, `✓ 支付成功,已自动发卡并激活生效!${res.expireTime ? '到期 ' + res.expireTime : ''}`); await this.renderAuthStatus(); const input = document.getElementById('card-key-input'); if (input && res.cardKey) input.value = res.cardKey; } else if (res.paid && res.cardKey && !res.activated) { clearInterval(this.orderPollTimer); this.orderPollTimer = null; this.showOrderStatus(false, '✗ 已支付但激活失败: ' + ((res.licenseState && res.licenseState.message) || '请手动输入卡密激活')); } else if (attempts % 6 === 0) { this.showOrderStatus(true, `⏳ 等待支付中…(订单 ${orderNo},已等待 ${attempts * 5} 秒)`); } } catch (error) { console.error('订单轮询失败:', error); } }, 5000); } showOrderStatus(ok, text) { const el = document.getElementById('order-status'); if (!el) return; el.textContent = text; el.className = 'order-status ' + (ok ? 'ok' : 'fail'); } // 拉取并渲染公告 async loadAnnouncements() { try { const res = await chrome.runtime.sendMessage({ action: 'authGetAnnouncements' }); const ann = res && res.announcements ? res.announcements : null; const cached = ann || (await chrome.storage.local.get(['announcements'])).announcements; if (cached) { this.renderAnnouncements(cached.list || []); await this.maybeShowPopupAnnouncements(cached.popup || []); } } catch (error) { console.error('加载公告失败:', error); } } renderAnnouncements(list) { const box = document.getElementById('announcement-box'); const container = document.getElementById('announcement-list'); if (!box || !container) return; if (!list || list.length === 0) { box.hidden = true; return; } box.hidden = false; container.innerHTML = ''; list.slice(0, 10).forEach((item) => { const div = document.createElement('div'); div.className = 'announcement-item'; if (item.created_at) { const time = document.createElement('span'); time.className = 'announcement-time'; time.textContent = String(item.created_at).slice(0, 16); div.appendChild(time); } // 后台字段:title + content if (item.title) { const strong = document.createElement('b'); strong.textContent = item.title; div.appendChild(strong); div.appendChild(document.createElement('br')); } div.appendChild(document.createTextNode(item.content || '')); container.appendChild(div); }); } // 弹窗公告:每条只弹一次 async maybeShowPopupAnnouncements(popupList) { if (!popupList || popupList.length === 0) return; const result = await chrome.storage.local.get(['seenPopupAnnouncements']); const seen = result.seenPopupAnnouncements || []; const unseen = popupList.filter((item) => !seen.includes(String(item.id))); if (unseen.length === 0) return; const texts = unseen.map((item) => { const title = item.title || ''; const content = item.content || ''; return [title, content].filter(Boolean).join('\n'); }).filter(Boolean); if (texts.length > 0) { alert('📢 公告\n\n' + texts.join('\n\n—\n\n')); } await chrome.storage.local.set({ seenPopupAnnouncements: [...seen, ...unseen.map((item) => String(item.id))] }); } // ===== 评论记录 ===== // 从存储加载评论记录 async loadCommentLogs() { try { const result = await chrome.storage.local.get(['commentLogs']); this.renderCommentLogs(result.commentLogs || []); } catch (error) { console.error('加载评论记录失败:', error); } } // 计算今日/本月/总计统计 computeLogStats(logs) { const now = new Date(); const todayKey = `${now.getFullYear()}-${now.getMonth()}-${now.getDate()}`; const monthKey = `${now.getFullYear()}-${now.getMonth()}`; let today = 0; let month = 0; logs.forEach((record) => { const d = new Date(record.timestamp); if (`${d.getFullYear()}-${d.getMonth()}-${d.getDate()}` === todayKey) today++; if (`${d.getFullYear()}-${d.getMonth()}` === monthKey) month++; }); return { today, month, total: logs.length }; } // 渲染统计 + 列表 renderCommentLogs(logs) { const stats = this.computeLogStats(logs); const setStat = (id, value) => { const el = document.getElementById(id); if (el) el.textContent = value; }; setStat('stat-today', stats.today); setStat('stat-month', stats.month); setStat('stat-total', stats.total); const list = document.getElementById('log-list'); if (!list) return; if (!logs || logs.length === 0) { list.innerHTML = '
暂无评论记录,成功评论后会自动出现在这里
'; return; } const modeLabels = { custom: '固定池', text: '文字', image: '图片', ai: 'AI' }; list.innerHTML = ''; logs.slice(0, 100).forEach((record) => { const item = document.createElement('div'); item.className = 'log-item' + (record.url ? '' : ' no-url'); item.title = record.url ? '点击打开笔记' : '无笔记链接'; if (record.thumb) { const thumb = document.createElement('img'); thumb.className = 'log-thumb'; thumb.src = record.thumb; item.appendChild(thumb); } const body = document.createElement('div'); body.className = 'log-item-body'; const title = document.createElement('div'); title.className = 'log-item-title'; title.textContent = record.title || '无标题笔记'; const comment = document.createElement('div'); comment.className = 'log-item-comment'; comment.textContent = record.comment || ''; const meta = document.createElement('div'); meta.className = 'log-item-meta'; const time = new Date(record.timestamp); const pad = (n) => String(n).padStart(2, '0'); const tag = document.createElement('span'); tag.className = 'log-mode-tag'; tag.textContent = modeLabels[record.mode] || record.mode || ''; meta.appendChild(tag); meta.appendChild(document.createTextNode( `${time.getFullYear()}-${pad(time.getMonth() + 1)}-${pad(time.getDate())} ${pad(time.getHours())}:${pad(time.getMinutes())}` )); body.appendChild(title); body.appendChild(comment); body.appendChild(meta); item.appendChild(body); if (record.url) { const openIcon = document.createElement('span'); openIcon.className = 'log-open-icon'; openIcon.textContent = '↗'; item.appendChild(openIcon); item.addEventListener('click', () => { chrome.tabs.create({ url: record.url }); }); } list.appendChild(item); }); } // 提示当前配置是否真正生效 async updateModeHint(mode) { const hint = document.getElementById('mode-hint'); if (!hint) return; const aiRadio = document.getElementById('mode-ai'); const current = mode || (aiRadio && aiRadio.checked ? 'ai' : 'custom'); const keyInput = document.getElementById('api-key'); const hasKey = !!(keyInput && keyInput.value && keyInput.value.trim()); let storageInfo = {}; try { storageInfo = await chrome.storage.local.get(['userComments']); } catch (error) { console.error('读取提示信息失败:', error); } const textCount = (storageInfo.userComments || []).length; hint.classList.remove('warn'); if (current === 'ai' && !hasKey) { hint.textContent = '⚠ AI 模式需要先填 API 密钥,否则会退回固定评论'; hint.classList.add('warn'); } else if (current === 'text' && textCount === 0) { hint.textContent = '⚠ 自定义文字模式还没有文字内容,请在下方填写并保存'; hint.classList.add('warn'); } else if (current === 'custom' && hasKey) { hint.textContent = '⚠ 当前是固定评论池,下方模型配置不会生效;要按笔记生成请点「AI 按笔记生成」'; hint.classList.add('warn'); } else if (current === 'ai') { hint.textContent = '✓ 每篇笔记都会调用下方模型,按笔记内容生成评论'; } else if (current === 'text') { hint.textContent = `✓ 每篇笔记发送一条自定义文字(共 ${textCount} 条循环)`; } else { hint.textContent = '固定评论池 = 循环发预设短评;下方模型配置暂不生效'; } } // 加载自定义模型配置(API地址 + 模型名) async loadModelConfig() { try { const result = await chrome.storage.local.get(['apiBaseUrl', 'modelName']); const baseUrl = (result.apiBaseUrl || '').trim() || DEFAULT_AI.apiBaseUrl; const modelName = (result.modelName || '').trim() || DEFAULT_AI.model; const baseUrlInput = document.getElementById('api-base-url'); const modelInput = document.getElementById('model-name'); if (baseUrlInput) baseUrlInput.value = baseUrl; if (modelInput) modelInput.value = modelName; // 首次使用时写入默认值 if (!result.apiBaseUrl || !result.modelName) { await chrome.storage.local.set({ apiBaseUrl: baseUrl, modelName: modelName }); } console.log('加载模型配置:', baseUrl, modelName); } catch (error) { console.error('加载模型配置失败:', error); } } // 保存自定义模型配置 async saveModelConfig(changes) { try { await chrome.storage.local.set(changes); console.log('保存模型配置:', Object.keys(changes).join(', ')); } catch (error) { console.error('保存模型配置失败:', error); } } // 加载AI提示词 async loadAiPrompt() { try { const result = await chrome.storage.local.get(['aiPrompt']); const defaultPrompt = '根据笔记内容来生成符合笔记的评论。最后只需要输出评论内容,不要输出你的推理,不要输出任何与评论无关的内容,不要输出#相关的内容,不要输出笔记关键词相关的内容。评论不要超过300字。这个是小红书的笔记内容:\n\n{{content}}\n\n'; const prompt = result.aiPrompt || defaultPrompt; const textarea = document.getElementById('ai-prompt'); if (textarea) textarea.value = prompt; console.log('加载AI提示词'); } catch (error) { console.error('加载AI提示词失败:', error); } } // 保存AI提示词 async saveAiPrompt() { try { const textarea = document.getElementById('ai-prompt'); const prompt = (textarea?.value || '').trim(); await chrome.storage.local.set({ aiPrompt: prompt }); console.log('保存AI提示词'); alert('提示词已保存'); } catch (error) { console.error('保存AI提示词失败:', error); alert('保存提示词失败: ' + error.message); } } // 恢复默认AI提示词 async resetAiPromptToDefault() { try { const defaultPrompt = '根据笔记内容来生成符合笔记的评论。最后只需要输出评论内容,不要输出你的推理,不要输出任何与评论无关的内容,不要输出#相关的内容,不要输出笔记关键词相关的内容。评论不要超过300字。这个是小红书的笔记内容:\n\n{{content}}\n\n'; await chrome.storage.local.set({ aiPrompt: defaultPrompt }); const textarea = document.getElementById('ai-prompt'); if (textarea) textarea.value = defaultPrompt; console.log('恢复默认AI提示词'); alert('已恢复默认提示词'); } catch (error) { console.error('恢复默认AI提示词失败:', error); alert('恢复默认提示词失败: ' + error.message); } } // 更新AI状态显示 updateAiStatus() { const apiKey = document.getElementById('api-key').value; const isEnabled = apiKey && apiKey.length > 0; const modelName = (document.getElementById('model-name')?.value || '').trim() || DEFAULT_AI.model; const baseUrl = (document.getElementById('api-base-url')?.value || '').trim(); const statusIndicator = document.querySelector('.status-indicator'); const statusText = document.querySelector('.status-text'); const testBtn = document.getElementById('test-ai-btn'); if (isEnabled) { statusIndicator.textContent = '🟢'; statusText.textContent = `已启用 · ${modelName} · ${baseUrl}`; testBtn.disabled = false; } else { statusIndicator.textContent = '🔴'; statusText.textContent = '未配置API密钥'; testBtn.disabled = true; } this.updateModeHint(); } // 测试AI生成功能(走后台调用自定义模型) async testAiGeneration() { const apiKey = document.getElementById('api-key').value; const baseUrl = document.getElementById('api-base-url')?.value; const modelName = document.getElementById('model-name')?.value; const resultEl = document.getElementById('test-ai-result'); const testBtn = document.getElementById('test-ai-btn'); const showResult = (ok, text) => { if (!resultEl) { alert(text); return; } resultEl.textContent = text; resultEl.className = 'test-ai-result ' + (ok ? 'ok' : 'fail'); }; if (!apiKey || apiKey.length === 0) { showResult(false, '✗ 请先配置API密钥'); return; } if (testBtn) { testBtn.disabled = true; testBtn.textContent = '测试中…'; } showResult(true, '⏳ 请求中…'); try { const apiResult = await chrome.runtime.sendMessage({ action: 'callAiApi', apiKey: apiKey, baseUrl: baseUrl, model: modelName, prompt: '你好,请简单回复一下', maxTokens: 300, timeoutMs: 60000 }); if (apiResult && apiResult.ok) { const reply = String(apiResult.content).slice(0, 40); showResult(true, `✓ 成功 · ${apiResult.model} · ${reply}`); } else { showResult(false, `✗ 失败 · ${(apiResult && apiResult.error) || '后台无响应,请重新加载插件'}`); } } catch (error) { console.error('AI测试失败:', error); showResult(false, '✗ 失败 · ' + error.message); } finally { if (testBtn) { testBtn.disabled = false; testBtn.textContent = '测试AI'; } this.updateAiStatus(); } } } // 初始化 document.addEventListener('DOMContentLoaded', () => { new AutoCommentManager(); });