樱花动漫

https://www.yinghua228.com

分享者: jianghubailei (2226)发布时间: 2天前

原作者:cute-pan

添加搜索 (原生阅读不支持)
美化UI(顶部播放器,中间显示详情,下方多线路播放列表)
添加 自动播放下一集

新增 播放记录,断点续播
二维码导入
{
    "articleStyle": 2,
    "cacheFirst": false,
    "customOrder": 0,
    "enableJs": true,
    "enabled": true,
    "enabledCookieJar": true,
    "header": "{\n  \"User-Agent\": \"Mozilla\/5.0 (Linux; Android 10; TAS-AN00 Build\/HUAWEITAS-AN00; wv) AppleWebKit\/537.36 (KHTML, like Gecko) Version\/4.0 Chrome\/114.0.5735.61 Mobile Safari\/537.36 Super 4.6.5\",\n  \"Referer\": \"https:\/\/www.yinghua228.com\/\",\n  \"Cookie\": \"ad_dismiss=1\"\n}",
    "injectJs": "let sources = [];\nlet currentSourceIndex = 0;\nlet currentEpisodeIndex = 0;\nlet dp = null;\nlet currentVideoUrl = '';\nlet autoPlayNext = true;\nlet currentVodId = '';\nlet currentVideoDuration = 0;\nlet progressTimer = null; \/\/ 全局定时器,精准控制\n\n\/\/ ======== 源变量的存取操作 =========\nfunction getSourceVar() {\n    if (typeof source !== 'undefined' && source) {\n        return String(source.getVariable()) || '';\n    }\n    return '';\n}\nfunction setSourceVar(value) {\n    if (typeof source !== 'undefined' && source) {\n        source.putVariable(value);\n    }\n}\n\n\/\/ == 播放记录管理(多剧集 + 断点续播)==\nconst PROGRESS_SAVE_INTERVAL = 3000; \/\/ 每3秒保存一次进度\nconst PROGRESS_MIN_SAVE = 2; \/\/ 最小保存进度,2秒就存,避免短播放不记录\nconst PROGRESS_THRESHOLD = 0.95;\n\nfunction getAllRecords() {\n    const varStr = getSourceVar();\n    if (!varStr) return {};\n    try { return JSON.parse(varStr); } catch (e) { return {}; }\n}\nfunction getRecord(vodId) {\n    const all = getAllRecords();\n    return all[vodId] || null;\n}\nfunction saveRecord(vodId, record) {\n    const all = getAllRecords();\n    all[vodId] = record;\n    setSourceVar(JSON.stringify(all));\n}\nfunction removeRecord(vodId) {\n    const all = getAllRecords();\n    delete all[vodId];\n    setSourceVar(JSON.stringify(all));\n}\nfunction cleanOldRecords() {\n    const all = getAllRecords();\n    const entries = Object.entries(all);\n    if (entries.length > 50) {\n        entries.sort((a,b) => (b[1].timestamp||0)-(a[1].timestamp||0));\n        const keep = entries.slice(0,50);\n        setSourceVar(JSON.stringify(Object.fromEntries(keep)));\n    }\n}\n\n\/\/ ========== 核心工具函数(精准存\/清进度)==========\nfunction stopProgressTimer() {\n    if (progressTimer) {\n        clearInterval(progressTimer);\n        progressTimer = null;\n    }\n}\n\n\/\/ 保存**当前播放集**的进度\nfunction saveCurrentProgressImmediately() {\n    if (!currentVodId || !dp || !sources[currentSourceIndex] || !sources[currentSourceIndex].episodes[currentEpisodeIndex]) {\n        return;\n    }\n    const current = dp.video.currentTime || 0;\n    const duration = dp.video.duration || 0;\n    \/\/ 小于最小保存进度\/视频过短,不保存\n    if (current < PROGRESS_MIN_SAVE || duration < 5) return;\n    \n    \/\/ 只保存**当前集**的进度,强制用全局当前的集数\/线路,杜绝串值\n    const record = {\n        sourceIndex: currentSourceIndex,\n        episodeIndex: currentEpisodeIndex,\n        episodeName: sources[currentSourceIndex].episodes[currentEpisodeIndex].name,\n        progress: (current\/duration) > PROGRESS_THRESHOLD ? 0 : current,\n        duration: duration,\n        timestamp: Date.now() \/\/ 时间戳,确保最新记录\n    };\n    saveRecord(currentVodId, record);\n}\nfunction extractVodIdFromSourceContainer() {\n    const html = $('#source-container').html() || '';\n    const match = html.match(\/\"id\":\"(\\d+)\"\/) || html.match(\/\\\/id\\\/(\\d+)\/);\n    return match ? match[1] : '';\n}\n\/\/ 【核心修复】重进时**严格校验记录的集数+时间戳**,只加载对应集的最新进度\nfunction playFromRecordOrFirstEpisode() {\n    if (!currentVodId) { \n        playEpisode(0,0, false, 0); \n        return; \n    }\n    const record = getRecord(currentVodId);\n    \/\/ 严格校验:记录存在+对应集数有效,才恢复进度\n    if (record && sources[record.sourceIndex] && sources[record.sourceIndex].episodes[record.episodeIndex]) {\n        currentSourceIndex = record.sourceIndex;\n        currentEpisodeIndex = record.episodeIndex;\n        $('.tab-btn').removeClass('active').eq(record.sourceIndex).addClass('active');\n        initPlaylist();\n        \/\/ 只恢复**该记录对应集**的进度,精准匹配\n        playEpisode(record.sourceIndex, record.episodeIndex, false, record.progress);\n        showError(`已恢复历史播放:${record.episodeName} ${formatTime(record.progress)}`);\n    } else {\n        playEpisode(0,0, false, 0);\n    }\n}\n\n\/\/ ========== 页面初始化 ==========\n$(document).ready(function() {\n    sources = parseSourcesFromHTML();\n    currentVodId = extractVodIdFromSourceContainer();\n    if (sources.length === 0) {\n        $('#playlistContainer').html('<div class=\"error-msg show\">未找到播放列表数据<\/div>');\n        return;\n    }\n    initTabs();\n    initPlaylist();\n    playFromRecordOrFirstEpisode();\n});\n\n\/\/ ========== 线路\/播放列表初始化(切集即时存进度)==========\nfunction initTabs() {\n    const $tabs = $('#sourceTabs'); $tabs.empty();\n    sources.forEach((src, index) => {\n        const $btn = $('<button>').addClass('tab-btn').text(src.name).toggleClass('active', index===0);\n        $btn.on('click', function() {\n            \/\/ 切线路:强制存当前进度+清定时器,新线路从头播\n            stopProgressTimer();\n            saveCurrentProgressImmediately();\n            $('.tab-btn').removeClass('active'); $(this).addClass('active');\n            currentSourceIndex = index; currentEpisodeIndex = 0;\n            initPlaylist();\n            playEpisode(currentSourceIndex, currentEpisodeIndex, true, 0);\n        });\n        $tabs.append($btn);\n    });\n    addAutoPlayToggle();\n}\nfunction addAutoPlayToggle() {\n    const $toggle = $('<button>').addClass('tab-btn').css({marginLeft:'auto',background:autoPlayNext?'#ff6b6b':'#666',fontSize:'12px'})\n        .text(autoPlayNext?'连播: 开':'连播: 关').on('click', function() {\n            autoPlayNext = !autoPlayNext;\n            $(this).text(autoPlayNext?'连播: 开':'连播: 关').css('background',autoPlayNext?'#ff6b6b':'#666');\n            showError(autoPlayNext?'已开启自动连播':'已关闭自动连播');\n            updateAutoPlayStatus();\n        });\n    $('#sourceTabs').append($toggle);\n}\nfunction updateAutoPlayStatus() {\n    $('#autoPlayStatus').text(autoPlayNext?'自动连播开启':'自动连播关闭').css('color',autoPlayNext?'#4ecdc4':'#888');\n}\n\/\/ 【核心修复】播放列表进度条**严格匹配记录的集数**,绝不显示错集进度\nfunction initPlaylist() {\n    const $container = $('#playlistContainer'); const $errorMsg = $('#errorMsg').detach();\n    $container.empty().append($errorMsg);\n    const currentSource = sources[currentSourceIndex];\n    const record = currentVodId ? getRecord(currentVodId) : null; \/\/ 只取当前视频的记录\n    \n    let recordTip = '';\n    \/\/ 记录提示只显示**当前视频最后播放集**的进度\n    if (record && (record.sourceIndex!==currentSourceIndex || record.episodeIndex!==currentEpisodeIndex)) {\n        const progressText = record.progress>0 ? ` (${formatTime(record.progress)})` : '';\n        recordTip = `<span style=\"color:#ff6b6b;margin-left:8px;cursor:pointer;\" onclick=\"resumeFromRecord()\">▶ 上次看到${record.episodeName}${progressText}<\/span>`;\n    }\n    $container.append(`\n        <div class=\"now-playing\">\n            <span class=\"label\">当前线路:<\/span><span class=\"source-name\">${currentSource.name}<\/span>\n            <span style=\"margin:0 5px;\">|<\/span><span class=\"label\">选中:<\/span><span id=\"currentEpiText\">${currentSource.episodes[currentEpisodeIndex]?.name||''}<\/span>\n            <span style=\"margin:0 5px;\">|<\/span><span id=\"autoPlayStatus\" style=\"color:${autoPlayNext?'#4ecdc4':'#888'};font-size:12px;\">${autoPlayNext?'自动连播开启':'自动连播关闭'}<\/span>\n            ${recordTip}\n        <\/div>\n    `);\n    $container.append(`<div class=\"playlist-title\"><span>播放列表<\/span><span class=\"episode-count\">共 ${currentSource.episodes.length} 集<\/span><\/div>`);\n    const $grid = $('<div>').addClass('episode-grid');\n    \n    currentSource.episodes.forEach((ep, idx) => {\n        const $btn = $('<button>').addClass('episode-btn').text(ep.name).toggleClass('playing', idx===currentEpisodeIndex);\n        \/\/ 【关键】只有**当前记录的集数**,才显示进度条,其他集绝不显示\n        if (record && record.sourceIndex === currentSourceIndex && record.episodeIndex === idx) {\n            $btn.css('border', '2px solid #ff6b6b');\n            if (record.progress > 0 && record.duration > 0) {\n                const percent = Math.min((record.progress \/ record.duration) * 100, 100);\n                const isFinished = percent > 95;\n                $btn.css('position', 'relative');\n                $btn.append(`\n                    <div style=\"\n                        position: absolute;\n                        bottom: 0;\n                        left: 0;\n                        height: 3px;\n                        background: ${isFinished ? '#4ecdc4' : '#ff6b6b'};\n                        width: ${isFinished ? 100 : percent}%;\n                    \"><\/div>\n                    <span style=\"\n                        position: absolute;\n                        top: 2px;\n                        right: 2px;\n                        font-size: 9px;\n                        color: ${isFinished ? '#4ecdc4' : '#ff6b6b'};\n                        background: rgba(0,0,0,0.7);\n                        padding: 1px 3px;\n                        border-radius: 3px;\n                    \">${isFinished ? '已看完' : formatTime(record.progress)}<\/span>\n                `);\n            } else {\n                $btn.append('<span style=\"position:absolute;top:2px;right:2px;font-size:10px;color:#ff6b6b;\">上次<\/span>');\n            }\n        }\n        $btn.on('click', function() {\n            \/\/ 切集:即时存当前进度+清定时器,新集强制0秒从头播\n            stopProgressTimer();\n            saveCurrentProgressImmediately();\n            currentEpisodeIndex = idx;\n            initPlaylist();\n            playEpisode(currentSourceIndex, currentEpisodeIndex, true, 0);\n        });\n        $grid.append($btn);\n    });\n    $container.append($grid);\n}\n\n\/\/ ========== 手动恢复历史记录(精准匹配集数)==========\nfunction resumeFromRecord() {\n    const record = getRecord(currentVodId);\n    if (!record) return;\n    if (sources[record.sourceIndex] && sources[record.sourceIndex].episodes[record.episodeIndex]) {\n        \/\/ 恢复前先保存当前播放的进度,避免丢失\n        stopProgressTimer();\n        saveCurrentProgressImmediately();\n        currentSourceIndex = record.sourceIndex; currentEpisodeIndex = record.episodeIndex;\n        $('.tab-btn').removeClass('active').eq(record.sourceIndex).addClass('active');\n        initPlaylist();\n        \/\/ 只恢复该记录对应集的进度\n        playEpisode(currentSourceIndex, currentEpisodeIndex, false, record.progress);\n        const progressText = record.progress>0 ? ` (${formatTime(record.progress)})` : '';\n        showError(`已恢复至: ${record.episodeName}${progressText}`);\n    }\n}\n\/\/ 时间格式化\nfunction formatTime(seconds) {\n    if (!seconds || seconds<0) return '00:00';\n    const h = Math.floor(seconds\/3600); const m = Math.floor((seconds%3600)\/60); const s = Math.floor(seconds%60);\n    return h>0 ? `${h}:${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}` : `${m.toString().padStart(2,'0')}:${s.toString().padStart(2,'0')}`;\n}\n\n\/\/ ========== 播放核心函数(切集0秒,重进精准加载对应进度)==========\nfunction playEpisode(sourceIdx, episodeIdx, showTip=false, resumeProgress=0) {\n    \/\/ 播放新集前,强制保存上一集进度,杜绝进度丢失\/串集\n    stopProgressTimer();\n    saveCurrentProgressImmediately();\n    \n    \/\/ 强制更新全局当前集数,确保后续保存进度精准\n    currentSourceIndex = sourceIdx; \n    currentEpisodeIndex = episodeIdx;\n    \n    const src = sources[sourceIdx]; \n    const episode = src.episodes[episodeIdx];\n    if (!episode) return; \/\/ 集数无效,直接返回\n    \n    $('#currentEpiText').text(episode.name);\n    $('.episode-btn').removeClass('playing').eq(episodeIdx).addClass('playing');\n    showLoading(true); hideError(); hideUrlTip();\n    showError('正在解析播放地址,请稍候...');\n\n    $.ajax({\n        url: episode.url, type:'GET', dataType:'html', timeout:15000,\n        success: function(html) {\n            showLoading(false); hideError();\n            if (!currentVodId) currentVodId = extractVodId(html);\n            \/\/ 只使用外部传入的进度,切集传0则0,恢复记录传多少则多少,绝不自动读其他进度\n            let startProgress = resumeProgress;\n            parseAndPlay(html, showTip, startProgress);\n        },\n        error: function(xhr, status, error) {\n            showLoading(false);\n            showError(status==='timeout'?'请求超时,请尝试切换线路或刷新页面':xhr.status===404?'播放页面不存在,请尝试其他线路':'获取播放地址失败,请尝试切换线路');\n        }\n    });\n}\nfunction extractVodId(html) {\n    if (currentVodId) return currentVodId;\n    const match = html.match(\/\"id\":\"(\\d+)\"\/); if (match) return match[1];\n    const urlMatch = html.match(\/\\\/id\\\/(\\d+)\/); if (urlMatch) return urlMatch[1];\n    return '';\n}\nfunction parseAndPlay(html, showTip=false, startProgress=0) {\n    try {\n        const match = html.match(\/var\\s+player_aaaa\\s*=\\s*(\\{.+?\\})[\\s;]*<\/);\n        if (!match) { showError('未找到播放器数据,该线路可能暂不可用'); return; }\n        let jsonStr = match[1].replace(\/\\\\\/g,\"\").trim();\n        let playerData; try { playerData = JSON.parse(jsonStr); } catch(e) { showError('数据解析失败,请尝试切换其他线路'); return; }\n        if (!currentVodId && playerData.id) currentVodId = String(playerData.id);\n        const videoUrl = playerData.url; if (!videoUrl) { showError('未找到视频链接,该线路可能暂不可用'); return; }\n        currentVideoUrl = videoUrl;\n        if (showTip) showUrlTip(videoUrl, '#playlistContainer');\n        isDirectVideo(videoUrl) ? playWithDPlayer(videoUrl, playerData, startProgress) : playWithIframe(videoUrl);\n    } catch(e) { showError('解析异常,请尝试切换线路:' + e.message.substring(0,50)); }\n}\nfunction isDirectVideo(url) {\n    const lowerUrl = url.toLowerCase();\n    return ['.mp4','.m3u8','.flv'].some(ext => lowerUrl.includes(ext));\n}\n\n\/\/ ========== DPlayer播放(精准按传入进度播放,实时存当前进度)==========\nfunction playWithDPlayer(url, playerData, startProgress=0) {\n    \/\/ 销毁旧播放器,杜绝旧播放器的进度干扰\n    if (dp) { \n        dp.destroy(); \n        dp = null; \n    }\n    $('#iframe-container').hide(); $('#dplayer').show(); hideUrlTip();\n    const isM3U8 = url.toLowerCase().includes('.m3u8');\n    dp = new DPlayer({\n        container: document.getElementById('dplayer'),\n        video: { url: url, type: isM3U8?'hls':'auto', pic: playerData.pic||'' },\n        autoplay: true, theme: '#ff6b6b', lang: 'zh-cn', hotkey: true, preload: 'auto', volume: 0.8,\n        playbackSpeed: [0.5,0.75,1,1.25,1.5,2]\n    });\n    let hasSeeked = false;\n\n    \/\/ 视频元数据加载完成后,精准跳转到传入的进度(只跳当前集的)\n    dp.on('loadedmetadata', function() {\n        currentVideoDuration = dp.video.duration || 0;\n        if (startProgress > 0 && currentVideoDuration > 0) {\n            const safeProgress = Math.min(startProgress, currentVideoDuration-2);\n            if (safeProgress > 0) { \n                dp.seek(safeProgress); \n                hasSeeked = true; \n                showError(`已从 ${formatTime(safeProgress)} 继续播放`); \n            }\n        }\n    });\n    \/\/ 播放时启动定时器,实时保存**当前集**进度\n    dp.on('play', function() {\n        stopProgressTimer();\n        progressTimer = setInterval(() => { saveCurrentProgressImmediately(); }, PROGRESS_SAVE_INTERVAL);\n    });\n    \/\/ 暂停时即时保存进度,避免退出丢失\n    dp.on('pause', function() {\n        stopProgressTimer();\n        saveCurrentProgressImmediately();\n    });\n    \/\/ 进度更新时,只校正**当前集**的恢复进度,绝不串集\n    dp.on('timeupdate', function() {\n        if (!hasSeeked && startProgress > 0 && dp.video.currentTime < 3) {\n            dp.seek(startProgress);\n            hasSeeked = true;\n        }\n    });\n    \/\/ 全屏\/退出全屏适配\n    dp.on('fullscreen', function() {\n        if (screen.orientation && screen.orientation.lock) {\n            screen.orientation.lock('landscape').catch(()=>{screen.orientation.lock('any').catch(()=>{});});\n        }\n    });\n    dp.on('fullscreen_cancel', function() {\n        if (screen.orientation && screen.orientation.unlock) screen.orientation.unlock().catch(()=>{});\n    });\n    \/\/ M3U8视频适配\n    if (isM3U8 && Hls.isSupported()) {\n        dp.video.type = 'customHls';\n        dp.video.customType = {\n            customHls: function(video) { const hls = new Hls(); hls.loadSource(video.src); hls.attachMedia(video); }\n        };\n    }\n    \/\/ 播放错误提示\n    setTimeout(() => {\n        dp.on('error', function() { showError('视频播放失败,请尝试切换线路或剧集'); });\n    }, 10000);\n    \/\/ 视频播放完毕\n    dp.on('ended', function() {\n        stopProgressTimer();\n        saveCurrentProgressImmediately();\n        if (autoPlayNext) playNextEpisode();\n        else showError('本集已结束,自动连播已关闭');\n    });\n    \/\/ 页面关闭\/刷新时,强制保存当前进度,这是关键!\n    window.addEventListener('beforeunload', function() {\n        stopProgressTimer();\n        saveCurrentProgressImmediately();\n    });\n    \/\/ 点击播放器显示视频地址\n    $('#dplayer').off('click').on('click', function(e) {\n        if ($(e.target).closest('.dplayer-controller, .dplayer-mask, #video-url-tip').length) return;\n        showUrlTip(url, '#dplayer');\n    });\n}\n\n\/\/ ========== 自动连播下一集(强制0秒从头播)==========\nfunction playNextEpisode() {\n    const currentSource = sources[currentSourceIndex];\n    const nextIndex = currentEpisodeIndex+1;\n    if (nextIndex < currentSource.episodes.length) {\n        showError(`即将播放下一集: ${currentSource.episodes[nextIndex].name}`);\n        \/\/ 自动连播也强制0秒,从头播放下一集\n        setTimeout(() => { playEpisode(currentSourceIndex, nextIndex, false, 0); }, 1500);\n    } else {\n        showError('本线路已播放完毕');\n        $('#autoPlayStatus').text('本线路完结').css('color','#ff6b6b');\n        cleanOldRecords();\n    }\n}\n\n\/\/ ========== 外部播放器iframe播放 ==========\nfunction playWithIframe(url) {\n    if (dp) { dp.destroy(); dp = null; }\n    $('#dplayer').hide(); const $container = $('#iframe-container'); $container.empty().show();\n    const $loading = $(`<div class=\"loading show\" style=\"position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);\"><div class=\"spinner\"><\/div><div>加载外部播放器...<\/div><\/div>`);\n    $container.append($loading);\n    const $iframe = $('<iframe>').attr('src','https:\/\/jx.quanmingjiexi.com\/?url='+url).attr('allowfullscreen','true').attr('frameborder','0').css({width:'100%',height:'100%',opacity:'0'}).on('load',function(){$loading.remove();$(this).css('opacity','1');});\n    setTimeout(() => { $loading.remove(); $iframe.css('opacity','1'); }, 5000);\n    $container.append($iframe);\n}\n\n\/\/ ========== 基础工具函数 ==========\nfunction showLoading(show) { $('#player-loading').toggleClass('show', show); }\nfunction showError(msg) { $('#errorMsg').text(msg).addClass('show'); setTimeout(()=>$('#errorMsg').removeClass('show'),3000); }\nfunction hideError() { $('#errorMsg').removeClass('show'); }\n\/\/ 全屏变化适配\ndocument.addEventListener('fullscreenchange', () => {\n    if (document.fullscreenElement) {\n        if (screen.orientation && screen.orientation.lock) screen.orientation.lock('landscape').catch(()=>{screen.orientation.lock('any').catch(()=>{});});\n    } else {\n        if (screen.orientation && screen.orientation.unlock) screen.orientation.unlock().catch(()=>{});\n    }\n});\n\/\/ 屏幕旋转适配播放器大小\nwindow.addEventListener('orientationchange', () => { setTimeout(()=>dp&&dp.resize(),300); });\n\/\/ 显示视频地址提示\nfunction showUrlTip(url, container) {\n    hideUrlTip(); if (window.urlTipTimeout) clearTimeout(window.urlTipTimeout);\n    const $tip = $(`<div id=\"video-url-tip\" style=\"position:absolute;top:0;left:0;background:rgba(0,0,0,0.6);color:#fff;padding:8px 12px;font-size:12px;z-index:1000;max-width:100%;word-break:break-all;\"><div style=\"user-select:text;line-height:1.5;\">${url}<\/div><\/div>`);\n    $(container||'body').append($tip);\n    window.urlTipTimeout = setTimeout(()=>{hideUrlTip();},3000);\n}\nfunction hideUrlTip() { $('#video-url-tip').remove(); if (window.urlTipTimeout) clearTimeout(window.urlTipTimeout); window.urlTipTimeout=null; }\n\/\/ 详情展开\/收起\nfunction toggleDetail() {\n    const content = document.getElementById('detailContent');\n    const btn = document.querySelector('#info .toggle-btn');\n    content.classList.toggle('show');\n    btn.classList.toggle('active');\n    btn.innerHTML = content.classList.contains('show') ? '收起详情 <span style=\"color:inherit\">▲<\/span>' : '查看详情 <span style=\"color:#ff6b6b\">▼<\/span>';\n}\n\/\/ 解析播放源和剧集\nfunction parseSourcesFromHTML() {\n    const sources = [];\n    $('.stui-pannel-box').each(function() {\n        const $box = $(this);\n        const sourceName = $box.find('.title').text().trim();\n        const episodes = [];\n        $box.find('.stui-content__playlist li a').each(function() {\n            const $a = $(this);\n            episodes.push({name:$a.text().trim(), url:$a.attr('href')});\n        });\n        if (episodes.length>0) sources.push({name:sourceName, episodes:episodes});\n    });\n    return sources;\n}\n",
    "lastUpdateTime": 1773791081203,
    "loadWithBaseUrl": true,
    "preload": false,
    "preloadJs": "window.java = java;\nwindow.source = source;",
    "ruleArticles": ".stui-vodlist li||.stui-vodlist__media li",
    "ruleContent": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n    <meta charset=\"utf-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n    <title>{{@@title@text}}<\/title>\n    <link rel=\"stylesheet\" href=\"https:\/\/cdn.jsdelivr.net\/npm\/dplayer@1.25.0\/dist\/DPlayer.min.css\">\n<\/head>\n<body>\n    <div id=\"dplayer\">\n        <div class=\"loading\" id=\"player-loading\">\n            <div class=\"spinner\"><\/div>\n            <div>加载中...<\/div>\n        <\/div>\n    <\/div>\n    \n    <div id=\"iframe-container\">\n        <div class=\"loading\" id=\"iframe-loading\">\n            <div class=\"spinner\"><\/div>\n            <div>加载外部播放器...<\/div>\n        <\/div>\n    <\/div>\n\n    <div id=\"info\">\n    <button class=\"toggle-btn\" onclick=\"toggleDetail()\">查看详情<\/button>\n    \n    <div class=\"detail-content\" id=\"detailContent\">\n        <div class=\"detail-inner\">\n            <div class=\"stui-content__detail\">\n{{@@.stui-content__detail > *:not(.play-btn)@html}}\n            <\/div>\n        <\/div>\n    <\/div>\n<\/div>\n    \n    <div class=\"source-tabs\" id=\"sourceTabs\"><\/div>\n\n    <div class=\"playlist-container\" id=\"playlistContainer\">\n        <div class=\"error-msg\" id=\"errorMsg\"><\/div>\n    <\/div>\n\n    <!-- 原始播放列表数据 -->\n    <div id=\"source-container\">\n        {{@@.stui-pannel > .playlist@all}}\n    <\/div>\n\n    <script src=\"https:\/\/pss.bdstatic.com\/static\/superman\/js\/lib\/jquery-1-edb203c114.10.2.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/hls.js@1.5.17\/dist\/hls.min.js\"><\/script>\n    <script src=\"https:\/\/cdn.jsdelivr.net\/npm\/dplayer@1.25.0\/dist\/DPlayer.min.js\"><\/script>\n\n<\/body>\n<\/html>",
    "ruleImage": "a[0]@data-original",
    "ruleLink": ".title a@href",
    "ruleNextPage": "text.下一页@href",
    "rulePubDate": "p@textNodes",
    "ruleTitle": ".title a@text",
    "searchUrl": "\/index.php\/vod\/search.html?wd={{key}}",
    "showWebLog": false,
    "singleUrl": false,
    "sortUrl": "日本动漫::\/index.php\/vod\/type\/id\/20.html\n国产动漫::\/index.php\/vod\/type\/id\/21.html\n欧美动漫::\/index.php\/vod\/type\/id\/22.html\n动漫电影::\/index.php\/vod\/type\/id\/23.html\n其他动漫::\/index.php\/vod\/type\/id\/24.html",
    "sourceComment": "原作者:cute-pan\n\n添加搜索 (原生阅读不支持)\n美化UI(顶部播放器,中间显示详情,下方多线路播放列表)\n添加 自动播放下一集\n\n新增 播放记录,断点续播",
    "sourceGroup": "视频",
    "sourceIcon": "https:\/\/tse2.mm.bing.net\/th\/id\/OIP.0w67-qvgh_VqOurfrC377AHaEK?dpr=3.5&pid=ImgDetMain&o=7&rm=3",
    "sourceName": "樱花动漫",
    "sourceUrl": "https:\/\/www.yinghua228.com",
    "style": "        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            background: #0d0d0d;\n            font-family: -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif;\n            color: #fff;\n            overflow-x: hidden;\n            -webkit-tap-highlight-color: transparent;\n        }\n\n        #dplayer {\n            width: 100%;\n            height: 56.25vw;\n            max-height: 70vh;\n            background: #000;\n            position: relative;\n            z-index: 10;\n        }\n\n        .source-tabs {\n            display: flex;\n            overflow-x: auto;\n            background: #1a1a1a;\n            padding: 10px 5px;\n            position: sticky;\n            top: 0;\n            z-index: 20;\n            border-bottom: 1px solid #333;\n            scrollbar-width: none;\n            -ms-overflow-style: none;\n        }\n\n        .source-tabs::-webkit-scrollbar {\n            display: none;\n        }\n\n        .tab-btn {\n            flex-shrink: 0;\n            padding: 8px 16px;\n            margin: 0 5px;\n            background: #2a2a2a;\n            border: 1px solid #444;\n            border-radius: 20px;\n            color: #aaa;\n            font-size: 14px;\n            cursor: pointer;\n            transition: all 0.3s ease;\n            white-space: nowrap;\n            outline: none;\n        }\n\n        .tab-btn.active {\n            background: #ff6b6b;\n            border-color: #ff6b6b;\n            color: #fff;\n            box-shadow: 0 2px 8px rgba(255, 107, 107, 0.4);\n        }\n      .tab-btn:active {\n            transform: scale(0.95);\n        }\n\n        .playlist-container {\n            padding: 15px;\n            background: #0d0d0d;\n            min-height: calc(100vh - 56.25vw - 60px);\n        }\n\n        .playlist-title {\n            font-size: 16px;\n            color: #fff;\n            margin-bottom: 15px;\n            padding-left: 10px;\n            border-left: 3px solid #ff6b6b;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .episode-count {\n            font-size: 12px;\n            color: #888;\n            font-weight: normal;\n        }\n\n        .episode-grid {\n            display: grid;\n            grid-template-columns: repeat(4, 1fr);\n            gap: 10px;\n        }\n\n        @media (min-width: 480px) {\n            .episode-grid {\n                grid-template-columns: repeat(5, 1fr);\n            }\n        }\n\n        @media (min-width: 600px) {\n            .episode-grid {\n                grid-template-columns: repeat(6, 1fr);\n            }\n        }\n\n        .episode-btn {\n            background: #1f1f1f;\n            border: 1px solid #333;\n            border-radius: 8px;\n            padding: 12px 5px;\n            color: #ccc;\n            font-size: 14px;\n            cursor: pointer;\n            transition: all 0.2s;\n            text-align: center;\n            outline: none;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .episode-btn::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: -100%;\n            width: 100%;\n            height: 100%;\n            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.1), transparent);\n            transition: left 0.5s;\n        }\n\n        .episode-btn:active::before {\n            left: 100%;\n        }\n\n        .episode-btn.playing {\n            background: #4ecdc4;\n            border-color: #4ecdc4;\n            color: #fff;\n            animation: pulse 2s infinite;\n        }\n\n        @keyframes pulse {\n            0%, 100% { box-shadow: 0 0 0 0 rgba(78, 205, 196, 0.4); }\n            50% { box-shadow: 0 0 0 8px rgba(78, 205, 196, 0); }\n        }\n\n        .episode-btn:active {\n            transform: scale(0.95);\n        }\n\n        #iframe-container {\n            width: 100%;\n            height: 56.25vw;\n            max-height: 70vh;\n            background: #000;\n            display: none;\n            position: relative;\n        }\n\n        #iframe-container iframe {\n            width: 100%;\n            height: 100%;\n            border: none;\n        }\n\n        .loading {\n            position: absolute;\n            top: 50%;\n            left: 50%;\n            transform: translate(-50%, -50%);\n            color: #ff6b6b;\n            font-size: 14px;\n            display: none;\n        }\n\n        .loading.show {\n            display: block;\n        }\n\n        .spinner {\n            width: 40px;\n            height: 40px;\n            border: 3px solid #333;\n            border-top-color: #ff6b6b;\n            border-radius: 50%;\n            animation: spin 1s linear infinite;\n            margin: 0 auto 10px;\n        }\n\n        @keyframes spin {\n            to { transform: rotate(360deg); }\n        }\n\n        .error-msg {\n            background: rgba(255, 107, 107, 0.1);\n            border: 1px solid #ff6b6b;\n            color: #ff6b6b;\n            padding: 10px;\n            border-radius: 8px;\n            margin: 10px 0;\n            font-size: 13px;\n            display: none;\n            text-align: center;\n        }\n\n        .error-msg.show {\n            display: block;\n        }\n\n        .now-playing {\n            background: linear-gradient(135deg, #1a1a1a 0%, #2a2a2a 100%);\n            padding: 12px 15px;\n            margin: -15px -15px 15px -15px;\n            border-bottom: 1px solid #333;\n            font-size: 14px;\n            color: #aaa;\n            display: flex;\n            align-items: center;\n            gap: 8px;\n        }\n\n        .now-playing .label {\n            color: #ff6b6b;\n            font-weight: bold;\n        }\n\n        .now-playing .source-name {\n            color: #4ecdc4;\n        }\n\n        .back-btn {\n            position: fixed;\n            top: 10px;\n            left: 10px;\n            z-index: 100;\n            background: rgba(0,0,0,0.6);\n            border: none;\n            color: #fff;\n            width: 36px;\n            height: 36px;\n            border-radius: 50%;\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 18px;\n            cursor: pointer;\n            backdrop-filter: blur(10px);\n        }\n\n        #source-container {\n            display: none;\n        }\n\n\/* ========== 详情区域样式(完全隔离,不会冲突)========== *\/\n#info {\n    background: #1a1a1a;\n    margin: 10px 15px;\n    border-radius: 12px;\n    overflow: hidden;\n    border: 1px solid #333;\n}\n\n#info .toggle-btn {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    gap: 8px;\n    width: 100%;\n    padding: 12px 20px;\n    background: #252525;\n    color: #fff;\n    border: none;\n    cursor: pointer;\n    font-size: 14px;\n    font-weight: 500;\n    transition: all 0.3s ease;\n}\n\n#info .toggle-btn:hover {\n    background: #2f2f2f;\n}\n\n#info .toggle-btn::after {\n    content: '▼';\n    font-size: 10px;\n    color: #ff6b6b;\n    transition: transform 0.3s ease;\n}\n\n#info .toggle-btn.active::after {\n    transform: rotate(180deg);\n}\n\n#info .toggle-btn.active {\n    background: #ff6b6b;\n    color: #fff;\n}\n\n#info .toggle-btn.active::after {\n    color: #fff;\n}\n\n#info .detail-content {\n    max-height: 0;\n    overflow: hidden;\n    transition: max-height 0.4s ease, opacity 0.3s ease;\n    opacity: 0;\n    background: #1a1a1a;\n}\n\n#info .detail-content.show {\n    max-height: 800px;\n    opacity: 1;\n}\n\n\/* 详情内部样式 - 全部加前缀避免冲突 *\/\n#info .detail-inner {\n    padding: 15px;\n}\n\n#info .detail-title {\n    font-size: 18px;\n    color: #fff;\n    margin-bottom: 12px;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    flex-wrap: wrap;\n    line-height: 1.4;\n}\n\n#info .detail-score {\n    background: #ff6b6b;\n    color: white;\n    padding: 2px 8px;\n    border-radius: 12px;\n    font-size: 12px;\n    font-weight: bold;\n    flex-shrink: 0;\n}\n\n#info .detail-row {\n    margin-bottom: 10px;\n    font-size: 13px;\n    line-height: 1.7;\n    color: #ccc;\n}\n\n#info .detail-label {\n    color: #888;\n    margin-right: 5px;\n}\n\n#info .detail-row a {\n    color: #4a9eff;\n    text-decoration: none;\n    margin-right: 6px;\n    transition: color 0.2s;\n}\n\n#info .detail-row a:hover {\n    color: #6ab3ff;\n    text-decoration: underline;\n}\n\n#info .detail-split {\n    display: inline-block;\n    width: 1px;\n    height: 10px;\n    background: #444;\n    margin: 0 8px;\n    vertical-align: middle;\n}\n\n#info .detail-desc {\n    background: #252525;\n    padding: 12px;\n    border-radius: 8px;\n    margin-top: 12px;\n    border-left: 3px solid #ff6b6b;\n    font-size: 13px;\n    line-height: 1.8;\n    color: #bbb;\n}\n\n#info .detail-desc-label {\n    display: block;\n    color: #ff6b6b;\n    margin-bottom: 6px;\n    font-weight: 500;\n}\n\n\/* 隐藏元素 *\/\n#info .hidden-xs {\n    display: inline;\n}\n\n@media (max-width: 480px) {\n    #info .hidden-xs {\n        display: none !important;\n    }\n    \n    #info {\n        margin: 8px;\n    }\n    \n    #info .detail-inner {\n        padding: 12px;\n    }\n}\n\n\/* 详情区域a标签样式 - 灰色不刺眼 *\/\n#info a {\n    color: #888 !important;\n    text-decoration: none !important;\n    pointer-events: none; \/* 阻止点击 *\/\n    cursor: default;\n    margin-right: 6px;\n}\n\n#info a:hover {\n    color: #888 !important;\n    text-decoration: none !important;\n}\n",
    "type": 0
}
广告