303 lines
10 KiB
JavaScript
303 lines
10 KiB
JavaScript
/**
|
|
* Pinduoduo Cookie Sync Extension Popup Script
|
|
* 拼多多 Cookie 自动保活弹窗界面交互与实时日志引擎
|
|
*/
|
|
|
|
document.addEventListener('DOMContentLoaded', async () => {
|
|
const statusBadge = document.getElementById('statusBadge')
|
|
const mmsStatus = document.getElementById('mmsStatus')
|
|
const shopDisplay = document.getElementById('shopDisplay')
|
|
const lastSyncDisplay = document.getElementById('lastSyncDisplay')
|
|
const orderCountRow = document.getElementById('orderCountRow')
|
|
const orderCountDisplay = document.getElementById('orderCountDisplay')
|
|
const statusMsg = document.getElementById('statusMsg')
|
|
|
|
const btnSyncNow = document.getElementById('btnSyncNow')
|
|
const btnSaveConfig = document.getElementById('btnSaveConfig')
|
|
const btnToggleImport = document.getElementById('btnToggleImport')
|
|
const btnApplyImport = document.getElementById('btnApplyImport')
|
|
const importArea = document.getElementById('importArea')
|
|
const importJsonInput = document.getElementById('importJsonInput')
|
|
|
|
const inputServerUrl = document.getElementById('inputServerUrl')
|
|
const inputShopId = document.getElementById('inputShopId')
|
|
const inputSyncToken = document.getElementById('inputSyncToken')
|
|
const inputManualCookie = document.getElementById('inputManualCookie')
|
|
|
|
const logBox = document.getElementById('logBox')
|
|
const logCountBadge = document.getElementById('logCountBadge')
|
|
const btnCopyLogs = document.getElementById('btnCopyLogs')
|
|
const btnClearLogs = document.getElementById('btnClearLogs')
|
|
|
|
let cachedLogs = []
|
|
|
|
function formatTime(timestamp) {
|
|
if (!timestamp) return '未同步'
|
|
const d = new Date(timestamp)
|
|
if (isNaN(d.getTime())) return '未同步'
|
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
const day = String(d.getDate()).padStart(2, '0')
|
|
const h = String(d.getHours()).padStart(2, '0')
|
|
const min = String(d.getMinutes()).padStart(2, '0')
|
|
const s = String(d.getSeconds()).padStart(2, '0')
|
|
return `${m}-${day} ${h}:${min}:${s}`
|
|
}
|
|
|
|
function showMsg(text, type = 'success') {
|
|
statusMsg.style.display = 'block'
|
|
if (type === 'error') {
|
|
statusMsg.className = 'msg-box msg-error'
|
|
} else if (type === 'warn') {
|
|
statusMsg.className = 'msg-box msg-warn'
|
|
} else {
|
|
statusMsg.className = 'msg-box msg-success'
|
|
}
|
|
statusMsg.textContent = text
|
|
}
|
|
|
|
// 加载并渲染运行日志
|
|
function refreshLogs() {
|
|
chrome.runtime.sendMessage({ action: 'get_logs' }, (res) => {
|
|
const logs = Array.isArray(res?.logs) ? res.logs : []
|
|
cachedLogs = logs
|
|
logCountBadge.textContent = `(${logs.length}条)`
|
|
|
|
if (logs.length === 0) {
|
|
logBox.innerHTML = '<div class="log-empty">暂无运行日志</div>'
|
|
return
|
|
}
|
|
|
|
let html = ''
|
|
for (const item of logs) {
|
|
let tagClass = 'log-tag-info'
|
|
let tagText = '[INFO]'
|
|
if (item.level === 'success') {
|
|
tagClass = 'log-tag-success'
|
|
tagText = '[OK]'
|
|
} else if (item.level === 'warn') {
|
|
tagClass = 'log-tag-warn'
|
|
tagText = '[WARN]'
|
|
} else if (item.level === 'error') {
|
|
tagClass = 'log-tag-error'
|
|
tagText = '[ERR]'
|
|
}
|
|
|
|
const detailsHtml = item.details
|
|
? `<div style="color: #94a3b8; font-size: 10px; margin-left: 12px;">↳ ${escapeHtml(item.details)}</div>`
|
|
: ''
|
|
|
|
html += `
|
|
<div class="log-item">
|
|
<span class="log-time">${item.time || ''}</span>
|
|
<span class="${tagClass}">${tagText}</span>
|
|
<span class="log-action">[${escapeHtml(item.action || '系统')}]</span>
|
|
<span>${escapeHtml(item.message || '')}</span>
|
|
${detailsHtml}
|
|
</div>
|
|
`
|
|
}
|
|
logBox.innerHTML = html
|
|
})
|
|
}
|
|
|
|
function escapeHtml(str) {
|
|
if (!str) return ''
|
|
return String(str)
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
}
|
|
|
|
function refreshStatusView(statusData) {
|
|
const storage = statusData?.storage || {}
|
|
const hasCookie = Boolean(statusData?.hasCookie)
|
|
const hasPassId = Boolean(statusData?.hasPassId)
|
|
const cookieCount = statusData?.cookieCount || 0
|
|
|
|
// 1. MMS 登录状态
|
|
if (hasPassId) {
|
|
mmsStatus.textContent = `已就绪 (包含 PASS_ID,共 ${cookieCount} 项)`
|
|
mmsStatus.style.color = '#15803d'
|
|
} else if (hasCookie) {
|
|
mmsStatus.textContent = `未含 PASS_ID (共 ${cookieCount} 项)`
|
|
mmsStatus.style.color = '#d97706'
|
|
} else {
|
|
mmsStatus.textContent = '未检测到拼多多登录 Cookie'
|
|
mmsStatus.style.color = '#b91c1c'
|
|
}
|
|
|
|
// 2. 店铺标识
|
|
if (storage.shopName) {
|
|
shopDisplay.textContent = `${storage.shopName} (${storage.shopId || ''})`
|
|
} else if (storage.shopId) {
|
|
shopDisplay.textContent = storage.shopId
|
|
} else {
|
|
shopDisplay.textContent = '未配置'
|
|
}
|
|
|
|
// 3. 上次同步
|
|
if (storage.lastSyncAt) {
|
|
lastSyncDisplay.textContent = formatTime(storage.lastSyncAt)
|
|
} else {
|
|
lastSyncDisplay.textContent = '从未同步'
|
|
}
|
|
|
|
// 4. 订单数
|
|
if (storage.totalOrders !== undefined) {
|
|
orderCountRow.style.display = 'flex'
|
|
orderCountDisplay.textContent = `${storage.totalOrders} 笔`
|
|
} else {
|
|
orderCountRow.style.display = 'none'
|
|
}
|
|
|
|
// 5. 顶部角标与状态消息
|
|
if (!storage.serverUrl || !storage.shopId || !storage.syncToken) {
|
|
statusBadge.className = 'badge badge-gray'
|
|
statusBadge.textContent = '待配对'
|
|
} else if (!hasPassId && !storage.manualCookie) {
|
|
statusBadge.className = 'badge badge-warn'
|
|
statusBadge.textContent = '未登录MMS'
|
|
showMsg('未检测到拼多多后台核心会话 (PASS_ID)。请在当前浏览器登录 mms.pinduoduo.com 并刷新后台页面', 'warn')
|
|
} else if (storage.lastStatus === 'ok') {
|
|
statusBadge.className = 'badge badge-ok'
|
|
statusBadge.textContent = '运行正常'
|
|
if (storage.lastMessage) {
|
|
showMsg(storage.lastMessage, 'success')
|
|
}
|
|
} else if (storage.lastStatus === 'error') {
|
|
statusBadge.className = 'badge badge-err'
|
|
statusBadge.textContent = '同步异常'
|
|
if (storage.lastMessage) {
|
|
showMsg(storage.lastMessage, 'error')
|
|
}
|
|
} else {
|
|
statusBadge.className = 'badge badge-gray'
|
|
statusBadge.textContent = '待同步'
|
|
}
|
|
|
|
// 填充输入框
|
|
inputServerUrl.value = storage.serverUrl || ''
|
|
inputShopId.value = storage.shopId || ''
|
|
inputSyncToken.value = storage.syncToken || ''
|
|
inputManualCookie.value = storage.manualCookie || ''
|
|
}
|
|
|
|
// 初始化加载状态与日志
|
|
chrome.runtime.sendMessage({ action: 'get_status' }, (res) => {
|
|
refreshStatusView(res)
|
|
})
|
|
refreshLogs()
|
|
|
|
// 切换一键导入框
|
|
btnToggleImport.addEventListener('click', (e) => {
|
|
e.preventDefault()
|
|
if (importArea.style.display === 'none') {
|
|
importArea.style.display = 'block'
|
|
importJsonInput.focus()
|
|
} else {
|
|
importArea.style.display = 'none'
|
|
}
|
|
})
|
|
|
|
// 执行一键导入解析
|
|
btnApplyImport.addEventListener('click', () => {
|
|
const raw = importJsonInput.value.trim()
|
|
if (!raw) return
|
|
try {
|
|
const parsed = JSON.parse(raw)
|
|
if (parsed.serverUrl) inputServerUrl.value = parsed.serverUrl
|
|
if (parsed.shopId) inputShopId.value = parsed.shopId
|
|
if (parsed.syncToken) inputSyncToken.value = parsed.syncToken
|
|
if (parsed.cookie) inputManualCookie.value = parsed.cookie
|
|
importArea.style.display = 'none'
|
|
showMsg('配置解析成功,请点击下方【保存配置并重连】生效', 'success')
|
|
} catch {
|
|
showMsg('JSON 格式不正确,请确保完整复制管理系统后台弹窗中的配置', 'error')
|
|
}
|
|
})
|
|
|
|
// 保存配置
|
|
btnSaveConfig.addEventListener('click', async () => {
|
|
const serverUrl = inputServerUrl.value.trim()
|
|
const shopId = inputShopId.value.trim()
|
|
const syncToken = inputSyncToken.value.trim()
|
|
const manualCookie = inputManualCookie.value.trim()
|
|
|
|
if (!serverUrl || !shopId || !syncToken) {
|
|
showMsg('请完整填写服务器地址、店铺标识和配对密钥', 'error')
|
|
return
|
|
}
|
|
|
|
await chrome.storage.local.set({
|
|
serverUrl,
|
|
shopId,
|
|
syncToken,
|
|
manualCookie,
|
|
})
|
|
|
|
showMsg('配置已保存!正在发起加密上传与网关校验...', 'success')
|
|
triggerSync()
|
|
})
|
|
|
|
// 手动触发同步
|
|
function triggerSync() {
|
|
btnSyncNow.disabled = true
|
|
btnSyncNow.textContent = '正在加密上传...'
|
|
|
|
chrome.runtime.sendMessage({ action: 'trigger_sync' }, (res) => {
|
|
btnSyncNow.disabled = false
|
|
btnSyncNow.textContent = '立即上传同步 Cookie'
|
|
|
|
if (res && res.success) {
|
|
showMsg(res.message || '同步成功!', 'success')
|
|
} else {
|
|
showMsg(res?.message || '同步失败,请检查网络或配置', 'error')
|
|
}
|
|
|
|
// 刷新界面与日志
|
|
chrome.runtime.sendMessage({ action: 'get_status' }, (s) => {
|
|
refreshStatusView(s)
|
|
})
|
|
refreshLogs()
|
|
})
|
|
}
|
|
|
|
btnSyncNow.addEventListener('click', triggerSync)
|
|
|
|
// 复制日志功能
|
|
btnCopyLogs.addEventListener('click', async () => {
|
|
if (!cachedLogs || cachedLogs.length === 0) {
|
|
alert('当前没有日志可复制')
|
|
return
|
|
}
|
|
|
|
const textLines = cachedLogs.map((item) => {
|
|
const detailStr = item.details ? ` | 详情: ${item.details}` : ''
|
|
return `[${item.time}] [${item.level.toUpperCase()}] [${item.action}] ${item.message}${detailStr}`
|
|
})
|
|
const fullText = `=== 拼多多保活插件运行日志 (共 ${cachedLogs.length} 条) ===\n` + textLines.join('\n')
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(fullText)
|
|
const origText = btnCopyLogs.textContent
|
|
btnCopyLogs.textContent = '已复制!'
|
|
setTimeout(() => {
|
|
btnCopyLogs.textContent = origText
|
|
}, 1500)
|
|
} catch (err) {
|
|
console.error('复制日志到剪贴板失败:', err)
|
|
alert('复制失败,请手动选择复制')
|
|
}
|
|
})
|
|
|
|
// 清空日志功能
|
|
btnClearLogs.addEventListener('click', () => {
|
|
if (confirm('确定要清空扩展的历史运行日志吗?')) {
|
|
chrome.runtime.sendMessage({ action: 'clear_logs' }, () => {
|
|
refreshLogs()
|
|
})
|
|
}
|
|
})
|
|
})
|