// ==UserScript==
// @name Jable一键下载收藏
// @namespace https://greasyfork.org/zh-CN/scripts/474848-jable%E4%B8%80%E9%94%AE%E4%B8%8B%E8%BD%BD%E6%94%B6%E8%97%8F
// @version 1.6.3
// @description Jable一键下载视频,并自动点击收藏
// @author Pandex
// @match *://jable.tv/*
// @match *://fs1.app/*
// @connect jable.tv
// @connect fs1.app
// @icon https://assets-cdn.jable.tv/assets/icon/favicon-32x32.png
// @grant GM_addStyle
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_deleteValue
// @grant GM_removeValueChangeListener
// @grant GM_addValueChangeListener
// @grant GM_registerMenuCommand
// @grant GM_unregisterMenuCommand
// @license MPL
// ==/UserScript==
(function () {
const defaultSaveFileDirectory = "D:\\videos\\jav";
var liked_codes = [];
var artistCNNameMap = {};
var destinationMenu, proxyMenu, nameFolderMenu, feedbackMenu
registMenus()
var downloadParams = '--maxThreads "48" --minThreads "16" --retryCount "100" --timeOut "100" --enableDelAfterDone';
var proxyParam = ' --noProxy'
function clickProxyMenu() {
GM_setValue("menu_proxy_status", !getProxyMenuStatus())
registMenus();
}
function getProxyMenuStatus() {
return GM_getValue("menu_proxy_status") ? true : false
}
function proxyMenuText() {
return getProxyMenuStatus() ? "使用系统代理下载✅" : "使用系统代理下载❌"
}
function clickNameFolderMenu() {
GM_setValue("menu_name_folder_status", !getNameFolderMenuStatus())
registMenus()
}
function openFeedBack() {
window.open("https://greasyfork.org/zh-CN/scripts/474848-jable%E4%B8%80%E9%94%AE%E4%B8%8B%E8%BD%BD%E6%94%B6%E8%97%8F/feedback", "_blank");
}
function getNameFolderMenuStatus() {
return GM_getValue("menu_name_folder_status") ? true : false
}
function nameFolderMenuText() {
return getNameFolderMenuStatus() ? "下载到艺术家名文件夹✅" : "下载到艺术家名文件夹❌"
}
GM_addValueChangeListener("menu_save_file_directory", (name, old_value, new_value, remote) => {
if (remote) { registMenus() }
});
GM_addValueChangeListener("menu_proxy_status", (name, old_value, new_value, remote) => {
if (remote) { registMenus() }
});
GM_addValueChangeListener("menu_name_folder_status", (name, old_value, new_value, remote) => {
if (remote) { registMenus() }
});
function clickDestinationMenu() {
let destination = prompt("请输入下载地址", getSaveFileDirectory());
if (destination) {
GM_setValue("menu_save_file_directory", destination);
registMenus();
}
}
function getSaveFileDirectory() {
return GM_getValue("menu_save_file_directory") || defaultSaveFileDirectory;
}
function registMenus() {
if (destinationMenu) {
GM_unregisterMenuCommand(destinationMenu);
}
destinationMenu = GM_registerMenuCommand(destinationMenuText(), clickDestinationMenu);
if (proxyMenu) {
GM_unregisterMenuCommand(proxyMenu);
}
proxyMenu = GM_registerMenuCommand(proxyMenuText(), clickProxyMenu);
if(nameFolderMenu) {
GM_unregisterMenuCommand(nameFolderMenu);
}
nameFolderMenu = GM_registerMenuCommand(nameFolderMenuText(), clickNameFolderMenu);
if (feedbackMenu) {
GM_unregisterMenuCommand(feedbackMenu);
}
feedbackMenu = GM_registerMenuCommand("给个好评", openFeedBack);
}
function destinationMenuText() {
return `下载地址:"${getRealSaveFileDirectory(['{艺术家名字}'])}"`
}
function getRealSaveFileDirectory(modelNames) {
let dir = getSaveFileDirectory()
if (!dir.match(/[\s\S]*\\$/)) {
dir = dir + '\\'
}
if (getNameFolderMenuStatus()) {
if (modelNames && modelNames.length > 0) {
if (modelNames.length == 1) {
let artistName = modelNames[0]
let artistCNName = getArtistCNName(artistName)
if (artistCNName) {
artistName = artistCNName
}
dir = dir + artistName + '\\'
} else {
dir = dir + '群星' + '\\'
}
} else {
dir = dir + '未知艺术家' + '\\'
}
}
// console.log('getRealSaveFileDirectory', modelNames, dir)
return dir
}
var _a, _b, _c, _d;
("use strict");
var linkPrefix = `https://${location.host}/videos/`;
var r = (_a = Reflect.get(document, "__monkeyWindow")) != null ? _a : window;
r.GM;
r.unsafeWindow = (_b = r.unsafeWindow) != null ? _b : window;
r.unsafeWindow;
r.GM_info;
r.GM_cookie;
var addStyle = (...e) => r.GM_addStyle(...e),
xmlhttpRequest = (...e) => r.GM_xmlhttpRequest(...e);
const jableStyle = `
#site-content > div.container {
max-width: 2000px !important;
}
.video-img-box .title {
white-space: normal;
}
.video-img-box.liked .title a::before {
content: '❤️ ';
}
.absolute-bottom-left.download {
left: 60px;
}
.absolute-bottom-left.download .action {
background: rgba(255,255,255,.18);
opacity: 0;
}
.absolute-bottom-left.download .action.loading {
cursor: wait;
}
.video-img-box:hover .absolute-bottom-left.download .action {
opacity: 1;
}
.video-img-box.hasurl .absolute-bottom-left.download .action {
background: rgba(98,91,255,.4);
}
.video-img-box.hasurl .absolute-bottom-left.download .action:hover {
background: rgba(98,91,255,.8);
}
.video-img-box .detail .sub-title.added-avatar .models {
display: -webkit-inline-box;
display: -ms-inline-flexbox;
display: inline-flex;
margin-left: 10px;
}
.video-img-box .detail .sub-title.added-avatar .models .model {
width: 1.5rem;
height: 1.5rem;
}
.video-img-box .detail .sub-title.added-avatar .models .placeholder {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
-webkit-box-align: center;
-ms-flex-align: center;
align-items: center;
-webkit-box-pack: center;
-ms-flex-pack: center;
justify-content: center;
background: #687ae8;
color: #fff;
font-size: .8125rem;
width: 100%;
height: 100%;
-webkit-box-shadow: 2px 2px 16px 0 rgba(17,18,20,.8);
box-shadow: 2px 2px 16px 0 rgba(17,18,20,.8);
}
.video-img-box.hot-1 .title a::after {
content: ' 🔥';
}
.video-img-box.hot-2 .title a::after {
content: ' 🔥🔥';
}
.video-img-box.hot-3 .title a::after {
content: ' 🔥🔥🔥';
}
.video-img-box.hot-1 .title {
color: #f9c8f1;
}
.video-img-box.hot-2 .title {
color: hotpink;
}
.video-img-box.hot-3 .title {
color: #ff367f;
}
.video-img-box.liked .hover-state {
opacity: 1;
}
.btn-action.fav svg {
color: gray !important;
}
.btn-action.fav.active svg {
color: white !important;
}
`;
const paths = {
video_like_btn: "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.text-center > div > button.btn.btn-action.fav.mr-2",
video_title_path: "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.info-header > div.header-left > h4",
video_avatar_path: "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.info-header > div.header-left > h6 > div.models",
model_title_name: "#site-content > section > div > div > div > h2"
};
function isVideoURL(url) {
return !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/videos\/*\/*/);
}
function isModelURL(url) {
return (
!!url.match(/https:\/\/(jable\.tv|fs1\.app)\/models\/*\/*/) ||
!!url.match(/https:\/\/(jable\.tv|fs1\.app)\/s1\/models\/*\/*/)
);
}
function isHotURL(url) {
return !!url.match(/https:\/\/(jable\.tv|fs1\.app)\/hot\/*\/*/);
}
function getCodeFromUrl(url) {
let code = url.replace(linkPrefix, "").replace(/\/[\s\S]*$/, "");
return code;
}
var isVideoPage = isVideoURL(location.href);
var isModelPage = isModelURL(location.href);
var isHotPage = isHotURL(location.href);
var modelPageName = null
if (isModelPage) {
const res = artistPageParseFromDoc(document)
modelPageName = res.modelPageName
}
function artistPageParser(responseText) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
let result = artistPageParseFromDoc(doc)
return result
}
function artistPageParseFromDoc(doc) {
let result = {
modelPageName: null,
modelPageChineseName: null
}
let name = doc.querySelector(paths.model_title_name)
if (name && name.innerText) {
result.modelPageName = name.innerText
}
let kwdMeta = doc.querySelector('head meta[name="keywords"]')
if (kwdMeta) {
let content = kwdMeta.getAttribute('content')
if (content) {
let titleSplitDict = {}
let kwdDict = {}
let keywords = content.split(',').map(a => {return a.trim()})
let titles = doc.querySelectorAll(".video-img-box .detail .title a");
titles.forEach(title => {
keywords.forEach(kwd => {
if (title.innerText && title.innerText.indexOf(kwd) > 0) {
if (kwdDict.hasOwnProperty(kwd)) {
kwdDict[kwd] = kwdDict[kwd] + 1
} else {
kwdDict[kwd] = 1
}
}
})
if (title.innerText) {
let splt = title.innerText.split(' ')
if (splt && splt.length > 1) {
let lastWord = splt[splt.length - 1]
if (titleSplitDict.hasOwnProperty(lastWord)) {
titleSplitDict[lastWord] = titleSplitDict[lastWord] + 1
} else {
titleSplitDict[lastWord] = 1
}
}
}
})
function getMaxTimesKVFromDict(dict) {
let maxKey = null
let maxTimes = null
for (const key in dict) {
if (Object.hasOwnProperty.call(dict, key)) {
const times = dict[key];
if (!maxTimes || times > maxTimes) {
maxTimes = times
maxKey = key
}
}
}
return {maxKey, maxTimes}
}
function getStringSameNum(str1, str2){
let a = str1.split('');
let b = str2.split('');
let len = 0;
let maxlength = a.length > b.length ? a : b;
let minlength = a.length < b.length ? a : b;
for(let i =0; i < minlength.length; ){
let isdelete = false;
for(let j = 0; j < maxlength.length; ){
if(minlength[i] == maxlength[j]){
len++;
maxlength.splice(j, 1)
isdelete = true;
break;
}else{
j++;
}
}
if(isdelete){
minlength.splice(i,1)
}else{
i++;
}
}
return len;
}
let timesRes = getMaxTimesKVFromDict(kwdDict)
if (!timesRes.maxKey) {
let spltRes = getMaxTimesKVFromDict(titleSplitDict)
if (spltRes.maxTimes && spltRes.maxTimes >= 3) {
// 起码出现3次重复才能判断为姓名
timesRes = spltRes
} else if (spltRes.maxKey && getStringSameNum(spltRes.maxKey, result.modelPageName) >= 2) {
// 中文和日文至少有两个字相同
timesRes = spltRes
}
}
if (timesRes.maxKey) {
result.modelPageChineseName = timesRes.maxKey
saveArtistCNName(result.modelPageName, result.modelPageChineseName)
}
}
}
// console.log('artistPageParseFromDoc', result)
return result
}
function saveArtistCNName(name, cnName) {
if (!artistCNNameMap.hasOwnProperty(name) || !artistCNNameMap[name]) {
artistCNNameMap[name] = cnName
GM_setValue('artist_CN_name_map', artistCNNameMap)
}
}
function getArtistCNName(name) {
// console.log('getArtistCNName', name, artistCNNameMap)
if (artistCNNameMap[name]) {
return artistCNNameMap[name]
} else {
if (Object.keys(artistCNNameMap).length == 0) {
artistCNNameMap = GM_getValue('artist_CN_name_map') || {}
}
return artistCNNameMap[name] || null
}
}
GM_addValueChangeListener("artist_CN_name_map", (name, old_value, new_value, remote) => {
if (remote) {
artistCNNameMap = new_value || {}
// console.log('artist_CN_name_map-Change', new_value)
}
});
async function requestArtistPage(siteUrl) {
let result = {
modelPageName: null,
modelPageChineseName: null
}
const xhrPromise = new Promise((resolve) => {
xmlhttpRequest({
method: "GET",
url: siteUrl,
onload: (response) => {
if (response.status === 404) {
} else {
result = artistPageParser(response.responseText);
}
resolve(result)
},
onerror: (error) => {
// console.log("xhr-error", error);
resolve(result);
},
});
});
return xhrPromise;
}
var logined = false;
var userName = null;
var userNameEl = document.querySelector(".d-lg-block");
if (userNameEl && userNameEl.innerText != "登入") {
logined = true;
userName = userNameEl.innerText;
}
const Base64 = {
encode(str) {
return btoa(
encodeURIComponent(str).replace(
/%([0-9A-F]{2})/g,
function toSolidBytes(match, p1) {
return String.fromCharCode("0x" + p1);
}
)
);
},
decode(str) {
// Going backwards: from bytestream, to percent-encoding, to original string.
return decodeURIComponent(
atob(str)
.split("")
.map(function (c) {
return "%" + ("00" + c.charCodeAt(0).toString(16)).slice(-2);
})
.join("")
);
},
};
// console.log('saveFileDirectory:', getRealSaveFileDirectory(), downloadParams, getProxyMenuStatus() ? '' : proxyParam)
function getDownloadSchemeFromHlsUrl(url, title, models) {
var title = title
var models = models.map(model => {
return model.name
})
if (isModelPage) {
if (models && models.length > 1) {
// 如果是在艺术家页面并且该电影包含多个演员,则直接下载到该艺术家的文件夹下
models = [modelPageName]
}
}
if (models && models.length === 1) {
// 检查文件名中是否包含艺术家名称
let artistName = models[0]
let artistCNName = getArtistCNName(artistName)
if (artistCNName) {
artistName = artistCNName
if (title.indexOf(artistName) < 0) {
title = title + ' ' + artistName
}
}
}
let dir = getRealSaveFileDirectory(models)
let proxy = getProxyMenuStatus() ? '' : proxyParam;
let params = `"${url}" --saveName "${title}" --workDir "${dir}" ${downloadParams}${proxy}`
let bs64 = "m3u8dl://" + Base64.encode(params);
// console.log('download-params:', params, url, title, dir, downloadParams, proxy, bs64);
return bs64;
}
async function detectDownload() {
let parseResult = await videoPageParserFromDoc(document)
parseResult.hlsUrl = hlsUrl
// console.log('detectDownload', parseResult)
// isSuccess: false,
// liked: false,
// models: [],
// hlsUrl: null,
// title: "",
// avatarDom: null,
var title_el = document.querySelector(paths.video_title_path);
var download_btn = document.createElement("a");
download_btn.className = "addtion";
download_btn.id = "download_m3u8";
download_btn.href = "javascript:void(0);";
if (logined) {
download_btn.innerText = "下载并收藏";
} else {
download_btn.innerText = "下载(无法收藏,未登录)";
}
download_btn.style.display = "inline-block";
download_btn.style.padding = "10px 20px";
download_btn.style.background = "cornflowerblue";
download_btn.style.color = "white";
download_btn.style.fontSize = "18px";
download_btn.style.margin = "0 10px";
download_btn.style.borderRadius = "5px";
title_el.appendChild(download_btn);
const likeBtn = document.querySelector(paths.video_like_btn);
saveVideoPageStatus();
likeBtn.addEventListener("click", () => {
saveVideoPageStatus(true);
});
function checkClickLike() {
const download = () => {
let downloadLink = getDownloadSchemeFromHlsUrl(parseResult.hlsUrl, parseResult.title, parseResult.models);
// console.log('开始下载', downloadLink);
window.open(downloadLink, "_blank");
};
if (likeBtn) {
if (likeBtn.classList.contains("active")) {
var r = confirm("你已收藏此影片,可能下载过,是否继续下载?");
if (r == true) {
download();
} else {
// console.log('取消下载');
}
} else {
likeBtn.click();
download();
}
} else {
download();
}
}
download_btn.addEventListener("click", function () {
checkClickLike();
});
}
function saveVideoPageStatus(isClick = false) {
if (!isVideoPage) {
return;
}
const likeBtn = document.querySelector(paths.video_like_btn);
if (!likeBtn) {
return;
}
let code = getCodeFromUrl(location.href);
let currentLike = likeBtn.classList.contains("active");
if (isClick) {
currentLike = !currentLike;
}
setLiked(code, currentLike);
}
var mouse_timer = null; // 定时器
var manual_loaded_codes = {};
function createNode(htmlStr) {
var div = document.createElement("div");
div.innerHTML = htmlStr;
return div.childNodes[0];
}
function isValidClassName(name) {
return name.match(/-?[_a-zA-Z]+[_a-zA-Z0-9-]*/)
}
// update website CSS
function updateBoxCardCSS(forceLoadLikeStatus = false) {
var imgBoxes = document.querySelectorAll(".video-img-box");
for (let index = 0; index < imgBoxes.length; index++) {
const box = imgBoxes[index];
let title = box.querySelector(".title");
if (!title) {
return;
}
let subTitle = box.querySelector(".sub-title");
if (
subTitle &&
subTitle.innerText &&
subTitle.innerText.split("\n").length >= 2
) {
// 根据观看数和点赞数设置标签
let playText = subTitle.innerText.split("\n")[0];
let likeText = subTitle.innerText.split("\n")[1];
if (playText && likeText) {
let playCount = parseInt(playText.replaceAll(" ", ""));
let likeCount = parseInt(likeText);
if (playCount > 1300000 || likeCount > 13000) {
box.classList.add("hot-3");
} else if (playCount > 1000000 || likeCount > 10000) {
box.classList.add("hot-2");
} else if (playCount > 500000 || likeCount > 5000) {
box.classList.add("hot-1");
}
}
}
let titleLink = title.querySelector("a");
if (titleLink && titleLink.href && isVideoURL(titleLink.href)) {
let code = getCodeFromUrl(titleLink.href);
if (code) {
let className = code
if (!isValidClassName(className)) {
className = 'valid-' + className
}
if (!box.classList.contains(className)) {
box.classList.add(className);
let heartEls = box.querySelectorAll(".action");
heartEls.forEach((heartEl) => {
if (heartEl) {
if (heartEl.classList.contains("fav-restore")) {
heartEl.addEventListener("click", (event) => {
event.preventDefault();
setLiked(code, true);
loadBoxStatus(box, code);
});
} else if (heartEl.classList.contains("fav-remove")) {
heartEl.addEventListener("click", (event) => {
event.preventDefault();
setLiked(code, false);
loadBoxStatus(box, code);
});
} else {
heartEl.classList.add("like");
heartEl.addEventListener("click", (event) => {
event.preventDefault();
let liked = !heartEl.classList.contains("active");
setLiked(code, liked);
loadBoxStatus(box, code);
// console.log('heartEl-click', code, liked);
setTimeout(() => {
requestLike(heartEl, liked)
}, 100);
});
}
}
});
let coverAEl = box.querySelector(".img-box a");
if (coverAEl) {
let downloadbtn = createNode('<div class="absolute-bottom-left download"><span class="action download d-sm-flex"><svg xmlns="http://www.w3.org/2000/svg" width="22" height="22" viewBox="0 0 24 24"><path fill="#ffffff" d="m12 16l-5-5l1.4-1.45l2.6 2.6V4h2v8.15l2.6-2.6L17 11zm-8 4v-5h2v3h12v-3h2v5z"></path></svg></span></div>')
coverAEl.appendChild(downloadbtn);
downloadbtn.addEventListener("click", (event) => {
event.preventDefault();
downloadFilm(box, code);
});
}
function stopMouseTimer() {
clearTimeout(mouse_timer);
mouse_timer = null;
}
box.addEventListener(
"mouseenter",
(event) => {
stopMouseTimer();
if (!manual_loaded_codes.hasOwnProperty(code)) {
mouse_timer = setTimeout(() => {
stopMouseTimer();
getFilmResult(code);
}, 500);
}
},
false
);
box.addEventListener(
"mouseleave",
(event) => {
if (mouse_timer) {
stopMouseTimer();
}
},
false
);
loadBoxStatus(box, code);
} else if (forceLoadLikeStatus) {
loadBoxStatus(box, code);
}
}
}
}
document.querySelectorAll('a').forEach(a => {
a.target = '_blank'
});
}
function downloadFilm(box, code) {
let result = manual_loaded_codes[code];
let liked = getLiked(code);
if (result && result.hlsUrl && result.title) {
let likeBtn = box.querySelector(".action.like");
const download = () => {
let downloadLink = getDownloadSchemeFromHlsUrl(result.hlsUrl, result.title, result.models);
// console.log('开始下载', downloadLink, result);
window.open(downloadLink, "_blank");
};
if (likeBtn) {
if (liked) {
var r = confirm("你已收藏此影片,可能下载过,是否继续下载?");
if (r == true) {
download();
}
} else {
likeBtn.click();
download();
}
} else {
download();
}
}
}
async function requestLike(heartEl, liked) {
if (!logined || !heartEl) {
return;
}
const action = liked ? "add_to_favourites" : "delete_from_favourites";
const fav_id = heartEl.getAttribute("data-fav-video-id");
const url = `${location.href}?mode=async&format=json&action=${action}&video_id=${fav_id}&video_ids%5B%5D=${fav_id}&fav_type=0&playlist_id=0`;
if (!fav_id) {
return;
}
// console.log("requestLike-start", url);
const xhrPromise = new Promise((resolve) => {
xmlhttpRequest({
method: "GET",
url: url,
onload: (response) => {
// console.log("requestLike-done", response);
if (response.status === 404) {
resolve({
status: "fail",
});
} else {
resolve({
status: "success",
});
}
},
onerror: (error) => {
console.log("requestLike-error", error);
resolve({
status: "fail",
});
},
});
});
return xhrPromise;
}
async function loadAllMyFavorites() {
if (!logined) {
return;
}
GM_addValueChangeListener(
userName + "_liked_codes",
(name, old_value, new_value, remote) => {
if (remote) {
liked_codes = new_value;
// console.log("initial-liked_codes-refresh", liked_codes);
updateBoxCardCSS(true);
}
}
);
const usrkey = userName + "_favorites_initialized_status";
if (GM_getValue(usrkey)) {
return;
}
var isSuccess = true;
var codes = [];
var result = await requestFavoritesPage(1);
if (result.status == "success") {
codes = codes.concat(result.liked_codes);
while (result.next) {
result = await requestFavoritesPage(result.next);
if (result.status == "success") {
codes = codes.concat(result.liked_codes);
} else {
isSuccess = false;
}
}
} else {
isSuccess = false;
}
if (isSuccess) {
GM_setValue(usrkey, true);
liked_codes = codes;
// console.log("set_liked_codes-1", userName + "_liked_codes", liked_codes);
GM_setValue(userName + "_liked_codes", liked_codes);
updateBoxCardCSS(true);
}
}
function favouritesPageParser(responseText) {
let res = {
status: "fail",
current: 0,
next: 0,
total: 0,
liked_codes: [],
};
const doc = new DOMParser().parseFromString(responseText, "text/html");
const page_item = doc.querySelectorAll(".page-item");
if (page_item && page_item.length > 0) {
let currentCount = 0;
let totalCount = 0;
let nextCount = 0;
const current = doc.querySelector(".page-item .page-link.active");
if (current && current.innerText) {
currentCount = parseInt(current.innerText);
res.current = currentCount;
}
const total = doc.querySelector(".page-item:last-child .page-link");
if (total && total.innerText) {
if (total.classList.contains("active")) {
res.total = total.innerText;
} else {
let parameters = total.attributes["data-parameters"].value;
parameters = parameters.split(";");
for (let index = 0; index < parameters.length; index++) {
const element = parameters[index];
if (element.indexOf("from_my_fav_videos:") == 0) {
res.total = element.split(":")[1];
break;
}
}
}
if (res.total) {
totalCount = parseInt(res.total);
res.total = totalCount;
}
}
if (currentCount && totalCount && currentCount < totalCount) {
nextCount = currentCount + 1;
res.next = nextCount;
}
}
let links = doc.querySelectorAll(".video-img-box .detail .title a");
if (links && links.length > 0) {
let liked_codes = [];
for (let index = 0; index < links.length; index++) {
const element = links[index];
if (element.href.indexOf(linkPrefix) == 0) {
liked_codes.push(getCodeFromUrl(element.href));
}
}
res.liked_codes = liked_codes;
if (liked_codes.length > 0) {
res.status = "success";
}
}
return res;
}
async function requestFavoritesPage(page) {
// console.log("requestFavoritesPage-start", page);
let url = `https://jable.tv/my/favourites/videos/?mode=async&function=get_block&block_id=list_videos_my_favourite_videos&fav_type=0&playlist_id=0&sort_by=&from_my_fav_videos=${page}&_=${new Date().getTime()}`;
const xhrPromise = new Promise((resolve) => {
xmlhttpRequest({
method: "GET",
url: url,
onload: (response) => {
if (response.status === 404) {
resolve({
status: "fail",
});
} else {
const res = favouritesPageParser(response.responseText);
// console.log("requestFavoritesPage-done", page, res);
resolve(res);
}
},
onerror: (error) => {
// console.log("requestFavoritesPage-error", error);
resolve({
status: "fail",
});
},
});
});
return xhrPromise;
}
function getLiked(code) {
if (liked_codes.length > 0) {
return liked_codes.indexOf(code) >= 0;
}
initialLikedCodes()
return liked_codes.indexOf(code) >= 0;
}
function initialLikedCodes() {
let res = GM_getValue(userName + "_liked_codes");
liked_codes = res || [];
// console.log("initial-liked_codes", liked_codes);
}
function setLiked(code, liked) {
if (liked) {
if (liked_codes.indexOf(code) < 0) {
liked_codes.push(code);
// console.log(
// "set_liked_codes-2",
// userName + "_liked_codes",
// liked_codes
// );
GM_setValue(userName + "_liked_codes", liked_codes);
}
} else {
let index = liked_codes.indexOf(code);
if (index >= 0) {
liked_codes.splice(index, 1);
// console.log(
// "set_liked_codes-3",
// userName + "_liked_codes",
// liked_codes
// );
GM_setValue(userName + "_liked_codes", liked_codes);
}
}
}
function loadBoxStatus(boxEl, code) {
let liked = getLiked(code);
if (boxEl) {
let heartEl = boxEl.querySelector(".action.like");
if (liked) {
boxEl.classList.add("liked");
if (heartEl) {
heartEl.classList.add("active");
}
} else {
if (boxEl.classList.contains("liked")) {
boxEl.classList.remove("liked");
if (heartEl && heartEl.classList.contains("active")) {
heartEl.classList.remove("active");
}
}
}
let requestData = manual_loaded_codes[code];
let downloadEl = boxEl.querySelector(".action.download");
if (requestData && requestData.hlsUrl) {
boxEl.classList.add("hasurl");
if (downloadEl) {
downloadEl.setAttribute("hlsUrl", requestData.hlsUrl);
}
} else {
boxEl.classList.remove("hasurl");
if (downloadEl) {
downloadEl.removeAttribute("hlsUrl");
}
}
if (requestData && requestData.avatarDom) {
let subTitle = boxEl.querySelector(".detail .sub-title");
if (subTitle && !subTitle.classList.contains("added-avatar")) {
subTitle.classList.add("added-avatar");
subTitle.appendChild(requestData.avatarDom);
}
}
}
}
async function getFilmResult(code) {
if (!logined) {
return;
}
let className = code
if (!isValidClassName(className)) {
className = 'valid-' + className
}
let boxEl = document.querySelector(`.video-img-box.${className}`);
let downloadEl = boxEl.querySelector(".action.download");
if (downloadEl) {
downloadEl.classList.add('loading')
}
// console.log("getFilmResult", code);
let item = {
status: "loading",
targetLink: `${linkPrefix}${code}/`,
hlsUrl: null,
models: [],
title: "",
code: code,
avatarDom: null,
request_at: 0,
liked: false,
};
const resItem = await requestVideoPage(item);
if (resItem.status != "success") {
return;
}
let liked = getLiked(code);
if (liked && !resItem.liked) {
// console.log('请求结果与记录不符,正在重新点赞!!!', code, resItem)
resItem.liked = true;
let heartEl = boxEl.querySelector(".action.like");
if (heartEl) {
requestLike(heartEl, true);
}
}
manual_loaded_codes[code] = resItem;
setLiked(code, resItem.liked);
// console.log("getFilmResult-finish", resItem);
if (downloadEl) {
downloadEl.classList.remove('loading')
}
loadBoxStatus(boxEl, code);
}
async function videoPageParser(responseText) {
const doc = new DOMParser().parseFromString(responseText, "text/html");
let result = await videoPageParserFromDoc(doc)
var regex = /var\s+hlsUrl\s*=\s*['"]([^'"]+)['"]/;
var match = responseText.match(regex);
// 输出提取的 hlsUrl 值
if (match && match.length > 1) {
result.hlsUrl = match[1];
// console.log("提取到的 hlsUrl 值为:", res.hlsUrl);
} else {
// console.log("未能提取到 hlsUrl 值.");
}
return result
}
async function videoPageParserFromDoc(doc) {
let res = {
isSuccess: false,
liked: false,
models: [],
hlsUrl: null,
title: "",
avatarDom: null,
};
const likeBtn = doc.querySelector(paths.video_like_btn);
if (likeBtn) {
res.isSuccess = true;
if (likeBtn.classList.contains("active")) {
res.liked = true;
}
}
var title_el = doc.querySelector(paths.video_title_path);
if (title_el && title_el.innerText) {
res.title = title_el.innerText;
}
var avatar_el = doc.querySelector(paths.video_avatar_path);
if (avatar_el) {
res.avatarDom = avatar_el;
let models = []
let aModel = avatar_el.querySelectorAll('a.model')
aModel.forEach(a => {
let rc = a.querySelector('.rounded-circle')
let title = rc.getAttribute('title') || rc.getAttribute('data-original-title')
// console.log('title',title)
if (a.href && title) {
models.push({
name: title,
url: a.href
})
}
})
res.models = models
if (res.models.length == 1) {
let cnName = getArtistCNName(res.models[0].name)
if (!cnName) {
await requestArtistPage(res.models[0].url)
}
}
}
return res;
}
async function requestVideoPage(siteItem) {
const siteUrl = siteItem.targetLink;
const xhrPromise = new Promise((resolve) => {
xmlhttpRequest({
method: "GET",
url: siteUrl,
onload: async (response) => {
siteItem.request_at = new Date().getTime();
if (response.status === 404) {
siteItem.status = "fail";
resolve(siteItem);
} else {
const { isSuccess, liked, hlsUrl, title, avatarDom, models } = await videoPageParser(response.responseText);
siteItem.status = isSuccess ? "success" : "fail";
siteItem.liked = liked;
siteItem.hlsUrl = hlsUrl
siteItem.models = models
siteItem.title = title
siteItem.avatarDom = avatarDom
setTimeout(() => {
resolve(siteItem);
}, 200);
}
},
onerror: (error) => {
// console.log("xhr-error", error);
siteItem.status = "fail";
resolve(siteItem);
},
});
});
return xhrPromise;
}
function observePageMutations() {
var targetNode = document.body;
var observerOptions = {
childList: true, // Observe direct children being added or removed
subtree: true, // Observe all descendants of the target node
};
var observer = new MutationObserver(function (mutationsList, observer) {
updateBoxCardCSS();
});
observer.observe(targetNode, observerOptions);
}
(function main() {
addStyle(jableStyle);
window.addEventListener("load", () => {
initialLikedCodes();
if (isVideoPage) {
detectDownload();
}
updateBoxCardCSS();
observePageMutations();
loadAllMyFavorites();
});
})();
})();