// ==UserScript== // @name Douyin-Auto-Keep-Fire(抖音自动续火花脚本) // @namespace Douyin-Auto-Keep-Fire // @version 2.0 // @description 每日自动发送续火花消息,允许自定义用户、消息内容、发送时间,支持TextAPI、记录火花天数等。 // @author 東亰藍調(iEastBlues) // @match https://www.douyin.com/chat* // @grant GM_xmlhttpRequest // @grant GM_registerMenuCommand // @grant GM_unregisterMenuCommand // @license MIT // @homepageURL https://i.eastblues.cn/Douyin-Auto-Keep-Fire.html // ==/UserScript== (function () { 'use strict'; const STORAGE_KEYS = { CONFIG: 'douyin_spark_auto_config', SEND_RECORD: 'douyin_spark_send_record', }; const DOM_SELECTORS = { SEARCH_INPUT: 'input.semi-input[placeholder="搜索"][type="text"]', CHAT_BTN: 'div[class*="SearchPanelitemchat_btn"]', SPARK_STATUS: 'div.commonStreaknormalText', CHAT_INPUT: 'div[data-slate-editor="true"][contenteditable="true"]', PAGE_CONTAINER: 'body' }; const GLOBAL_STATE = { config: { targetUsers: [], sendContent: '', sendTime: '', }, todaySendRecord: {}, taskTimer: null, dayResetTimer: null, isTaskRunning: false, currentDate: new Date().toLocaleDateString(), menuCommandId: null, }; function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, '0'); const day = String(date.getDate()).padStart(2, '0'); const hours = String(date.getHours()).padStart(2, '0'); const minutes = String(date.getMinutes()).padStart(2, '0'); const seconds = String(date.getSeconds()).padStart(2, '0'); return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hours) .replace('mm', minutes) .replace('ss', seconds); } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } function validateTimeFormat(timeStr) { const timeReg = /^([01][0-9]|2[0-3]):([0-5][0-9])$/; return timeReg.test(timeStr); } function isReachSendTime(targetTime) { if (!validateTimeFormat(targetTime)) return false; const [targetHour, targetMinute] = targetTime.split(':').map(Number); const now = new Date(); const currentHour = now.getHours(); const currentMinute = now.getMinutes(); return currentHour > targetHour || (currentHour === targetHour && currentMinute >= targetMinute); } function simulateMouseClick(element) { if (!element || !(element instanceof HTMLElement)) { return false; } const rect = element.getBoundingClientRect(); const x = rect.left + rect.width / 2; const y = rect.top + rect.height / 2; const event = new MouseEvent('click', { bubbles: true, clientX: x, clientY: y }); element.dispatchEvent(event); return true; } async function simulateHumanInput(inputElement, content, interval = 50) { if (!inputElement || !(inputElement instanceof HTMLElement)) { return false; } if (typeof content !== 'string') { return false; } inputElement.focus(); simulateMouseClick(inputElement); await sleep(30); if (inputElement.tagName === 'INPUT' || inputElement.tagName === 'TEXTAREA') { inputElement.select(); document.execCommand('delete'); } else if (inputElement.isContentEditable) { inputElement.innerHTML = ''; } inputElement.dispatchEvent(new Event('input', { bubbles: true })); inputElement.dispatchEvent(new Event('change', { bubbles: true })); await sleep(50); const charArray = content.split(''); for (let i = 0; i < charArray.length; i++) { const char = charArray[i]; if (inputElement.tagName === 'INPUT' || inputElement.tagName === 'TEXTAREA') { document.execCommand('insertText', false, char); inputElement.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: char, inputType: 'insertText' })); } else if (inputElement.isContentEditable) { const textNode = document.createTextNode(char); const selection = window.getSelection(); const range = selection.getRangeAt(0); range.insertNode(textNode); range.setStartAfter(textNode); range.setEndAfter(textNode); selection.removeAllRanges(); selection.addRange(range); inputElement.dispatchEvent(new InputEvent('input', { bubbles: true, cancelable: true, data: char, inputType: 'insertText' })); } inputElement.dispatchEvent(new KeyboardEvent('keydown', { bubbles: true, key: char })); inputElement.dispatchEvent(new KeyboardEvent('keypress', { bubbles: true, key: char })); inputElement.dispatchEvent(new KeyboardEvent('keyup', { bubbles: true, key: char })); await sleep(interval); } inputElement.dispatchEvent(new Event('change', { bubbles: true })); inputElement.dispatchEvent(new Event('blur', { bubbles: true })); return true; } function waitForElement(selector, timeout = 20000, parent = document) { return new Promise((resolve) => { const existElement = parent.querySelector(selector); if (existElement) { return resolve(existElement); } const interval = 100; let elapsedTime = 0; const timer = setInterval(() => { elapsedTime += interval; const element = parent.querySelector(selector); if (element) { clearInterval(timer); resolve(element); } else if (elapsedTime >= timeout) { clearInterval(timer); resolve(null); } }, interval); }); } function saveConfig() { try { const targetUsersInput = document.getElementById('spark-target-users').value.trim(); const sendContentInput = document.getElementById('spark-send-content').value.trim(); const sendTimeInput = document.getElementById('spark-send-time').value.trim(); if (!targetUsersInput) { alert('Invalid input'); return false; } if (!sendContentInput) { alert('Invalid input'); return false; } if (!validateTimeFormat(sendTimeInput)) { alert('Invalid input Example: 00:30'); return false; } const targetUsers = targetUsersInput.split(',').map(item => item.trim()).filter(item => item); const config = { targetUsers, sendContent: sendContentInput, sendTime: sendTimeInput }; GLOBAL_STATE.config = config; localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(config)); alert('Success'); return true; } catch (error) { alert('Failure'); return false; } } function loadConfig() { try { const configStr = localStorage.getItem(STORAGE_KEYS.CONFIG); if (!configStr) { return; } const config = JSON.parse(configStr); GLOBAL_STATE.config = config; document.getElementById('spark-target-users').value = config.targetUsers.join(', '); document.getElementById('spark-send-content').value = config.sendContent; document.getElementById('spark-send-time').value = config.sendTime; } catch (error) { GLOBAL_STATE.config = { targetUsers: [], sendContent: '', sendTime: '', }; } } function exportConfig() { try { const configStr = JSON.stringify(GLOBAL_STATE.config, null, 2); navigator.clipboard.writeText(configStr).then(() => { alert('Success'); }); } catch (error) { alert('Failure'); } } function importConfig() { const input = prompt('请粘贴配置文件:'); if (!input) return; try { const config = JSON.parse(input); if (!Array.isArray(config.targetUsers) || typeof config.sendContent !== 'string' || typeof config.sendTime !== 'string') { } GLOBAL_STATE.config = config; localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(config)); document.getElementById('spark-target-users').value = config.targetUsers.join(', '); document.getElementById('spark-send-content').value = config.sendContent; document.getElementById('spark-send-time').value = config.sendTime; alert('Success'); } catch (error) { alert('Error:请检查JSON格式并重试'); } } function resetAllData() { const isConfirm = confirm('警告:此操作将清空所有数据,是否继续?'); if (!isConfirm) return; try { Object.values(STORAGE_KEYS).forEach(key => localStorage.removeItem(key)); GLOBAL_STATE.config = { targetUsers: [], sendContent: '', sendTime: '' }; GLOBAL_STATE.todaySendRecord = {}; stopTask(); document.getElementById('spark-target-users').value = ''; document.getElementById('spark-send-content').value = ''; document.getElementById('spark-send-time').value = ''; updateSparkStatusPanel(); alert('Success'); } catch (error) { alert('Failure'); } } function resetRecord() { const isConfirm = confirm('此操作将重置今日发送记录,是否继续?'); if (!isConfirm) return; try { localStorage.removeItem(STORAGE_KEYS.SEND_RECORD); GLOBAL_STATE.todaySendRecord = {}; updateSparkStatusPanel(); alert('Success'); } catch (error) { alert('Failure'); } } async function parseApiContent(content) { if (!content || typeof content !== 'string') return content; const apiReg = /\(API:([^)]+)\)/g; const matches = [...content.matchAll(apiReg)]; if (matches.length === 0) { return content; } let finalContent = content; for (const match of matches) { const [fullMatch, apiUrl] = match; try { const apiResult = await new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: apiUrl, timeout: 8000, onload: (response) => { if (response.status >= 200 && response.status < 300) { resolve(response.responseText); } else { reject(new Error(`API请求失败,状态码:${response.status}`)); } }, onerror: (error) => reject(new Error(`API请求异常:${error.message}`)), ontimeout: () => reject(new Error(`API请求超时,超时时间8000ms`)) }); }); finalContent = finalContent.replace(fullMatch, apiResult.trim()); } catch (error) { finalContent = finalContent.replace(fullMatch, `[API]`); } } return finalContent; } async function sendMessageToUser(userName, sendContent) { let sparkDays = 0; try { const searchInput = await waitForElement(DOM_SELECTORS.SEARCH_INPUT); if (!searchInput) { return { success: false, sparkDays }; } const inputSuccess = await simulateHumanInput(searchInput, userName); if (!inputSuccess) { return { success: false, sparkDays }; } await sleep(1000); let searchResultContainer = searchInput.closest('div') || searchInput.parentElement; if (!searchResultContainer.querySelector(DOM_SELECTORS.CHAT_BTN)) { let current = searchInput.parentElement; while (current && current !== document.body) { if (current.querySelector(DOM_SELECTORS.CHAT_BTN)) { searchResultContainer = current; break; } current = current.parentElement; } } const chatBtn = await waitForElement(DOM_SELECTORS.CHAT_BTN, 5000, searchResultContainer); if (!chatBtn) { return { success: false, sparkDays }; } const clickSuccess = simulateMouseClick(chatBtn); if (!clickSuccess) { return { success: false, sparkDays }; } await sleep(1500); const chatInput = await waitForElement(DOM_SELECTORS.CHAT_INPUT); if (!chatInput) { return { success: false, sparkDays }; } const contentInputSuccess = await simulateHumanInput(chatInput, sendContent); if (!contentInputSuccess) { return { success: false, sparkDays }; } await sleep(500); const enterEvent = new KeyboardEvent('keydown', { key: 'Enter', code: 'Enter', bubbles: true, cancelable: true }); chatInput.dispatchEvent(enterEvent); await sleep(1000); const conversationListWrapper = await waitForElement('.conversationConversationListwrapper', 3000); if (conversationListWrapper) { await sleep(300); conversationListWrapper.scrollTop = 0; } const sparkElement = document.querySelector(DOM_SELECTORS.SPARK_STATUS); if (sparkElement) { sparkDays = parseInt(sparkElement.textContent.trim()) || 0; } return { success: true, sparkDays }; } catch (error) { return { success: false, sparkDays }; } } async function batchSendMessage(userList, originalContent) { if (!userList || userList.length === 0) { return; } const finalContent = await parseApiContent(originalContent); const pendingUsers = userList.filter(user => !GLOBAL_STATE.todaySendRecord[user]?.isSuccess); if (pendingUsers.length === 0) { return; } for (let i = 0; i < pendingUsers.length; i++) { const userName = pendingUsers[i]; const { success, sparkDays } = await sendMessageToUser(userName, finalContent); GLOBAL_STATE.todaySendRecord[userName] = { userName, isSuccess: success, sendTime: formatDate(new Date()), sparkDays: sparkDays }; localStorage.setItem(STORAGE_KEYS.SEND_RECORD, JSON.stringify(GLOBAL_STATE.todaySendRecord)); updateSparkStatusPanel(); if (i < pendingUsers.length - 1) { await sleep(3000); } } } function startTask() { const { targetUsers, sendContent, sendTime } = GLOBAL_STATE.config; if (targetUsers.length === 0 || !sendContent || !validateTimeFormat(sendTime)) { alert('请先填写并保存完整的配置信息'); return; } if (GLOBAL_STATE.taskTimer) clearInterval(GLOBAL_STATE.taskTimer); GLOBAL_STATE.isTaskRunning = true; initTodaySendRecord(); const startBtn = document.getElementById('spark-task-start-btn'); startBtn.textContent = '停止'; startBtn.style.background = '#ff3b30'; startBtn.style.color = '#fff'; checkAndSend(); updateSparkStatusPanel(); GLOBAL_STATE.taskTimer = setInterval(() => { const nowDate = new Date().toLocaleDateString(); if (nowDate !== GLOBAL_STATE.currentDate) { crossDayReset(); } checkAndSend(); }, 30 * 1000); } function stopTask() { if (GLOBAL_STATE.taskTimer) { clearInterval(GLOBAL_STATE.taskTimer); GLOBAL_STATE.taskTimer = null; } GLOBAL_STATE.isTaskRunning = false; const startBtn = document.getElementById('spark-task-start-btn'); startBtn.textContent = '启动'; startBtn.style.background = '#007aff'; startBtn.style.color = '#fff'; } function checkAndSend() { const { targetUsers, sendContent, sendTime } = GLOBAL_STATE.config; if (!GLOBAL_STATE.isTaskRunning) return; if (targetUsers.length === 0 || !sendContent || !validateTimeFormat(sendTime)) { stopTask(); return; } if (!isReachSendTime(sendTime)) { return; } const allSend = targetUsers.every(user => GLOBAL_STATE.todaySendRecord[user]?.isSuccess); if (allSend) { return; } batchSendMessage(targetUsers, sendContent); } function initTodaySendRecord() { const recordStr = localStorage.getItem(STORAGE_KEYS.SEND_RECORD); if (!recordStr) { GLOBAL_STATE.todaySendRecord = {}; return; } const record = JSON.parse(recordStr); const nowDate = new Date().toLocaleDateString(); for (const userName in record) { const sendDate = new Date(record[userName].sendTime).toLocaleDateString(); if (sendDate === nowDate) { GLOBAL_STATE.todaySendRecord[userName] = record[userName]; } } updateSparkStatusPanel(); } function crossDayReset() { GLOBAL_STATE.currentDate = new Date().toLocaleDateString(); GLOBAL_STATE.todaySendRecord = {}; localStorage.removeItem(STORAGE_KEYS.SEND_RECORD); updateSparkStatusPanel(); scheduleNextDayReset(); } function scheduleNextDayReset() { if (GLOBAL_STATE.dayResetTimer) { clearTimeout(GLOBAL_STATE.dayResetTimer); GLOBAL_STATE.dayResetTimer = null; } const now = new Date(); const nextReset = new Date(now); nextReset.setDate(now.getDate() + 1); nextReset.setHours(0, 0, 0, 0); const timeUntilReset = nextReset.getTime() - now.getTime(); const delay = timeUntilReset > 0 ? timeUntilReset : 60 * 1000; GLOBAL_STATE.dayResetTimer = setTimeout(() => { crossDayReset(); }, delay); } function startDayResetTimer() { scheduleNextDayReset(); } function updateSparkStatusPanel() { const statusContainer = document.getElementById('spark-status-container'); if (!statusContainer) return; const { targetUsers } = GLOBAL_STATE.config; if (targetUsers.length === 0) { statusContainer.innerHTML = '
暂无数据......
'; return; } statusContainer.innerHTML = targetUsers.map(userName => { const record = GLOBAL_STATE.todaySendRecord[userName]; let statusText = '等待发送'; let statusColor = '#ff9500'; let sparkDaysText = '-'; let sendTimeText = '-'; if (record) { sparkDaysText = record.sparkDays || '-'; sendTimeText = record.sendTime || '-'; if (record.isSuccess) { statusText = '发送成功'; statusColor = '#34c759'; } else { statusText = '发送失败'; statusColor = '#ff3b30'; } } return `
${userName}
${sparkDaysText}天
${statusText}
${sendTimeText}
`; }).join(''); updateFloatingStatusPanel(); } function createControlPanel() { if (document.getElementById('douyin-spark-control-panel')) { return; } const panelStyle = document.createElement('style'); panelStyle.textContent = ` #douyin-spark-control-panel { position: fixed; top: 20px; right: 20px; width: 420px; max-height: 90vh; z-index: 99999; background: #e0e5ec; border-radius: 16px; box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1), inset -2px -2px 5px rgba(255,255,255,0.8); padding: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; transition: all 0.3s ease; overflow: hidden; opacity: 1; visibility: visible; } #douyin-spark-control-panel.panel-hidden { opacity: 0; visibility: hidden; transform: scale(0.95); } .spark-panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; padding-bottom: 12px; border-bottom: 1px solid rgba(0,0,0,0.08); } .spark-panel-title { font-size: 16px; font-weight: 700; color: #333; margin: 0; } .spark-panel-collapse-btn { width: 32px; height: 32px; border-radius: 8px; background: #e0e5ec; box-shadow: 2px 2px 5px rgba(0,0,0,0.1), -2px -2px 5px rgba(255,255,255,0.8); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #333; transition: all 0.2s ease; } .spark-panel-collapse-btn:active { box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1), inset -2px -2px 5px rgba(255,255,255,0.8); } .spark-input-group { margin-bottom: 12px; } .spark-input-label { display: block; font-size: 12px; color: #666; margin-bottom: 6px; font-weight: 500; } .spark-input { width: 100%; box-sizing: border-box; padding: 10px 12px; border-radius: 8px; border: none; background: #e0e5ec; box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1), inset -2px -2px 5px rgba(255,255,255,0.8); font-size: 14px; color: #333; outline: none; transition: all 0.2s ease; } .spark-input:focus { box-shadow: inset 1px 1px 3px rgba(0,0,0,0.1), inset -1px -1px 3px rgba(255,255,255,0.8); } .spark-btn-group { display: flex; gap: 10px; margin-bottom: 16px; flex-wrap: wrap; } .spark-btn { flex: 1; min-width: 80px; padding: 10px 0; border-radius: 8px; border: none; background: #e0e5ec; box-shadow: 2px 2px 5px rgba(0,0,0,0.1), -2px -2px 5px rgba(255,255,255,0.8); font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease; color: #333; } .spark-btn:active { box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1), inset -2px -2px 5px rgba(255,255,255,0.8); } .spark-btn.primary { background: #007aff; color: #fff; } .spark-btn.danger { background: #ff3b30; color: #fff; } .spark-advanced-menu { max-height: 0; overflow: hidden; transition: max-height 0.3s ease; margin-bottom: 0; } .spark-advanced-menu.open { max-height: 200px; margin-bottom: 16px; } .spark-advanced-btn-group { display: flex; gap: 10px; flex-wrap: wrap; padding: 12px; border-radius: 8px; background: rgba(0,0,0,0.02); } .spark-advanced-btn-group .spark-btn { flex: 0 0 calc(33.333% - 7px); box-sizing: border-box; } .spark-status-panel { margin-bottom: 12px; border-radius: 8px; background: #e0e5ec; box-shadow: inset 2px 2px 5px rgba(0,0,0,0.1), inset -2px -2px 5px rgba(255,255,255,0.8); padding: 12px; cursor: pointer; } .spark-status-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; font-size: 12px; font-weight: 600; color: #666; } .spark-status-header span:nth-child(1) { width: 25%; text-align: left; } .spark-status-header span:nth-child(2) { width: 15%; text-align: center; } .spark-status-header span:nth-child(3) { width: 20%; text-align: center; } .spark-status-header span:nth-child(4) { width: 40%; text-align: right; } #douyin-spark-status-floating-panel { position: fixed; top: 20px; right: 20px; width: 420px; max-height: 90vh; z-index: 99998; background: rgba(30, 30, 30, 0.7); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); border-radius: 16px; padding: 16px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #fff; display: block; overflow: auto; opacity: 0; visibility: hidden; transform: scale(0.95); transition: all 0.3s ease; } #douyin-spark-status-floating-panel.panel-visible { opacity: 1; visibility: visible; transform: scale(1); } .floating-status-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid rgba(255,255,255,0.1); } .floating-status-title { font-size: 16px; font-weight: 700; margin: 0; } .floating-status-close { width: 32px; height: 32px; border-radius: 8px; background: rgba(255,255,255,0.1); border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 14px; color: #fff; transition: all 0.2s ease; } .floating-status-item { display: flex; justify-content: space-between; align-items: center; padding: 6px 0; border-bottom: 1px solid rgba(255,255,255,0.05); font-size: 12px; } .floating-status-item div:nth-child(1) { width: 25%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .floating-status-item div:nth-child(2) { width: 15%; text-align: center; } .floating-status-item div:nth-child(3) { width: 20%; text-align: center; font-weight: 600; } .floating-status-item div:nth-child(4) { width: 40%; text-align: right; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .status-success { color: #4cd964; } .status-fail { color: #ff453a; } .status-pending { color: #ff9500; } `; document.head.appendChild(panelStyle); const panel = document.createElement('div'); panel.id = 'douyin-spark-control-panel'; panel.innerHTML = `

Douyin Auto Keep Fire

用户名 火花天数 发送状态 发送时间
暂无数据......
`; document.body.appendChild(panel); const floatingPanel = document.createElement('div'); floatingPanel.id = 'douyin-spark-status-floating-panel'; floatingPanel.innerHTML = `

Status

暂无数据......
`; document.body.appendChild(floatingPanel); bindPanelEvents(); } function bindPanelEvents() { const statusPanelClickArea = document.getElementById('spark-status-panel-click-area'); const floatingPanel = document.getElementById('douyin-spark-status-floating-panel'); const floatingCloseBtn = document.getElementById('floating-status-close-btn'); const dragHandle = document.getElementById('floating-status-drag-handle'); statusPanelClickArea.addEventListener('click', () => { const panel = document.getElementById('douyin-spark-control-panel'); panel.classList.add('panel-hidden'); setTimeout(() => { floatingPanel.classList.add('panel-visible'); }, 100); }); const collapseBtn = document.getElementById('spark-collapse-btn'); collapseBtn.addEventListener('click', () => { const panel = document.getElementById('douyin-spark-control-panel'); panel.classList.add('panel-hidden'); if (!GLOBAL_STATE.menuCommandId) { GLOBAL_STATE.menuCommandId = GM_registerMenuCommand('重载面板', () => { const panel = document.getElementById('douyin-spark-control-panel'); if (panel) { panel.classList.remove('panel-hidden'); } if (GLOBAL_STATE.menuCommandId) { GM_unregisterMenuCommand(GLOBAL_STATE.menuCommandId); GLOBAL_STATE.menuCommandId = null; } }); } }); floatingCloseBtn.addEventListener('click', () => { floatingPanel.classList.remove('panel-visible'); setTimeout(() => { const panel = document.getElementById('douyin-spark-control-panel'); panel.classList.remove('panel-hidden'); if (GLOBAL_STATE.menuCommandId) { GM_unregisterMenuCommand(GLOBAL_STATE.menuCommandId); GLOBAL_STATE.menuCommandId = null; } }, 100); }); let isDragging = false; let offsetX, offsetY; dragHandle.addEventListener('mousedown', (e) => { isDragging = true; offsetX = e.clientX - floatingPanel.getBoundingClientRect().left; offsetY = e.clientY - floatingPanel.getBoundingClientRect().top; e.preventDefault(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const x = e.clientX - offsetX; const y = e.clientY - offsetY; const maxX = window.innerWidth - floatingPanel.offsetWidth; const maxY = window.innerHeight - floatingPanel.offsetHeight; floatingPanel.style.left = Math.max(0, Math.min(x, maxX)) + 'px'; floatingPanel.style.top = Math.max(0, Math.min(y, maxY)) + 'px'; }); document.addEventListener('mouseup', () => { isDragging = false; }); document.getElementById('spark-save-config-btn').addEventListener('click', saveConfig); document.getElementById('spark-task-start-btn').addEventListener('click', function() { if (GLOBAL_STATE.isTaskRunning) { stopTask(); } else { startTask(); } }); document.getElementById('spark-reset-record-btn').addEventListener('click', resetRecord); document.getElementById('spark-advanced-btn').addEventListener('click', function() { const advancedMenu = document.getElementById('spark-advanced-menu'); advancedMenu.classList.toggle('open'); }); document.getElementById('spark-reset-all-btn').addEventListener('click', resetAllData); document.getElementById('spark-export-config-btn').addEventListener('click', exportConfig); document.getElementById('spark-import-config-btn').addEventListener('click', importConfig); document.getElementById('spark-update-script-btn').addEventListener('click', function() { window.open('https://drive_i.eastblues.cn/d/Others/Douyin-Auto-Keep-Fire.user.js', '_blank'); }); } function updateFloatingStatusPanel() { const floatingContainer = document.getElementById('floating-status-container'); if (!floatingContainer) return; const { targetUsers } = GLOBAL_STATE.config; if (targetUsers.length === 0) { floatingContainer.innerHTML = '
暂无数据……
'; return; } floatingContainer.innerHTML = targetUsers.map(userName => { const record = GLOBAL_STATE.todaySendRecord[userName]; let statusText = '等待发送'; let statusClass = 'status-pending'; let sparkDaysText = '-'; let sendTimeText = '-'; if (record) { sparkDaysText = record.sparkDays || '-'; sendTimeText = record.sendTime || '-'; if (record.isSuccess) { statusText = '发送成功'; statusClass = 'status-success'; } else { statusText = '发送失败'; statusClass = 'status-fail'; } } return `
${userName}
${sparkDaysText}天
${statusText}
${sendTimeText}
`; }).join(''); } window.addEventListener('load', function() { document.title = 'Douyin Auto Keep Fire Work Page'; createControlPanel(); loadConfig(); initTodaySendRecord(); startDayResetTimer(); updateSparkStatusPanel(); }); })();