// ==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.3.2
// @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
// @license MPL
// ==/UserScript==
(function () {
const saveFileDirectory = "D:\\videos\\jav";
const downloadParams =
' --maxThreads "48" --minThreads "16" --retryCount "100" --timeOut "100" --enableDelAfterDone';
const autoDetectLiked = true;
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 l = (...e) => r.GM_addStyle(...e),
b = (...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: '❤️ ';
}
.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"
};
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 memoryData = {};
var isVideoPage = isVideoURL(location.href);
var isModelPage = isModelURL(location.href);
var isHotPage = isHotURL(location.href);
var logined = false;
var userNameEl = document.querySelector(".d-lg-block");
if (userNameEl && userNameEl.innerText != "登入") {
logined = true;
}
function detectDownload() {
let 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("")
);
},
};
var title_path = "#site-content > div > div > div:nth-child(1) > section.video-info.pb-3 > div.info-header > div.header-left > h4";
var title_el = document.querySelector(title_path);
var title = title_el.innerText;
var url = hlsUrl;
var download_btn = document.createElement("a");
download_btn.className = "addtion";
download_btn.id = "download_m3u8";
var params =
'"' +
url +
'"' +
' --saveName "' +
title +
'" --workDir "' +
saveFileDirectory +
'"' +
downloadParams;
params = Base64.encode(params);
var downloadLink = "m3u8dl://" + params;
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 = () => {
// 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();
}
}
document
.getElementById("download_m3u8")
.addEventListener("click", function () {
checkClickLike();
});
}
function isInViewPort (el) {
const viewPortHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const top = el.getBoundingClientRect() && el.getBoundingClientRect().top
return top <= viewPortHeight + 100
}
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
}
let data = {
status: "success",
targetLink: `${linkPrefix}${code}/`,
code: code,
liked: currentLike,
}
setData(code, data)
}
var mouse_code = null;
var mouse_timer = null; // 定时器
var manual_loaded_codes = [];
// update website CSS
function updateBoxCardCSS() {
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) {
if (!box.classList.contains(code)) {
box.classList.add(code);
box.classList.add("waiting");
let heartEl = box.querySelector(".action");
if (heartEl) {
heartEl.addEventListener("click", () => {
let data = {
status: "success",
targetLink: `${linkPrefix}${code}/`,
code: code,
liked: !heartEl.classList.contains("active"),
}
setData(code, data)
loadBoxStatus(box, data)
});
}
function stopMouseTimer() {
clearTimeout(mouse_timer);
mouse_timer = null;
}
box.addEventListener("mouseenter", (event) => {
mouse_code = code;
stopMouseTimer();
if (manual_loaded_codes.indexOf(code) < 0) {
mouse_timer = setTimeout(() => {
stopMouseTimer()
getFilmResult(code);
manual_loaded_codes.push(code);
}, 500);;
}
}, false);
box.addEventListener("mouseleave", (event) => {
mouse_code = null;
if (mouse_timer) {
stopMouseTimer()
}
}, false);
}
}
}
}
}
async function loadFilmInfo() {
if (!autoDetectLiked) {
return
}
if (!isModelPage && !isHotPage) {
return
}
var imgBoxes = document.querySelectorAll(".video-img-box.waiting");
// console.log('loadFilmInfo', imgBoxes.length)
for (let index = 0; index < imgBoxes.length; index++) {
const box = imgBoxes[index];
let titleLink = box.querySelector(".title a");
if (titleLink && titleLink.href && isVideoURL(titleLink.href)) {
let code = getCodeFromUrl(titleLink.href);
if (code) {
let result = getData(code);
if (result) {
if (result.status == "success") {
loadBoxStatus(box, result);
} else if (result.status == "fail") {
if (isInViewPort(box)) {
await getFilmResult(code);
}
} else {
// loading
}
} else {
if (isInViewPort(box)) {
await getFilmResult(code);
}
}
}
}
}
}
function getData(code) {
if (memoryData.hasOwnProperty(code)) {
return memoryData[code]
}
let res = GM_getValue(code)
if (res) {
memoryData[code] = res
}
return res
}
function setData(code, data) {
memoryData[code] = data
if (data.status == 'success') {
GM_setValue(code, data)
}
}
function loadBoxStatus(boxEl, result) {
if (boxEl) {
boxEl.classList.remove("waiting");
let heartEl = boxEl.querySelector(".action");
if (result.status == "success" && result.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");
}
}
}
}
}
async function getFilmResult(code) {
if (!logined) {
return;
}
console.log("getFilmResult", code);
let item = {
status: "loading",
targetLink: `${linkPrefix}${code}/`,
code: code,
liked: false,
};
setData(code, item);
const resItem = await xhr(item);
setData(code, resItem);
console.log("getFilmResult-finish", resItem);
let boxEl = document.querySelector(`.video-img-box.${code}`);
loadBoxStatus(boxEl, resItem);
}
function videoPageParser(responseText) {
let res = {
isSuccess: false,
liked: false,
};
const doc = new DOMParser().parseFromString(responseText, "text/html");
const likeBtn = doc.querySelector(paths.video_like_btn);
if (likeBtn) {
res.isSuccess = true;
if (likeBtn.classList.contains("active")) {
res.liked = true;
}
}
return res;
}
async function xhr(siteItem) {
const siteUrl = siteItem.targetLink;
const xhrPromise = new Promise((resolve) => {
b({
method: "GET",
url: siteUrl,
onload: (response) => {
if (response.status === 404) {
siteItem.status = "fail";
resolve(siteItem);
} else {
const {
isSuccess,
liked
} = videoPageParser(response.responseText);
siteItem.status = isSuccess ? "success" : "fail";
siteItem.liked = liked;
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);
}
var scroll_t1 = 0;
var scroll_t2 = 0;
var scroll_timer = null; // 定时器
function isScrollEnd() {
scroll_t2 = document.documentElement.scrollTop || document.body.scrollTop;
if (scroll_t2 == scroll_t1){
loadFilmInfo();
}
}
(function main() {
l(jableStyle);
if (isVideoPage) {
detectDownload();
}
window.addEventListener("load", () => {
updateBoxCardCSS();
loadFilmInfo();
observePageMutations();
});
window.addEventListener("scroll", () => {
clearTimeout(scroll_timer);
scroll_timer = setTimeout(isScrollEnd, 1000);
scroll_t1 = document.documentElement.scrollTop || document.body.scrollTop;
})
})();
})();