抖音直播推荐列表显示开播时间

显示抖音直播开播时间

// ==UserScript==
// @name         抖音直播推荐列表显示开播时间
// @namespace    http://tampermonkey.net/
// @version      0.1.2
// @description  显示抖音直播开播时间
// @author       duqings
// @require      https://libs.baidu.com/jquery/2.1.1/jquery.min.js
// @match        *://*.douyin.com/*
// @match        *://douyin.com/*
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// @run-at       document-start
// @license MIT
// ==/UserScript==

(function () {
    'use strict';

    // 调试模式
    const DEBUG = false;
    const log = (...args) => DEBUG && console.log('[抖音直播时间]', ...args);

    log('脚本开始初始化');

    // 存储处理后的直播数据
    let processedLiveData = new Map();

    // 定义需要监听的 API URL 模式
    const API_PATTERNS = [
        '/aweme/v1/web/live/search',
    ];

    // 延迟函数
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));

    // 检查 URL 是否匹配任意模式
    function isTargetUrl(url) {
        return API_PATTERNS.some(pattern => url.includes(pattern));
    }

    // 劫持 XMLHttpRequest
    function hookXHR() {
        const originalXHR = unsafeWindow.XMLHttpRequest;
        
        function MyXHR() {
            const xhr = new originalXHR();
            const originalOpen = xhr.open;
            const originalSend = xhr.send;

            xhr.open = function () {
                const url = arguments[1];
                if (url) {
                    // log('检测到 XHR 请求:', url);
                    if (isTargetUrl(url)) {
                        log('匹配到目标 API 请求:', url);
                        this.isLiveSearchRequest = true;
                    }
                }
                return originalOpen.apply(this, arguments);
            };

            xhr.send = function () {
                if (this.isLiveSearchRequest) {
                    const originalOnReadyStateChange = this.onreadystatechange;
                    this.onreadystatechange = function () {
                        if (this.readyState === 4) {
                            log('API 请求状态:', this.status);
                            if (this.status === 200) {
                                try {
                                    const responseData = JSON.parse(this.responseText);
                                    log('成功获取响应数据');
                                    processLiveData(responseData);
                                } catch (error) {
                                    console.error('处理直播数据时出错:', error);
                                }
                            }
                        }
                        if (originalOnReadyStateChange) {
                            originalOnReadyStateChange.apply(this, arguments);
                        }
                    };
                }
                return originalSend.apply(this, arguments);
            };

            return xhr;
        }

        // 替换原始的 XMLHttpRequest
        unsafeWindow.XMLHttpRequest = MyXHR;
        log('XMLHttpRequest 已被成功劫持');
    }

    // 处理直播数据
    function processLiveData(responseData) {
        if (!responseData?.data?.length) {
            log('返回的数据格式不正确或为空');
            return;
        }

        log('开始处理直播数据,数据条数:', responseData.data.length);

        // 处理每个直播数据
        responseData.data.forEach((item, index) => {
            if (item.type === 1 && item.lives?.rawdata) {
                try {
                    const rawDataStr = item.lives.rawdata.replace(/\.\.\.$/, '');
                    const rawDataObj = JSON.parse(rawDataStr);
                    
                    // 将解析后的数据存储到原始数据中
                    responseData.data[index].rawData = rawDataObj;
                    // 使用Map存储,key为房间ID
                    const webRid = rawDataObj.owner.web_rid;
                    processedLiveData.set(webRid, {
                        webRid,
                        roomId: rawDataObj.id_str,
                        createTime: rawDataObj.create_time,
                        title: rawDataObj.title,
                        userCount: rawDataObj.user_count
                    });
                    
                    log('成功处理直播间数据:', rawDataObj.id_str);
                } catch (error) {
                    console.error('解析rawdata时出错:', error);
                }
            }
        });

        log('数据处理完成,当前缓存数量:', processedLiveData.size);
        
        // 更新页面显示
        updateLiveTimeDisplay();
    }

    // 更新直播时间显示
    async function updateLiveTimeDisplay(useDelay = true) {
        // 等待3秒,确保 dom 完全准备好
        if (useDelay) {
            await delay(1000 * 3);
        }

        // 查找列表主容器
        const searchContainer = document.getElementById('search-result-container');
        if (!searchContainer) {
            log('未找到搜索结果容器');
            return;
        }

        // 先找到所有的 basicPlayer 容器
        const basicPlayers = searchContainer.querySelectorAll('.basicPlayer');
        const liveSearchPlayers = searchContainer.querySelectorAll('.liveSearchPlayer');
        const playerContainers = basicPlayers.length ? basicPlayers : liveSearchPlayers;
        log('找到播放器容器数量:', playerContainers.length);

        playerContainers.forEach(playerContainer => {
            const link = playerContainer.querySelector('a[href*="live.douyin.com"]');
            if (!link) {
                log('播放器容器中未找到直播链接');
                return;
            }

            // 从href中提取房间ID
            const href = link.getAttribute('href');
            const webRidMatch = href.match(/\/(\d+)(?:\?|$)/);
            
            if (!webRidMatch) {
                log('无法从链接提取房间ID:', href);
                return;
            }
            
            const webRid = webRidMatch[1];
            const liveData = processedLiveData.get(webRid);
            
            if (liveData) {
                log('找到直播数据:', liveData.webRid);
                
                // 创建或更新时间显示元素
                let timeElement = playerContainer.querySelector('.live-start-time');
                
                if (!timeElement) {
                    timeElement = document.createElement('div');
                    timeElement.className = 'live-start-time';
                    Object.assign(timeElement.style, {
                        position: 'absolute',
                        bottom: '5px',
                        right: '5px',
                        backgroundColor: 'rgba(0, 0, 0, 0.7)',
                        color: 'white',
                        padding: '2px 5px',
                        borderRadius: '3px',
                        fontSize: '26px',
                        zIndex: '100'
                    });
                    playerContainer.appendChild(timeElement);
                    log('创建时间显示元素:', webRid);
                }
                
                // 格式化并显示时间
                const startTime = formatTime(liveData.createTime);
                
                // 创建更新时长的函数
                const updateDuration = () => {
                    const duration = Math.floor((Date.now() - liveData.createTime * 1000) / 1000);
                    const durationHours = Math.floor(duration / 3600);
                    const durationMinutes = Math.floor((duration % 3600) / 60);
                    const durationSeconds = duration % 60;
                    timeElement.textContent = `开播时间: ${startTime},开播时长: ${durationHours}小时${durationMinutes}分钟${durationSeconds}秒`;
                };
                
                // 立即更新
                updateDuration();
                
                // 设置定时器每秒更新一次
                const timerId = setInterval(updateDuration, 1000);
                
                timeElement.dataset.timerId = timerId;
                
                log('更新时间显示:', webRid, startTime);
            } else {
                log('未找到直播数据:', webRid);
            }
        });
    }

    // 格式化时间戳为可读时间
    function formatTime(timestamp) {
        const date = new Date(timestamp * 1000);
        const hours = date.getHours().toString().padStart(2, '0');
        const minutes = date.getMinutes().toString().padStart(2, '0');
        return `${hours}:${minutes}`;
    }

    // 监听DOM变化
    const observer = new MutationObserver((mutations) => {
        let shouldUpdate = false;
        
        for (const mutation of mutations) {
            if (mutation.addedNodes.length > 0) {
                const hasNewLiveLinks = Array.from(mutation.addedNodes).some(node => {
                    return node.nodeType === 1 && (
                        node.querySelector('a[href*="live.douyin.com"]') ||
                        (node.tagName === 'A' && node.href?.includes('live.douyin.com'))
                    );
                });
                
                if (hasNewLiveLinks) {
                    shouldUpdate = true;
                    break;
                }
            }
        }
        
        if (shouldUpdate && processedLiveData.size > 0) {
            updateLiveTimeDisplay(false);
        }
    });

    // 初始化
    function initialize() {
        log('开始初始化脚本');
        hookXHR();
        
        // 开始监听DOM变化
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
        
        log('初始化完成');
    }

    // 确保在页面加载开始时就运行初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', initialize);
    } else {
        initialize();
    }
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址