Strip直播

https://zh.topcams.tv

分享者: haobai1 (12083)发布时间: 5天前

修复播放器异常裁切
现在双击可以进入全屏
二维码导入
{
    "articleStyle": 3,
    "cacheFirst": false,
    "customOrder": 0,
    "enableJs": true,
    "enabled": true,
    "enabledCookieJar": false,
    "header": "{\n\"User-Agent\": \"Mozilla\/5.0 (Android)\"\n}",
    "injectJs": "const toggleFull = async () => {\n  try {\n    if (!document.fullscreenElement) {\n      await document.documentElement.requestFullscreen().catch(() => {});\n      await screen.orientation.lock('any').catch(() => {});\n    } else {\n      await document.exitFullscreen();\n    }\n  } catch (e) {}\n};\n\ndocument.addEventListener('dblclick', (e) => {\n  if (e.target.closest('html, body, img, p, span, h1, h2, h3, h4, h5, h6')) {\n    toggleFull();\n  }\n});\n\nwindow.onload = toggleFull;\nsetTimeout(toggleFull, 50);\n\ndocument.addEventListener('fullscreenchange', () => {\n  if (document.fullscreenElement) {\n    screen.orientation.lock('any').catch(() => {});\n  }\n});",
    "lastUpdateTime": 1771211640359,
    "loadWithBaseUrl": true,
    "preload": false,
    "ruleArticles": "$.blocks..models[*]&&$.models[*]",
    "ruleContent": "<js>\nconst parts = baseUrl.split(',').map(item => item.trim());\nconst domain = parts[0].split('\/').slice(0, 3).join('\/');\nconst username = parts[0].split('\/').pop() || parts[0];\n\nconst camAjax = java.ajax(domain + '\/api\/front\/v2\/models\/username\/' + username + '\/cam?uniq=0');\nconst configAjax = java.ajax(domain + '\/api\/front\/v3\/config\/initial');\nconst membersAjax = java.ajax(domain + '\/api\/front\/v2\/models\/username\/' + username + '\/members?uniq=0');\n\nlet responseData = { status: \"offline\", avatarUrl: \"\", startTime: \"\", coverImg: \"\", userDescription: \"\", goalDescription: \"\", fanClubDescription: \"\", topic: \"\", username: username, stream: \"\", topBestPlace: \"\", hlsLines: [], cdn: \"\", pixelatedResolutions: [], membersCount: 0, tipMenuPriceList: [], isLive: false, tipMenuCreatedAt: \"\", websocketUrl: \"\", websocketToken: \"\", modelId: \"\" };\n\nif (camAjax) {\n    const camData = JSON.parse(camAjax);\n    const user = camData.user?.user || {};\n    const cam = camData.cam || {};\n    \n    responseData.status = user.status || \"offline\";\n    responseData.avatarUrl = user.avatarUrl || \"\";\n    responseData.startTime = user.statusChangedAt || \"\";\n    responseData.userDescription = user.description || \"\";\n    responseData.goalDescription = cam.goal?.description || \"\";\n    responseData.fanClubDescription = cam.userFanClub?.description || \"\";\n    responseData.topic = cam.topic || \"\";\n    responseData.stream = cam.streamName || \"\";\n    responseData.topBestPlace = user.topBestPlace || \"\";\n    responseData.modelId = user.id || \"\";\n    \n    if (user.snapshotTimestamp && responseData.modelId) {\n        responseData.coverImg = \"https:\/\/img.strpst.com\/thumbs\/\" + user.snapshotTimestamp + \"\/\" + responseData.modelId;\n    }\n    if (cam.broadcastSettings?.presets?.pixelated) {\n        responseData.pixelatedResolutions = cam.broadcastSettings.presets.pixelated;\n    }\n    \n    responseData.isLive = user.isOnline === true;\n    responseData.tipMenuCreatedAt = cam.tipMenu?.createdAt || \"\";\n    \n    if (cam.tipMenu?.settings) {\n        responseData.tipMenuPriceList = cam.tipMenu.settings.map(item => ({\n            activity: item.activity || \"\",\n            price: item.price || 0\n        }));\n    }\n}\n\nif (configAjax) {\n    const configData = JSON.parse(configAjax);\n    const hosts = configData.initial?.common?.hlsStreamHosts || {};\n    responseData.hlsLines = [hosts.A, hosts.B, hosts.C, hosts.D, hosts.E, hosts.F].filter(h => h);\n    responseData.cdn = responseData.hlsLines[0] || \"\";\n    \n    const client = configData.initial?.client || {};\n    responseData.websocketUrl = client.websocket?.url || \"\";\n    responseData.websocketToken = client.websocket?.token || \"\";\n}\n\nif (membersAjax) {\n    try {\n        const membersData = JSON.parse(membersAjax);\n        responseData.membersCount = \n            (membersData?.guests || 0) + \n            (membersData?.spies || 0) + \n            (membersData?.invisibles || 0) + \n            (membersData?.greens || 0) + \n            (membersData?.golds || 0) + \n            (membersData?.regulars || 0);\n    } catch (error) {\n        responseData.membersCount = 0;\n    }\n}\n\nJSON.stringify(responseData);\n<\/js>\n<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>StripChat直播<\/title>\n<\/head>\n    <style>\n        * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Microsoft Yahei', sans-serif; }\n        body { color: #333; padding: 0.1rem; min-height: 100vh; background: #f5f5f5; }\n        .app-container { display: grid; grid-template-columns: 1fr; gap: 1rem; max-width: 1200px; margin: 0 auto; }\n        .live-section { border-radius: 12px; overflow: hidden; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); border: 1px solid #eaeaea; background: #fff; }\n        .topic-section { padding: 14px 20px; border-bottom: 1px solid #eaeaea; background: #fff; display: none; }\n        .topic-label { font-size: 12px; color: #666; margin-bottom: 6px; display: flex; align-items: center; gap: 8px; font-weight: 500; }\n        .topic-content { font-size: 15px; color: #333; font-weight: 500; line-height: 1.4; }\n        .chat-section { border-radius: 12px; padding: 1rem; display: flex; flex-direction: column; height: 500px; border: 1px solid #eaeaea; background: #fff; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); }\n        .chat-messages { flex: 1; overflow-y: auto; padding-right: 0.5rem; width: 100%; }\n        .chat-messages::-webkit-scrollbar { width: 6px; }\n        .chat-messages::-webkit-scrollbar-track { background: #f0f0f0; border-radius: 3px; }\n        .chat-messages::-webkit-scrollbar-thumb { background: #c0c0c0; border-radius: 3px; }\n        .message-wrapper { display: block; margin-bottom: 0.3rem; width: 100%; }\n        .message-item { padding: 0.3rem 0.6rem; border-radius: 16px; font-size: 0.85rem; line-height: 1.3; border: 1px solid rgba(224, 224, 224, 0.6); display: block; background: #f8f8f8; color: #333; width: 100%; word-break: break-word; box-sizing: border-box; }\n        .lovense-item { border-color: #e0e0e0; background: #f8f8f8; }\n        .tip-item { border-color: #e0e0e0; background: #f8f8f8; }\n        .system-message { border-color: #e0e0e0; color: #666; font-size: 0.85rem; margin: 0 auto 0.3rem auto; background: #f8f8f8; }\n        .user-name { font-weight: 600; color: #222; margin-left: 0.4rem; }\n        .level-tag { font-size: 0.75rem; color: #666; background: #f0f0f0; padding: 0.1rem 0.4rem; border-radius: 8px; }\n        .tip-amount { color: #e74c3c; font-weight: 600; }\n        .live-header { padding: 16px 20px; border-bottom: 1px solid #eaeaea; display: flex; align-items: center; gap: 15px; background: #fff; }\n        .avatar-container { width: 56px; height: 56px; flex-shrink: 0; }\n        .avatar { width: 100%; height: 100%; border-radius: 50%; object-fit: cover; border: 3px solid #007aff; }\n        .host-info { flex: 1; min-width: 0; }\n        .host-info h2 { font-size: 18px; margin-bottom: 6px; font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #333; }\n        .status-container { display: flex; align-items: center; gap: 12px; flex-wrap: wrap; }\n        .live-badge { display: inline-flex; align-items: center; background: #ff3b30; color: white; padding: 4px 10px; border-radius: 4px; font-size: 12px; font-weight: 600; letter-spacing: 0.3px; }\n        .live-badge::before { content: ''; display: inline-block; width: 6px; height: 6px; background-color: white; border-radius: 50%; margin-right: 6px; animation: blink 1.5s infinite; }\n        @keyframes blink { 0%, 100% { opacity: 1; } 50% { opacity: 0.3; } }\n        .viewer-count { font-size: 13px; color: #666; display: flex; align-items: center; gap: 5px; }\n        .player-container { position: relative; background: #000000; overflow: hidden; }\n        .player-container:fullscreen { display: flex; align-items: center; justify-content: center; }\n        .player-container:-webkit-full-screen { display: flex; align-items: center; justify-content: center; }\n        #dplayer { width: 100% !important; height: 100% !important; display: flex; align-items: center; }\n        #dplayer video { object-fit: contain !important; }\n        .dplayer-controller { display: none !important; }\n        .dplayer-notice { display: none !important; }\n        .custom-controls { position: absolute; bottom: 0; left: 0; right: 0; background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent); padding: 12px 15px 6px; display: flex; align-items: center; justify-content: space-between; opacity: 0; transition: opacity 0.2s; z-index: 1000; pointer-events: auto; }\n        .player-container:hover .custom-controls, .player-container.loading .custom-controls { opacity: 1; }\n        .left-controls, .right-controls { display: flex; align-items: center; gap: 8px; pointer-events: auto; }\n        .control-btn { background: rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.25); color: #ffffff; width: 36px; height: 36px; border-radius: 6px; display: flex; align-items: center; justify-content: center; cursor: pointer; font-size: 16px; transition: all 0.15s; pointer-events: auto; }\n        .control-btn:disabled { opacity: 0.5; cursor: not-allowed; }\n        .selector-btn { background: rgba(255, 255, 255, 0.15); border: 1px solid rgba(255, 255, 255, 0.25); color: #ffffff; padding: 6px 10px; border-radius: 6px; cursor: pointer; font-size: 13px; font-weight: 500; white-space: nowrap; display: flex; align-items: center; gap: 5px; transition: all 0.15s; min-width: 65px; justify-content: center; pointer-events: auto; }\n        .selector-menu { position: absolute; bottom: 42px; background: #ffffff; border-radius: 8px; min-width: 90px; display: none; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.16); border: 1px solid #d1d1d6; overflow: hidden; z-index: 1001; pointer-events: auto; }\n        .menu-item { padding: 8px 12px; color: #333; cursor: pointer; font-size: 13px; transition: all 0.15s; border-bottom: 1px solid #eaeaea; display: flex; align-items: center; justify-content: space-between; pointer-events: auto; }\n        .menu-item:last-child { border-bottom: none; }\n        .menu-item:hover:not(:disabled) { background: #007aff; color: white; }\n        .menu-item.active { background: rgba(10, 132, 255, 0.1); color: #007aff; }\n        .menu-item.active:after { content: \"✓\"; font-size: 12px; color: #007aff; }\n        .loading-indicator { position: absolute; top: 15px; left: 15px; background: rgba(0, 0, 0, 0.8); color: white; padding: 6px 12px; border-radius: 4px; font-size: 12px; z-index: 1002; display: none; border: 1px solid rgba(255, 255, 255, 0.1); }\n        .floating-button { position: fixed; bottom: 20px; right: 20px; width: 60px; height: 60px; border-radius: 50%; background: #007aff; color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 122, 255, 0.3); z-index: 10000; border: none; font-size: 24px; }\n        .floating-panel { position: fixed; bottom: 90px; right: 20px; width: 400px; background: #ffffff; border-radius: 12px; box-shadow: 0 8px 32px rgba(0, 0, 0, 0.15); padding: 20px; z-index: 9999; border: 1px solid #eaeaea; display: none; max-height: 80vh; overflow-y: auto; }\n        .floating-panel::-webkit-scrollbar { width: 6px; }\n        .floating-panel::-webkit-scrollbar-track { background: #f0f0f0; border-radius: 3px; }\n        .floating-panel::-webkit-scrollbar-thumb { background: #c0c0c0; border-radius: 3px; }\n        .floating-panel-title { font-size: 18px; font-weight: 600; margin-bottom: 15px; color: #333; padding-bottom: 10px; border-bottom: 1px solid #eaeaea; }\n        .floating-info-section { margin-bottom: 20px; }\n        .floating-info-section-title { font-size: 15px; color: #007aff; margin-bottom: 12px; font-weight: 600; display: flex; align-items: center; gap: 8px; }\n        .floating-info-item { margin-bottom: 12px; display: flex; justify-content: space-between; padding: 8px 0; border-bottom: 1px solid #f5f5f5; }\n        .floating-info-item:last-child { border-bottom: none; margin-bottom: 0; }\n        .floating-info-label { font-size: 13px; color: #666; min-width: 120px; }\n        .floating-info-value { font-size: 14px; color: #333; font-weight: 500; text-align: right; flex: 1; }\n        .floating-content { background: #f8f9fa; border: 1px solid #eaeaea; border-radius: 8px; padding: 12px; font-size: 14px; color: #333; line-height: 1.5; margin-bottom: 15px; }\n        .floating-price-list { display: flex; flex-direction: column; gap: 8px; }\n        .floating-price-item { background: #ffffff; border: 1px solid #eaeaea; border-radius: 8px; padding: 10px 12px; display: flex; align-items: center; justify-content: space-between; transition: all 0.2s; }\n        .floating-price-item:hover { border-color: #007aff; }\n        .floating-price-activity { font-size: 14px; color: #333; flex: 1; }\n        .floating-price-value { font-size: 14px; color: #ff3b30; font-weight: 600; }\n        @media (max-width: 768px) {\n            .floating-panel { width: 350px; right: 10px; bottom: 80px; }\n            .floating-button { width: 50px; height: 50px; font-size: 20px; right: 15px; bottom: 15px; }\n            .live-header { padding: 14px 16px; }\n            .avatar-container { width: 48px; height: 48px; }\n            .host-info h2 { font-size: 16px; }\n        }\n        @media (max-width: 480px) {\n            .floating-panel { width: 300px; }\n            .chat-section { height: 400px; }\n            .app-container { padding: 0.5rem; gap: 1rem; }\n        }\n    <\/style>\n<body>\n    <div id=\"config\" style=\"display:none;\">\n        <div id=\"liveData\" style=\"display:none;\">{{result}}<\/div>\n    <\/div>\n    \n    <div class=\"app-container\">\n        <div class=\"live-section\">\n            <div class=\"topic-section\" id=\"topicContainer\">\n                <div class=\"topic-label\">\n                    <i class=\"fas fa-bullhorn\"><\/i> 直播标题\n                <\/div>\n                <div class=\"topic-content\" id=\"topic\"><\/div>\n            <\/div>\n            \n            <div class=\"live-header\">\n                <div class=\"avatar-container\">\n                    <img class=\"avatar\" id=\"avatarImg\" src=\"\" alt=\"\">\n                <\/div>\n                <div class=\"host-info\">\n                    <h2 id=\"hostName\">正在加载...<\/h2>\n                    <div class=\"status-container\">\n                        <span class=\"live-badge\">直播中<\/span>\n                        <div class=\"viewer-count\">\n                            <i class=\"fas fa-users\"><\/i>\n                            <span id=\"membersCount\">--<\/span> 人\n                        <\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n            \n            <div class=\"player-container\" id=\"playerContainer\">\n                <div id=\"dplayer\"><\/div>\n                <div class=\"loading-indicator\" id=\"loadingIndicator\">正在切换...<\/div>\n                <div class=\"custom-controls\">\n                    <div class=\"left-controls\">\n                        <button class=\"control-btn\" id=\"refreshBtn\" title=\"刷新播放\">\n                            <i class=\"fas fa-redo-alt\"><\/i>\n                        <\/button>\n                        <button class=\"control-btn\" id=\"volumeBtn\" title=\"静音\/取消静音\">\n                            <i class=\"fas fa-volume-up\"><\/i>\n                        <\/button>\n                    <\/div>\n                    <div class=\"right-controls\">\n                        <div class=\"line-selector\">\n                            <div class=\"selector-btn\" id=\"lineBtn\" title=\"选择线路\">线路1 <i class=\"fas fa-chevron-down\"><\/i><\/div>\n                            <div class=\"selector-menu\" id=\"lineMenu\"><\/div>\n                        <\/div>\n                        <div class=\"quality-selector\">\n                            <div class=\"selector-btn\" id=\"qualityBtn\" title=\"选择清晰度\">默认 <i class=\"fas fa-chevron-down\"><\/i><\/div>\n                            <div class=\"selector-menu\" id=\"qualityMenu\"><\/div>\n                        <\/div>\n                        <button class=\"control-btn\" id=\"fullscreenBtn\" title=\"全屏\">\n                            <i class=\"fas fa-expand\"><\/i>\n                        <\/button>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n        \n        <div class=\"chat-section\">\n             <div class=\"chat-messages\" id=\"chatContainer\"><\/div>\n        <\/div>\n    <\/div>\n    \n    <button class=\"floating-button\" id=\"floatingBtn\">\n        <i class=\"fas fa-info\"><\/i>\n    <\/button>\n    \n    <div class=\"floating-panel\" id=\"floatingPanel\">\n        <div class=\"floating-panel-title\">直播信息<\/div>\n        \n        <div class=\"floating-info-section\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-user\"><\/i> 主播信息\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">主播名称:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingHostName\">--<\/span>\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">历史最高排行:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingTopBestPlace\">--<\/span>\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">总人数:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingMembersCount\">--<\/span>\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">首次开播日期:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingTipMenuCreatedAt\">--<\/span>\n            <\/div>\n            <!-- 原来的时间信息 移到这里 -->\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\" id=\"floatingDurationLabel\">时长标签:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingDuration\">--<\/span>\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\" id=\"floatingTimeLabel\">时间标签:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingStatusTime\">--<\/span>\n            <\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\" id=\"floatingTopicSection\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-comment-alt\"><\/i> 直播标题\n            <\/div>\n            <div class=\"floating-content\" id=\"floatingTopic\"><\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\" id=\"floatingUserDescriptionSection\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-user-circle\"><\/i> 主播介绍\n            <\/div>\n            <div class=\"floating-content\" id=\"floatingUserDescription\"><\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\" id=\"floatingGoalSection\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-bullseye\"><\/i> 直播目标\n            <\/div>\n            <div class=\"floating-content\" id=\"floatingGoalDescription\"><\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\" id=\"floatingFanClubSection\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-users\"><\/i> 粉丝团留言\n            <\/div>\n            <div class=\"floating-content\" id=\"floatingFanClubDescription\"><\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-gift\"><\/i> 打赏菜单\n            <\/div>\n            <div class=\"floating-price-list\" id=\"floatingPriceList\"><\/div>\n        <\/div>\n        \n        <div class=\"floating-info-section\">\n            <div class=\"floating-info-section-title\">\n                <i class=\"fas fa-tv\"><\/i> 播放设置\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">当前线路:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingLine\">线路1<\/span>\n            <\/div>\n            <div class=\"floating-info-item\">\n                <span class=\"floating-info-label\">清晰度:<\/span>\n                <span class=\"floating-info-value\" id=\"floatingQuality\">默认<\/span>\n            <\/div>\n        <\/div>\n    <\/div>\n\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/dplayer@1.27.1\/dist\/DPlayer.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@1.4.10\/dist\/hls.min.js\"><\/script>\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/font-awesome\/6.0.0\/js\/all.min.js\"><\/script>\n    <script>\n        const liveDataElement = document.getElementById('liveData');\n        let liveData = {};\n        \n        if (liveDataElement.textContent) {\n            try {\n                liveData = JSON.parse(liveDataElement.textContent);\n            } catch (e) {}\n        }\n        \n        const chatContainer = document.getElementById('chatContainer');\n        let chatWs = null;\n\n        function addSystemMessage(text) {\n            const systemDiv = document.createElement('div');\n            systemDiv.className = 'message-item system-message';\n            systemDiv.textContent = text;\n            chatContainer.prepend(systemDiv);\n            chatContainer.scrollTop = 0;\n        }\n        \n        function initChat() {\n            if (!liveData.modelId || !liveData.websocketUrl || !liveData.websocketToken) return;\n\n            addSystemMessage('开始连接弹幕服务器');\n            \n            const timeoutMs = 10000;\n            let connectTimeoutTimer = setTimeout(() => {\n                if (chatWs && chatWs.readyState !== WebSocket.OPEN) {\n                    chatWs.close();\n                    addSystemMessage('连接超时:未能成功连接弹幕服务器');\n                }\n            }, timeoutMs);\n            \n            if (chatWs && chatWs.readyState === WebSocket.OPEN) {\n                chatWs.close();\n            }\n            \n            chatWs = new WebSocket(`${liveData.websocketUrl}`);\n            const sendQueue = [\n                '{\"connect\":{\"token\":\"' + liveData.websocketToken + '\"},\"id\":1}',\n                `{\"subscribe\":{\"channel\":\"newChatMessage@${liveData.modelId}\"},\"id\":2}`\n            ];\n            \n            function getUserLevelTag(userData) {\n                return userData?.userRanking?.level ? `<span class=\"level-tag\">Lv.${userData.userRanking.level}<\/span>` : '';\n            }\n            \n            function createMessageElement(data) {\n                const messageDiv = document.createElement('div');\n                messageDiv.className = 'message-item';\n                \n                const { type, userData, details } = data.message;\n                const levelTag = getUserLevelTag(userData);\n                \n                let content = '';\n                switch(type) {\n                    case 'text':\n                        messageDiv.className += ' text-item';\n                        content = `${levelTag}<span class=\"user-name\">${userData.username}<\/span>: ${details.body}`;\n                        break;\n                    case 'tip':\n                        messageDiv.className += ' tip-item';\n                        content = `${levelTag}<span class=\"user-name\">${userData.username}<\/span>: 已支付<span class=\"tip-amount\">${details.amount}<\/span>代币`;\n                        break;\n                    case 'lovense':\n                        messageDiv.className += ' lovense-item';\n                        const { power, time, amount } = details.lovenseDetails.detail;\n                        const powerText = power === 'low' ? '低' : power === 'medium' ? '中' : '高';\n                        const clientLevelTag = getUserLevelTag(details.lovenseDetails.clientUserInfo);\n                        content = `${clientLevelTag}<span class=\"user-name\">${details.lovenseDetails.clientUserInfo.username}<\/span>: ${powerText}强度 · ${time}秒`;\n                        break;\n                    default:\n                        return null;\n                }\n                \n                messageDiv.innerHTML = content;\n                return messageDiv;\n            }\n            \n            function addMessageToPanel(messageData) {\n                const messageElement = createMessageElement(messageData);\n                if (messageElement) {\n                    const messageWrapper = document.createElement('div');\n                    messageWrapper.className = 'message-wrapper';\n                    messageWrapper.appendChild(messageElement);\n                    chatContainer.prepend(messageWrapper);\n                    chatContainer.scrollTop = 0;\n                }\n            }\n            \n            chatWs.onopen = () => {\n                clearTimeout(connectTimeoutTimer);\n                sendQueue.forEach(cmd => chatWs.send(cmd));\n                addSystemMessage('弹幕服务器连接正常');\n            };\n            \n            chatWs.onmessage = (e) => {\n                if (e.data.trim() === '{}') {\n                    chatWs.send('{}');\n                    return;\n                }\n                try {\n                    const data = JSON.parse(e.data);\n                    if (data.ping) {\n                        chatWs.send(JSON.stringify({ pong: data.ping }));\n                    }\n                    if (data.push?.channel === `newChatMessage@${liveData.modelId}`) {\n                        addMessageToPanel(data.push.pub.data);\n                    }\n                } catch (error) {}\n            };\n            \n            chatWs.onerror = (err) => {\n                const errorDiv = document.createElement('div');\n                errorDiv.className = 'message-item system-message';\n                errorDiv.textContent = '连接错误';\n                chatContainer.prepend(errorDiv);\n                chatContainer.scrollTop = 0;\n            };\n            \n            chatWs.onclose = (e) => {\n                const closeDiv = document.createElement('div');\n                closeDiv.className = 'message-item system-message';\n                closeDiv.textContent = `连接关闭 | 错误码: ${e.code}`;\n                chatContainer.prepend(closeDiv);\n                chatContainer.scrollTop = 0;\n            };\n        }\n        \n        const elements = {\n            avatarImg: document.getElementById('avatarImg'),\n            hostName: document.getElementById('hostName'),\n            refreshBtn: document.getElementById('refreshBtn'),\n            volumeBtn: document.getElementById('volumeBtn'),\n            lineBtn: document.getElementById('lineBtn'),\n            lineMenu: document.getElementById('lineMenu'),\n            qualityBtn: document.getElementById('qualityBtn'),\n            qualityMenu: document.getElementById('qualityMenu'),\n            fullscreenBtn: document.getElementById('fullscreenBtn'),\n            loadingIndicator: document.getElementById('loadingIndicator'),\n            playerContainer: document.getElementById('playerContainer'),\n            membersCount: document.getElementById('membersCount'),\n            floatingBtn: document.getElementById('floatingBtn'),\n            floatingPanel: document.getElementById('floatingPanel'),\n            floatingHostName: document.getElementById('floatingHostName'),\n            floatingTopBestPlace: document.getElementById('floatingTopBestPlace'),\n            floatingMembersCount: document.getElementById('floatingMembersCount'),\n            floatingTipMenuCreatedAt: document.getElementById('floatingTipMenuCreatedAt'),\n            floatingTopic: document.getElementById('floatingTopic'),\n            floatingTopicSection: document.getElementById('floatingTopicSection'),\n            floatingGoalDescription: document.getElementById('floatingGoalDescription'),\n            floatingGoalSection: document.getElementById('floatingGoalSection'),\n            floatingUserDescription: document.getElementById('floatingUserDescription'),\n            floatingUserDescriptionSection: document.getElementById('floatingUserDescriptionSection'),\n            floatingFanClubDescription: document.getElementById('floatingFanClubDescription'),\n            floatingFanClubSection: document.getElementById('floatingFanClubSection'),\n            floatingPriceList: document.getElementById('floatingPriceList'),\n            floatingDurationLabel: document.getElementById('floatingDurationLabel'),\n            floatingDuration: document.getElementById('floatingDuration'),\n            floatingTimeLabel: document.getElementById('floatingTimeLabel'),\n            floatingStatusTime: document.getElementById('floatingStatusTime'),\n            floatingLine: document.getElementById('floatingLine'),\n            floatingQuality: document.getElementById('floatingQuality'),\n            topicContainer: document.getElementById('topicContainer'),\n            topic: document.getElementById('topic')\n        };\n        \n        let dp = null;\n        let isMuted = false;\n        let isFullscreen = false;\n        let hlsInstance = null;\n        let durationInterval = null;\n        let config = {};\n        \n        const getStreamUrl = (lineIndex = config.currentLine || 0, quality = config.currentQuality || 'auto') => {\n            const line = config.hlsLines?.[lineIndex];\n            if (!line || !config.stream) return '';\n            \n            const pureStatus = (config.status || '').toLowerCase();\n            let suffix = '_auto';\n            \n            if (pureStatus !== 'public') {\n                suffix = '_160p_blurred';\n            } else if (quality === 'auto') {\n                suffix = '_auto';\n            } else {\n                suffix = '_' + quality;\n            }\n            \n            return `https:\/\/edge-hls.${line}\/hls\/${config.stream}\/master\/${config.stream}${suffix}.m3u8?pkey=bXorqTB5ZhP5FcpX`;\n        };\n        \n        function switchStream(url) {\n            if (!dp || !dp.video || !url) return;\n            \n            config.isSwitching = true;\n            elements.loadingIndicator.style.display = 'block';\n            elements.playerContainer.classList.add('loading');\n            \n            const video = dp.video;\n            video.pause();\n            video.src = '';\n            video.load();\n            \n            if (hlsInstance) {\n                try {\n                    hlsInstance.stopLoad();\n                    setTimeout(() => {\n                        hlsInstance.destroy();\n                        hlsInstance = null;\n                        startNewStream(url, video);\n                    }, 50);\n                } catch (e) {\n                    hlsInstance.destroy();\n                    hlsInstance = null;\n                    startNewStream(url, video);\n                }\n            } else {\n                startNewStream(url, video);\n            }\n        }\n        \n        function startNewStream(url, video) {\n            video.src = url;\n            \n            if (Hls.isSupported()) {\n                hlsInstance = new Hls({\n                    enableWorker: true,\n                    lowLatencyMode: true,\n                    backBufferLength: 60,\n                    maxBufferSize: 30 * 1000 * 1000,\n                    maxBufferLength: 30\n                });\n                \n                hlsInstance.loadSource(url);\n                hlsInstance.attachMedia(video);\n                \n                hlsInstance.on(Hls.Events.MANIFEST_PARSED, () => {\n                    completeSwitch();\n                    video.play().catch(e => {});\n                });\n                \n                hlsInstance.on(Hls.Events.ERROR, (event, data) => {\n                    if (data.fatal) {\n                        switch(data.type) {\n                            case Hls.ErrorTypes.NETWORK_ERROR:\n                                hlsInstance.startLoad();\n                                break;\n                            case Hls.ErrorTypes.MEDIA_ERROR:\n                                hlsInstance.recoverMediaError();\n                                break;\n                            default:\n                                completeSwitch();\n                                break;\n                        }\n                    }\n                });\n                \n                setTimeout(() => {\n                    if (config.isSwitching) {\n                        completeSwitch();\n                    }\n                }, 5000);\n                \n            } else if (video.canPlayType('application\/vnd.apple.mpegurl')) {\n                video.addEventListener('loadedmetadata', () => {\n                    completeSwitch();\n                    video.play().catch(e => {});\n                });\n                \n                video.addEventListener('error', () => {\n                    completeSwitch();\n                });\n                \n                setTimeout(() => {\n                    if (config.isSwitching) {\n                        completeSwitch();\n                    }\n                }, 5000);\n            } else {\n                completeSwitch();\n            }\n        }\n        \n        function completeSwitch() {\n            config.isSwitching = false;\n            elements.loadingIndicator.style.display = 'none';\n            elements.playerContainer.classList.remove('loading');\n        }\n        \n        function initPlayer() {\n            config = { ...liveData, currentLine: 0, currentQuality: 'auto', isSwitching: false };\n            \n            const url = getStreamUrl();\n            if (!url) return;\n            \n            if (dp) {\n                dp.destroy();\n                dp = null;\n            }\n            \n            dp = new DPlayer({\n                container: document.getElementById('dplayer'),\n                live: true,\n                autoplay: true,\n                theme: '#00a1d6',\n                loop: false,\n                lang: 'zh-cn',\n                screenshot: false,\n                hotkey: false,\n                preload: 'auto',\n                volume: 0.7,\n                mutex: true,\n                controls: false,\n                video: {\n                    url: url,\n                    pic: config.coverImg,\n                    type: 'customHls',\n                    customType: {\n                        customHls: function(video, player) {}\n                    }\n                },\n                contextmenu: [],\n                danmaku: false\n            });\n            \n            const videoElement = dp.video;\n            if (videoElement) {\n                videoElement.style.pointerEvents = 'none';\n                videoElement.addEventListener('click', function(e) {\n                    e.preventDefault();\n                    e.stopPropagation();\n                    return false;\n                });\n            }\n            \n            const dplayerContainer = dp.container;\n            dplayerContainer.style.pointerEvents = 'none';\n            dplayerContainer.addEventListener('click', function(e) {\n                e.preventDefault();\n                e.stopPropagation();\n                return false;\n            });\n            \n            isMuted = dp.video.muted;\n            elements.volumeBtn.innerHTML = isMuted \n                ? '<i class=\"fas fa-volume-mute\"><\/i>'\n                : '<i class=\"fas fa-volume-up\"><\/i>';\n            \n            switchStream(url);\n        }\n\n        function refreshPlayer() {\n            if (config.isSwitching || !config.stream) return;\n            const url = getStreamUrl(config.currentLine, config.currentQuality);\n            switchStream(url);\n        }\n        \n        function switchLine(lineIndex) {\n            if (config.isSwitching || !config.hlsLines || lineIndex >= config.hlsLines.length) return;\n            config.currentLine = lineIndex;\n            elements.lineBtn.innerHTML = `线路${lineIndex + 1} <i class=\"fas fa-chevron-down\"><\/i>`;\n            elements.floatingLine.textContent = `线路${lineIndex + 1}`;\n            updateLineMenuHighlights();\n            elements.lineMenu.style.display = 'none';\n            \n            const url = getStreamUrl(lineIndex, config.currentQuality);\n            switchStream(url);\n        }\n        \n        function switchQuality(quality) {\n            if (config.isSwitching) return;\n            config.currentQuality = quality;\n            const displayText = quality === 'auto' ? '默认' : quality;\n            elements.qualityBtn.innerHTML = `${displayText} <i class=\"fas fa-chevron-down\"><\/i>`;\n            elements.floatingQuality.textContent = displayText;\n            updateQualityMenuHighlights();\n            elements.qualityMenu.style.display = 'none';\n            \n            const url = getStreamUrl(config.currentLine, quality);\n            switchStream(url);\n        }\n        \n        function formatDuration(seconds) {\n            if (!seconds) return '0秒';\n            \n            const days = Math.floor(seconds \/ (24 * 60 * 60));\n            const hours = Math.floor((seconds % (24 * 60 * 60)) \/ (60 * 60));\n            const minutes = Math.floor((seconds % (60 * 60)) \/ 60);\n            const secs = Math.floor(seconds % 60);\n            \n            let result = '';\n            if (days > 0) result += `${days}天`;\n            if (hours > 0) result += `${hours}小时`;\n            if (minutes > 0) result += `${minutes}分钟`;\n            if (secs > 0 || result === '') result += `${secs}秒`;\n            \n            return result;\n        }\n        \n        function updateTimeInfo() {\n            if (!config.status || !config.startTime) return;\n            \n            const pureStatus = config.status.replace(\/^状态:\/, '').toLowerCase();\n            const isPublic = pureStatus === 'public';\n            const startTime = config.startTime ? new Date(config.startTime).getTime() : null;\n            const now = Date.now();\n            \n            if (config.isLive && startTime) {\n                if (isPublic) {\n                    elements.floatingDurationLabel.textContent = '直播时长:';\n                    elements.floatingTimeLabel.textContent = '直播时间:';\n                    \n                    const duration = Math.floor((now - startTime) \/ 1000);\n                    const durationText = formatDuration(duration);\n                    elements.floatingDuration.textContent = durationText;\n                    \n                    const startDate = new Date(startTime);\n                    elements.floatingStatusTime.textContent = startDate.toLocaleString();\n                } else {\n                    elements.floatingDurationLabel.textContent = '持续时长:';\n                    elements.floatingTimeLabel.textContent = '私密直播:';\n                    \n                    const duration = Math.floor((now - startTime) \/ 1000);\n                    const durationText = formatDuration(duration);\n                    elements.floatingDuration.textContent = durationText;\n                    \n                    const startDate = new Date(startTime);\n                    elements.floatingStatusTime.textContent = startDate.toLocaleString();\n                }\n            } else if (startTime) {\n                elements.floatingDurationLabel.textContent = '持续时长:';\n                elements.floatingTimeLabel.textContent = '下播时长:';\n                \n                const duration = Math.floor((now - startTime) \/ 1000);\n                durationText = formatDuration(duration);\n                elements.floatingDuration.textContent = durationText;\n                \n                const endDate = new Date(startTime);\n                elements.floatingStatusTime.textContent = endDate.toLocaleString();\n            } else {\n                elements.floatingDuration.textContent = '--';\n                elements.floatingStatusTime.textContent = '--';\n            }\n        }\n        \n        function startDurationUpdate() {\n            if (durationInterval) {\n                clearInterval(durationInterval);\n            }\n            \n            updateTimeInfo();\n            durationInterval = setInterval(updateTimeInfo, 1000);\n        }\n        \n        function updateFloatingPanel() {\n            const pureUsername = (config.username || '').split('\/').pop() || config.username || '';\n            elements.floatingHostName.textContent = pureUsername || '未知主播';\n            elements.membersCount.textContent = config.membersCount > 0 ? config.membersCount.toLocaleString() : '--';\n            elements.floatingMembersCount.textContent = config.membersCount > 0 ? config.membersCount.toLocaleString() : '--';\n            \n            if (config.topBestPlace > 0) {\n                const rankText = `TOP ${config.topBestPlace}`;\n                elements.floatingTopBestPlace.textContent = rankText;\n            } else {\n                elements.floatingTopBestPlace.textContent = '未上榜';\n            }\n            \n            if (config.tipMenuCreatedAt) {\n                const createdAt = new Date(config.tipMenuCreatedAt);\n                elements.floatingTipMenuCreatedAt.textContent = createdAt.toLocaleString();\n            } else {\n                elements.floatingTipMenuCreatedAt.textContent = '--';\n            }\n            \n            if (config.topic && config.topic.trim() !== '') {\n                elements.floatingTopic.textContent = config.topic;\n                elements.floatingTopicSection.style.display = 'block';\n            } else {\n                elements.floatingTopicSection.style.display = 'none';\n            }\n            \n            if (config.goalDescription && config.goalDescription.trim() !== '') {\n                elements.floatingGoalDescription.textContent = config.goalDescription;\n                elements.floatingGoalSection.style.display = 'block';\n            } else {\n                elements.floatingGoalSection.style.display = 'none';\n            }\n            \n            if (config.userDescription && config.userDescription.trim() !== '') {\n                elements.floatingUserDescription.textContent = config.userDescription;\n                elements.floatingUserDescriptionSection.style.display = 'block';\n            } else {\n                elements.floatingUserDescriptionSection.style.display = 'none';\n            }\n            \n            if (config.fanClubDescription && config.fanClubDescription.trim() !== '') {\n                elements.floatingFanClubDescription.textContent = config.fanClubDescription;\n                elements.floatingFanClubSection.style.display = 'block';\n            } else {\n                elements.floatingFanClubSection.style.display = 'none';\n            }\n            \n            if (config.tipMenuPriceList && config.tipMenuPriceList.length > 0) {\n                elements.floatingPriceList.innerHTML = '';\n                config.tipMenuPriceList.forEach((tip) => {\n                    const priceItem = document.createElement('div');\n                    priceItem.className = 'floating-price-item';\n                    \n                    const activityDiv = document.createElement('div');\n                    activityDiv.className = 'floating-price-activity';\n                    activityDiv.textContent = tip.activity || '未命名';\n                    \n                    const priceDiv = document.createElement('div');\n                    priceDiv.className = 'floating-price-value';\n                    priceDiv.textContent = `${tip.price || 0} 代币`;\n                    \n                    priceItem.appendChild(activityDiv);\n                    priceItem.appendChild(priceDiv);\n                    elements.floatingPriceList.appendChild(priceItem);\n                });\n            } else {\n                elements.floatingPriceList.innerHTML = '<div class=\"floating-price-item\"><div class=\"floating-price-activity\">暂无打赏菜单<\/div><\/div>';\n            }\n            \n            startDurationUpdate();\n        }\n        \n        function initUI() {\n            const pureUsername = (config.username || '').split('\/').pop() || config.username || '';\n            elements.hostName.textContent = pureUsername || '未知主播';\n            \n            if (config.topic && config.topic.trim() !== '') {\n                elements.topic.textContent = config.topic;\n                elements.topicContainer.style.display = 'block';\n            } else {\n                elements.topicContainer.style.display = 'none';\n            }\n            \n            if (config.avatarUrl) {\n                const prefix = 'https:\/\/static-cdn.strpst.com';\n                if (config.avatarUrl.startsWith(prefix + prefix)) {\n                    config.avatarUrl = config.avatarUrl.replace(prefix + prefix, prefix);\n                }\n                elements.avatarImg.src = config.avatarUrl;\n            } else {\n                elements.avatarImg.src = '主播';\n            }\n            \n            updateFloatingPanel();\n        }\n        \n        function initLineMenu() {\n            elements.lineMenu.innerHTML = '';\n            \n            if (!config.hlsLines || config.hlsLines.length === 0) {\n                const item = document.createElement('div');\n                item.className = 'menu-item';\n                item.textContent = '无线路';\n                item.style.opacity = '0.6';\n                elements.lineMenu.appendChild(item);\n                return;\n            }\n            \n            config.hlsLines.forEach((line, index) => {\n                const item = document.createElement('div');\n                item.className = `menu-item ${index === config.currentLine ? 'active' : ''}`;\n                item.textContent = `线路${index + 1}`;\n                item.dataset.index = index;\n                item.onclick = (e) => {\n                    e.stopPropagation();\n                    switchLine(index);\n                };\n                elements.lineMenu.appendChild(item);\n            });\n        }\n        \n        function initQualityMenu() {\n            elements.qualityMenu.innerHTML = '';\n            const pureStatus = (config.status || '').replace(\/^状态:\/, '').toLowerCase();\n            \n            let availableQualities = [];\n            availableQualities.push({ value: 'auto', display: '默认' });\n            \n            if (pureStatus !== 'public') {\n                availableQualities = [{ value: '160p_blurred', display: '模糊' }];\n                config.currentQuality = '160p_blurred';\n            } else if (config.pixelatedResolutions && config.pixelatedResolutions.length > 0) {\n                config.pixelatedResolutions.forEach(resolution => {\n                    if (resolution !== '160p_blurred') {\n                        availableQualities.push({ \n                            value: resolution, \n                            display: resolution \n                        });\n                    }\n                });\n            }\n            \n            availableQualities.forEach((quality) => {\n                const isActive = quality.value === config.currentQuality;\n                const item = document.createElement('div');\n                item.className = `menu-item ${isActive ? 'active' : ''}`;\n                item.textContent = quality.display;\n                item.dataset.quality = quality.value;\n                item.onclick = (e) => {\n                    e.stopPropagation();\n                    switchQuality(quality.value);\n                };\n                elements.qualityMenu.appendChild(item);\n            });\n            \n            const currentQuality = availableQualities.find(q => q.value === config.currentQuality);\n            const displayText = currentQuality ? currentQuality.display : '默认';\n            elements.qualityBtn.innerHTML = `${displayText} <i class=\"fas fa-chevron-down\"><\/i>`;\n            elements.floatingQuality.textContent = displayText;\n        }\n        \n        function updateLineMenuHighlights() {\n            const items = elements.lineMenu.querySelectorAll('.menu-item');\n            items.forEach((item, index) => {\n                if (index === config.currentLine) {\n                    item.classList.add('active');\n                } else {\n                    item.classList.remove('active');\n                }\n            });\n        }\n        \n        function updateQualityMenuHighlights() {\n            const items = elements.qualityMenu.querySelectorAll('.menu-item');\n            items.forEach((item) => {\n                const quality = item.dataset.quality;\n                if (quality === config.currentQuality) {\n                    item.classList.add('active');\n                } else {\n                    item.classList.remove('active');\n                }\n            });\n        }\n        \n        function bindEvents() {\n            elements.refreshBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                refreshPlayer();\n            });\n            \n            elements.volumeBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                if (dp && dp.video && !config.isSwitching) {\n                    isMuted = !isMuted;\n                    dp.video.muted = isMuted;\n                    elements.volumeBtn.innerHTML = isMuted \n                        ? '<i class=\"fas fa-volume-mute\"><\/i>'\n                        : '<i class=\"fas fa-volume-up\"><\/i>';\n                }\n            });\n            \n            elements.fullscreenBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                if (!isFullscreen) {\n                    const container = document.querySelector('.player-container');\n                    if (container.requestFullscreen) {\n                        container.requestFullscreen();\n                    } else if (container.webkitRequestFullscreen) {\n                        container.webkitRequestFullscreen();\n                    }\n                    elements.fullscreenBtn.innerHTML = '<i class=\"fas fa-compress\"><\/i>';\n                } else {\n                    if (document.exitFullscreen) {\n                        document.exitFullscreen();\n                    } else if (document.webkitExitFullscreen) {\n                        document.webkitExitFullscreen();\n                    }\n                    elements.fullscreenBtn.innerHTML = '<i class=\"fas fa-expand\"><\/i>';\n                }\n                isFullscreen = !isFullscreen;\n            });\n            \n            elements.lineBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                elements.lineMenu.style.display = \n                    elements.lineMenu.style.display === 'block' ? 'none' : 'block';\n                elements.qualityMenu.style.display = 'none';\n                updateLineMenuHighlights();\n            });\n            \n            elements.qualityBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                elements.qualityMenu.style.display = \n                    elements.qualityMenu.style.display === 'block' ? 'none' : 'block';\n                elements.lineMenu.style.display = 'none';\n                updateQualityMenuHighlights();\n            });\n            \n            document.addEventListener('click', (e) => {\n                if (!e.target.closest('.selector-btn')) {\n                    elements.lineMenu.style.display = 'none';\n                    elements.qualityMenu.style.display = 'none';\n                }\n            });\n            \n            document.addEventListener('fullscreenchange', handleFullscreenChange);\n            document.addEventListener('webkitfullscreenchange', handleFullscreenChange);\n            \n            elements.floatingBtn.addEventListener('click', (e) => {\n                e.stopPropagation();\n                elements.floatingPanel.style.display = \n                    elements.floatingPanel.style.display === 'block' ? 'none' : 'block';\n            });\n            \n            document.addEventListener('click', (e) => {\n                if (!e.target.closest('.floating-button') && !e.target.closest('.floating-panel')) {\n                    elements.floatingPanel.style.display = 'none';\n                }\n            });\n        }\n        \n        function handleFullscreenChange() {\n            const isFullscreenNow = !!(document.fullscreenElement || \n                document.webkitFullscreenElement);\n            if (!isFullscreenNow) {\n                elements.fullscreenBtn.innerHTML = '<i class=\"fas fa-expand\"><\/i>';\n                isFullscreen = false;\n            }\n        }\n        \n        function initializeApp() {\n            if (!liveData || Object.keys(liveData).length === 0) return;\n            \n            addSystemMessage('正在读取直播间信息');\n            \n            config = { ...liveData, currentLine: 0, currentQuality: 'auto', isSwitching: false };\n            \n            initUI();\n            initLineMenu();\n            initQualityMenu();\n            initPlayer();\n            bindEvents();\n            initChat();\n        }\n        \n        document.addEventListener('DOMContentLoaded', initializeApp);\n    <\/script>\n<\/body>\n<\/html>",
    "ruleImage": "https:\/\/img.strpst.com\/thumbs\/{$.popularSnapshotTimestamp}\/{$..id}_webp",
    "ruleLink": "{$.username}",
    "rulePubDate": "观众:{$.viewersCount}人 状态:{$.status} @js:result.replace(\/(状态:)public\/g, \"$1公开\").replace(\/(状态:)(?!公开).+\/g, \"$1付费\")",
    "ruleTitle": "$.username",
    "showWebLog": false,
    "singleUrl": false,
    "sortUrl": "全部直播::\/api\/front\/v2\/models?limit=60&primaryTag=girls\n直播推荐::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"recommended\"]]\n竖屏直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"mobile\"]]\n中文直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"tagLanguageChinese\"]]\n日本直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"tagLanguageJapanese\"]]\n韩国直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"tagLanguageKorean\"]]\n户外直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"doPublicPlace\"]]\n青少年直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"ageTeen\"]]\n俄罗斯直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"tagLanguageRussianSpeaking\"]]\nASMR直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"asmr\"]]\nCOS直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"doCosplay\"]]\n情侣直播::\/api\/front\/models?limit=60&primaryTag=couples\n变性直播::\/api\/front\/models?limit=60&primaryTag=trans\n新人直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"autoTagNew\"]]\n互动玩具::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"autoTagInteractiveToy\"]]\n炮机直播::\/api\/front\/models?limit=60&primaryTag=girls&filterGroupTags=[[\"fuckMachine\"]]",
    "sourceComment": "备用:\nhttps:\/\/zh.mywebcamroom.com\nhttps:\/\/zh.topcams.tv\nhttps:\/\/zh.stripchatgirls.com\nhttps:\/\/zh.stripchat.com\nhttps:\/\/zh.xhamsterlive.com\nhttps:\/\/zh.hotzcam.com\nhttps:\/\/go.tklivechat.com\nhttps:\/\/zh.spankbanglive.com\nhttps:\/\/zh.live.91pinse.com\npkey=Iecohquahc5RieQu\npkey=bXorqTB5ZhP5FcpX\n\n修复播放器异常裁切\n现在双击可以进入全屏",
    "sourceIcon": "https:\/\/i.imgs.ovh\/2025\/12\/29\/Cwo6eX.png",
    "sourceName": "Strip直播",
    "sourceUrl": "https:\/\/zh.topcams.tv",
    "type": 0
}
广告