// Copyright 2022 shadows
//
// Distributed under MIT license.
// See file LICENSE for detail or copy at https://opensource.org/licenses/MIT
// ==UserScript==
// @name 爱奇艺字幕下载
// @namespace http://tampermonkey.net/shadows
// @version 0.1.4
// @description 下载爱奇艺视频的外挂字幕
// @author shadows
// @license MIT License
// @copyright Copyright (c) 2021 shadows
// @match https://www.iq.com/play/*
// @include /^https:\/\/www\.iqiyi\.com\/v_(\w+)\.html.*$/
// @icon https://www.iqiyipic.com/common/images/logo.ico
// @grant GM_xmlhttpRequest
// @grant GM.xmlhttpRequest
// @grant GM_getValue
// @grant GM.getValue
// @grant GM_setValue
// @grant GM.setValue
// @grant GM_deleteValue
// @grant GM.deleteValue
// @grant GM_addValueChangeListener
// @grant GM_registerMenuCommand
// @grant GM.registerMenuCommand
// @run-at document-end
// @require https://gf.qytechs.cn/scripts/371339-gm-webextpref/code/GM_webextPref.js?version=961539
// @require https://gcore.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js
// ==/UserScript==
/* jshint esversion: 6 */
'use strict';
const pref = GM_webextPref({
default: {
filetype: "srt",
},
body: [
{
key: "filetype",
label: "选择想要下载的文件格式",
type: "select",
options: {
srt: "srt",
webvtt: "webvtt",
xml: "xml",
}
},
]
});
pref.ready();
const extensionDict = {
srt: "srt",
webvtt: "vtt",
xml: "xml",
};
const xhr = option => new Promise((resolve, reject) => {
GM.xmlHttpRequest({
...option,
onerror: reject,
onload: (response) => {
if (response.status >= 200 && response.status < 300) {
resolve(response);
} else {
reject(response);
}
},
});
});
async function download(url,name) {
await xhr({
method: "GET",
url: url,
responseType: "blob"
}).then(resp => saveAs(resp.response,name));
}
async function main() {
const url = new URL(window.location.href);
const site = websiteRules[url.host];
if (url.searchParams.get("download_subtitles") == "true") {
if (await site.hasSubtitles()) {
console.log("自动下载字幕");
await site.downloadSubtitles(false);
console.log("自动下载字幕已完成");
}
let nextUrl = site.getNextUrl();
if (nextUrl) {
window.location.assign(nextUrl);
}
// clear the download_subtitles query param
url.searchParams.delete("download_subtitles");
window.history.replaceState(null, '', url);
}
// await sleep(500);
if (await site.hasSubtitles()) {
await site.addDownloadButton();
}
}
const websiteRules = {};
websiteRules["www.iqiyi.com"] = {
hasSubtitles: async function () {
// check has video pleyer
console.log("check has video player");
if (document.querySelector("[data-player-hook]") == null) return false;
for (let i = 0; i < 100; ++i) {
await sleep(200);
if (playerObject._player.package.engine == undefined) continue;
this.playerData = playerObject._player.package.engine;
if (this.playerData?.movieinfo.tvid == undefined) continue;
this.tvid = this.playerData.movieinfo.tvid;
if (this.playerData.episode.EpisodeStore[this.tvid].movieInfo?.originalData.data == undefined) continue;
this.data = this.playerData.episode.EpisodeStore[this.tvid].movieInfo.originalData.data;
// check has subtitles
if (this.data.program?.stl == undefined) continue;
if (document.querySelectorAll("[data-player-hook=subtitles_language_list] .iqp-set-zimu").length == 0) continue;
this.stl = this.data.program.stl;
return true;
}
return false;
},
addDownloadButton: async function () {
console.log("addDownloadButton");
let parentElement = document.querySelector(".qy-player-title");
let downloadButton = document.createElement("button");
downloadButton.innerText = "下载当前字幕";
downloadButton.style.cssText = buttonCSS;
downloadButton.id = "download-subtitles";
parentElement.append(downloadButton);
document.addEventListener('click', event => {
if (event.target.id == "download-subtitles") {
event.stopPropagation();
this.downloadSubtitles(true);
return;
}
}, true);
if (this.stl.length > 1) {
let downloadAllButton = document.createElement("button");
downloadAllButton.innerText = "下载所有字幕";
downloadAllButton.style.cssText = buttonCSS;
downloadAllButton.id = "download-all-subtitles";
parentElement.append(downloadAllButton);
document.addEventListener('click', event => {
if (event.target.id == "download-all-subtitles") {
event.stopPropagation();
this.downloadSubtitles(false);
return;
}
}, true);
}
let downloadListAllButton = document.createElement("button");
downloadListAllButton.innerText = "下载所有视频的字幕";
downloadListAllButton.style.cssText = buttonCSS;
downloadListAllButton.id = "download-list-all-subtitles";
parentElement.append(downloadListAllButton);
document.addEventListener('click', event => {
if (event.target.id == "download-list-all-subtitles") {
event.stopPropagation();
this.downloadSubtitles(false);
let nextUrl = this.getNextUrl();
if (nextUrl != null) {
window.location.assign(this.getNextUrl());
}
return;
}
}, true);
let settingButton = document.createElement("button");
settingButton.innerText = "设置";
settingButton.style.cssText = buttonCSS;
settingButton.addEventListener("click", () => {
pref.openDialog();
});
parentElement.append(settingButton);
},
getSubtitles: function (onlySeleted = true) {
console.log("getSubtitles");
const prefix = this.data.dstl;
let subtitles = [];
const filetype = pref.get("filetype");
const extension = extensionDict[filetype];
const videoTitle = document.querySelector(".title-txt").textContent;
const languages = document.querySelectorAll("[data-player-hook=subtitles_language_list] .iqp-set-zimu");
for (let i = 0; i < languages.length; ++i) {
if (onlySeleted && !languages[i].classList.contains("selected")) continue;
let name = `${videoTitle}_${languages[i].textContent}.${extension}`;
let url = prefix + this.stl[i][filetype];
console.log(url);
subtitles.push({ name, url });
if (onlySeleted) break;
}
return subtitles;
},
downloadSubtitles: function (onlySeleted = true) {
let subtitles = this.getSubtitles(onlySeleted);
for (let item of subtitles) {
download(item.url, item.name);
}
},
getNextUrl: function () {
const nextEpisode = document.querySelector(".qy-episode-tab~[qs-request-id] li.selected~li a");
if (nextEpisode != null) {
let url = new URL(nextEpisode.href);
url.searchParams.set("download_subtitles", "true");
return url;
}
return null;
},
};
websiteRules["www.iq.com"] = {
hasSubtitles: async function () {
for (let i = 0; i < 100; ++i) {
await sleep(200);
if (playerObject?._player.package.engine == undefined) continue;
this.playerData = playerObject._player.package.engine;
if (this.playerData?.movieinfo.tvid == undefined) continue;
this.tvid = this.playerData.movieinfo.tvid;
if (this.playerData.episode.EpisodeStore[this.tvid].movieInfo?.originalData.data == undefined) continue;
this.data = this.playerData.episode.EpisodeStore[this.tvid].movieInfo.originalData.data;
// check has subtitles
if (this.data.program?.stl == undefined) continue;
if (this.data.program.stl?.length == 0) continue;
this.stl = this.data.program.stl;
console.log(this.data);
return true;
}
return false;
},
addDownloadButton: async function () {
console.log("addDownloadButton");
let parentElement = document.querySelector(".left-section");
let downloadButton = document.createElement("button");
downloadButton.innerText = "下载当前字幕";
downloadButton.style.cssText = buttonCSS;
downloadButton.addEventListener("click", () => {
this.downloadSubtitles(true);
});
parentElement.append(downloadButton);
if (this.stl.length > 1) {
let downloadAllButton = document.createElement("button");
downloadAllButton.innerText = "下载所有字幕";
downloadAllButton.style.cssText = buttonCSS;
downloadAllButton.addEventListener("click", () => { this.downloadSubtitles(false); });
parentElement.append(downloadAllButton);
}
let downloadListAllButton = document.createElement("button");
downloadListAllButton.innerText = "下载所有视频的字幕";
downloadListAllButton.style.cssText = buttonCSS;
downloadListAllButton.addEventListener("click", () => {
this.downloadSubtitles(false);
let nextUrl = this.getNextUrl();
if (nextUrl != null) {
window.location.assign(this.getNextUrl());
}
});
parentElement.append(downloadListAllButton);
let settingButton = document.createElement("button");
settingButton.innerText = "设置";
settingButton.style.cssText = buttonCSS;
settingButton.addEventListener("click", () => {
pref.openDialog();
});
parentElement.append(settingButton);
},
getSubtitles: function (onlySeleted = true) {
const prefix = this.data.dstl;
let subtitles = [];
const filetype = pref.get("filetype");
const extension = extensionDict[filetype];
const videoTitle = document.querySelector('#pageMetaTitle').previousElementSibling.textContent;
for (let item of this.stl) {
if (onlySeleted && !item._selected) continue;
let name = `${videoTitle}_${item._name}.${extension}`;
let url = prefix + item[filetype];
subtitles.push({ name, url });
if (onlySeleted) break;
}
return subtitles;
},
downloadSubtitles: async function (onlySeleted = true) {
const subtitles = this.getSubtitles(onlySeleted);
for (let item of subtitles) {
download(item.url, item.name);
}
},
getNextUrl: function () {
const nextEpisode = document.querySelector(".intl-episodes-list li.selected~li a");
if (nextEpisode) {
let url = new URL(nextEpisode.href);
url.searchParams.set("download_subtitles", "true");
return url.toString();
}
return null;
},
};
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const buttonCSS = `display: inline-block;
background: linear-gradient(135deg, #6e8efb, #a777e3);
color: white;
padding: 3px 3px;
margin: 8px 8px;
text-align: center;
border-radius: 3px;
border-width: 0px;`;
window.addEventListener('load', async function () {
console.log("load");
await main();
});
window.addEventListener('popstate', async function () {
console.log("popstate");
await main();
});