// ==UserScript==
// @name JavScript
// @namespace https://greasyfork.org/users/175514
// @version 2.5.3
// @author blc
// @description 一站式体验, JavBus & JavDB 兼容
// @icon https://z3.ax1x.com/2021/10/15/53gMFS.png
// @include *
// @require https://unpkg.com/masonry-layout@4/dist/masonry.pkgd.min.js
// @require https://unpkg.com/infinite-scroll@4/dist/infinite-scroll.pkgd.min.js
// @resource fail https://z3.ax1x.com/2021/10/15/53gcex.png
// @resource info https://z3.ax1x.com/2021/10/15/53g2TK.png
// @resource success https://z3.ax1x.com/2021/10/15/53gqTf.png
// @resource play https://s4.ax1x.com/2022/01/12/7nYuKe.png
// @resource loading https://via.placeholder.com/824x40
// @connect *
// @run-at document-start
// @grant GM_registerMenuCommand
// @grant GM_getResourceURL
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_notification
// @grant GM_openInTab
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_info
// @license GPL
// @compatible edge
// @compatible chrome
// @compatible firefox 未测试
// ==/UserScript==
(function () {
// domains
const createReg = items => new RegExp(`(${items.join("|").replace(/\./g, "\\.")})+`, "gi");
const MatchDomains = [
{
domain: "JavBus",
regex: /(jav|bus|dmm|see|cdn|fan){2}\./g,
},
{
domain: "JavDB",
regex: /javdb\d*\.com/g,
},
];
// document
const DOC = document;
DOC.create = (tag, attr = {}, child = "") => {
if (!tag) return null;
tag = DOC.createElement(tag);
Object.keys(attr).forEach(name => tag.setAttribute(name, attr[name]));
typeof child === "string" && tag.appendChild(DOC.createTextNode(child));
typeof child === "object" && tag.appendChild(child);
return tag;
};
// request
const request = (url, data = {}, method = "GET", params = {}) => {
if (typeof data === "object") {
data = Object.keys(data).reduce(
(pre, cur) => `${pre ? `${pre}&` : ""}${cur}=${encodeURIComponent(data[cur])}`,
""
);
}
if (method === "GET" && data) url = `${url}?${data}`;
return new Promise(resolve => {
GM_xmlhttpRequest({
url,
data,
method,
timeout: 20000,
onload: ({ status, response }) => {
if (status === 404) resolve(false);
const htmlReg = /<\/?[a-z][\s\S]*>/i;
const jsonReg = /^{.*}$/;
if (htmlReg.test(response)) {
response = new DOMParser().parseFromString(response, "text/html");
} else if (jsonReg.test(response)) {
response = JSON.parse(response);
}
if (response?.errcode === 911) verify();
resolve(response);
},
...params,
});
});
};
const verify = () => {
const h = 667;
const w = 375;
const t = (window.screen.availHeight - h) / 2;
const l = (window.screen.availWidth - w) / 2;
window.open(
`https://captchaapi.115.com/?ac=security_code&type=web&cb=Close911_${new Date().getTime()}`,
"验证账号",
`height=${h},width=${w},top=${t},left=${l},toolbar=no,menubar=no,scrollbars=no,resizable=no,location=no,status=no`
);
};
const notify = msg => {
GM_notification({
...msg,
text: msg?.text ?? GM_info.script.name,
image: GM_getResourceURL(msg?.image ?? "info"),
onclick: msg?.clickUrl ? () => GM_openInTab(msg.clickUrl, { active: true }) : () => {},
highlight: true,
timeout: 3000,
});
};
const getDate = timestamp => {
const date = timestamp ? new Date(timestamp) : new Date();
const Y = date.getFullYear();
const M = date.getMonth() + 1 < 10 ? `0${date.getMonth() + 1}` : date.getMonth() + 1;
const D = date.getDate() < 10 ? `0${date.getDate()}` : date.getDate();
return `${Y}-${M}-${D}`;
};
const getScrollTop = () => {
let scrollTop = 0;
if (DOC.documentElement && DOC.documentElement.scrollTop) {
scrollTop = DOC.documentElement.scrollTop;
} else if (DOC.body) {
scrollTop = DOC.body.scrollTop;
}
return scrollTop;
};
const delay = ms => new Promise(resolve => setTimeout(resolve, ms));
const getItem = key => {
const details = GM_getValue("DETAILS") ?? {};
return details[key] ?? {};
};
const upItem = (key, val) => {
const details = GM_getValue("DETAILS") ?? {};
val = { ...getItem(key), ...val };
details[key] = val;
GM_setValue("DETAILS", details);
};
const transToBytes = sizeStr => {
const limit = 1024;
const step = 8;
const sizer = [
{
unit: /gb/gi,
transform: size => size * limit * limit * limit,
},
{
unit: /gib/gi,
transform: size => (size / step) * limit * limit * limit,
},
{
unit: /mb/gi,
transform: size => size * limit * limit,
},
{
unit: /mib/gi,
transform: size => (size / step) * limit * limit,
},
{
unit: /kb/gi,
transform: size => size * limit,
},
{
unit: /kib/gi,
transform: size => (size / step) * limit,
},
{
unit: /byte/gi,
transform: size => size,
},
{
unit: /bit/gi,
transform: size => size / step,
},
];
const size = sizeStr.replace(/[a-zA-Z\s]/g, "");
if (size <= 0) return 0;
return (
sizer
.find(({ unit }) => unit.test(sizeStr))
?.transform(size)
?.toFixed(2) ?? 0
);
};
const unique = (arr, key) => {
if (!arr) return arr;
if (key === undefined) return [...new Set(arr)];
const map = { string: e => e[key], function: e => key(e) };
const fn = map[typeof key];
const obj = arr.reduce((o, e) => ((o[fn(e)] = e), o), {});
return Object.values(obj);
};
const tooltip = msg => {
console.log(
`%c ${msg} %c By ${GM_info.script.name} v${GM_info.script.version} %c`,
"background:#555555;padding:1px;border-radius:3px 0 0 3px;color:#fff",
"background:#1890FF;padding:1px;border-radius:0 3px 3px 0;color:#fff",
"background:transparent"
);
};
class Common {
docStart = () => {};
contentLoaded = () => {};
load = () => {};
mPoint = 0;
pcPreview = "https://v.anxia.com/?pickcode=";
clickUrl = "http://115.com/?tab=offline&mode=wangpan";
expHei = 640;
expBtnHei = 40;
menus = [
{ title: "点击事件", key: "CK", command: "c", defaultVal: true },
{ title: "暗黑模式", key: "DM", command: "d", defaultVal: true },
{ title: "滚动加载", key: "LM", command: "s", defaultVal: true },
{
title: "离线后操作目录CID",
key: "CID",
command: "a",
cb: uniKey => {
const val = prompt("用于离线下载后移动,删除操作", GM_getValue(uniKey) ?? "");
if (!val) return;
GM_setValue(uniKey, val);
location.reload();
},
defaultVal: "",
},
];
registerMenu = (menus = this.menus) => {
for (const menu of menus) {
const { title, key, command, defaultVal } = menu;
const uniKey = `${this.constructor.name}_${key}`;
const val = GM_getValue(`${uniKey}`) ?? defaultVal;
GM_registerMenuCommand(
`${menu?.cb ? "设置" : val ? "关闭" : "开启"}${title}`,
menu?.cb
? () => menu.cb(uniKey)
: () => {
GM_setValue(uniKey, !val);
location.reload();
},
command
);
this[key] = val;
}
};
initDB = () => {
const date = getDate();
const rcdKey = `CD`;
if (GM_getValue(rcdKey) !== date) {
GM_setValue("DETAILS", {});
GM_setValue("RESOURCE", []);
GM_setValue(rcdKey, date);
}
};
customStyle = () => {
GM_addStyle(`
video, img { vertical-align: middle !important; }
.ellipsis {
overflow : hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
}
.playBtn { position: relative; }
.playBtn::after {
position: absolute;
background: url(${GM_getResourceURL("play")}) 50% no-repeat;
background-color: rgba(0,0,0,.2);
background-size: 40px;
content: "";
top: 0;
right: 0;
bottom: 0;
left: 0;
transition: all .3s ease-out;
opacity: .85;
}
.playBtn:hover::after { background-color: rgba(0,0,0,0); }
.playBtn img { filter: none !important; }
.mask {
position: fixed;
width: 100%;
height: 100%;
z-index: 9999;
left: 0;
top: 0;
background: rgba(11, 11, 11, .8);
justify-content: center;
align-items: center;
display: none;
}
.matched {
font-weight: bold;
color: rgb(10,132,255) !important;
}
.hidden { display: none !important; }
`);
};
darkStyle = () => {
const Background = "rgb(18,18,18)";
const SecondaryBackground = "rgb(32,32,32)";
const LabelColor = "rgba(255,255,255,.95)";
const SecondaryLabelColor = "rgb(170,170,170)";
const Grey = "rgb(49,49,49)";
const Blue = "rgb(10,132,255)";
const Orange = "rgb(255,159,10)";
const Green = "rgb(48,209,88)";
const Red = "rgb(255,69,58)";
GM_addStyle(`
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background-color: ${Grey};
}
*:not(span) {
border-color: ${Grey} !important;
outline: ${Grey} !important;
text-shadow: none !important;
}
body, footer {
background: ${Background} !important;
color: ${SecondaryLabelColor} !important;
}
img { filter: brightness(90%) !important; }
nav { background: ${SecondaryBackground} !important; }
input { background: ${Background} !important; }
*::placeholder { color: ${SecondaryLabelColor} !important; }
button, input, a, h1, h2, h3, h4, h5, h6, p {
color: ${LabelColor} !important;
box-shadow: none !important;
outline: none !important;
}
`);
return {
Background,
SecondaryBackground,
LabelColor,
SecondaryLabelColor,
Grey,
Blue,
Orange,
Green,
Red,
};
};
handleClick = (selectors, node = DOC) => {
for (const item of node.querySelectorAll(selectors)) {
const href = item?.href;
if (!href) continue;
item.addEventListener("contextmenu", e => e.preventDefault());
item.addEventListener("click", e => {
e.preventDefault();
GM_openInTab(href, { setParent: true, active: true });
});
item.addEventListener("mousedown", e => {
if (e.button !== 2) return;
e.preventDefault();
this.mPoint = e.screenX + e.screenY;
});
item.addEventListener("mouseup", e => {
const num = e.screenX + e.screenY - this.mPoint;
if (e.button !== 2 || num > 5 || num < -5) return;
e.preventDefault();
GM_openInTab(href, { setParent: true, active: false });
});
}
};
waterfallLayout = (selectors, itemSelectors, mParams = {}, iParams = {}) => {
const node = DOC.querySelector(selectors);
if (!node) return;
const msnry = new Masonry(node, {
itemSelector: "none",
columnWidth: ".item-sizer",
gutter: ".gutter-sizer",
horizontalOrder: true,
fitWidth: true,
stagger: 30,
visibleStyle: { transform: "translateY(0)", opacity: 1 },
hiddenStyle: { transform: "translateY(120px)", opacity: 0 },
...mParams,
});
imagesLoaded(node, () => {
msnry.options.itemSelector = itemSelectors;
const items = node.querySelectorAll(itemSelectors);
msnry.appended(items);
GM_addStyle(`${selectors} { opacity: 1; }`);
});
const infScroll = new InfiniteScroll(node, {
path: "#next",
append: itemSelectors,
outlayer: msnry,
history: false,
historyTitle: false,
hideNav: ".pagination",
debug: false,
...iParams,
});
return infScroll;
};
handleCopy = selectors => {
const node = DOC.querySelector(selectors);
if (!node) return;
const copy = node?.textContent.trim();
if (!copy) return;
const copyNode = DOC.create(
"a",
{ title: copy, "data-copy": copy, href: "", style: "margin-left:16px" },
"复制"
);
copyNode.addEventListener("click", this.copyTxt);
node.appendChild(copyNode);
};
copyTxt = e => {
e.preventDefault();
e.stopPropagation();
const { target: node } = e;
if (!node) return;
const { copy } = node.dataset;
if (!copy) return;
GM_setClipboard(copy);
const initial = node?.textContent ?? "";
node.textContent = "成功";
setTimeout(() => {
node.textContent = initial;
}, 600);
};
fetchTranslate = async sentence => {
const data = {
async: `translate,sl:auto,tl:zh-CN,st:${encodeURIComponent(
sentence.trim()
)},id:1642125176704,qc:true,ac:false,_id:tw-async-translate,_pms:s,_fmt:pc`,
};
sentence = await request(
"https://www.google.com/async/translate?vet=12ahUKEwi03Jv2kLD1AhWRI0QIHe_TDKAQqDh6BAgCECY..i&ei=ZtfgYbSRO5HHkPIP76ezgAo&yv=3",
data,
"POST",
{ headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" } }
);
return sentence?.querySelector("#tw-answ-target-text").textContent ?? "";
};
// search prefix match list
fetchMatch = async code => {
let res = getItem(code)?.resource;
if (!res) {
const resource = GM_getValue("RESOURCE") ?? [];
const prefix = code.split(/(-|_)/g)[0];
let item = resource.find(item => item.prefix === prefix);
if (!item) {
item = { prefix, res: await this.fetchSearch(prefix) };
resource.push(item);
GM_setValue("RESOURCE", resource);
}
res = await this.fetchResource(code, item.res);
}
return res;
};
// 115 files search
fetchSearch = async search_value => {
const res = await request(
"https://webapi.115.com/files/search",
{
search_value,
offset: 0,
limit: 10000,
date: "",
aid: 1,
cid: 0,
pick_code: "",
type: 4,
source: "",
format: "json",
o: "user_ptime",
asc: 0,
star: "",
suffix: "",
},
"GET",
{ responseType: "json" }
);
return (res?.data ?? []).map(({ fid, cid, n, pc, t, te, tp, play_long }) => {
return { fid, cid, n, pc, t, te, tp, play_long };
});
};
// 115 files search result match
fetchResource = async (code = "", res) => {
if (!res) res = await this.fetchSearch(code.split(/(-|_)/g)[0]);
if (res?.length) {
const codes = unique([
code,
code.replace(/-/g, ""),
code.replace(/-/g, "."),
code.replace(/-0/g, "."),
code.replace(/-/g, "-0"),
code.replace(/-/g, "0"),
code.replace(/-/g, "00"),
code.replace(/-/g, "_"),
code.replace(/-/g, "_0"),
code.replace(/-0/g, ""),
code.replace(/-0/g, "-"),
code.replace(/-0/g, "00"),
]);
const reg = createReg(codes);
res = res
.filter(({ n, play_long }) => n.match(reg) && play_long > 0)
.map(({ n: name, pc: pickCode, fid, cid, te, tp, t: date }) => {
return { name, pickCode, fid, cid, timestamp: Math.max(te, tp), date };
});
}
return res;
};
fetchStar = async code => {
const site = "https://javdb.com";
let res = await request(`${site}/search?q=${code}`);
const href = res?.querySelector("#videos .grid-item a").getAttribute("href");
if (!href) return;
res = await request(`${site}${href}`);
res = res?.querySelectorAll(".panel-block");
if (!res?.length) return;
res = res[res.length - 3]?.querySelector(".value").textContent.trim();
return res
.split(/\n/)
.filter(item => item.indexOf("♀") !== -1)
.map(item => item.replace("♀", "").trim());
};
fetchImage = async (code, date) => {
date = date.split("-");
const jb = `http://img.japanese-bukkake.net/${date[0]}/${date[1]}/${code}_s.jpg`;
const js = `https://javstore.net/search/${code}.html`;
let [jbRes, jsRes] = await Promise.all([request(jb), request(js)]);
const href = jsRes?.querySelector("#content_news li a")?.href;
if (href) {
jsRes = await request(href);
jsRes = jsRes?.querySelector(".news a img[alt*='.th']").src.replace(".th", "");
if (jsRes && (await request(jsRes))) return jsRes;
}
if (typeof jbRes === "object") return jb;
};
fetchVideo = async ({ code, studio }) => {
code = code.toLowerCase();
if (studio) {
const fetchVideoByStudio = [
{ name: "東京熱", match: "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4" },
{ name: "カリビアンコム", match: "https://smovie.caribbeancom.com/sample/movies/%s/1080p.mp4" },
{ name: "一本道", match: "http://smovie.1pondo.tv/sample/movies/%s/1080p.mp4" },
{
name: "HEYZO",
transform: code => code.replace(/HEYZO\-/gi, ""),
match: "https://www.heyzo.com/contents/3000/%s/heyzo_hd_%s_sample.mp4",
},
];
const matched = fetchVideoByStudio.find(({ name }) => name === studio);
if (matched) return matched.match.replace(/%s/g, matched.transform ? matched.transform(code) : code);
}
const [r18, xrmoo] = await Promise.all([
request(`https://www.r18.com/common/search/searchword=${code}/`),
request(`http://dmm.xrmoo.com/sindex.php?searchstr=${code}`),
]);
return (
r18?.querySelector("a.js-view-sample")?.getAttribute("data-video-high") ||
xrmoo
?.querySelector(".card .card-footer a.viewVideo")
?.getAttribute("data-link")
.replace("_sm_w", "_dmb_w") ||
""
);
};
fetchMagnet = async code => {
const matchList = [
{
site: "Sukebei",
host: "https://sukebei.nyaa.si/",
search: "?f=0&c=0_0&q=%s",
selectors: ".table-responsive table tbody tr",
filter: {
name: e => e?.querySelector("td:nth-child(2) a").textContent,
link: e => e?.querySelector("td:nth-child(3) a:last-child").href,
size: e => e?.querySelector("td:nth-child(4)").textContent,
date: e => e?.querySelector("td:nth-child(5)").textContent.split(" ")[0],
href: e => e?.querySelector("td:nth-child(2) a").getAttribute("href"),
},
styleClass: "primary",
},
{
site: "BTGG",
host: "https://www.btgg.cc/",
search: "search?q=%s",
selectors: ".el-main .list .item",
filter: {
name: e => e?.querySelector(".name a").textContent,
link: e => e?.querySelector(".meta a").href,
size: e => e?.querySelector(".meta span").textContent.split(",")[0],
date: e => e?.querySelector(".meta span:nth-child(4)").textContent.split(",")[0],
href: e => e?.querySelector(".name a").getAttribute("href"),
},
styleClass: "success",
},
{
site: "BTSOW",
host: "https://btsow.rest/",
search: "search/%s",
selectors: ".data-list .row:not(.hidden-xs)",
filter: {
name: e => e?.querySelector(".file").textContent,
link: e => `magnet:?xt=urn:btih:${e?.querySelector("a").href.split("/").pop()}`,
size: e => e?.querySelector(".size").textContent,
date: e => e?.querySelector(".date").textContent,
href: e => e?.querySelector("a").getAttribute("href"),
},
styleClass: "danger",
},
];
const matched = await Promise.all(
matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
);
const magnets = [];
for (let index = 0; index < matchList.length; index++) {
let node = matched[index];
if (!node) continue;
const { selectors, site, styleClass, filter, host } = matchList[index];
node = node?.querySelectorAll(selectors);
if (!node || !node?.length) continue;
for (const item of node) {
const magnet = { from: site, styleClass };
Object.keys(filter).map(key => {
magnet[key] = filter[key](item).trim();
});
if (!("zh" in magnet)) magnet.zh = /中文/g.test(magnet.name);
magnet.link = magnet.link.split("&")[0];
const href = magnet?.href ?? "";
if (href && !href.includes("//")) magnet.href = `${host}${href.replace(/^\//, "")}`;
magnets.push(magnet);
}
}
return magnets;
};
fetchPlayer = async code => {
const codeReg = new RegExp(code, "gi");
const matchList = [
{
site: "Netflav",
host: "https://netflav.com/",
search: "search?type=title&keyword=%s",
selectors: ".grid_root .grid_cell",
filter: { name: e => e?.querySelector(".grid_title").textContent },
},
{
site: "BestJavPorn",
host: "https://www2.bestjavporn.com/",
search: "search/%s/",
selectors: "#main article",
filter: {
name: e => e?.querySelector("a").title,
zh: e => /中文/g.test(e?.querySelector(".hd-video")?.textContent ?? ""),
},
},
{
site: "JavHHH",
host: "https://javhhh.com/",
search: "v/?wd=%s",
selectors: "#wrapper .typelist .i-container",
filter: { name: e => e?.querySelector("img.img-responsive").title },
},
{
site: "Avgle",
host: "https://avgle.com/",
search: "search/videos?search_query=%s&search_type=videos",
selectors: ".row .well.well-sm",
filter: { name: e => e?.querySelector(".video-title")?.textContent },
},
];
const matched = await Promise.all(
matchList.map(({ host, search }) => request(`${host}${search.replace(/%s/g, code)}`))
);
const playList = [];
for (let index = 0; index < matchList.length; index++) {
let node = matched[index];
if (!node) continue;
const { selectors, site, filter, host } = matchList[index];
node = node?.querySelectorAll(selectors);
if (!node || !node?.length) continue;
for (const item of node) {
const player = { from: site };
for (const key of Object.keys(filter)) player[key] = filter[key](item);
const name = player?.name ?? "";
if (!name || !name.match(codeReg)?.length) continue;
if (!("zh" in player)) player.zh = /中文/g.test(name);
if (!player?.link) player.link = item?.querySelector("a").getAttribute("href");
const link = player?.link ?? "";
if (link && !link.includes("//")) player.link = `${host}${link.replace(/^\//, "")}`;
playList.push(player);
}
}
return playList;
};
fetchSign = async () => {
const res = await request("http://115.com/", { ct: "offline", ac: "space", _: new Date().getTime() });
if (res?.sign) return { sign: res.sign, time: res.time };
notify({
title: "请求失败,115未登录",
text: "请登录115账户后再离线下载",
image: "fail",
clickUrl: "http://115.com/?mode=login",
});
};
fetchOffLine = async url => {
let res = await this.fetchSign();
if (!res) return;
return await request(
"http://115.com/web/lixian/?ct=lixian&ac=add_task_url",
{ url, uid: 0, ...res },
"POST",
{ headers: { "Content-Type": "application/x-www-form-urlencoded" } }
);
};
fetchRename = async res => {
const data = {};
for (const { fid, file_name } of res) data[`files_new_name[${fid}]`] = file_name;
return await request("https://webapi.115.com/files/batch_rename", data, "POST", {
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
responseType: "json",
});
};
fetchMove = async res => {
const data = { pid: this.CID, move_proid: `${new Date()}_${~(100 * Math.random())}_0` };
for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index].fid;
return await request("https://webapi.115.com/files/move", data, "POST", {
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
responseType: "json",
});
};
fetchDelDir = async res => {
const data = { pid: this.CID, ignore_warn: 1 };
res = unique(res.map(({ cid }) => cid).filter(item => item !== this.CID));
for (let index = 0; index < res.length; index++) data[`fid[${index}]`] = res[index];
return await request("https://webapi.115.com/rb/delete", data, "POST", {
headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
responseType: "json",
});
};
searchOnKey = selectors => {
DOC.addEventListener("DOMContentLoaded", () => {
DOC.addEventListener("keyup", event => {
const e = event || window.event;
if (e && e.keyCode === 191 && !["INPUT", "TEXTAREA"].includes(DOC.activeElement.nodeName)) {
DOC.querySelector(selectors).focus();
}
});
});
};
handlePreviewFold = selectors => {
GM_addStyle(`
${selectors} {
overflow: hidden;
height: fit-content;
max-height: ${this.expHei}px;
}
#expBtn {
top: 0;
left: 0;
right: 0;
color: #fff;
z-index: 9;
cursor: pointer;
position: absolute;
background: rgba(0,0,0,.7);
height: ${this.expBtnHei}px;
line-height: ${this.expBtnHei}px;
margin-top: ${this.expHei - this.expBtnHei}px;
}
#expBtn::after { content: "▼ 展开"; }
#exp:checked + ${selectors} > #expBtn::after { content: "▲ 收起"; }
#exp:checked + ${selectors} { max-height: none; }
#exp:checked + ${selectors} > #expBtn {
top: auto;
bottom: 0;
margin-bottom: 0;
}
`);
const modifyMaxHei = () => {
const expHei = this.expHei;
const img = DOC.querySelector(`${selectors} img`);
const styleHei = img.height || img.clientHeight || img.offsetHeight;
if (styleHei > expHei) {
GM_addStyle(`
${selectors} { max-height: ${expHei}px; }
#expBtn { margin-top: ${expHei - this.expBtnHei}px; }
`);
} else {
GM_addStyle(`
${selectors} { max-height: ${styleHei + this.expBtnHei}px; }
#expBtn { margin-top: ${styleHei}px; }
`);
}
};
const box = DOC.querySelector(selectors);
const img = box.querySelector("img");
if (img.complete) modifyMaxHei();
img.onload = () => modifyMaxHei();
box.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`);
box.insertAdjacentHTML("beforeend", `<label for="exp" id="expBtn"></label>`);
};
}
class JavBus extends Common {
constructor() {
super();
this.start();
const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname));
return { ...this, ...this[tag] };
}
routeReg = {
waterfall:
/^\/((uncensored|uncensored\/)?(page\/\d+)?$)|((uncensored\/)?((search|searchstar|actresses|genre|star|studio|label|series|director|member)+\/)|actresses(\/\d+)?)+/i,
genre: /^\/(uncensored\/)?genre$/i,
forum: /^\/forum\//i,
details: /^\/[\w]+(-|_)?[\d]*.*$/i,
};
start = () => {
this.registerMenu();
this.initDB();
this.customStyle();
this.customMode();
this.searchOnKey("#search-input");
};
customMode = () => {
GM_addStyle(`.ad-box { display: none !important; }`);
};
darkMode = dStyle => {
const { Grey, LabelColor, Blue, Green, Red, Orange, Background, SecondaryBackground } = dStyle;
GM_addStyle(`
.btn.disabled, .btn[disabled], fieldset[disabled] .btn { opacity: .85 !important; }
button, .btn-default, .input-group-addon {
background: ${Grey} !important;
color: ${LabelColor} !important;
}
.btn-primary {
background: ${Blue} !important;
border-color: ${Blue} !important;
}
.btn-success {
background: ${Green} !important;
border-color: ${Green} !important;
}
.btn-danger {
background: ${Red} !important;
border-color: ${Red} !important;
}
.btn-warning {
background: ${Orange} !important;
border-color: ${Orange} !important;
}
.navbar-nav>.active>a, .navbar-nav>.active>a:focus, .navbar-nav>.active>a:hover, .navbar-nav>.open>a, .dropdown-menu { background: ${Background} !important; }
.dropdown-menu>li>a:focus, .dropdown-menu>li>a:hover { background: ${Grey} !important; }
.pagination .active a {
border: none !important;
color: ${LabelColor} !important;
}
.pagination>li>a, .pagination>li>span {
background-color: ${Grey} !important;
border: none !important;
color: ${LabelColor} !important;
}
tr, .modal-content, .alert {
background: ${SecondaryBackground} !important;
box-shadow: none !important;
}
tr:hover { background: ${Grey} !important; }
`);
};
waterfall = {
docStart() {
GM_addStyle(`
.search-header {
padding: 0 !important;
background: none !important;
box-shadow: none !important;
}
.photo-frame {
position: relative;
margin: 10px !important;
}
.photo-frame img {
height: 100% !important;
width: 100% !important;
object-fit: cover !important;
margin: 0 !important;
}
.photo-info { padding: 10px !important; }
.alert-page { margin: 20px !important; }
`);
if (this.LM) {
const itemSizer = `167px`;
const gutterSizer = `20px`;
GM_addStyle(`
.pagination, footer { display: none !important; }
.page-load-status {
display: none;
padding-bottom: ${gutterSizer};
text-align: center;
}
body { overflow: hidden; }
.scrollBox {
height: calc(100vh - 51px);
overflow: hidden scroll;
}
#waterfall {
opacity: 0;
margin: ${gutterSizer} auto 0 auto !important;
}
.item-sizer, .item a { width: ${itemSizer} !important; }
.gutter-sizer { width: ${gutterSizer} !important; }
.item a { margin: 0 0 ${gutterSizer} 0 !important; }
`);
}
if (this.DM) {
const dStyle = this.darkStyle();
this.darkMode(dStyle);
const { SecondaryBackground, LabelColor, SecondaryLabelColor, Grey } = dStyle;
GM_addStyle(`
.item a { background: ${SecondaryBackground} !important; }
.photo-info {
background: ${SecondaryBackground} !important;
color: ${LabelColor} !important;
}
date { color: ${SecondaryLabelColor} !important; }
.nav-pills>li.active>a, .nav-pills>li.active>a:focus, .nav-pills>li.active>a:hover, .nav-pills>li>a:focus, .nav-pills>li>a:hover { background-color: ${Grey} !important; }
`);
}
},
contentLoaded() {
const nav = DOC.querySelector(".search-header .nav");
if (nav) nav.setAttribute("class", "nav nav-pills");
this.modifyItem();
if (!this.LM) return;
this.modifyLayout();
this.handleWaterfall();
},
async modifyItem(node = DOC) {
if (this.CK) this.handleClick(".item a", node);
const items = node.querySelectorAll(".item");
for (const item of items) {
const info = item.querySelector("a .photo-info span:not(.mleft)");
if (!info) continue;
const [titleNode, secondaryNode] = info.childNodes;
const titleTxt = titleNode.textContent.trim();
const title = DOC.create("div", { class: "title", title: titleTxt }, titleTxt);
if (this.LM) title.classList.add("ellipsis");
if (secondaryNode?.nodeName === "BR") info.removeChild(secondaryNode);
info.replaceChild(title, titleNode);
}
for (const item of items) {
let code = item.querySelector("date");
if (!code) continue;
code = code.textContent.trim().toUpperCase();
const resource = await this.fetchMatch(code);
if (!resource?.length) continue;
item.querySelector(".title").classList.add("matched");
const photo = item.querySelector(".photo-frame");
photo.classList.add("playBtn");
photo.setAttribute("title", "点击播放");
photo.addEventListener("click", e => {
e.stopPropagation();
e.preventDefault();
GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true });
});
}
},
modifyLayout() {
const oldWaterfall = DOC.querySelector("#waterfall");
if (!oldWaterfall) return;
const newWaterfall = DOC.querySelector("#waterfall #waterfall");
if (newWaterfall) oldWaterfall.parentNode.replaceChild(newWaterfall, oldWaterfall);
const waterfall = DOC.querySelector("#waterfall");
waterfall.insertAdjacentHTML(
"afterbegin",
`<div class="item-sizer"></div><div class="gutter-sizer"></div>`
);
waterfall.insertAdjacentHTML(
"afterend",
`<div class="page-load-status"><span class="loader-ellips infinite-scroll-request">Loading...</span><span class="infinite-scroll-last">End of content</span><span class="infinite-scroll-error">No more pages to load</span></div>`
);
DOC.querySelector(".container-fluid .row")?.classList.add("scrollBox");
},
handleWaterfall() {
const infScroll = this.waterfallLayout(
"#waterfall",
".item",
{},
{
path: !/^\/(uncensored\/)?(search|searchstar)+\//i.test(location.pathname)
? "#next"
: function () {
const { pathname } = location;
const items = ["search", "searchstar"];
for (const item of items) {
if (pathname.indexOf(`${item}/`) < 0) continue;
let [prefix, suffix] = pathname.split("&");
suffix = suffix ?? "";
prefix = prefix.split("/");
let pre = "";
for (let index = 0; index <= prefix.indexOf(item) + 1; index++) {
pre = `${pre}${prefix[index]}/`;
}
return `${pre}${this.loadCount + 2}&${suffix}`;
}
},
elementScroll: ".scrollBox",
status: ".page-load-status",
}
);
infScroll?.on("load", e => this.modifyItem(e));
},
};
genre = {
docStart() {
GM_addStyle(`
footer { display: none !important; }
button.btn.btn-danger.btn-block.btn-genre {
position: fixed !important;
bottom: 0 !important;
margin: 0 !important;
left: 0 !important;
border: 0 !important;
border-radius: 0 !important;
}
`);
if (this.DM) {
const dStyle = this.darkStyle();
this.darkMode(dStyle);
const { SecondaryBackground } = dStyle;
GM_addStyle(`.genre-box { background-color: ${SecondaryBackground} !important; }`);
}
},
contentLoaded() {
if (!DOC.querySelector("button.btn.btn-danger.btn-block.btn-genre")) return;
const box = DOC.querySelectorAll(".genre-box");
box[box.length - 1].setAttribute("style", "margin-bottom:65px");
},
};
forum = {
docStart() {
GM_addStyle(`
.bcpic, .banner728, .sd.sd_allbox > div:last-child { display: none !important; }
.jav-button { margin-top: -3px !important; }
#toptb {
position: fixed !important;
top: 0 !important;
left: 0 !important;
right: 0 !important;
z-index: 999 !important;
}
#wp { margin-top: 55px !important; }
`);
},
};
details = {
docStart() {
GM_addStyle(`
.info .glyphicon-info-sign,
h4[style="position:relative"],
h4[style="position:relative"] + .row { display: none !important; }
.info ul { margin: 0 !important; }
#avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px !important; }
.photo-info { height: auto !important; }
#resBox a { color: #CC0000 !important; }
#smartOff { width: 100%; }
#expBtn { margin-left: 15px; margin-right: 15px; }
`);
GM_addStyle(`
#collapseBtn::after { content: "▼ 展开表格"; }
#collapse:checked + .movie { max-height: none; }
#collapse:checked + .movie #collapseBtn::after { content: "▲ 收起表格"; }
`);
if (this.DM) {
const dStyle = this.darkStyle();
this.darkMode(dStyle);
const { SecondaryBackground, LabelColor, Grey } = dStyle;
GM_addStyle(`
.movie, .sample-box, .movie-box, .photo-info { background: ${SecondaryBackground} !important; }
.photo-info { color: ${LabelColor} !important; }
.avatar-box, .avatar-box span, .info ul li, .info .star-name {
background: ${SecondaryBackground} !important;
border-color: ${Grey} !important;
color: ${LabelColor} !important;
}
`);
}
},
contentLoaded() {
if (this.CK) {
this.handleClick("a.movie-box");
this.handleClick("a.avatar-box");
}
// insert copy
this.handleCopy("h3");
this.handleCopy("span[style='color:#CC0000;']");
// expBtn
this.handlePreviewFold(".screencap");
const info = DOC.querySelector(".info");
// resource, player
info.insertAdjacentHTML(
"beforeend",
`<p id="playerBox"><span class="header">在线播放:</span><span class="genre">查询中...</span></p>
<p id="resBox"><span class="header">网盘资源:</span><span class="genre">查询中...</span></p>`
);
// smart offLine
const smartRes = DOC.create("button", { class: "btn btn-default", id: "smartOff" }, "一键离线");
smartRes.addEventListener("click", e => this.handleOffLine(e, "smart"));
info.insertAdjacentElement("beforeend", smartRes);
// table
DOC.querySelector("#magnet-table tbody tr").insertAdjacentHTML(
"beforeend",
`<td style="text-align:center;white-space:nowrap">操作</td>`
);
// ellipsis
for (const item of DOC.querySelectorAll(".photo-info span")) item.classList.add("ellipsis");
// handleFetch
const params = this.getParams();
if (!params) return;
this.handleTransTitle(params);
this.handleResource(params);
this.handleStar(params);
this.handleImage(params);
this.handleVideo(params);
this.handlePlayer(params);
const magnetObs = new MutationObserver(() => this.handleMagnet(params));
magnetObs.observe(DOC.querySelector("#movie-loading"), {
attributes: true,
attributeFilter: ["style"],
});
const tableObs = new MutationObserver(mutationsList => {
this.modifyTable();
mutationsList.forEach(({ addedNodes }) => {
if (addedNodes) {
for (const node of addedNodes) {
if (node.nodeName === "TR") this.modifyTR(node);
if (node.nodeName === "TBODY") {
const trs = node?.querySelectorAll("tr");
if (trs.length) for (const tr of trs) this.modifyTR(tr);
}
}
}
});
});
tableObs.observe(DOC.querySelector("#magnet-table"), { childList: true });
},
getParams() {
// regex
const charReg = /[\u4e00-\u9fa5:]/g;
const studioReg = /製作商:/g;
const starReg = /暫無出演者資訊/g;
// dom
const info = DOC.querySelector(".info");
const infos = info.querySelectorAll("p");
// params
let [code, date] = infos;
if (!code || !date) return;
code = code.textContent.replace(charReg, "").trim().toUpperCase();
date = date.textContent.replace(charReg, "").trim().toUpperCase();
let studio = "";
for (const { textContent: text } of infos) {
if (!studioReg.test(text)) continue;
studio = text.replace(studioReg, "").trim();
break;
}
return {
code,
date,
studio,
star: !starReg.test(info.textContent),
title: DOC.querySelector("h3").textContent.replace("复制", "").trim(),
res: getItem(code),
};
},
modifyTR(node) {
const href = node?.querySelector("td a")?.href;
if (!href) return;
const td = DOC.create("td", { style: "text-align:center;white-space:nowrap" });
const copy = DOC.create(
"a",
{ href, "data-copy": href, title: "复制磁力链接", style: "margin-right:16px" },
"复制"
);
const offline = DOC.create("a", { href, title: "仅添加离线任务" }, "离线下载");
copy.addEventListener("click", this.copyTxt);
offline.addEventListener("click", e => this.handleOffLine(e, href));
td.appendChild(copy);
td.appendChild(offline);
node.appendChild(td);
},
modifyTable() {
if (DOC.querySelector("#collapse")) return;
const trs = DOC.querySelectorAll("#magnet-table tr");
const limit = 7;
if (trs?.length <= limit) return;
let maxHeight = 12;
for (let index = 0; index < limit; index++) {
const tr = trs[index];
maxHeight += tr.offsetHeight;
if (index) continue;
const td = tr.querySelector("td:last-child");
td.textContent = "";
td.insertAdjacentHTML(
"beforeend",
`<label
for="collapse"
id="collapseBtn"
class="btn btn-mini-new btn-primary"
title="点击展开/收起表格"
>
</label>`
);
}
DOC.querySelector("#magneturlpost + .movie").insertAdjacentHTML(
"beforebegin",
`<input type="checkbox" id="collapse" class="hidden">`
);
GM_addStyle(`#collapse + .movie { max-height: ${maxHeight}px; }`);
},
async handleTransTitle({ code, title, res }) {
let transTitle = res?.transTitle ?? "";
if (!transTitle) {
transTitle = await this.fetchTranslate(title);
if (!transTitle) return;
upItem(code, { transTitle });
}
DOC.querySelector("h3").setAttribute("title", transTitle);
},
async handleResource({ code }) {
const resource = await this.fetchResource(code);
upItem(code, { resource });
const resBox = DOC.querySelector("#resBox");
for (const old of resBox.querySelectorAll(".genre")) resBox.removeChild(old);
if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="genre">无</span>`);
resBox.insertAdjacentHTML(
"beforeend",
resource.reduce(
(acc, { name, pickCode, date }) => `
${acc}
<span class="genre">
<a href="${this.pcPreview}${pickCode}" title="${date}/${name}" target="_blank">
${name.length > 20 ? `${name.substr(0, 20)}...` : name}
</a>
</span>`,
""
)
);
return resource;
},
async handleStar({ code, star, res }) {
if (star) return;
star = res?.star ?? [];
if (!star?.length) {
star = await this.fetchStar(code);
if (!star?.length) return;
upItem(code, { star });
}
const p = DOC.create("p");
p.insertAdjacentHTML(
"beforeend",
star.reduce(
(acc, cur) => `${acc}<span class="genre"><a href="/search/${cur}">${cur}</a></span>`,
""
)
);
DOC.querySelector(".info").replaceChild(
p,
DOC.querySelector("span.glyphicon.glyphicon-info-sign.mb20")?.nextSibling
);
},
async handleImage({ code, res, date }) {
let image = res?.image ?? "";
if (!image) {
image = await this.fetchImage(code, date);
if (!image) return;
upItem(code, { image });
}
const screencap = DOC.querySelector(".screencap");
const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") });
screencap.appendChild(placeholder);
const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
img.addEventListener("click", () => {
if (getScrollTop() >= this.expHei) window.scrollTo(0, 0);
DOC.querySelector("#exp").checked = false;
});
img.onload = () => screencap.replaceChild(img, placeholder);
},
async handleVideo({ code, studio, res }) {
let video = res?.video ?? "";
if (!video) {
video = await this.fetchVideo({ code, studio });
if (!video) return;
upItem(code, { video });
}
// func
const title = "预览视频";
const playVideo = e => {
e.preventDefault();
e.stopPropagation();
DOC.querySelector("#video-mask").setAttribute("style", "display:flex");
const video = DOC.querySelector("video");
video.play();
video.focus();
DOC.onkeydown = event => {
const e = event || window.event;
if (e && e.keyCode === 27) pauseVideo();
};
};
const pauseVideo = () => {
DOC.querySelector("#video-mask").setAttribute("style", "display:none");
DOC.querySelector("video").pause();
DOC.onkeydown = null;
};
// mask
const videoNode = DOC.create("video", { controls: "controls", src: video, width: 720 });
videoNode.currentTime = 3;
videoNode.preload = "auto";
videoNode.muted = true;
const closeBtn = DOC.create(
"button",
{ title: "Close (Esc)", type: "button", class: "mfp-close" },
"×"
);
closeBtn.addEventListener("click", pauseVideo);
const mask = DOC.create("div", { id: "video-mask", class: "mask" });
mask.appendChild(closeBtn);
mask.appendChild(videoNode);
DOC.body.appendChild(mask);
// screencap
const bigImage = DOC.querySelector(".bigImage");
const playBtn = DOC.create("div", { class: "playBtn", title });
const bImg = bigImage.querySelector("img");
playBtn.addEventListener("click", playVideo);
playBtn.appendChild(bImg);
bigImage.appendChild(playBtn);
// sample-waterfall
const thumb = bImg.src;
const box = DOC.create("a", { class: "sample-box", href: thumb, title });
box.addEventListener("click", playVideo);
box.insertAdjacentHTML("beforeend", `<div class="photo-frame playBtn"><img src="${thumb}"></div>`);
let waterfall = DOC.querySelector("#sample-waterfall");
if (!waterfall) {
DOC.querySelector("div.clearfix").insertAdjacentHTML(
"beforebegin",
`<div id="sample-waterfall"></div>`
);
waterfall = DOC.querySelector("#sample-waterfall");
waterfall.insertAdjacentHTML("beforebegin", `<h4>樣品圖像</h4>`);
return waterfall.appendChild(box);
}
// jump to preview
const ref = waterfall.querySelector("a");
waterfall.insertBefore(box, ref);
const imgBtn = DOC.create(
"button",
{ title: "樣品圖像", type: "button", class: "mfp-close", style: "right:44px" },
"📷"
);
imgBtn.addEventListener("click", () => {
pauseVideo();
ref.click();
});
mask.appendChild(imgBtn);
},
async handlePlayer({ code, res }) {
let player = res?.player ?? [];
if (!player?.length) {
player = await this.fetchPlayer(code);
if (player?.length) upItem(code, { player });
}
const box = DOC.querySelector("#playerBox");
const genre = box.querySelector(".genre");
if (!player?.length) return (genre.textContent = "无");
genre.remove();
box.insertAdjacentHTML(
"beforeend",
player.reduce(
(pre, { zh, link, name, from }) => `
${pre}
<button
class="btn btn-mini-new ${zh ? "btn-warning" : "btn-primary"}"
data-link="${link}"
title="${name}"
>
<strong>▶ ${from}</strong>
</button>
`,
""
)
);
for (const btn of DOC.querySelectorAll("#playerBox button")) {
btn.addEventListener("click", () => GM_openInTab(btn.dataset.link, { active: true }));
}
},
async handleMagnet({ code, res }) {
let magnet = res?.magnet ?? [];
if (!magnet?.length) {
magnet = await this.fetchMagnet(code);
if (!magnet?.length) return;
upItem(code, { magnet });
}
const tdStyle = "text-align:center;white-space:nowrap";
magnet = magnet.reduce(
(pre, { link, name, styleClass, href, from, zh, size, date }) => `
${pre}
<tr>
<td>
<a href="${link}">${name}</a>
<a
class="btn btn-mini-new btn-${styleClass}"
href="${href}"
target="_blank"
title="磁链详情页"
>
<strong>★ ${from}</strong>
</a>
${zh ? `<a class="btn btn-mini-new btn-warning disabled">字幕</a>` : ""}
</td>
<td style="${tdStyle}">${size}</td>
<td style="${tdStyle}">${date}</td>
</tr>
`,
`<tr style="color:#CC0000"><td colspan="4">以下磁链来自外部</td></tr>`
);
DOC.querySelector("#magnet-table").insertAdjacentHTML("beforeend", magnet);
},
async handleOffLine(e, link) {
e.preventDefault();
e.stopPropagation();
if (!link) return;
const node = e.target;
const text = node?.textContent ?? "";
node.textContent = "请求中...";
node.setAttribute("disabled", true);
node.setAttribute("style", "pointer-events:none");
const clickUrl = this.clickUrl;
if (link !== "smart") {
let res = await this.fetchOffLine(link);
const { state = false, error_msg = "" } = res;
notify({
title: `请求${state ? "成功" : "失败"}`,
text: `${error_msg ? error_msg : "仅添加离线任务"}`,
image: `${state ? "info" : "fail"}`,
clickUrl,
});
} else {
let links = [];
const trs = DOC.querySelectorAll("#magnet-table tr");
for (let index = 1; index < trs.length; index++) {
let [link, size, date] = trs[index]?.querySelectorAll("td");
if (!link || !size || !date) continue;
links.push({
link: link.querySelector("a").href.split("&")[0],
zh: !!link.querySelector("a.btn.btn-mini-new.btn-warning.disabled"),
size: transToBytes(size.textContent.trim()),
date: date.textContent.trim(),
});
}
if (links.length) {
if (links.length > 1) links = unique(links, "link");
if (links.length > 1) {
links.sort((pre, next) => {
if (pre.zh === next.zh) {
if (pre.size === next.size) return next.date - pre.date;
return next.size - pre.size;
} else {
return pre.zh > next.zh ? -1 : 1;
}
});
}
tooltip("磁链列表");
console.table(links);
const params = this.getParams();
for (let index = 0; index < links.length; index++) {
const { link, zh } = links[index];
let res = await this.fetchOffLine(link);
if (!res || res?.errcode === 911) break;
if (res?.state) {
res = await this.checkResource(params);
if (res?.length) {
this.afterAction({ ...params, zh, res });
notify({
title: "一键离线任务成功",
text: "进行后续操作",
image: "success",
clickUrl,
});
break;
}
}
if (index !== links.length - 1) continue;
notify({ title: "一键离线任务失败", image: "fail", clickUrl });
}
}
}
node.textContent = text;
node.removeAttribute("disabled");
node.removeAttribute("style");
},
async checkResource({ code }) {
const old = getItem(code)?.resource ?? [];
await delay(2500);
const res = await this.handleResource({ code });
if (Array.isArray(res) && res?.length) {
const today = res.filter(({ date }) => date === getDate());
if (today.length && res.length > old.length) return today;
}
return false;
},
async afterAction({ zh, res, title, code }) {
res = res.filter(({ name }) => name.indexOf(title) === -1);
if (!res.length) return;
title = `${zh ? "【中文字幕】" : ""}${title}`;
res = res.map(item => {
const suffix = item.name.split(".").pop().toLowerCase();
return { ...item, file_name: `${title}.${suffix}` };
});
await this.fetchRename(res);
if (this.CID) {
const moveRes = await this.fetchMove(res);
if (moveRes?.state) this.fetchDelDir(res);
}
this.handleResource({ code });
},
};
}
class JavDB extends Common {
constructor() {
super();
this.start();
const tag = Object.keys(this.routeReg).find(key => this.routeReg[key].test(location.pathname));
return { ...this, ...this[tag] };
}
routeReg = {
waterfall:
/^\/$|^\/(censored|uncensored|western|fc2|anime|search|video_codes|tags|rankings|actors|series|makers|directors|publishers)/i,
details: /^\/v\//i,
};
start = () => {
this.registerMenu(this.menus.filter(item => !["DM"].includes(item.key)));
this.initDB();
this.customStyle();
this.customMode();
this.searchOnKey("#video-search");
};
customMode = () => {
GM_addStyle(`.message-body .moj-content { display: none !important; }`);
};
waterfall = {
container: {},
containerList: [
{
selectors: "#videos .columns",
itemSelectors: "#videos .columns .column",
item: "#videos .columns .column a",
itemSizer: "160px",
},
{
selectors: "#actors",
itemSelectors: "#actors .box",
item: "#actors .box",
itemSizer: "130px",
},
],
docStart() {
GM_addStyle(`
#tags { display: none; }
#expBtn::after { content: "展开"; }
#exp:checked + .tabs.is-boxed + #tags { display: block; }
#exp:checked + .tabs.is-boxed > #expBtn::after { content: "收起"; }
`);
if (this.LM) {
const gutterSizer = `20px`;
GM_addStyle(`
nav.pagination, nav#footer { display: none; }
.gutter-sizer { width: ${gutterSizer} !important; }
#videos .columns .column img {
height: 200px !important;
object-fit: cover;
}
${this.containerList.map(item => item.selectors).join(", ")} { opacity: 0; }
${this.containerList.reduce(
(pre, { itemSelectors, item, itemSizer }) => `
${pre}
${itemSelectors} {
padding: 0 !important;
margin: 0 0 ${gutterSizer} 0 !important;
width: ${itemSizer} !important;
}
${item} { width: ${itemSizer} !important; }
`,
""
)}
`);
}
},
contentLoaded() {
this.modifyItem();
if (DOC.querySelector("#tags")) {
const tabs = DOC.querySelector(".tabs.is-boxed");
tabs.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp" class="hidden">`);
tabs.insertAdjacentHTML(
"beforeend",
`<label class="button is-link" for="exp" id="expBtn"></label>`
);
}
if (!this.LM) return;
this.container = this.containerList.find(({ selectors }) => DOC.querySelector(selectors));
if (location.pathname === "/rankings/fanza_award" || !this.container) {
return GM_addStyle(`nav.pagination, nav#footer { display: flex; }`);
}
GM_addStyle(`
.item-sizer { width: ${this.container.itemSizer} !important; }
${this.container.selectors} { margin: 0 auto !important; }
`);
this.modifyLayout();
this.handleWaterfall();
},
async modifyItem(node = DOC) {
if (this.CK) {
const extra = [
{ itemSelectors: "#series .columns .column" },
{ itemSelectors: "#makers .columns .column" },
{ itemSelectors: "#directors .columns .column" },
];
for (const { itemSelectors } of unique([...this.containerList, ...extra], "itemSelectors")) {
this.handleClick(`${itemSelectors} a`, node);
}
}
const items = node.querySelectorAll("#videos .columns .column");
for (const item of items) {
let img = item.querySelector("img");
if (img) img.src = img.dataset.src;
}
for (const item of items) {
let code = item.querySelector(".uid");
if (!code) continue;
code = code.textContent.trim().toUpperCase();
const resource = await this.fetchMatch(code);
if (!resource?.length) continue;
const title = item.querySelector(".video-title");
const titleTxt = title.textContent;
title.textContent = "";
title.insertAdjacentHTML("beforeend", `<span class="matched">${titleTxt}</span>`);
const photo = item.querySelector(".item-image");
photo.classList.add("playBtn");
photo.setAttribute("title", "点击播放");
photo.addEventListener("click", e => {
e.stopPropagation();
e.preventDefault();
GM_openInTab(`${this.pcPreview}${resource[0].pickCode}`, { active: true });
});
}
},
modifyLayout() {
const waterfall = DOC.querySelector(this.container.selectors);
waterfall.classList.remove("grid");
waterfall.insertAdjacentHTML(
"afterbegin",
`<div class="item-sizer"></div><div class="gutter-sizer"></div>`
);
},
handleWaterfall() {
const infScroll = this.waterfallLayout(
this.container.selectors,
this.container.itemSelectors,
{},
{ path: ".pagination-next" }
);
infScroll?.on("load", e => this.modifyItem(e));
},
};
details = {
docStart() {
GM_addStyle(`
.video-meta-panel {
padding-left: 0;
padding-right: 0;
}
.video-meta-panel > .columns {
margin-left: 0;
margin-right: 0;
}
.video-meta-panel > .columns > .column {
padding: 0;
margin: .75rem;
}
.column-video-cover .cover-container::after { height: 100%; }
.column-video-cover img {
width: 100%;
max-height: none;
}
.movie-panel-info div.panel-block { padding: 8px 0; }
#smartOff { flex: 1; }
#resBox .button,
#playerBox .button {
height: auto;
padding: 3px 6px;
line-height: unset;
}
#playerBox { border-bottom: 0; }
.preview-video-container::after { height: 100%; }
`);
GM_addStyle(`
#magnets-content { overflow: hidden; }
#collapseBtn::after { content: "▼ 展开表格" }
#collapse:checked + .message-body > #magnets-content { max-height: none; }
#collapse:checked + .message-body > #collapseBtn::after { content: "▲ 收起表格" }
`);
},
contentLoaded() {
if (this.CK) this.handleClick(".tile-small a.tile-item");
// insert copy
this.handleCopy("h2");
this.handlePreviewFold(".column-video-cover");
const info = DOC.querySelector("nav.movie-panel-info");
// insert smartOff
const smartRes = DOC.create(
"a",
{ class: "button is-info", id: "smartOff", href: "javascript:;" },
"一键离线"
);
smartRes.addEventListener("click", e => this.handleOffLine(e, "smart"));
const infos = info.querySelectorAll(".panel-block");
const lastPanel = infos[infos.length - 1];
lastPanel.querySelector(".columns").setAttribute("style", "flex:1");
lastPanel.querySelector(".buttons").appendChild(smartRes);
// insert player & resource
info.insertAdjacentHTML(
"beforeend",
`<div class="panel-block" id="resBox">
<strong>网盘资源:</strong><span class="value">查询中...</span>
</div>
<div class="panel-block" id="playerBox">
<strong>在线播放:</strong><span class="value">查询中...</span>
</div>`
);
// handleFetch
const params = this.getParams();
if (!params) return;
this.handleTransTitle(params);
this.handleResource(params);
this.handleImage(params);
this.handleVideo(params);
this.handlePlayer(params);
this.handleMagnet(params);
},
getParams() {
const charReg = /[\u4e00-\u9fa5:]/g;
const studioReg = /片商:/g;
const infos = DOC.querySelectorAll("nav.movie-panel-info .panel-block");
let [code, date] = infos;
if (!code || !date) return;
code = code.textContent.replace(charReg, "").trim().toUpperCase();
date = date.textContent.replace(charReg, "").trim().toUpperCase();
let studio = "";
for (const { textContent: text } of infos) {
if (!studioReg.test(text)) continue;
studio = text.replace(studioReg, "").trim();
break;
}
return {
code,
date,
studio,
video: !!DOC.querySelector(".preview-video-container"),
title: DOC.querySelector("h2").textContent.replace("复制", "").trim(),
res: getItem(code),
};
},
modifyTable() {
const table = DOC.querySelector("#magnets-content table");
if (!table) return;
const trs = table.querySelectorAll("tr");
for (const tr of trs) {
const href = tr.querySelector(".magnet-name a").href;
if (!href || tr.querySelector(".offline")) continue;
const td = DOC.create("td", { class: "sub-column offline", width: "94" });
const btn = DOC.create(
"button",
{ class: "button is-info is-small", type: "button", title: "仅添加离线任务" },
"离线下载"
);
btn.addEventListener("click", e => this.handleOffLine(e, href));
td.appendChild(btn);
tr.appendChild(td);
}
const limit = 6;
if (trs?.length <= limit) return;
let maxHeight = 0;
for (let index = 0; index < limit; index++) maxHeight += trs[index].offsetHeight;
const body = DOC.querySelector("#magnet-links .message-body");
body.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="collapse" class="hidden">`);
body.querySelector(".moj-content").insertAdjacentHTML(
"beforebegin",
`<label for="collapse" id="collapseBtn" class="button is-info is-small mb-2"></label>`
);
GM_addStyle(`#magnets-content { max-height: ${maxHeight}px; }`);
},
async handleTransTitle({ code, title, res }) {
let transTitle = res?.transTitle ?? "";
if (!transTitle) {
transTitle = await this.fetchTranslate(title);
if (!transTitle) return;
upItem(code, { transTitle });
}
DOC.querySelector("h2").setAttribute("title", transTitle);
},
async handleResource({ code }) {
const resource = await this.fetchResource(code);
upItem(code, { resource });
const resBox = DOC.querySelector("#resBox");
resBox.querySelector(".value")?.remove();
resBox.querySelector(".columns")?.remove();
if (!resource?.length) return resBox.insertAdjacentHTML("beforeend", `<span class="value">无</span>`);
resBox.insertAdjacentHTML(
"beforeend",
`
<div class="columns">
<div class="column">
<div class="buttons are-small review-buttons">
${resource.reduce(
(acc, { name, pickCode, date }) => `
${acc}
<a
class="button is-small is-danger"
href="${this.pcPreview}${pickCode}"
title="${date}/${name}"
target="_blank"
>
${name.length > 20 ? `${name.substr(0, 20)}...` : name}
</a>
`,
""
)}
</div>
</div>
</div>
`
);
return resource;
},
async handleImage({ code, res, date }) {
let image = res?.image ?? "";
if (!image) {
image = await this.fetchImage(code, date);
if (!image) return;
upItem(code, { image });
}
const cover = DOC.querySelector(".column-video-cover");
const placeholder = DOC.create("img", { src: GM_getResourceURL("loading") });
cover.appendChild(placeholder);
const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
img.addEventListener("click", () => {
if (getScrollTop() >= this.expHei) window.scrollTo(0, 0);
DOC.querySelector("#exp").checked = false;
});
img.onload = () => cover.replaceChild(img, placeholder);
},
async handleVideo({ video, code, studio, res }) {
if (video) {
video = DOC.querySelector("#preview-video source").src;
} else {
video = res?.video ?? "";
if (!video) video = await this.fetchVideo({ code, studio });
}
if (!video) return;
upItem(code, { video });
let preview = DOC.querySelector(".tile-images.preview-images");
if (!preview) {
DOC.querySelector(".video-meta-panel").insertAdjacentHTML(
"afterend",
`<div class="columns">
<div class="column">
<article class="message video-panel">
<div class="message-body">
<div class="tile-images preview-images">
</div>
</div>
</article>
</div>
</div>`
);
preview = DOC.querySelector(".tile-images.preview-images");
}
if (!preview.querySelector(".preview-video-container")) {
preview.querySelector("#preview-video")?.remove();
preview.insertAdjacentHTML(
"afterbegin",
`<a class="preview-video-container" data-fancybox="gallery" href="#preview-video">
<span>預告片</span>
<img
src="${DOC.querySelector(".column-video-cover img").src}"
class="video-cover"
style="width:150px;height:auto"
/>
</a>
<video
id="preview-video"
playsinline=""
controls=""
muted=""
preload="auto"
style="display:none"
>
<source src="${video}"/>
</video>`
);
}
const playBtn = DOC.create("a", { class: "play-button" });
playBtn.addEventListener("click", e => {
e.stopPropagation();
e.preventDefault();
DOC.querySelector(".preview-video-container").click();
});
playBtn.insertAdjacentHTML(
"beforeend",
`<span class="icon"><img src="/packs/media/images/btn-play-b414746c.svg"></span>
<span class="text">播放预览</span>`
);
let cover = DOC.querySelector(".column-video-cover .cover-container");
if (!cover) {
const gallery = DOC.querySelector(".column-video-cover a");
gallery.removeAttribute("data-fancybox");
gallery.classList.add("cover-container");
cover = DOC.querySelector(".column-video-cover .cover-container");
playBtn.setAttribute("style", "z-index:9");
} else {
playBtn.setAttribute("style", "margin-top:60px;z-index:9");
}
cover.insertAdjacentElement("beforeend", playBtn);
},
async handlePlayer({ code, res }) {
let player = res?.player ?? [];
if (!player?.length) {
player = await this.fetchPlayer(code);
if (player?.length) upItem(code, { player });
}
const box = DOC.querySelector("#playerBox");
box.querySelector(".value")?.remove();
box.querySelector(".columns")?.remove();
if (!player?.length) return box.insertAdjacentHTML("beforeend", `<span class="value">无</span>`);
box.insertAdjacentHTML(
"beforeend",
`
<div class="columns">
<div class="column">
<div class="buttons are-small review-buttons">
${player.reduce(
(acc, { zh, link, name, from }) => `
${acc}
<a
class="button is-small ${zh ? "is-warning" : "is-info"}"
href="${link}"
title="${name}"
target="_blank"
>
▶ ${from}
</a>
`,
""
)}
</div>
</div>
</div>
`
);
},
async handleMagnet({ code, res }) {
let magnet = res?.magnet ?? [];
if (!magnet?.length) {
magnet = await this.fetchMagnet(code);
if (!magnet?.length) return this.modifyTable();
upItem(code, { magnet });
}
magnet = magnet.reduce(
(pre, { link, name, styleClass, href, from, zh, size, date }) => `
${pre}
<tr>
<td class="magnet-name">
<a href="${link}">
<span>${name}</span>
<br>
${zh ? `<span class="tag is-warning is-small">字幕</span>` : ""}
<span class="tag is-${styleClass} is-small">${from}</span>
<span class="meta"> ( ${size} )</span>
</a>
</td>
<td class="sub-column" width="80">
<span class="time">${date}</span>
</td>
<td class="sub-column" width="70">
<button
class="button is-info is-small copy-to-clipboard"
data-clipboard-text="${link}"
type="button"
>
複製
</button>
</td>
</tr>`,
""
);
const magnets = DOC.querySelector("#magnets-content");
let tbody = magnets.querySelector("table tbody");
if (!tbody) {
magnets.textContent = "";
magnets.insertAdjacentHTML("beforeend", `<table width="100%"><tbody></tbody></table>`);
tbody = magnets.querySelector("table tbody");
}
tbody.insertAdjacentHTML("beforeend", magnet);
this.modifyTable();
},
async handleOffLine(e, link) {
e.preventDefault();
e.stopPropagation();
if (!link) return;
const node = e.target;
const text = node?.textContent ?? "";
node.textContent = "请求中...";
node.setAttribute("disabled", true);
node.setAttribute("style", "pointer-events:none");
const clickUrl = this.clickUrl;
if (link !== "smart") {
let res = await this.fetchOffLine(link);
const { state = false, error_msg = "" } = res;
notify({
title: `请求${state ? "成功" : "失败"}`,
text: `${error_msg ? error_msg : "仅添加离线任务"}`,
image: `${state ? "info" : "fail"}`,
clickUrl,
});
} else {
let links = [];
const trs = DOC.querySelectorAll("#magnets-content tr");
for (const tr of trs) {
let [info, date] = tr?.querySelectorAll("td");
if (!info || !date) continue;
links.push({
link: info.querySelector("a").href.split("&")[0],
zh: info.textContent.includes("字幕"),
size: (() => {
let size = info.querySelector(".meta").textContent.trim();
size = size.split(",")[0].replace(/\(|\)/gi, "").trim();
return transToBytes(size);
})(),
date: date.textContent.trim(),
});
}
if (links.length) {
if (links.length > 1) links = unique(links, "link");
if (links.length > 1) {
links.sort((pre, next) => {
if (pre.zh === next.zh) {
if (pre.size === next.size) return next.date - pre.date;
return next.size - pre.size;
} else {
return pre.zh > next.zh ? -1 : 1;
}
});
}
tooltip("磁链列表");
console.table(links);
const params = this.getParams();
for (let index = 0; index < links.length; index++) {
const { link, zh } = links[index];
let res = await this.fetchOffLine(link);
if (!res || res?.errcode === 911) break;
if (res?.state) {
res = await this.checkResource(params);
if (res?.length) {
this.afterAction({ ...params, zh, res });
notify({
title: "一键离线任务成功",
text: "进行后续操作",
image: "success",
clickUrl,
});
break;
}
}
if (index !== links.length - 1) continue;
notify({ title: "一键离线任务失败", image: "fail", clickUrl });
}
}
}
node.textContent = text;
node.removeAttribute("disabled");
node.removeAttribute("style");
},
async checkResource({ code }) {
const old = getItem(code)?.resource ?? [];
await delay(2500);
const res = await this.handleResource({ code });
if (Array.isArray(res) && res?.length) {
const today = res.filter(({ date }) => date === getDate());
if (today.length && res.length > old.length) return today;
}
return false;
},
async afterAction({ zh, res, title, code }) {
res = res.filter(({ name }) => name.indexOf(title) === -1);
if (!res.length) return;
title = `${zh ? "【中文字幕】" : ""}${title}`;
res = res.map(item => {
const suffix = item.name.split(".").pop().toLowerCase();
return { ...item, file_name: `${title}.${suffix}` };
});
await this.fetchRename(res);
if (this.CID) {
const moveRes = await this.fetchMove(res);
if (moveRes?.state) this.fetchDelDir(res);
}
this.handleResource({ code });
},
};
}
let matched = MatchDomains.find(({ regex }) => regex.test(location.host))?.domain;
if (matched) matched = eval(`new ${matched}();`);
matched?.docStart();
DOC.addEventListener("DOMContentLoaded", () => matched?.contentLoaded());
window.addEventListener("load", () => matched?.load());
if (/captchaapi\.115\.com/g.test(location.host)) {
DOC.addEventListener("DOMContentLoaded", () => {
window.focus();
const btn = DOC.querySelector("#js_ver_code_box button[rel='verify']");
btn.addEventListener("click", () => {
const interval = setInterval(() => {
if (DOC.querySelector("div[rel='error_box']").getAttribute("style").indexOf("none") !== -1) {
window.open("", "_self");
window.close();
}
}, 300);
setTimeout(() => clearInterval(interval), 600);
});
});
}
})();