Greasy Fork is available in English.
显示已过期的集数,尝试提供下载功能
当前为
// ==UserScript==
// @name Viu More
// @namespace http://tampermonkey.net/
// @version 0.4
// @description 显示已过期的集数,尝试提供下载功能
// @author cw2012
// @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);
init();
function init(){
const btn = document.createElement('div');
btn.innerText = '解 析';
btn.className = 'floating-btn';
btn.addEventListener('click', ev=>{
getSeasonEposideList();
});
document.body.append(btn);
}
function getSeasonEposideList(){
if(typeof document.querySelector('#page-wrap') == "undefined"){
showMsg('请在页面加载完成后重试',0);
return;
}
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);
showOutdatedEpisodeList(res.episodes);}, 1000);
}else{
setTimeout(()=>updateUiEpisodeList(res.episodes), 2000);
}
}
});
}
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, list[index].productSubtitle);
});
item.append(div);
});
setTimeout(()=>{
const li = document.createElement('li');
li.innerText = `共${totalCount}集`;
li.className = 'react-tabs__tab-list';
document.querySelector('.react-tabs__tab-list').append(li);
},500);
}
function showOutdatedEpisodeList(list){
const listBox = document.querySelector('.Episodes');
// 添加因过期而未能显示的列表
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), list[i].productSubtitle);
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), list[i].productSubtitle);
listBox.append(div);
}
}
}
function createOutdateEpisode(id, num, subtitleList){
let div = document.createElement('div');
div.className = 'VideoItem outdated_episode'; //
div.innerText =`下载第${sn(num,2)}集字幕并复制MPD文件的url`;
div.addEventListener('click',ev=>{
getSubtitleWithProductId(id, num, subtitleList);
});
return div;
}
function getSubtitleWithProductId(id, episodeNum, subtitleList){
subtitleList = subtitleList.split(',');
subtitles=[];
subtitleList.forEach(item=>{
if(item == 'Chinese'){
subtitles.push('TRD');
}else if(item == 'English'){
subtitles.push('GBR');
}
});
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":
var videoUrl = res.asset[0];
var m3u8Index = videoUrl.indexOf('.m3u8');
GM_setClipboard(videoUrl);
showMsg(`${m3u8Index > -1?'m3u8':'MPD'}文件的url已复制成功`,1);
downloadSubtitles(id,episodeNum, videoUrl, m3u8Index);
/*GM_xmlhttpRequest({
method:'GET',
url: videoUrl,
onload:r=>{
// 还是m3u8的格式
if(m3u8Index>-1){
console.log(res);
}else{ // MPD
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 langName = {TRD:"zh", GBR:"en"};
function downloadSubtitles(id,episodeNum, videoUrl, m3u8Index){
subtitles.forEach(item=>{
const url = m3u8Index > -1? videoUrl.substr(0,m3u8Index) + `-${item}.srt`:`https://static.viu.tv/subtitle/${id}/${id}-${item}.srt`;
GM_download({
// https://ewcdn07.nowe.com/session/p8-5-ba0f1017-U5e83045b551442088/hls/vodcp20/201908280872207/201908280872207.m3u8?token=45c3d39ad58b27c019f7f8abc7062cf7_1628834731
// https://ewcdn08.nowe.com/session/p8-5-cd2b597c-U5e83045b551442088/hls/vodcp20/201908280872207/201908280872207-TRD.srt
// https://static.viu.tv/subtitle/202104211351468/202104211351468-TRD.srt
url: url,
name:`${sn(episodeNum,2)}.${langName[item]}.srt`,
onerror:e=>showMsg(`${sn(episodeNum,2)}.${langName[item]}.srt\n${url}下载失败`,0)
})
});
}
function showMsg(msg,type){
msgBox.innerText = msg;
msgBox.className=type?'showing':'err';
setTimeout(()=>{msgBox.className='';},type==0?4000: 2500);
console.log(msg);
}
function sn(num,length){
return num.toString().padStart(length, '0');
}
function addStyle(){
GM_addStyle(`
.floating-btn{
position:fixed;
background: #0a7deb;
text-align: center;
color:white;
font-size:1.5em;
cursor: pointer;
border-radius:10px;
border:solid #0a7deb 1px;
padding:6px;
top:50%;
right:20px;
box-shadow:#0a7deb 2px 2px 6px, #0a7deb 6px 6px 19px;
}
.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;
}
`);
}
})();