{"version":"https://jsonfeed.org/version/1.1","title":"DBD-raws","home_page_url":"https://cms.dbdraws.dpdns.org","feed_url":"https://cms-dbdraws.pages.dev/json/","description":"<style>\n        #api-text-container {\n            font-size: 22px;\n            \n            max-width: 90%;\n            text-align: center;\n            line-height: 1.6;\n            padding: 20px;\n            border-left: 4px solid #4CAF50;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n        }\n@media (max-width: 768px) {\n            #api-text-container {\n                font-size: 18px; /* 移动端减小到18px */\n                max-width: 95%; /* 移动端宽度占比更大（减少留白） */\n                padding: 15px; /* 移动端内边距减小 */\n            }\n    </style>\n<p id=\"api-text-container\"></p>\n<script>\n  // 调用文字API（以「一言」为例，返回随机句子）\n  fetch(\"https://v1.hitokoto.cn/?c=d\") // API地址（返回JSON）\n    .then(response => response.json())\n    .then(data => {\n      // 将API返回的文字填入容器（不添加任何样式，使用网站默认文本样式）\n      document.getElementById(\"api-text-container\").textContent = data.hitokoto;\n    })\n    .catch(error => {\n      // 加载失败时显示提示（同样使用默认样式）\n      document.getElementById(\"api-text-container\").textContent = \"加载失败，请稍后刷新\";\n    });\n</script>\n\n<p>这是一款轻量级的内容管理系统（CMS），可自行托管于Cloudflare平台。通过microfeed，您能够便捷地发布多种形式的内容——包括音频、视频、照片、文档、博客文章及外部链接，并以网页、RSS订阅和JSON格式输出信息流。</p>","icon":"https://cms-dbdraws.pages.dev/assets/default/channel-image.png","favicon":"https://cms-dbdraws.pages.dev/assets/default/favicon.png","authors":[{"name":"DBD制作组"}],"language":"zh-cn","items":[{"id":"mX7-wXOi5JR","title":"随机小姐姐-v1.3-单源重试机制","url":"https://cms.dbdraws.dpdns.org/i/11-mX7-wXOi5JR/","content_html":"<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>随机小姐姐</title>\n    <style>\n        /* 原有样式不变 */\n        body {\n            font-family: 'Arial', sans-serif;\n            max-width: 1000px;\n            margin: 0 auto;\n            padding: 15px;\n            background-color: #f5f5f5;\n        }\n        \n        #player-container {\n            background: #000;\n            border-radius: 8px;\n            margin-bottom: 15px;\n            overflow: hidden;\n            box-shadow: 0 4px 8px rgba(0,0,0,0.1);\n        }\n        \n        #player {\n            width: 100%;\n            height: auto;\n            max-height: 80vh;\n            min-height: 250px;\n            max-height: 550px;\n            display: block;\n        }\n        \n        .control-panel {\n            background: white;\n            padding: 15px;\n            border-radius: 8px;\n            box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n            margin-bottom: 15px;\n        }\n        \n        .button-group {\n            display: flex;\n            gap: 8px;\n            margin-bottom: 12px;\n            flex-wrap: wrap;\n        }\n        \n        button {\n            background: #7F9CCC;\n            color: #fff;\n            border: none;\n            padding: 10px 12px;\n            border-radius: 6px;\n            font-weight: bold;\n            cursor: pointer;\n            transition: all 0.3s;\n            flex-grow: 1;\n            font-size: 14px;\n            min-width: 80px;\n        }\n        \n        button:hover {\n            background: #6A8BBC;\n            transform: translateY(-1px);\n        }\n        \n        button#toggleAuto {\n            flex-grow: 2;\n        }\n        \n        .source-management {\n            margin-top: 15px;\n        }\n        \n        .source-list {\n            margin-top: 10px;\n            max-height: 30vh;\n            overflow-y: auto;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            padding: 8px;\n            background: white;\n        }\n        \n        .source-item {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            padding: 8px;\n            border-bottom: 1px solid #eee;\n        }\n        \n        .source-item:last-child {\n            border-bottom: none;\n        }\n        \n        .source-url {\n            flex-grow: 1;\n            overflow: hidden;\n            text-overflow: ellipsis;\n            white-space: nowrap;\n            margin-right: 8px;\n            font-size: 14px;\n        }\n        \n        .source-actions {\n            display: flex;\n            gap: 5px;\n        }\n        \n        .source-actions button {\n            padding: 5px 8px;\n            font-size: 12px;\n            background: #ff6b6b;\n            min-width: 50px;\n            flex-grow: 0;\n        }\n        \n        .source-actions button:first-child {\n            background: #4CAF50;\n        }\n        \n        .add-source-form {\n            display: flex;\n            gap: 8px;\n            margin-top: 12px;\n        }\n        \n        .add-source-form input {\n            flex-grow: 1;\n            padding: 10px;\n            border: 1px solid #ddd;\n            border-radius: 4px;\n            font-size: 14px;\n        }\n        \n        .add-source-form button {\n            width: 90px;\n            flex-grow: 0;\n        }\n        \n        .status {\n            margin-top: 10px;\n            font-size: 14px;\n            color: #666;\n            text-align: center;\n        }\n        \n        .current-source {\n            font-weight: bold;\n            color: #7F9CCC;\n            word-break: break-all;\n        }\n        \n        .default-source-indicator {\n            color: #888;\n            font-style: italic;\n            margin-top: 5px;\n        }\n        \n        /* 手机端优化 */\n        @media (max-width: 600px) {\n            body {\n                padding: 10px;\n            }\n            \n            #player {\n                min-height: 200px;\n            }\n            \n            .control-panel {\n                padding: 12px;\n            }\n            \n            button {\n                padding: 8px 10px;\n                font-size: 13px;\n            }\n            \n            .source-item {\n                flex-direction: column;\n                align-items: flex-start;\n            }\n            \n            .source-actions {\n                margin-top: 5px;\n                width: 100%;\n                justify-content: flex-end;\n            }\n            \n            .add-source-form {\n                flex-direction: column;\n            }\n            \n            .add-source-form button {\n                width: 100%;\n            }\n        }\n    </style>\n</head>\n\n\n    <h1 style=\"text-align: center; margin-bottom: 15px;\">随机小姐姐</h1>\n    \n    <div id=\"player-container\">\n        <video id=\"player\" controls playsinline></video>\n    </div>\n    \n    <div class=\"control-panel\">\n        <div class=\"button-group\">\n            <button id=\"toggleAuto\">连续播放: 开</button>\n            <button id=\"nextVideo\">换一个 ▶</button>\n            <button id=\"switchSource\">切换源</button>\n        </div>\n        \n        <div class=\"status\" id=\"statusArea\">\n            <!-- 状态信息将在这里动态生成 -->\n        </div>\n        \n        <div class=\"source-management\">\n            <h3 style=\"margin-bottom: 8px;\">视频源管理</h3>\n            <div class=\"source-list\" id=\"sourceList\">\n                <!-- 视频源列表将在这里动态生成 -->\n            </div>\n            \n            <div class=\"add-source-form\">\n                <input type=\"text\" id=\"newSourceInput\" placeholder=\"输入视频API地址\">\n                <button id=\"addSource\">添加源</button>\n            </div>\n        </div>\n    </div>\n\n    <script>\n        document.addEventListener('DOMContentLoaded', function() {\n            // 隐藏的默认源 (不在界面显示)\n            const defaultSources = [\n                'http://api.xkwat.com/vid.php',\n                'https://www.ximi.me/video/list.php'\n            ];\n            \n            // 用户添加的源\n            let userSources = JSON.parse(localStorage.getItem('userVideoSources')) || [];\n            \n            // 获取所有源 (默认源 + 用户源)\n            function getAllSources() {\n                return [...userSources, ...defaultSources];\n            }\n            \n            let currentSourceIndex = 0;\n            let autoPlay = localStorage.getItem('autoPlay') !== 'false';\n            const player = document.getElementById('player');\n            let isProcessing = false; // 防止重复操作\n            \n            // 添加：单源内重试控制（核心修改）\n            let currentSourceRetries = 0; // 当前源的重试次数\n            const maxRetriesPerSource = 2; // 单个源的最大重试次数（可自定义）\n            \n            // DOM元素\n            const elements = {\n                toggleAuto: document.getElementById('toggleAuto'),\n                nextVideo: document.getElementById('nextVideo'),\n                switchSource: document.getElementById('switchSource'),\n                sourceList: document.getElementById('sourceList'),\n                newSourceInput: document.getElementById('newSourceInput'),\n                addSource: document.getElementById('addSource'),\n                statusArea: document.getElementById('statusArea')\n            };\n            \n            // 添加：重置当前源的重试计数\n            function resetSourceRetry() {\n                currentSourceRetries = 0;\n            }\n            \n            // 初始化播放器\n            function initPlayer() {\n                updateAutoPlayButton();\n                updateSourceDisplay();\n                renderSourceList();\n                \n                // 如果有用户源，优先使用第一个用户源\n                if (userSources.length > 0) {\n                    currentSourceIndex = 0;\n                } else {\n                    // 否则使用第一个默认源\n                    currentSourceIndex = defaultSources.length > 0 ? userSources.length : 0;\n                }\n                \n                resetSourceRetry(); // 初始化时重置重试计数\n                loadRandomVideo();\n            }\n            \n            // 更新自动播放按钮状态\n            function updateAutoPlayButton() {\n                elements.toggleAuto.textContent = `连续播放: ${autoPlay ? '开' : '关'}`;\n            }\n            \n            // 加载随机视频（核心修改：单源内重试逻辑）\n            function loadRandomVideo() {\n                if (isProcessing) return;\n                isProcessing = true;\n                \n                const allSources = getAllSources();\n                if (allSources.length === 0) {\n                    showStatus('无可用视频源，请添加视频源', true);\n                    isProcessing = false;\n                    return;\n                }\n                \n                // 确保索引在有效范围内\n                if (currentSourceIndex >= allSources.length) {\n                    currentSourceIndex = 0;\n                }\n                \n                const currentSource = allSources[currentSourceIndex];\n                const timestamp = new Date().getTime();\n                const randomParam = Math.random();\n                \n                let videoUrl = currentSource;\n                if (currentSource.includes('?')) {\n                    videoUrl += `&_t=${timestamp}_${randomParam}`;\n                } else {\n                    videoUrl += `?_t=${timestamp}_${randomParam}`;\n                }\n                \n                player.src = videoUrl;\n                updateSourceDisplay();\n                \n                // 使用requestAnimationFrame来优化性能\n                requestAnimationFrame(() => {\n                    player.play().catch(e => {\n                        console.error(`当前源第${currentSourceRetries+1}次播放失败:`, e);\n                        \n                        // 1. 区分错误类型处理\n                        if (e.name === 'NotAllowedError') {\n                            // 浏览器拦截自动播放（非源错误），提示用户手动点击\n                            showStatus('请点击播放按钮开始（浏览器限制自动播放）', true);\n                        } else {\n                            // 源错误：判断是否达到单源最大重试次数\n                            if (currentSourceRetries < maxRetriesPerSource) {\n                                // 未达最大次数，重试当前源（同一源多次尝试）\n                                currentSourceRetries++;\n                                showStatus(`当前源重试中(${currentSourceRetries}/${maxRetriesPerSource})...`, true);\n                                \n                                // 短暂延迟后重试（避免频繁请求）\n                                setTimeout(() => {\n                                    loadRandomVideo(); // 重试当前源\n                                }, 1000);\n                                return; // 提前退出，不执行后续解锁逻辑（交由重试处理）\n                            } else {\n                                // 达到单源最大重试次数，切换到下一个源\n                                showStatus(`当前源已重试${maxRetriesPerSource}次失败，切换到下一个源...`, true);\n                                resetSourceRetry(); // 重置当前源重试计数\n                                switchToNextSource(); // 切换到新源\n                            }\n                        }\n                    }).finally(() => {\n                        // 2. 无论成败，最终解锁（确保下次能操作）\n                        isProcessing = false;\n                        \n                        // 播放成功时重置当前源重试计数（例如用户手动点击播放）\n                        if (player.paused === false) {\n                            resetSourceRetry();\n                        }\n                    });\n                });\n            }\n            \n            // 显示状态信息\n            function showStatus(message, isError = false) {\n                requestAnimationFrame(() => {\n                    elements.statusArea.innerHTML = `<div class=\"status-message\" style=\"color: ${isError ? '#ff4444' : '#666'}\">${message}</div>`;\n                });\n            }\n            \n            // 切换到下一个源\n            function switchToNextSource() {\n                if (isProcessing) return;\n                \n                const allSources = getAllSources();\n                if (allSources.length === 0) return;\n                \n                currentSourceIndex = (currentSourceIndex + 1) % allSources.length;\n                resetSourceRetry(); // 切换源时重置重试计数\n                loadRandomVideo();\n            }\n            \n            // 更新当前源显示\n            function updateSourceDisplay() {\n                const allSources = getAllSources();\n                if (allSources.length > 0) {\n                    const currentSource = allSources[currentSourceIndex];\n                    const isDefaultSource = defaultSources.includes(currentSource);\n                    \n                    requestAnimationFrame(() => {\n                        if (isDefaultSource) {\n                            // 使用默认源时不显示具体URL\n                            elements.statusArea.innerHTML = `\n                                <div class=\"status-message\">正在使用系统默认视频源</div>\n                                <div class=\"default-source-indicator\">如需更好体验，请添加自定义视频源</div>\n                            `;\n                        } else {\n                            // 使用用户源时显示URL\n                            elements.statusArea.innerHTML = `\n                                <div class=\"status-message\">当前视频源:</div>\n                                <div class=\"current-source\">${currentSource}</div>\n                            `;\n                        }\n                    });\n                } else {\n                    showStatus('无可用视频源，请添加视频源', true);\n                }\n            }\n            \n            // 渲染源列表 (只显示用户添加的源)\n            function renderSourceList() {\n                requestAnimationFrame(() => {\n                    elements.sourceList.innerHTML = '';\n                    \n                    if (userSources.length === 0) {\n                        elements.sourceList.innerHTML = '<div style=\"text-align: center; padding: 10px; color: #888;\">暂无自定义视频源，请添加</div>';\n                        return;\n                    }\n                    \n                    const fragment = document.createDocumentFragment();\n                    \n                    userSources.forEach((source, index) => {\n                        const sourceItem = document.createElement('div');\n                        sourceItem.className = 'source-item';\n                        \n                        const sourceUrl = document.createElement('span');\n                        sourceUrl.className = 'source-url';\n                        sourceUrl.textContent = source;\n                        \n                        const sourceActions = document.createElement('div');\n                        sourceActions.className = 'source-actions';\n                        \n                        const useBtn = document.createElement('button');\n                        useBtn.textContent = '使用';\n                        useBtn.addEventListener('click', () => {\n                            if (!isProcessing) {\n                                resetSourceRetry(); // 用户主动操作，重置重试计数\n                                useSource(index);\n                            }\n                        });\n                        \n                        const deleteBtn = document.createElement('button');\n                        deleteBtn.textContent = '删除';\n                        deleteBtn.addEventListener('click', () => {\n                            if (!isProcessing) deleteSource(index);\n                        });\n                        \n                        sourceActions.appendChild(useBtn);\n                        sourceActions.appendChild(deleteBtn);\n                        \n                        sourceItem.appendChild(sourceUrl);\n                        sourceItem.appendChild(sourceActions);\n                        \n                        fragment.appendChild(sourceItem);\n                    });\n                    \n                    elements.sourceList.appendChild(fragment);\n                });\n            }\n            \n            // 删除源\n            function deleteSource(userSourceIndex) {\n                if (isProcessing) return;\n                isProcessing = true;\n                \n                const allSources = getAllSources();\n                const sourceToDelete = userSources[userSourceIndex];\n                \n                // 检查是否正在使用要删除的源\n                if (allSources[currentSourceIndex] === sourceToDelete) {\n                    // 如果删除的是当前正在使用的源，切换到下一个源\n                    switchToNextSource();\n                }\n                \n                userSources.splice(userSourceIndex, 1);\n                saveUserSources();\n                renderSourceList();\n                updateSourceDisplay();\n                \n                // 如果没有用户源了，自动切换到默认源\n                if (userSources.length === 0 && allSources.length > 0) {\n                    currentSourceIndex = allSources.length - 1; // 切换到最后一个源(默认源)\n                    loadRandomVideo();\n                }\n                \n                isProcessing = false;\n            }\n            \n            // 使用指定源\n            function useSource(index) {\n                if (isProcessing) return;\n                currentSourceIndex = index;\n                loadRandomVideo();\n            }\n            \n            // 添加新源\n            function addNewSource() {\n                if (isProcessing) return;\n                isProcessing = true;\n                \n                const newSource = elements.newSourceInput.value.trim();\n                \n                if (!newSource) {\n                    showStatus('请输入有效的视频API地址', true);\n                    isProcessing = false;\n                    return;\n                }\n                \n                // 简单验证URL格式\n                if (!newSource.startsWith('http://') && !newSource.startsWith('https://')) {\n                    showStatus('URL必须以http://或https://开头', true);\n                    isProcessing = false;\n                    return;\n                }\n                \n                // 检查是否已存在(包括默认源)\n                if (getAllSources().includes(newSource)) {\n                    showStatus('该视频源已存在', true);\n                    isProcessing = false;\n                    return;\n                }\n                \n                userSources.unshift(newSource); // 添加到开头\n                elements.newSourceInput.value = '';\n                saveUserSources();\n                \n                // 使用新添加的源\n                currentSourceIndex = 0;\n                resetSourceRetry(); // 添加新源后重置重试计数\n                loadRandomVideo();\n                renderSourceList();\n                \n                showStatus('视频源添加成功', false);\n                isProcessing = false;\n            }\n            \n            // 保存用户源到本地存储\n            function saveUserSources() {\n                localStorage.setItem('userVideoSources', JSON.stringify(userSources));\n            }\n            \n            // 事件监听 - 使用事件委托优化性能\n            document.addEventListener('click', function(e) {\n                if (e.target === elements.toggleAuto) {\n                    autoPlay = !autoPlay;\n                    localStorage.setItem('autoPlay', autoPlay);\n                    updateAutoPlayButton();\n                } else if (e.target === elements.nextVideo) {\n                    resetSourceRetry(); // 用户主动点击\"换一个\"，重置重试计数\n                    loadRandomVideo();\n                } else if (e.target === elements.switchSource) {\n                    resetSourceRetry(); // 用户主动切换源，重置重试计数\n                    switchToNextSource();\n                } else if (e.target === elements.addSource) {\n                    addNewSource();\n                }\n            });\n            \n            // 按Enter键添加源\n            elements.newSourceInput.addEventListener('keypress', function(e) {\n                if (e.key === 'Enter') {\n                    addNewSource();\n                }\n            });\n            \n            // 视频结束事件\n            player.addEventListener('ended', function() {\n                if (autoPlay && !isProcessing) {\n                    resetSourceRetry(); // 视频正常结束后，重置重试计数\n                    loadRandomVideo();\n                }\n            });\n            \n            // 视频错误事件（补充处理）\n            player.addEventListener('error', function() {\n                if (!isProcessing && currentSourceRetries < maxRetriesPerSource) {\n                    // 播放器错误事件也触发重试逻辑\n                    currentSourceRetries++;\n                    showStatus(`当前源重试中(${currentSourceRetries}/${maxRetriesPerSource})...`, true);\n                    setTimeout(() => loadRandomVideo(), 1000);\n                }\n            });\n            \n            // 初始化\n            initPlayer();\n        });\n    </script>\n","content_text":"随机小姐姐\n\n\n随机小姐姐\n\n\n连续播放: 开 换一个 ▶ 切换源\n\n\n\n视频源管理\n\n\n添加源","date_published":"2025-08-07T06:40:28.292Z","_microfeed":{"web_url":"https://cms-dbdraws.pages.dev/i/v13-mX7-wXOi5JR/","json_url":"https://cms-dbdraws.pages.dev/i/mX7-wXOi5JR/json/","rss_url":"https://cms-dbdraws.pages.dev/i/mX7-wXOi5JR/rss/","guid":"mX7-wXOi5JR","status":"published","itunes:episodeType":"full","date_published_short":"Thu Aug 07 2025","date_published_ms":1754548828292}}],"_microfeed":{"microfeed_version":"0.1.5","base_url":"https://cms-dbdraws.pages.dev","categories":[],"subscribe_methods":[{"name":"JSON","type":"json","url":"https://cms-dbdraws.pages.dev/json/","image":"https://cms-dbdraws.pages.dev/assets/brands/subscribe/json.png","enabled":true,"editable":false,"id":"p0wHIR3D42R"},{"name":"友情链接","type":"google podcasts","url":"https://blog.dbdraws.dpdns.org/friends","image":"https://cms-dbdraws.pages.dev/assets/brands/subscribe/google.png","enabled":true,"editable":true,"id":"BRWdIXxcFlR"}],"description_text":"这是一款轻量级的内容管理系统（CMS），可自行托管于Cloudflare平台。通过microfeed，您能够便捷地发布多种形式的内容——包括音频、视频、照片、文档、博客文章及外部链接，并以网页、RSS订阅和JSON格式输出信息流。","copyright":"Copyright © DBD制作组.2026","itunes:type":"episodic","items_sort_order":"newest_first"}}