feat: v1.7.9 试用72h+100条额度+到期降级+24h优惠码;v1.7.8 移除图片回复模式

注:本提交由 Gitee v1.7.7 基线重建(原 GitHub 仓库账号暂停、源码包已丢),重建后经真机功能测试与 v1.7.9 行为一致。
This commit is contained in:
personal82555 committed 2026-10-17 22:01:13 +08:00
1 parent fae84ff6c7
commit 540f506c78
8 files changed
+360 -436

No files matched your search

+105 -153
View File
@@ -16,10 +16,9 @@ class AutoCommentManager {
await this.loadRegion(); // 加载按地区搜索设置
await this.loadApiKey(); // 加载API密钥
await this.loadModelConfig(); // 加载自定义模型配置
await this.loadCommentMode(); // 加载评论方式(固定评论池 / 自定义文字 / 图片 / AI)
await this.loadCommentMode(); // 加载评论方式(固定评论池 / 自定义文字 / AI)
await this.loadAiPrompt(); // 加载AI提示词
await this.loadUserComments(); // 加载自定义文字库
await this.loadImagePreviews(); // 加载已上传的评论图片
this.bindEvents();
this.updateAiStatus();
this.loadCommentLogs(); // 加载评论记录
@@ -112,15 +111,6 @@ class AutoCommentManager {
});
}
// 图片回复:选择图片即压缩保存
const imageInput = document.getElementById('comment-image-input');
if (imageInput) {
imageInput.addEventListener('change', (e) => {
this.handleImageFiles(e.target.files);
imageInput.value = ''; // 允许重复选择同一文件
});
}
// 评论记录:手动刷新
const refreshLogBtn = document.getElementById('refresh-log-btn');
if (refreshLogBtn) {
@@ -537,12 +527,16 @@ class AutoCommentManager {
}
}
// 加载评论方式(custom / text / image / ai)
// 加载评论方式(custom / text / ai)
async loadCommentMode() {
try {
const result = await chrome.storage.local.get(['commentMode']);
const validModes = ['custom', 'text', 'image', 'ai'];
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);
@@ -567,9 +561,7 @@ class AutoCommentManager {
// 根据评论方式显示/隐藏对应的配置面板
updateModePanels(mode) {
const textPanel = document.getElementById('text-comment-panel');
const imagePanel = document.getElementById('image-comment-panel');
if (textPanel) textPanel.style.display = mode === 'text' ? 'block' : 'none';
if (imagePanel) imagePanel.style.display = mode === 'image' ? 'block' : 'none';
}
// 加载自定义文字库
@@ -614,133 +606,21 @@ class AutoCommentManager {
if (el) el.textContent = count > 0 ? `共 ${count} 条` : '暂无内容';
}
// 处理选择的图片文件(压缩后保存到本地存储)
async handleImageFiles(fileList) {
const files = Array.from(fileList || []).filter((f) => f.type.startsWith('image/'));
if (files.length === 0) return;
try {
this.showActionStatus(true, `⏳ 正在处理 ${files.length} 张图片…`);
const result = await chrome.storage.local.get(['commentImages']);
const images = result.commentImages || [];
for (const file of files) {
const dataUrl = await this.compressImage(file);
images.push({
id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
name: file.name,
dataUrl
});
}
await chrome.storage.local.set({ commentImages: images });
this.renderImagePreviews(images);
this.showActionStatus(true, `✓ 已添加 ${files.length} 张图片,共 ${images.length} 张`);
await this.updateModeHint('image');
} catch (error) {
console.error('处理评论图片失败:', error);
this.showActionStatus(false, '✗ 图片处理失败: ' + error.message);
}
}
// 压缩图片:超过 400KB 或边长超过 1280px 时压缩为 JPEG
async compressImage(file) {
const shouldCompress = file.size > 400 * 1024;
const originalDataUrl = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(file);
});
if (!shouldCompress) return originalDataUrl;
const img = await new Promise((resolve, reject) => {
const image = new Image();
image.onload = () => resolve(image);
image.onerror = reject;
image.src = originalDataUrl;
});
const maxSide = 1280;
let { width, height } = img;
if (width > maxSide || height > maxSide) {
const scale = Math.min(maxSide / width, maxSide / height);
width = Math.round(width * scale);
height = Math.round(height * scale);
}
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(img, 0, 0, width, height);
return canvas.toDataURL('image/jpeg', 0.85);
}
// 渲染图片预览列表
async loadImagePreviews() {
try {
const result = await chrome.storage.local.get(['commentImages']);
this.renderImagePreviews(result.commentImages || []);
} catch (error) {
console.error('加载评论图片失败:', error);
}
}
renderImagePreviews(images) {
const list = document.getElementById('image-preview-list');
if (!list) return;
if (!images || images.length === 0) {
list.innerHTML = '<div class="image-preview-empty">暂无图片</div>';
return;
}
list.innerHTML = '';
images.forEach((image) => {
const item = document.createElement('div');
item.className = 'image-preview-item';
const img = document.createElement('img');
img.src = image.dataUrl;
img.alt = image.name || '';
const del = document.createElement('button');
del.type = 'button';
del.className = 'image-delete-btn';
del.textContent = '×';
del.title = '删除这张图片';
del.addEventListener('click', () => this.deleteCommentImage(image.id));
item.appendChild(img);
item.appendChild(del);
list.appendChild(item);
});
}
// 删除一张评论图片
async deleteCommentImage(id) {
try {
const result = await chrome.storage.local.get(['commentImages']);
const images = (result.commentImages || []).filter((img) => img.id !== id);
await chrome.storage.local.set({ commentImages: images });
this.renderImagePreviews(images);
this.showActionStatus(true, `✓ 已删除,剩余 ${images.length} 张`);
await this.updateModeHint('image');
} catch (error) {
console.error('删除评论图片失败:', error);
}
}
// ===== 授权系统(CardAuth) =====
// 功能门槛:有效授权 或 试用期内可用;试用结束锁定
// 功能门槛:授权 / 试用期 / 到期降级(每日 3 条)都可开始;降级额度用完才拦
async isFeatureAllowed() {
try {
const status = await chrome.runtime.sendMessage({ action: 'authStatus' });
const state = status && status.status ? status.status.state : 'locked';
if (state === 'locked') {
this.showActionStatus(false, '✗ 试用已结束,请购买授权或输入卡密激活');
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;
@@ -756,10 +636,31 @@ class AutoCommentManager {
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) this.renderAuthStatus();
if (changes.licenseState || changes.trialUsage) this.renderAuthStatus();
if (changes.announcements) this.renderAnnouncements((changes.announcements.newValue || {}).list || []);
});
}
@@ -774,7 +675,7 @@ class AutoCommentManager {
const status = await chrome.runtime.sendMessage({ action: 'authStatus' });
const s = status && status.status ? status.status : { state: 'locked' };
badge.classList.remove('authorized', 'trial', 'locked');
badge.classList.remove('authorized', 'trial', 'locked', 'grace');
this.stopTrialCountdown();
if (s.state === 'authorized') {
@@ -789,14 +690,23 @@ class AutoCommentManager {
} else {
detail.textContent = '授权有效';
}
this.hideCouponBar();
} else if (s.state === 'trial') {
badge.classList.add('trial');
badge.textContent = '试用中';
this.startTrialCountdown(s.trialEndAt || (Date.now() + 3600000));
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 = '试用已结束:请购买授权或输入卡密激活后使用';
detail.textContent = '授权状态异常,请重新打开侧边栏';
this.hideCouponBar();
}
} catch (error) {
console.error('获取授权状态失败:', error);
@@ -807,8 +717,9 @@ class AutoCommentManager {
}
// 试用倒计时(精确到秒,每秒刷新)
startTrialCountdown(endAt) {
startTrialCountdown(endAt, status) {
this.stopTrialCountdown();
const quotaLeft = status && status.quotaLeft != null ? status.quotaLeft : null;
const tick = () => {
const remain = endAt - Date.now();
@@ -816,12 +727,13 @@ class AutoCommentManager {
if (remain <= 0) {
this.stopTrialCountdown();
this.renderAuthStatus(); // 到期后刷新为锁定状态
this.renderAuthStatus(); // 到期后刷新为降级体验状态
return;
}
if (detail) {
detail.textContent = `免费试用剩余 ${this.formatRemain(remain)} · 试用结束后需购买授权或输入卡密激活`;
const quotaText = quotaLeft != null ? ` · 额度剩 ${quotaLeft} 条` : '';
detail.textContent = `免费试用剩余 ${this.formatRemain(remain)}${quotaText} · 到期后每天仍可体验 3 条`;
}
};
@@ -836,6 +748,50 @@ class AutoCommentManager {
}
}
// ===== 到期 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));
@@ -893,7 +849,10 @@ class AutoCommentManager {
}
const order = res.order;
this.showOrderStatus(true, `订单 ${order.orderNo}(¥${order.amount})已创建,请在新页面完成支付,支付成功后自动激活…`);
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 });
}
@@ -1139,12 +1098,11 @@ class AutoCommentManager {
let storageInfo = {};
try {
storageInfo = await chrome.storage.local.get(['userComments', 'commentImages']);
storageInfo = await chrome.storage.local.get(['userComments']);
} catch (error) {
console.error('读取提示信息失败:', error);
}
const textCount = (storageInfo.userComments || []).length;
const imageCount = (storageInfo.commentImages || []).length;
hint.classList.remove('warn');
@@ -1154,9 +1112,6 @@ class AutoCommentManager {
} else if (current === 'text' && textCount === 0) {
hint.textContent = '⚠ 自定义文字模式还没有文字内容,请在下方填写并保存';
hint.classList.add('warn');
} else if (current === 'image' && imageCount === 0) {
hint.textContent = '⚠ 小红书网页版评论不支持发图片(仅手机App支持),此模式大概率发不出,建议用「自定义文字」或「AI」';
hint.classList.add('warn');
} else if (current === 'custom' && hasKey) {
hint.textContent = '⚠ 当前是固定评论池,下方模型配置不会生效;要按笔记生成请点「AI 按笔记生成」';
hint.classList.add('warn');
@@ -1164,9 +1119,6 @@ class AutoCommentManager {
hint.textContent = '✓ 每篇笔记都会调用下方模型,按笔记内容生成评论';
} else if (current === 'text') {
hint.textContent = `✓ 每篇笔记发送一条自定义文字(共 ${textCount} 条循环)`;
} else if (current === 'image') {
hint.textContent = `⚠ 已上传 ${imageCount} 张,但小红书网页版评论不支持发图片(仅手机App支持),大概率发送失败,建议改用「自定义文字」或「AI」`;
hint.classList.add('warn');
} else {
hint.textContent = '固定评论池 = 循环发预设短评;下方模型配置暂不生效';
}