feat: v1.7.9 试用72h+100条额度+到期降级+24h优惠码;v1.7.8 移除图片回复模式
注:本提交由 Gitee v1.7.7 基线重建(原 GitHub 仓库账号暂停、源码包已丢),重建后经真机功能测试与 v1.7.9 行为一致。
This commit is contained in:
1 parent
fae84ff6c7
commit
540f506c78
8 files changed
+360
-436
No files matched your search
+105
-153
@@ -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 = '固定评论池 = 循环发预设短评;下方模型配置暂不生效';
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user