2025年10月24日星期五

某音火花任务脚本

1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/Bg6shY若失效,可用地址

1.购买服务器

阿里云:

服务器购买地址

https://t.aliyun.com/U/Bg6shY

若失效,可用地址

https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d

腾讯云:

https://curl.qcloud.com/wJpWmSfU

若失效,可用地址

https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console

华为云

https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905

2.部署教程

2024年最新青龙面板跑脚本教程(一)持续更新中

3.代码如下

(function() {    'use strict';
    // 默认配置    const DEFAULT_CONFIG = {        baseMessage: "续火",        sendTime: "00:01:00",        checkInterval: 1000,        maxWaitTime: 30000,        maxRetryCount: 3,        hitokotoTimeout: 60000,        txtApiTimeout: 60000,        useHitokoto: true,        useTxtApi: true// 默认开启TXTAPI        txtApiMode: "manual"// 默认手动模式        txtApiManualRandom: true// 手动模式是否随机选择        customMessage: "—————每日续火—————\n\n[TXTAPI]\n\n—————每日一言—————\n\n[API]\n",        hitokotoFormat: "{hitokoto}\n—— {from}{from_who}",        fromFormat: "{from}",        fromWhoFormat: "「{from_who}」",        txtApiUrl: "https://xxx/?encode=text",        txtApiManualText: "文本1\n文本2\n文本3",        targetUsername: "请修改此处为续火目标用户名"// 新增:目标用户名        maxLogCount: 200 // 最大日志数量    };
    // 状态变量    let isProcessing = false;    let retryCount = 0;    let countdownInterval = null;    let userConfig = {};    let nextSendTime = null;    let chatObserver = null// 聊天列表观察器    let hasClickedTarget = false// 是否已点击目标用户    let initialAttemptTimer = null// 初始尝试定时器    let debounceTimer = null// 防抖定时器    let allLogs = []; // 所有日志记录    let sendProcessObserver = null// 发送流程中的观察器


    // 初始化配置    function initConfig() {        // 从存储中获取用户配置        const savedConfig GM_getValue('userConfig');        userConfig = savedConfig ? {...DEFAULT_CONFIG, ...savedConfig} : {...DEFAULT_CONFIG};
        // 确保所有配置项都存在        for (const key in DEFAULT_CONFIG) {            if (userConfig[key] === undefined) {                userConfig[key] = DEFAULT_CONFIG[key];            }        }
        // 初始化手动文本发送记录        if (!GM_getValue('txtApiManualSentIndexes')) {            GM_setValue('txtApiManualSentIndexes', []);        }
        // 初始化日志记录        allLogs = GM_getValue('allLogs', []);
        GM_setValue('userConfig', userConfig);        return userConfig;    }
    // 保存配置    function saveConfig() {        GM_setValue('userConfig', userConfig);    }
    // 创建UI控制面板    function createControlPanel() {        // 移除可能已存在的面板        const existingPanel = document.getElementById('dy-fire-helper');        if (existingPanel) {            existingPanel.remove();        }
        const panel = document.createElement('div');        panel.id = 'dy-fire-helper';        panel.style.cssText = `            position: fixed;            top: 20px;            right: 20px;            width: 400px;            background: rgba(2552552550.98);            border-radius: 12px;            box-shadow: 0 4px 20px rgba(0000.15);            z-index: 9999;            font-family: 'PingFang SC''Microsoft YaHei', sans-serif;            padding: 15px;            color: #333;            transition: all 0.3s ease;            max-height: 500px;            overflow: hidden;            border: 1px solid #eee;        `;
        panel.innerHTML = `            <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;">                <h3 style="margin: 0; color: #ff2c54; font-size: 16px; display: flex; align-items: center;">                    <span style="display: inline-block; width: 12px; height: 12px; border-radius: 50%; background: #28a745; margin-right: 8px;"></span>                </h3>                <button id="dy-fire-helper-close" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #999;">×</button>            </div>
            <div style="margin-bottom: 15px;">                <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">                    <span style="font-weight: 500;">今日状态:</span>                    <span id="dy-fire-status" style="color: #28a745; font-weight: 600;">已发送</span>                </div>                <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">                    <span style="font-weight: 500;">下次发送:</span>                    <span id="dy-fire-next">2023-11-05 00:01:00</span>                </div>                <div style="display: flex; justify-content: space-between; margin-bottom: 8px;">                    <span style="font-weight: 500;">倒计时:</span>                    <span id="dy-fire-countdown" style="color: #dc3545; font-weight: 700;">23:45:12</span>                </div>                <div style="display: flex; justify-content: space-between;">                    <span style="font-weight: 500;">重试次数:</span>                    <span id="dy-fire-retry">0/${userConfig.maxRetryCount}</span>                </div>                <div style="display: flex; justify-content: between; margin-top: 8px;">                    <span style="font-weight: 500;">一言状态:</span>                    <span id="dy-fire-hitokoto">未获取</span>                </div>                <div style="display: flex; justify-content: between; margin-top: 8px;">                    <span style="font-weight: 500;">TXTAPI状态:</span>                    <span id="dy-fire-txtapi">未获取</span>                </div>                <div style="display: flex; justify-content: between; margin-top: 8px;">                    <span style="font-weight: 500;">聊天目标:</span>                    <span id="dy-fire-chat-target">${hasClickedTarget ? '已找到' : '寻找中'}</span>                </div>            </div>
            <div style="display: flex; flex-direction: column; gap: 10px; margin-bottom: 15px;">                <button id="dy-fire-send" style="padding: 8px 12px; background: #007bff; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500;">立即发送续火消息</button>                <button id="dy-fire-settings" style="padding: 8px 12px; background: #6c757d; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500;">设置</button>                <div style="display: flex; gap: 10px;">                    <button id="dy-fire-history" style="flex: 1; padding: 8px 12px; background: #17a2b8; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500;">历史日志</button>                    <button id="dy-fire-clear" style="flex: 1; padding: 8px 12px; background: #dc3545; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500;">清空记录</button>                </div>                <button id="dy-fire-reset" style="padding: 8px 12px; background: #ffc107; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500;">重置配置</button>            </div>
            <div style="margin-top: 15px; border-top: 1px solid #eee; padding-top: 10px;">                <div style="font-weight: 500; margin-bottom: 8px;">操作日志</div>                <div id="dy-fire-log" style="font-size: 12px; height: 100px; overflow-y: auto; line-height: 1.4;">                    <div style="color: #28a745;">系统已就绪,等待执行...</div>                </div>            </div>        `;
        document.body.appendChild(panel);
        // 添加关闭按钮事件        document.getElementById('dy-fire-helper-close').addEventListener('click', function() {            panel.style.display = 'none';        });
        // 添加按钮事件        document.getElementById('dy-fire-send').addEventListener('click', sendMessage);        document.getElementById('dy-fire-settings').addEventListener('click', showSettingsPanel);        document.getElementById('dy-fire-history').addEventListener('click', showHistoryPanel);        document.getElementById('dy-fire-clear').addEventListener('click', clearData);        document.getElementById('dy-fire-reset').addEventListener('click', resetAllConfig);    }
    // 显示设置面板    function showSettingsPanel() {        // 移除可能已存在的设置面板        const existingSettings = document.getElementById('dy-fire-settings-panel');        if (existingSettings) {            existingSettings.remove();            return;        }
        const settingsPanel = document.createElement('div');        settingsPanel.id = 'dy-fire-settings-panel';        settingsPanel.style.cssText = `            position: fixed;            top: 50%;            left: 50%;            transform: translate(-50%, -50%);            max-width: 90vw;            width: 500px;            background: white;            border-radius: 12px;            box-shadow: 0 5px 25px rgba(0000.2);            z-index: 10000;            padding: 20px;            font-family: 'PingFang SC''Microsoft YaHei', sans-serif;            max-height: 90vh;            overflow-y: auto;            box-sizing: border-box;        `;
        settingsPanel.innerHTML = `            <h3 style="margin: 0 0 20px 0; color: #ff2c54; display: flex; justify-content: space-between; align-items: center;">                <span>设置</span>                <button id="dy-fire-settings-close" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #999;">×</button>            </h3>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">发送时间 (HH:mm:ss)</label>                <input type="text" id="dy-fire-settings-time" value="${userConfig.sendTime}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;" placeholder="例如: 00:01:00">            </div>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">目标用户名</label>                <input type="text" id="dy-fire-settings-target-username" value="${userConfig.targetUsername}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;" placeholder="要自动点击聊天的用户名">            </div>
            <div style="margin-bottom: 15px;">                <label style="display: flex; align-items: center; cursor: pointer;">                    <input type="checkbox" id="dy-fire-settings-use-hitokoto" ${userConfig.useHitokoto ? 'checked' : ''} style="margin-right: 8px;">                    使用一言API                </label>            </div>
            <div style="margin-bottom: 15px;">                <label style="display: flex; align-items: center; cursor: pointer;">                    <input type="checkbox" id="dy-fire-settings-use-txtapi" ${userConfig.useTxtApi ? 'checked' : ''} style="margin-right: 8px;">                    使用TXTAPI                </label>            </div>
            <div id="txt-api-mode-container" style="margin-bottom: 15px; ${userConfig.useTxtApi ? '' : 'display: none;'}">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">TXTAPI模式</label>                <div style="display: flex; gap: 15px;">                    <label style="display: flex; align-items: center; cursor: pointer;">                        <input type="radio" name="txt-api-mode" value="api" ${userConfig.txtApiMode === 'api' ? 'checked' : ''} style="margin-right: 5px;">                        API模式                    </label>                    <label style="display: flex; align-items: center; cursor: pointer;">                        <input type="radio" name="txt-api-mode" value="manual" ${userConfig.txtApiMode === 'manual' ? 'checked' : ''} style="margin-right: 5px;">                        手动模式                    </label>                </div>            </div>
            <div id="txt-api-url-container" style="margin-bottom: 15px; ${userConfig.useTxtApi && userConfig.txtApiMode === 'api' ? '' : 'display: none;'}">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">TXTAPI链接</label>                <input type="text" id="dy-fire-settings-txtapi-url" value="${userConfig.txtApiUrl}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;" placeholder="例如: https://123456.cn">            </div>
            <div id="txt-api-manual-container" style="margin-bottom: 15px; ${userConfig.useTxtApi && userConfig.txtApiMode === 'manual' ? '' : 'display: none;'}">                <div style="margin-bottom: 10px;">                    <label style="display: flex; align-items: center; cursor: pointer;">                        <input type="checkbox" id="dy-fire-settings-txtapi-random" ${userConfig.txtApiManualRandom ? 'checked' : ''} style="margin-right: 8px;">                        随机选择文本                    </label>                </div>                <label style="display: block; margin-bottom: 5px; font-weight: 500;">手动文本内容(一行一个)</label>                <textarea id="dy-fire-settings-txtapi-manual" style="width: 100%; height: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; box-sizing: border-box;">${userConfig.txtApiManualText}</textarea>            </div>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">最大重试次数</label>                <input type="number" id="dy-fire-settings-retry-count" min="1" max="10" value="${userConfig.maxRetryCount}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;">            </div>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">一言API格式</label>                <textarea id="dy-fire-settings-hitokoto-format" style="width: 100%; height: 60px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; box-sizing: border-box;">${userConfig.hitokotoFormat}</textarea>                <div style="font-size: 12px; color: #666; margin-top: 5px;">                    可用变量: {hitokoto} {from} {from_who}<br>                    示例: {hitokoto} ------ {from}{from_who}                </div>            </div>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">from格式</label>                <input type="text" id="dy-fire-settings-from-format" value="${userConfig.fromFormat}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;" placeholder="例如: {from}">                <div style="font-size: 12px; color: #666; margin-top: 5px;">                    当from不为空时显示此格式,为空时不显示                </div>            </div>
            <div style="margin-bottom: 15px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">from_who格式</label>                <input type="text" id="dy-fire-settings-from-who-format" value="${userConfig.fromWhoFormat}" style="width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; box-sizing: border-box;" placeholder="例如: 「{from_who}」">                <div style="font-size: 12px; color: #666; margin-top: 5px;">                    当from_who不为空时显示此格式,为空时不显示                </div>            </div>
            <div style="margin-bottom: 20px;">                <label style="display: block; margin-bottom: 5px; font-weight: 500;">自定义消息内容</label>                <textarea id="dy-fire-settings-custom-message" style="width: 100%; height: 100px; padding: 8px; border: 1px solid #ddd; border-radius: 4px; resize: vertical; box-sizing: border-box;">${userConfig.customMessage}</textarea>                <div style="font-size: 12px; color: #666; margin-top: 5px;">                    使用 [API] 作为一言内容的占位符<br>                    使用 [TXTAPI] 作为TXTAPI内容的占位符<br>                    支持换行符,关闭API时占位符标记将保留                </div>            </div>
            <button id="dy-fire-settings-save" style="width: 100%; padding: 10px; background: #28a745; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; box-sizing: border-box;">保存设置</button>        `;
        document.body.appendChild(settingsPanel);
        // 添加TXTAPI模式切换事件        const modeRadios = document.querySelectorAll('input[name="txt-api-mode"]');        modeRadios.forEach(radio => {            radio.addEventListener('change', function() {                const mode = this.value;                document.getElementById('txt-api-url-container').style.display = mode === 'api' ? 'block' 'none';                document.getElementById('txt-api-manual-container').style.display = mode === 'manual' ? 'block' 'none';            });        });
        // 添加TXTAPI开关切换事件        document.getElementById('dy-fire-settings-use-txtapi').addEventListener('change', function() {            const useTxtApi = this.checked;            document.getElementById('txt-api-mode-container').style.display = useTxtApi ? 'block' : 'none';
            // 根据当前选择的模式显示相应的表单            const currentMode = document.querySelector('input[name="txt-api-mode"]:checked').value;            document.getElementById('txt-api-url-container').style.display = (useTxtApi && currentMode === 'api') ? 'block' : 'none';            document.getElementById('txt-api-manual-container').style.display = (useTxtApi && currentMode === 'manual') ? 'block' : 'none';        });
        // 添加关闭按钮事件        document.getElementById('dy-fire-settings-close').addEventListener('click', function() {            settingsPanel.remove();        });
        // 添加保存按钮事件        document.getElementById('dy-fire-settings-save').addEventListener('click', function() {            const timeValue = document.getElementById('dy-fire-settings-time').value;            const targetUsername = document.getElementById('dy-fire-settings-target-username').value;            const useHitokoto = document.getElementById('dy-fire-settings-use-hitokoto').checked;            const useTxtApi = document.getElementById('dy-fire-settings-use-txtapi').checked;            const txtApiMode = document.querySelector('input[name="txt-api-mode"]:checked').value;            const txtApiRandom = document.getElementById('dy-fire-settings-txtapi-random').checked;            const txtApiUrl = document.getElementById('dy-fire-settings-txtapi-url').value;            const txtApiManualText = document.getElementById('dy-fire-settings-txtapi-manual').value;            const maxRetryCount parseInt(document.getElementById('dy-fire-settings-retry-count').value, 10);            const hitokotoFormat = document.getElementById('dy-fire-settings-hitokoto-format').value;            const fromFormat = document.getElementById('dy-fire-settings-from-format').value;            const fromWhoFormat = document.getElementById('dy-fire-settings-from-who-format').value;            const customMessage = document.getElementById('dy-fire-settings-custom-message').value;
            // 验证时间格式 (HH:mm:ss)            if (!/^([0-1]?[0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/.test(timeValue)) {                addLog('时间格式错误,请使用HH:mm:ss格式''error');                return;            }
            // 验证重试次数            if (isNaN(maxRetryCount) || maxRetryCount < 1 || maxRetryCount > 10) {                addLog('重试次数必须是1-10之间的数字''error');                return;            }
            // 验证TXTAPI链接(仅在API模式下)            if (useTxtApi && txtApiMode === 'api' && !txtApiUrl) {                addLog('请填写TXTAPI链接''error');                return;            }
            // 验证手动文本(仅在手动模式下)            if (useTxtApi && txtApiMode === 'manual' && !txtApiManualText.trim()) {                addLog('请填写手动文本内容''error');                return;            }
            // 更新配置            userConfig.sendTime = timeValue;            userConfig.targetUsername = targetUsername;            userConfig.useHitokoto = useHitokoto;            userConfig.useTxtApi = useTxtApi;            userConfig.txtApiMode = txtApiMode;            userConfig.txtApiManualRandom = txtApiRandom;            userConfig.txtApiUrl = txtApiUrl;            userConfig.txtApiManualText = txtApiManualText;            userConfig.maxRetryCount = maxRetryCount;            userConfig.hitokotoFormat = hitokotoFormat;            userConfig.fromFormat = fromFormat;            userConfig.fromWhoFormat = fromWhoFormat;            userConfig.customMessage = customMessage;
            // 保存配置            saveConfig();
            // 更新状态            updateStatus(GM_getValue('lastSentDate''') === new Date().toDateString());            updateRetryCount();
            // 关闭设置面板            settingsPanel.remove();
            addLog('设置已保存''success');
            // 重启聊天观察器            initDynamicChatClick();        });    }
    // 显示历史日志面板    function showHistoryPanel() {        // 移除可能已存在的历史面板        const existingHistory = document.getElementById('dy-fire-history-panel');        if (existingHistory) {            existingHistory.remove();            return;        }
        const historyPanel = document.createElement('div');        historyPanel.id = 'dy-fire-history-panel';        historyPanel.style.cssText = `            position: fixed;            top: 50%;            left: 50%;            transform: translate(-50%, -50%);            max-width: 90vw;            width: 600px;            background: white;            border-radius: 12px;            box-shadow: 0 5px 25px rgba(0000.2);            z-index: 10000;            padding: 20px;            font-family: 'PingFang SC''Microsoft YaHei', sans-serif;            max-height: 90vh;            overflow: hidden;            display: flex;            flex-direction: column;            box-sizing: border-box;        `;
        // 获取所有日志        const logs GM_getValue('allLogs', []);
        historyPanel.innerHTML = `            <h3 style="margin: 0 0 20px 0; color: #ff2c54; display: flex; justify-content: space-between; align-items: center;">                <span>历史日志 (${logs.length} 条)</span>                <button id="dy-fire-history-close" style="background: none; border: none; font-size: 20px; cursor: pointer; color: #999;">×</button>            </h3>
            <div style="display: flex; justify-content: space-between; margin-bottom: 10px;">                <button id="dy-fire-history-clear" style="padding: 6px 12px; background: #dc3545; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">清空所有日志</button>                <button id="dy-fire-history-export" style="padding: 6px 12px; background: #17a2b8; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">导出日志</button>            </div>
            <div id="dy-fire-history-content" style="flex: 1; overflow-y: auto; border: 1px solid #eee; border-radius: 4px; padding: 10px; font-size: 12px; line-height: 1.4;">                ${logs.length === 0 ? '<div style="text-align: center; color: #999; padding: 20px;">暂无历史日志</div>' : ''}                ${logs.map(log => `                    <div style="margin-bottom: 5px; color: ${log.type === 'success' ? '#28a745' : log.type === 'error' ? '#dc3545' : log.type === 'warning' ? '#ffc107' : '#17a2b8'};">                        ${log.time} - ${log.message}                    </div>                `).join('')}            </div>        `;
        document.body.appendChild(historyPanel);
        // 添加关闭按钮事件        document.getElementById('dy-fire-history-close').addEventListener('click', function() {            historyPanel.remove();        });
        // 添加清空日志按钮事件        document.getElementById('dy-fire-history-clear').addEventListener('click', function() {            if (confirm('确定要清空所有历史日志吗?此操作不可恢复。')) {                GM_setValue('allLogs', []);                allLogs = [];                document.getElementById('dy-fire-history-content').innerHTML = '<div style="text-align: center; color: #999; padding: 20px;">暂无历史日志</div>';                addLog('历史日志已清空''info');            }        });
        // 添加导出日志按钮事件        document.getElementById('dy-fire-history-export').addEventListener('click', function() {            if (logs.length === 0) {                alert('没有日志可导出');                return;            }
            const logText = logs.map(log => `${log.time} - ${log.message}`).join('\n');            const blob new Blob([logText], { type'text/plain' });            const url = URL.createObjectURL(blob);            const = document.createElement('a');            a.href = url;            a.download = `抖音续火助手日志_${new Date().toISOString().slice(010)}.txt`;            document.body.appendChild(a);            a.click();            document.body.removeChild(a);            URL.revokeObjectURL(url);
            addLog('日志已导出''success');        });
        // 自动滚动到底部        const content = document.getElementById('dy-fire-history-content');        if (content) {            content.scrollTop = content.scrollHeight;        }    }
    // 添加日志    function addLog(message, type = 'info'{        const now new Date();        const timeString = now.toLocaleTimeString();        const logEntry = {            time: timeString,            message: message,            type: type        };
        // 添加到内存中的日志        allLogs.push(logEntry);
        // 限制日志数量        if (allLogs.length > userConfig.maxLogCount) {            allLogs = allLogs.slice(-userConfig.maxLogCount);        }
        // 保存到存储        GM_setValue('allLogs', allLogs);
        const logEntryElement = document.createElement('div');        logEntryElement.style.color = type === 'success' ? '#28a745' : type === 'error' ? '#dc3545' : type === 'warning' ? '#ffc107' : '#17a2b8';        logEntryElement.textContent = `${timeString} - ${message}`;
        const logContainer = document.getElementById('dy-fire-log');        if (logContainer) {            logContainer.prepend(logEntryElement);
            // 限制日志数量            if (logContainer.children.length > 8) {                logContainer.removeChild(logContainer.lastChild);            }
            // 自动滚动到顶部            logContainer.scrollTop = 0;        }    }
    // 更新重试计数显示    function updateRetryCount() {        const retryElement = document.getElementById('dy-fire-retry');        if (retryElement) {            retryElement.textContent = `${retryCount}/${userConfig.maxRetryCount}`;        }    }
    // 更新一言状态显示    function updateHitokotoStatus(status, isSuccess = true{        const statusEl = document.getElementById('dy-fire-hitokoto');        if (statusEl) {            statusEl.textContent = status;            statusEl.style.color = isSuccess ? '#28a745' : '#dc3545';        }    }
    // 更新TXTAPI状态显示    function updateTxtApiStatus(status, isSuccess = true{        const statusEl = document.getElementById('dy-fire-txtapi');        if (statusEl) {            statusEl.textContent = status;            statusEl.style.color = isSuccess ? '#28a745' : '#dc3545';        }    }
    // 更新聊天目标状态显示    function updateChatTargetStatus(status, isSuccess = true{        const statusEl = document.getElementById('dy-fire-chat-target');        if (statusEl) {            statusEl.textContent = status;            statusEl.style.color = isSuccess ? '#28a745' : '#dc3545';        }    }
    // 获取消息内容    async function getMessageContent() {        let customMessage = userConfig.customMessage || userConfig.baseMessage;
        // 获取一言内容        let hitokotoContent = '';        if (userConfig.useHitokoto) {            try {                addLog('正在获取一言内容...''info');                hitokotoContent = await getHitokoto();                addLog('一言内容获取成功''success');            } catch (error) {                addLog(`一言获取失败: ${error.message}`, 'error');                hitokotoContent = '一言获取失败~';            }        }
        // 获取TXTAPI内容        let txtApiContent = '';        if (userConfig.useTxtApi) {            try {                addLog('正在获取TXTAPI内容...''info');                txtApiContent = await getTxtApiContent();                addLog('TXTAPI内容获取成功''success');            } catch (error) {                addLog(`TXTAPI获取失败: ${error.message}`, 'error');                txtApiContent = 'TXTAPI获取失败~';            }        }
        // 替换自定义消息中的占位符        if (customMessage.includes('[API]')) {            customMessage = customMessage.replace('[API]', hitokotoContent);        } else if (userConfig.useHitokoto) {            customMessage += ` | ${hitokotoContent}`;        }
        if (customMessage.includes('[TXTAPI]')) {            customMessage = customMessage.replace('[TXTAPI]', txtApiContent);        } else if (userConfig.useTxtApi) {            customMessage += ` | ${txtApiContent}`;        }
        return customMessage;    }
    // 获取一言内容    function getHitokoto() {        return new Promise((resolve, reject) => {            const timeout setTimeout(() => {                reject(new Error('一言API请求超时'));            }, userConfig.hitokotoTimeout);
            GM_xmlhttpRequest({                method'GET',                url'https://hit.cn/',                responseType'json',                onload: function(response) {                    clearTimeout(timeout);                    if (response.status === 200) {                        try {                            const data = response.response;                            let message = formatHitokoto(userConfig.hitokotoFormat, data);
                            updateHitokotoStatus('获取成功');                            resolve(message);                        } catch (e) {                            updateHitokotoStatus('解析失败'false);                            reject(new Error('一言API响应解析失败'));                        }                    } else {                        updateHitokotoStatus('请求失败'false);                        reject(new Error(`一言API请求失败: ${response.status}`));                    }                },                onerror: function(error{                    clearTimeout(timeout);                    updateHitokotoStatus('网络错误'false);                    reject(new Error('一言API网络错误'));                },                ontimeout: function() {                    clearTimeout(timeout);                    updateHitokotoStatus('请求超时'false);                    reject(new Error('一言API请求超时'));                }            });        });    }
    // 格式化一言内容    function formatHitokoto(format, data{        // 首先替换基本变量        let result = format            .replace(/{hitokoto}/g, data.hitokoto || '');
        // 格式化from        let fromFormatted = '';        if (data.from) {            fromFormatted = userConfig.fromFormat.replace(/{from}/g, data.from);        }        result = result.replace(/{from}/g, fromFormatted);
        // 格式化from_who        let fromWhoFormatted = '';        if (data.from_who) {            fromWhoFormatted = userConfig.fromWhoFormat.replace(/{from_who}/g, data.from_who);        }        result = result.replace(/{from_who}/g, fromWhoFormatted);
        return result;    }
    // 获取TXTAPI内容    function getTxtApiContent() {        return new Promise((resolve, reject) => {            if (userConfig.txtApiMode === 'api') {                // API模式                const timeout setTimeout(() => {                    reject(new Error('TXTAPI请求超时'));                }, userConfig.txtApiTimeout);
                GM_xmlhttpRequest({                    method'GET',                    url: userConfig.txtApiUrl,                    onload: function(response) {                        clearTimeout(timeout);                        if (response.status === 200) {                            try {                                updateTxtApiStatus('获取成功');                                resolve(response.responseText.trim());                            } catch (e) {                                updateTxtApiStatus('解析失败'false);                                reject(new Error('TXTAPI响应解析失败'));                            }                        } else {                            updateTxtApiStatus('请求失败'false);                            reject(new Error(`TXTAPI请求失败: ${response.status}`));                        }                    },                    onerror: function(error{                        clearTimeout(timeout);                        updateTxtApiStatus('网络错误'false);                        reject(new Error('TXTAPI网络错误'));                    },                    ontimeout: function() {                        clearTimeout(timeout);                        updateTxtApiStatus('请求超时'false);                        reject(new Error('TXTAPI请求超时'));                    }                });            } else {                // 手动模式                try {                    const lines = userConfig.txtApiManualText.split('\n').filter(line => line.trim());                    if (lines.length === 0) {                        updateTxtApiStatus('无内容'false);                        reject(new Error('手动文本内容为空'));                        return;                    }
                    // 获取已发送的索引                    let sentIndexes = GM_getValue('txtApiManualSentIndexes', []);
                    if (userConfig.txtApiManualRandom) {                        // 随机模式:找出未发送的索引                        let availableIndexes = [];                        for (let i = 0; i < lines.length; i++) {                            if (!sentIndexes.includes(i)) {                                availableIndexes.push(i);                            }                        }
                        // 如果所有行都已发送,重置已发送记录                        if (availableIndexes.length === 0) {                            sentIndexes = [];                            availableIndexes = Array.from({length: lines.length}, (_, i) => i);                            GM_setValue('txtApiManualSentIndexes', []);                        }
                        // 随机选择一个未发送的索引                        const randomIndex = Math.floor(Math.random() * availableIndexes.length);                        const selectedIndex = availableIndexes[randomIndex];                        const selectedText = lines[selectedIndex].trim();
                        // 记录已发送的索引                        sentIndexes.push(selectedIndex);                        GM_setValue('txtApiManualSentIndexes', sentIndexes);
                        updateTxtApiStatus('获取成功');                        resolve(selectedText);                    } else {                        // 顺序模式:按顺序选择下一行                        let nextIndex = 0;                        if (sentIndexes.length > 0) {                            nextIndex = (sentIndexes[sentIndexes.length - 1] + 1) % lines.length;                        }
                        const selectedText = lines[nextIndex].trim();
                        // 记录已发送的索引                        sentIndexes.push(nextIndex);                        GM_setValue('txtApiManualSentIndexes', sentIndexes);
                        updateTxtApiStatus('获取成功');                        resolve(selectedText);                    }                } catch (e) {                    updateTxtApiStatus('解析失败'false);                    reject(new Error('手动文本解析失败'));                }            }        });    }
    // 发送消息函数    async function sendMessage() {        if (isProcessing) {            addLog('已有任务正在进行中''error');            return;        }        // 检查是否今天已发送        const lastSentDate GM_getValue('lastSentDate''');        const today new Date().toDateString();        if (lastSentDate === today) {            addLog('今天已经发送过消息''info');            return;        }        isProcessing = true;        retryCount = 0;        updateRetryCount();        addLog('开始发送流程...''info');        // 执行发送流程        executeSendProcess();    }
    // 执行发送流程    async function executeSendProcess() {        retryCount++;        updateRetryCount();        if (retryCount > userConfig.maxRetryCount) {            addLog(`已达到最大重试次数 (${userConfig.maxRetryCount})`, 'error');            isProcessing = false;            return;        }        addLog(`尝试发送 (${retryCount}/${userConfig.maxRetryCount})`, 'info');
        // 根据目标用户状态决定下一步操作        if (!hasClickedTarget) {            // 如果目标用户未找到,先查找并点击目标用户            addLog('目标用户未找到,先查找目标用户...''info');            findAndClickTargetUser();        } else {            // 如果目标用户已找到,直接查找聊天输入框            addLog('目标用户已找到,直接查找聊天输入框...''info');            setTimeout(tryFindChatInput, 1000);        }    }
    // 查找并点击目标用户    function findAndClickTargetUser() {        addLog(`开始查找目标用户: ${userConfig.targetUsername}`, 'info');
        // 停止之前的观察器        if (sendProcessObserver) {            sendProcessObserver.disconnect();            sendProcessObserver = null;        }
        // 先立即尝试一次查找        if (tryClickTargetUser()) {            return;        }
        // 如果没找到,启动观察器监听DOM变化        sendProcessObserver = new MutationObserver((mutations) => {            if (hasClickedTarget) {                sendProcessObserver.disconnect();                return;            }
            for (let mutation of mutations) {                if (mutation.addedNodes.length > 0) {                    if (tryClickTargetUser()) {                        sendProcessObserver.disconnect();                        return;                    }                }            }        });
        // 开始观察整个文档的变化        sendProcessObserver.observe(document.body, {            childListtrue,            subtreetrue        });
        // 设置超时,如果一段时间内没找到就重试        setTimeout(() => {            if (!hasClickedTarget && sendProcessObserver) {                sendProcessObserver.disconnect();                addLog('查找目标用户超时,重试中...''warning');                setTimeout(executeSendProcess, 2000);            }        }, 10000); // 10秒超时    }
    // 尝试点击目标用户    function tryClickTargetUser() {        if (hasClickedTarget) return true;
        try {            // 使用多种选择器尝试查找目标用户            const selectors = [                // 抖音聊天列表常见的选择器                '[class*="name"]',                '[class*="username"]',                '[class*="header"]',                '[class*="item"]',                '[class*="contact"]',                '[class*="list"] [class*="name"]',                '[class*="chat"] [class*="name"]',                '[class*="message"] [class*="name"]'            ];
            for (let selector of selectors) {                const elements = document.querySelectorAll(selector);                for (let element of elements) {                    if (element.textContent && element.textContent.trim() === userConfig.targetUsername) {                        // 找到目标用户,点击它                        element.click();                        addLog(`成功点击动态加载的用户: ${userConfig.targetUsername}`, 'success');                        hasClickedTarget = true;                        updateChatTargetStatus('已找到'true);
                        // 等待聊天界面加载,然后查找聊天输入框                        setTimeout(() => {                            addLog('目标用户已点击,等待聊天界面加载...''info');                            setTimeout(tryFindChatInput, 2000);                        }, 1000);
                        return true;                    }                }            }
            return false;        } catch (error) {            addLog(`寻找目标用户时出错: ${error.message}`, 'error');            return false;        }    }
    // 尝试查找聊天输入框并发送消息    async function tryFindChatInput() {        const inputSelectors = [            '.chat-input-dccKiL',            '[class*="input"]',            '[class*="textarea"]',            '[contenteditable="true"]',            'textarea',            'input[type="text"]'        ];
        let input = null;        for (let selector of inputSelectors) {            input = document.querySelector(selector);            if (input) break;        }
        if (input) {            addLog('找到聊天输入框''info');            // 获取消息内容            let messageToSend;            try {                messageToSend = await getMessageContent();                addLog('消息内容准备完成''success');            } catch (error) {                addLog(`消息获取失败: ${error.message}`, 'error');                messageToSend = `${userConfig.baseMessage} | 消息获取失败~`;            }
            // 清空输入框            input.textContent = '';            // 输入消息            input.focus();
            // 处理换行符            const lines = messageToSend.split('\n');            for (let i = 0; i < lines.length; i++) {                document.execCommand('insertText'false, lines[i]);                if (i < lines.length - 1) {                    // 插入换行符                    document.execCommand('insertLineBreak');                }            }
            input.dispatchEvent(new Event('input', { bubblestrue }));
            // 查找发送按钮            const sendBtnSelectors = [                '.chat-btn',                '[class*="send"]',                '[class*="button"]:not([disabled])',                'button:not([disabled])'            ];
            let sendBtn = null;            for (let selector of sendBtnSelectors) {                sendBtn = document.querySelector(selector);                if (sendBtn) break;            }
            // 检查发送按钮状态            setTimeout(() => {                if (sendBtn && !sendBtn.disabled) {                    addLog('正在发送消息...''info');                    sendBtn.click();                    // 确认发送成功                    setTimeout(() => {                        addLog('消息发送成功!''success');                        // 立即记录发送状态                        const today new Date().toDateString();                        GM_setValue('lastSentDate', today);                        // 更新状态显示                        updateStatus(true);                        isProcessing = false;                        // 显示通知                        if (typeof GM_notification !== 'undefined') {                            try {                                GM_notification({                                    title'抖音续火助手',                                    text'续火消息发送成功!',                                    timeout3000                                });                            } catch (e) {                                GM_notification('续火消息发送成功!''抖音续火助手');                            }                        }                    }, 1000);                } else {                    addLog('发送按钮不可用或未找到''error');                    setTimeout(executeSendProcess, 2000);                }            }, 500);        } else {            addLog('未找到输入框,重试中...''error');            setTimeout(executeSendProcess, 2000);        }    }
    // 解析时间字符串为日期对象    function parseTimeString(timeStr{        const [hours, minutes, seconds] = timeStr.split(':').map(Number);        const now new Date();        const targetTime new Date(now);        targetTime.setHours(hours, minutes, seconds || 00);
        // 如果目标时间已经过去,设置为明天        if (targetTime <= now) {            targetTime.setDate(targetTime.getDate() + 1);        }
        return targetTime;    }
    // 更新状态    function updateStatus(isSent{        const statusEl = document.getElementById('dy-fire-status');        if (statusEl) {            if (isSent) {                statusEl.textContent = '已发送';                statusEl.style.color = '#28a745';            } else {                statusEl.textContent = '未发送';                statusEl.style.color = '#dc3545';
                // 如果是"未发送"状态,检查是否需要自动发送                autoSendIfNeeded();            }        }
        // 更新下次发送时间        const now new Date();
        if (isSent) {            // 如果已发送,下次发送时间是明天指定时间            nextSendTime = parseTimeString(userConfig.sendTime);            // 确保是明天而不是后天            const tomorrow new Date(now);            tomorrow.setDate(tomorrow.getDate() + 1);            if (nextSendTime.getDate() !== tomorrow.getDate()) {                nextSendTime.setDate(tomorrow.getDate());            }        } else {            // 如果未发送,检查当前时间是否已过指定时间            nextSendTime = parseTimeString(userConfig.sendTime);
            // 如果目标时间已经过去,设置为明天            if (nextSendTime <= now) {                nextSendTime.setDate(nextSendTime.getDate() + 1);            }        }
        const nextElement = document.getElementById('dy-fire-next');        if (nextElement) {            nextElement.textContent = nextSendTime.toLocaleString();        }
        // 开始倒计时        startCountdown(nextSendTime);    }
    // 检查是否需要自动发送    function autoSendIfNeeded() {        const now new Date();        const today new Date().toDateString();        const lastSentDate GM_getValue('lastSentDate''');
        // 解析发送时间        const [targetHour, targetMinute, targetSecond] = userConfig.sendTime.split(':').map(Number);
        // 如果今天未发送且当前时间已过指定时间,则自动发送        if (lastSentDate !== today) {            const targetTimeToday new Date();            targetTimeToday.setHours(targetHour, targetMinute, targetSecond || 00);
            if (now >= targetTimeToday && !isProcessing) {                addLog(`检测到今日未发送且已过${userConfig.sendTime},自动发送`, 'info');                sendMessage();            }        }    }
    // 开始倒计时    function startCountdown(targetTime{        // 清除之前的倒计时        if (countdownInterval) {            clearInterval(countdownInterval);        }
        function update() {            const now new Date();            const diff = targetTime - now;
            if (diff <= 0) {                const countdownElement = document.getElementById('dy-fire-countdown');                if (countdownElement) {                    countdownElement.textContent = '00:00:00';                }
                // 检查是否今天已发送                const lastSentDate GM_getValue('lastSentDate''');                const today new Date().toDateString();
                if (lastSentDate === today) {                    // 如果已发送,设置明天指定时间为目标时间                    nextSendTime = parseTimeString(userConfig.sendTime);                    // 确保是明天而不是后天                    const tomorrow new Date(now);                    tomorrow.setDate(tomorrow.getDate() + 1);                    if (nextSendTime.getDate() !== tomorrow.getDate()) {                        nextSendTime.setDate(tomorrow.getDate());                    }                    startCountdown(nextSendTime);                } else {                    // 如果未发送,清空数据并发送消息                    if (!isProcessing) {                        // 清空数据,避免重复发送                        GM_setValue('lastSentDate''');                        updateStatus(false);                        addLog('已清空发送记录,准备发送新消息''info');                        sendMessage();                    }                }                return;            }
            const hours = Math.floor(diff / (1000 * 60 * 60));            const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));            const seconds = Math.floor((diff % (1000 * 60)) / 1000);
            const countdownElement = document.getElementById('dy-fire-countdown');            if (countdownElement) {                countdownElement.textContent =                    `${hours.toString().padStart(2'0')}:${minutes.toString().padStart(2'0')}:${seconds.toString().padStart(2'0')}`;            }        }
        update();        countdownInterval = setInterval(update, 1000);    }
    // 动态聊天列表点击功能    function initDynamicChatClick() {        // 停止之前的观察器和定时器        stopDynamicChatClick();
        // 刷新页面后重置为寻找中        hasClickedTarget = false;        updateChatTargetStatus('寻找中'false);
        function clickIfFound() {            if (hasClickedTarget) return;
            try {                // 使用多种选择器尝试查找目标用户                const selectors = [                    '[class*="name"]',                    '[class*="username"]',                    '[class*="header"]',                    '[class*="item"]',                    '[class*="contact"]',                    '[class*="list"] [class*="name"]',                    '[class*="chat"] [class*="name"]',                    '[class*="message"] [class*="name"]'                ];
                for (let selector of selectors) {                    const elements = document.querySelectorAll(selector);                    for (let element of elements) {                        if (element.textContent && element.textContent.trim() === userConfig.targetUsername) {                            element.click();                            addLog(`成功点击动态加载的用户: ${userConfig.targetUsername}`, 'success');                            hasClickedTarget = true;                            updateChatTargetStatus('已找到'true);
                            // 找到后停止观察器                            stopDynamicChatClick();                            return true;                        }                    }                }                return false;            } catch (error) {                addLog(`寻找目标用户时出错: ${error.message}`, 'error');                return false;            }        }
        // 使用MutationObserver监听DOM变化,但限制观察范围        chatObserver = new MutationObserver((mutations) => {            // 修复:检查mutations是否为可迭代对象            if (!mutations || typeof mutations[Symbol.iterator] !== 'function') {                return;            }
            try {                for (let mutation of mutations) {                    if (mutation.addedNodes.length > 0 && !hasClickedTarget) {                        clickIfFound();                        break;                    }                }            } catch (error) {                addLog(`处理DOM变化时出错: ${error.message}`, 'error');            }        });
        // 只观察body的子节点变化,减少性能消耗        try {            chatObserver.observe(document.body, {                childListtrue,                subtreetrue // 改为true,观察所有子节点            });        } catch (error) {            addLog(`启动DOM观察器时出错: ${error.message}`, 'error');        }
        // 初始尝试,但限制尝试次数        let initialAttempts = 0;        const maxInitialAttempts 5;
        function initialTry() {            if (hasClickedTarget || initialAttempts >= maxInitialAttempts) return;
            if (clickIfFound()) {                return;            }
            initialAttempts++;            if (initialAttempts < maxInitialAttempts) {                initialAttemptTimer = setTimeout(initialTry, 1000);            } else {                updateChatTargetStatus('未找到'false);                addLog(`未找到目标用户: ${userConfig.targetUsername}`, 'warning');            }        }
        // 延迟初始尝试,等待页面加载        initialAttemptTimer = setTimeout(initialTry, 2000);    }
    // 停止动态聊天点击功能    function stopDynamicChatClick() {        if (chatObserver) {            chatObserver.disconnect();            chatObserver = null;        }
        if (sendProcessObserver) {            sendProcessObserver.disconnect();            sendProcessObserver = null;        }
        if (initialAttemptTimer) {            clearTimeout(initialAttemptTimer);            initialAttemptTimer = null;        }
        if (debounceTimer) {            clearTimeout(debounceTimer);            debounceTimer = null;        }    }
    // 清空数据    function clearData() {        GM_setValue('lastSentDate''');        GM_setValue('txtApiManualSentIndexes', []);        addLog('发送记录已清空''info');        updateStatus(false);        retryCount = 0;        updateRetryCount();        updateHitokotoStatus('未获取');        updateTxtApiStatus('未获取');        hasClickedTarget = false// 重置目标用户状态        updateChatTargetStatus('寻找中'false);
        // 重启聊天观察器        initDynamicChatClick();    }
    // 重置所有配置    function resetAllConfig() {        // 获取所有存储的值并删除        if (typeof GM_listValues !== 'undefined' && typeof GM_deleteValue !== 'undefined') {            try {                const values GM_listValues();                values.forEach(key => {                    GM_deleteValue(key);                });            } catch (e) {                GM_setValue('lastSentDate''');                GM_setValue('userConfig''');                GM_setValue('txtApiManualSentIndexes', []);                GM_setValue('allLogs', []);            }        } else {            // 如果GM_listValues不可用,只删除已知的key            GM_setValue('lastSentDate''');            GM_setValue('userConfig''');            GM_setValue('txtApiManualSentIndexes', []);            GM_setValue('allLogs', []);        }
        // 重新初始化配置        initConfig();
        addLog('所有配置已重置''info');        updateStatus(false);        retryCount = 0;        updateRetryCount();        updateHitokotoStatus('未获取');        updateTxtApiStatus('未获取');        hasClickedTarget = false// 重置目标用户状态        updateChatTargetStatus('寻找中'false);
        // 显示通知        if (typeof GM_notification !== 'undefined') {            try {                GM_notification({                    title'抖音续火助手',                    text'所有配置已重置!',                    timeout3000                });            } catch (e) {                GM_notification('所有配置已重置!''抖音续火助手');            }        }
        // 重启聊天观察器        initDynamicChatClick();    }
    // 初始化函数    function init() {

        // 初始化配置        initConfig();
        // 创建控制面板        createControlPanel();
        // 初始化动态聊天列表点击功能        initDynamicChatClick();
        // 检查今天是否已发送        const lastSentDate GM_getValue('lastSentDate''');        const today new Date().toDateString();        const isSentToday = lastSentDate === today;
        // 更新状态        updateStatus(isSentToday);
        // 注册菜单命令        if (typeof GM_registerMenuCommand !== 'undefined') {            try {                GM_registerMenuCommand('抖音续火助手-显示面板', function() {                    const panel = document.getElementById('dy-fire-helper');                    if (panel) {                        panel.style.display = 'block';                    }                });                GM_registerMenuCommand('立即发送续火消息', sendMessage);                GM_registerMenuCommand('设置', showSettingsPanel);                GM_registerMenuCommand('历史日志', showHistoryPanel);                GM_registerMenuCommand('清空发送记录', clearData);                GM_registerMenuCommand('重置所有配置', resetAllConfig);            } catch (e) {                addLog('菜单命令注册失败,使用面板控制''error');            }        }
        addLog('抖音续火助手已启动''info');
        // 优化定时检查:改为每5分钟检查一次,而不是每分钟        setInterval(() => {            const now new Date();            const [targetHour, targetMinute] = userConfig.sendTime.split(':').map(Number);
            // 只在目标时间前后5分钟内进行检查            if (Math.abs(now.getHours() - targetHour) <= 1 &&                 Math.abs(now.getMinutes() - targetMinute) <= 5) {                checkAndSend();            }        }, 300000); // 5分钟检查一次
        function checkAndSend() {            const now new Date();            const [targetHour, targetMinute] = userConfig.sendTime.split(':').map(Number);
            if (now.getHours() === targetHour && now.getMinutes() === targetMinute) {                const lastSentDate GM_getValue('lastSentDate''');                const today new Date().toDateString();
                if (lastSentDate !== today && !isProcessing) {                    addLog('定时检查:准备发送消息''info');                    sendMessage();                }            }        }    }
    // 等待页面加载完成后初始化    if (document.readyState === 'loading') {        document.addEventListener('DOMContentLoaded', init);    } else {        init();    }})();

解析

该脚本为某音续火花自动发送脚本。

主要作用

  1. 定时自动续火
    每天在指定时间(默认 00:01:00)自动给目标用户发送续火消息,支持重试、倒计时与状态展示。

  2. 内容自动拼装

    • 调用「一言」API(v1.hitokoto.cn)并按自定义格式插入。

    • TXTAPI 两种模式:远程 API 拉取文本,或本地手动文本库(顺序/随机且带"已发送索引"轮转机制)。

    • 将上述内容替换到自定义模板占位符 [API][TXTAPI] 中生成最终消息。

  3. 自动定位聊天对象并发送
    通过多套选择器+MutationObserver 动态查找聊天列表中的目标用户名并点击进入会话;找到输入框后自动填入多行文本并点击发送按钮。

  4. 可视化控制面板 + 历史日志
    浮动面板展示今日状态、下次发送时间、倒计时、重试次数、一言/TXTAPI状态、目标用户状态;提供"立即发送/设置/历史日志/清空记录/重置配置"等操作,并持久化运行日志(可导出)。

主要方法

配置与面板

  • initConfig() / saveConfig()
    合并默认与已保存配置,确保字段完整;保存到 GM_setValue

  • createControlPanel()
    搭建右上角悬浮面板(状态、按钮、日志区域),绑定按钮事件:立即发送、设置、历史日志、清空、重置。

  • showSettingsPanel()
    设置项编辑与校验(发送时间、目标用户名、一言/TXTAPI开关与模式、格式模板、重试次数、自定义消息等),保存后重载聊天监听。

  • showHistoryPanel()
    读取 allLogs 展示历史记录,支持清空与导出 .txt

日志与状态

  • addLog(message, type)
    写入内存+持久化日志(限量滚动),同步到面板日志区(着色)。

  • updateRetryCount() / updateHitokotoStatus() / updateTxtApiStatus() / updateChatTargetStatus()
    更新面板上的重试次数与各模块状态文案/颜色。

  • parseTimeString(timeStr)
    将 HH:mm:ss 转为下次执行的 Date(若已过则推到明日)。

  • updateStatus(isSent)
    设置"已/未发送"、计算并展示下次发送时间,并启动倒计时。

  • startCountdown(targetTime)
    1s 刷新倒计时;到点后触发自动发送或滚动到明天。

  • autoSendIfNeeded()
    若"今日未发"且已过指定时间,自动调用发送流程。

内容生成

  • getMessageContent()
    串行获取一言与 TXTAPI 内容,替换到自定义模板的 [API][TXTAPI],返回最终要发的文本。

  • getHitokoto() → formatHitokoto(format, data)
    请求一言 JSON,并按 hitokotoFormatfromFormatfromWhoFormat 组装显示(当 from / from_who 为空时对应格式不显示)。

  • getTxtApiContent()

    • API 模式:GET 指定 URL,返回文本。

    • 手动模式:从多行文本中顺序或随机取一条;带"已发索引"记忆与"轮转重置"。

发送流程(核心链路)

  • sendMessage()
    入口:检查是否"今日已发/正在进行",初始化重试计数后进入执行流程。

  • executeSendProcess()
    控制重试节奏;若未找到目标用户先定位用户,已找到则去找输入框。

  • findAndClickTargetUser() / tryClickTargetUser()
    多套选择器扫描聊天列表文本;匹配到 targetUsername 就点击并标记 hasClickedTarget,随后等待界面加载去找输入框。若超时则重试。

  • tryFindChatInput()
    多选择器寻找输入框与发送按钮;将拼装好的多行消息逐行插入(含换行)、触发 input 事件,找到"可用"的发送按钮后点击;成功则记录 lastSentDate、更新状态与通知;否则重试。

动态聊天列表监听

  • initDynamicChatClick()
    启动 MutationObserver 监听 DOM 变化 + 若干次延迟"初始尝试",在列表异步渲染时自动点击目标用户;找到后停止观察器。

  • stopDynamicChatClick()
    关闭相关 Observer/定时器(避免资源占用)。

数据维护

  • clearData()
    清空"今日发送记录""手动文本发送索引"等状态,并重启聊天监听。

  • resetAllConfig()
    尝试清空所有 GM 存储键,恢复默认配置与状态,重新启动监听。

初始化

  • init()
    检测环境 → 初始化配置/面板 → 启动聊天监听 → 根据 lastSentDate 更新状态/倒计时 → 注册右键菜单命令 → 每 5 分钟做一次"接近目标时间"的轻量检查以触发定时发送。



注意

本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。


历史脚本txt文件获取>>
服务器搭建,人工服务咨询>>

没有评论:

发表评论

Bitget 与 Visa 推出联名银行卡:一张面向全球支付的工具详解 限时开通

随着数字经济的发展,跨境支付和多币种支付的需求不断增长。 随着数字经济的发展,跨境支付和多币种支付的需求不断增长。为了满足用户在全球消费和资金管理上的多样化需求,Bitget近期推出了与Visa合作的联名银行卡,为用户提供了更加便捷、高效的支付体验。 Bitget 与 Vis...