Viu More

显示已过期的集数,尝试提供下载功能

目前为 2021-08-04 提交的版本。查看 最新版本

// ==UserScript==
// @name         Viu More
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  显示已过期的集数,尝试提供下载功能
// @author       You
// @match        http*://viu.tv/encore/*
// @icon         https://www.viu.com/ott/hk/v1/images/web_loading_icon.gif
// @require      https://cdn.jsdelivr.net/npm/toastify-js
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_download
// @grant        GM_setClipboard
// @connect      viu.tv
// @connect      now.com
// @connect      nowe.com
// @run-at       document-end
// ==/UserScript==

(function() {
    'use strict';

    let totalCount, isAsc, episodeList=[], seasonTitle, subtitles=[];

    const seasonName = location.href.split('/')[4];
    const cookie = 'b13b2e6a06a230f8b2'; // f7da2aac5e3df01adc


    const notificationAvailable = "Notification" in window;
    addStyle();
    const msgBox = document.createElement('div');
    msgBox.id = 'msg-box';
    document.querySelector('#page-wrap').append(msgBox);

    const observer = new MutationObserver(getSeasonEposideList);
    observer.observe(document.querySelector('#outer-container'), {
        attributes: false,
        childList: true,
        subtree: false
    });


    function getSeasonEposideList(){
        if(typeof document.querySelector('#page-wrap') == "undefined"){
            return;
        }
        observer.disconnect();
        GM_xmlhttpRequest({
            method:'GET',
            url: `https://api.viu.tv/production/programmes/${seasonName}`,
            responseType: 'json',
            onerror:e=>{showMsg(`请求发生错误:${e}`,0)},
            onload:res=>{
                res = res.response.programme;
                totalCount = res.programmeMeta.totalEpisodeNo;
                seasonTitle = res.programmeMeta.seriesTitle;
                if(totalCount !== res.episodes.length){
                    // 只显示一集,说明是最后一集
                    // 这里一定是总集数和显示集数不一致才会被调用的
                    for(let i=0;i<res.episodes.length;i++){
                        episodeList.push({episodeNum:res.episodes[i].episodeNum,productId:res.episodes[i].productId});
                    }
                    if(res.episodes.length === 1){
                        isAsc = false;
                    }else{
                        isAsc = res.episodes[0].episodeNum < res.episodes[1].episodeNum;
                    }
                    setTimeout(()=>updateUiEpisodeList(res.episodes), 1000);
                }
            }
        });
    }

    function updateUiEpisodeList(list){
        // 先操作已显示的列表
        const listBox = document.querySelector('.Episodes');
        const shownEpisode = listBox.querySelectorAll('.VideoItem.undefined');
        shownEpisode.forEach((item, index)=>{
            let div = document.createElement('div');
            div.className = 'floating-div';
            div.innerText = '下载字幕,并复制MPD文件的url';
            div.addEventListener('click', ev=>{
                window.event? window.event.cancelBubble = true : ev.stopPropagation();
                const productId = list[index].productId;
                getSubtitleWithProductId(productId, episodeList[index].episodeNum);
            });
            item.append(div);
        });

        // 添加因过期而未能显示的列表
        let len2add, firstProductId,firstEpisodeNum;
        if(isAsc){
            len2add = list[0].episodeNum>15?15:(list[0].episodeNum-1);
            firstProductId = parseInt(list[0].productId);
            firstEpisodeNum = list[0].episodeNum;
            let prevDiv;
            for(let i=0;i<len2add;i++){
                const div = createOutdateEpisode(firstProductId-(i+1),firstEpisodeNum - (i+1));
                if(i==0){
                    listBox.insertBefore(div, listBox.firstChild);
                    prevDiv = div;
                }else{
                    listBox.insertBefore(div, prevDiv);
                    prevDiv = div;
                }
            }
        }else{
            len2add = list[list.length-1].episodeNum>15?15:(list[list.length -1].episodeNum-1);
            firstProductId = parseInt(list[list.length-1].productId);
            firstEpisodeNum = list[list.length-1].episodeNum;
            for(let i=0;i<len2add;i++){
                const div = createOutdateEpisode(firstProductId-(i+1),firstEpisodeNum - (i+1));
                listBox.append(div);
            }
        }
    }

    function createOutdateEpisode(id, num){
        let div = document.createElement('div');
        div.className = 'VideoItem outdated_episode'; //
        div.innerText =`下载第${sn(num,2)}集字幕并复制MPD文件的url`;
        div.addEventListener('click',ev=>{
            getSubtitleWithProductId(id, num);
        });
        return div;
    }

    function getSubtitleWithProductId(id, episodeNum){
        GM_xmlhttpRequest({
            method:'POST',
            url: 'https://api.viu.now.com/p8/3/getVodURL',
            headers: {
                'accept':'*/*',
                'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.107 Safari/537.36 Edg/92.0.902.62'},
            data: JSON.stringify({"callerReferenceNo":getTimeStamp(new Date()),"productId":id,"contentId":id,
                                  "contentType":"Vod","mode":"prod","PIN":"password","cookie":cookie,"deviceId":"U5e83045b551442088","deviceType":"ANDROID_WEB","format":"HLS"}),
            onerror:e=>{showMsg('获取字幕时出错:'+e,0)},
            onload:res=>{
                res = JSON.parse(res.responseText);
                switch(res.responseCode){
                    case "MISSING_INPUT":
                        showMsg('输入的参数不对',0);
                        break;
                    case "GEO_CHECK_FAIL":
                        showMsg('IP不是香港的',0);
                        break;
                    case "INTERNAL_ERROR":
                        showMsg("发生错误,该视频可能已经永久下架了",0);
                        break;
                    case "SUCCESS":
                        GM_setClipboard(res.asset[0]);
                        showMsg("MPD文件的url已复制成功",1);
                        GM_xmlhttpRequest({
                            method:'GET',
                            url: res.asset[0],
                            onload:r=>{
                                r = r.responseXML;
                                if(typeof r.childNodes[0].childNodes[1].childNodes !='undefined'){
                                    r = r.childNodes[0].childNodes[1].childNodes;
                                    r.forEach(item=>{
                                        if(item.tagName == 'AdaptationSet' && ("text/vtt" ==item.getAttribute('mimeType'))){
                                            subtitles.push(item.getAttribute('lang'));
                                        }
                                    });
                                    if(subtitles.length>0){
                                        downloadSubtitles(id,episodeNum);
                                    }
                                }else{
                                    showMsg('该视频没有字幕',1);
                                }
                            }
                        });
                        break;
                }
            },
            ontimeout:e=>showMsg('呵呵,超时了',0)
        });
    }

    function getTimeStamp(date){
        const timeZone = date.getTimezoneOffset() / 60;
        date.setTime(date.getTime() - timeZone * 3600 * 1000);
        return date.toISOString().replaceAll(/[-T:Z.]/g,'').substr(0,14);
    }

    const langList = {yue:"TRD", eng:"GBR"};
    const langName = {yue:"zh", eng:"en"};
    function downloadSubtitles(id,episodeNum){
        subtitles.forEach(item=>{
            GM_download({
                // https://static.viu.tv/subtitle/202104211351468/202104211351468-TRD.srt
                url: `https://static.viu.tv/subtitle/${id}/${id}-${langList[item]}.srt`,
                name:`${sn(episodeNum,2)}.${langName[item]}.srt`,
                onerror:e=>showMsg(`${sn(episodeNum,2)}.${langName[item]}.srt\nhttps://static.viu.tv/subtitle/${id}/${id}-${langList[item]}.srt下载失败`,0)
            })
        });
    }

    function showMsg(msg,type){
        if(!notificationAvailable){
            alert(msg);
        }else{
            msgBox.innerText = msg;
            msgBox.className=type?'showing':'err';
            setTimeout(()=>{msgBox.className='';}, 3000);
        }
    }

    function sn(num,length){
        return num.toString().padStart(length, '0');
    }

    function addStyle(){
        GM_addStyle(`.floating-div{
        position:relative;
            background: #0a7deb;
    text-align: center;
    color:white;
        cursor: pointer;
        border-radius:10px;
        border:solid #0a7deb 1px;
        padding:6px;
        display:none;
        }
        .VideoItem.undefined:hover .floating-div{
        display:block;
        }
        #msg-box{
        transition:all 0.5s ease-in-out;
        font-size:15px;
        position:fixed;
        right:30px;
        top:10px;
        background: #0a7deb;
        color:white;
        border-radius:7px;
        padding:10px;
        opacity:0;
        box-shadow:#0a7deb 2px 2px 6px, #0a7deb 6px 6px 19px;
        }
        #msg-box.showing{
        opacity:1;
        top:130px;
        }
        #msg-box.err{
        background:red;
        box-shadow:red 2px 2px 6px, red 6px 6px 19px;
        opacity:1;
        top:130px;
        }
        .VideoItem.outdated_episode{
        text-align: center;
        background:#0a7deb;
        border-radius:10px;
        border:solid #0a7deb 1px ;
        padding:6px;
        margin:10px;
        color:white;
            cursor: pointer;
        }
        `);
    }
})();

QingJ © 2025

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