// ==UserScript== // @name 抖音推流码获取工具 // @namespace http://tampermonkey.net/ // @version 1.3.0 // @description 获取抖音直播推流信息并支持心跳保活(5秒随机间隔,返回原始API响应) // @author xifan // @match https://live.douyin.com/* // @match https://www.douyin.com/* // @grant GM_xmlhttpRequest // @grant GM_cookie // @grant GM_setValue // @grant GM_getValue // @grant GM_notification // @connect douyin.com // @connect webcast.amemv.com // @downloadURL https://raw.githubusercontent.com/xifan2333/userscripts/main/scripts/douyin-live-helper.user.js // @updateURL https://raw.githubusercontent.com/xifan2333/userscripts/main/scripts/douyin-live-helper.user.js // ==/UserScript== (function() { 'use strict'; // 添加简洁样式 const style = document.createElement('style'); style.textContent = ` #douyin-live-helper { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; font-size: 14px; line-height: 1.5; } #douyin-live-helper .input-field { width: 100%; padding: 6px 8px; border: 1px solid #ddd; border-radius: 4px; font-size: 13px; margin-bottom: 8px; box-sizing: border-box; } #douyin-live-helper .btn { padding: 6px 12px; background: #1890ff; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 13px; margin-right: 6px; margin-bottom: 6px; } #douyin-live-helper .btn:hover { background: #40a9ff; } #douyin-live-helper .btn.success { background: #52c41a; } #douyin-live-helper .result-box { background: #f5f5f5; border: 1px solid #d9d9d9; border-radius: 3px; padding: 6px; margin: 6px 0; word-break: break-all; font-family: monospace; font-size: 11px; max-height: 60px; overflow-y: auto; } #douyin-live-helper .copy-btn { padding: 3px 8px; background: #1890ff; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px; margin-left: 6px; flex-shrink: 0; } #douyin-live-helper .copy-btn:hover { background: #40a9ff; } #douyin-live-helper .error { color: #ff4d4f; background: #fff2f0; border: 1px solid #ffccc7; padding: 6px; border-radius: 3px; font-size: 12px; } #douyin-live-helper .success { color: #52c41a; background: #f6ffed; border: 1px solid #b7eb8f; padding: 6px; border-radius: 3px; font-size: 12px; } #douyin-live-helper .warning { color: #fa8c16; background: #fff7e6; border: 1px solid #ffd591; padding: 8px; border-radius: 3px; font-size: 12px; line-height: 1.5; } `; document.head.appendChild(style); // 全局变量存储设备ID let userDeviceId = GM_getValue('douyin_device_id', ''); let isPanelExpanded = GM_getValue('panel_expanded', true); // 心跳相关变量 let heartbeatInterval = null; let isHeartbeatActive = false; let heartbeatCount = 0; let currentRoomId = ''; let currentStreamId = ''; // 获取设备ID和aid参数 function getDeviceParams() { const aid = 1128; // 默认使用1128 return { deviceId: userDeviceId, aid }; } // 设置设备ID function setDeviceId(deviceId) { userDeviceId = deviceId; GM_setValue('douyin_device_id', deviceId); } // 发送心跳包 (萧条包) function sendHeartbeat(roomId, streamId, status = 2) { return new Promise((resolve) => { const { aid } = getDeviceParams(); const url = `https://webcast.amemv.com/webcast/room/ping/anchor/?room_id=${roomId}&status=${status}&stream_id=${streamId}&reason_no=0&aid=${aid}`; GM_cookie("list", { url: "https://www.douyin.com" }, (cookieList, error) => { if (error) { console.error('获取cookies失败:', error); resolve({ success: false, error: '获取cookies失败', fatal: false }); return; } const cookies = cookieList.map(cookie => `${cookie.name}=${cookie.value}`).join('; '); GM_xmlhttpRequest({ method: 'GET', url: url, headers: { 'User-Agent': navigator.userAgent, 'Referer': 'https://live.douyin.com/', 'Accept': 'application/json, text/plain, */*', 'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8', 'Cookie': cookies }, onload: function(response) { try { if (response.status !== 200) { resolve({ success: false, error: `HTTP ${response.status}: ${response.statusText}`, response: response.responseText, fatal: false }); return; } const data = JSON.parse(response.responseText); console.log('心跳响应:', data); if (data.status_code === 0) { resolve({ success: true, data: data }); } else if (data.status_code === 30003) { // 直播已结束,这是致命错误 resolve({ success: false, fatal: true, statusCode: data.status_code, message: data.data?.prompts || data.data?.message || '直播已结束', data: data }); } else { // 其他错误,返回完整响应 resolve({ success: false, fatal: false, statusCode: data.status_code, data: data }); } } catch (error) { console.error('解析心跳响应失败:', error); resolve({ success: false, error: `解析失败: ${error.message}`, response: response.responseText, fatal: false }); } }, onerror: function(error) { console.error('心跳请求失败:', error); resolve({ success: false, error: '网络请求失败', details: error, fatal: false }); } }); }); }); } // 启动心跳 function startHeartbeat(roomId, streamId) { if (heartbeatInterval) { clearInterval(heartbeatInterval); } currentRoomId = roomId; currentStreamId = streamId; isHeartbeatActive = true; heartbeatCount = 0; console.log('启动心跳保活, 房间ID:', roomId, '推流ID:', streamId); // 立即发送一次心跳 sendHeartbeat(roomId, streamId).then(result => { if (result.success) { heartbeatCount++; updateHeartbeatStatus(); } }); // 发送心跳的函数,带随机延迟 const scheduleNextHeartbeat = () => { // 基础间隔 5000ms (5秒),随机波动 ±500ms (即 4.5-5.5秒) const baseInterval = 5000; const randomOffset = Math.floor(Math.random() * 1000) - 500; // -500 到 +500 const interval = baseInterval + randomOffset; heartbeatInterval = setTimeout(async () => { const result = await sendHeartbeat(roomId, streamId); if (result.success) { heartbeatCount++; updateHeartbeatStatus(); console.log(`心跳发送成功 #${heartbeatCount}, 下次间隔: ${interval}ms`); // 继续调度下一次心跳 scheduleNextHeartbeat(); } else { console.error('心跳发送失败:', result); // 检查是否为致命错误(如直播已结束) if (result.fatal) { console.log('检测到致命错误,停止心跳保活'); stopHeartbeat(); // 发送桌面通知 const notificationMessage = result.message || JSON.stringify(result.data || result); if (typeof GM_notification !== 'undefined') { GM_notification({ title: '抖音直播助手', text: notificationMessage, timeout: 5000 }); } else if ('Notification' in window && Notification.permission === 'granted') { new Notification('抖音直播助手', { body: notificationMessage, icon: 'https://www.douyin.com/favicon.ico' }); } // 更新UI显示 - 显示完整错误信息 const errorDisplay = result.data ? JSON.stringify(result.data, null, 2) : (result.error || '未知错误'); updateHeartbeatStatus(errorDisplay, true); // 隐藏停止按钮 const stopBtn = document.getElementById('stopHeartbeatBtn'); if (stopBtn) { stopBtn.style.display = 'none'; } } else { // 非致命错误,显示完整错误信息 const errorDisplay = result.data ? JSON.stringify(result.data, null, 2) : (result.error || '未知错误'); updateHeartbeatStatus(errorDisplay); // 继续尝试 scheduleNextHeartbeat(); } } }, interval); }; // 开始调度心跳 scheduleNextHeartbeat(); } // 停止心跳 function stopHeartbeat() { if (heartbeatInterval) { clearTimeout(heartbeatInterval); heartbeatInterval = null; } // 发送停止状态的心跳 if (currentRoomId && currentStreamId) { sendHeartbeat(currentRoomId, currentStreamId, 4).then(result => { console.log('发送停止心跳:', result); }); } isHeartbeatActive = false; heartbeatCount = 0; currentRoomId = ''; currentStreamId = ''; console.log('心跳保活已停止'); updateHeartbeatStatus(); } // 更新心跳状态显示 function updateHeartbeatStatus(errorMsg = '', isFatal = false) { const statusDiv = document.getElementById('heartbeatStatus'); if (!statusDiv) return; if (errorMsg) { statusDiv.innerHTML = `
${JSON.stringify(result.data, null, 2)}`
: (result.error || '未知错误');
infoDiv.innerHTML = `