// ==UserScript==
// @name JavScript
// @namespace https://greasyfork.org/users/175514
// @description 暗黑模式、滚动加载、115资源匹配 & 离线下载、预览视频、预览大图...
// @version 1.1.1
// @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
// @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
// @connect *
// @license MIT
// ==/UserScript==
(function () {
// domains
const createReg = items => new RegExp(`(${items.join("|").replace(/\./g, "\\.")})+`, "gi");
const MatchDomains = [
{
domain: "JavBus",
regex: createReg([
"javbus.com",
"dmmbus.fun",
"dmmsee.fun",
"busjav.fun",
"busjav.bar",
"cdnbus.fun",
"busfan.fun",
"seedmm.fun",
"busdmm.fun",
"buscdn.fun",
]),
},
];
// 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");
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 = document.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);
};
class Common {
docStart = () => {};
contentLoaded = () => {};
load = () => {};
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",
},
];
mPoint = 0;
pcPreview = "https://v.anxia.com/?pickcode=";
menus = [
{ title: "点击事件", key: "CK", command: "c" },
{ title: "暗黑模式", key: "DM", command: "d" },
{ title: "滚动加载", key: "LM", command: "s" },
{
title: "离线后操作目录cid",
key: "CID",
command: "a",
cb: uniKey => {
const val = prompt("用于离线下载后移动,删除操作", GM_getValue(uniKey) ?? "");
if (!val) return;
GM_setValue(uniKey, val);
location.reload();
},
},
];
registerMenu = (menus = this.menus) => {
for (const menu of menus) {
const { title, key, command } = menu;
const uniKey = `${this.constructor.name}_${key}`;
const val = GM_getValue(`${uniKey}`) ?? "";
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;
}
.line-4 { -webkit-line-clamp: 4; }
.playBtn { position: relative; }
.playBtn:after {
position: absolute;
background: url(https://javdb.com/packs/media/images/btn-play-b414746c.svg) 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 { color: rgb(10,132,255) !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, { 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);
});
}
};
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,
elementScroll: ".scrollBox",
history: false,
historyTitle: false,
hideNav: ".pagination",
status: ".page-load-status",
debug: false,
...iParams,
});
return infScroll;
};
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);
};
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;
};
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 };
});
};
fetchResource = async (code = "", res) => {
if (!res) res = await this.fetchSearch(code.split(/(-|_)/g)[0]);
if (res?.length) {
let codes = [
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"),
];
codes = Array.from(new Set(codes));
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 jpBukkake = `http://img.japanese-bukkake.net/${date[0]}/${date[1]}/${code}_s.jpg`;
const javScreens = `http://javscreens.com/images/${code}.jpg`;
const [jb, js] = await Promise.all([request(jpBukkake), request(javScreens)]);
if (typeof jb === "object") return jpBukkake;
if (typeof js === "object") return javScreens;
let res = await request(`https://javstore.net/search/${code}.html`);
const href = res?.querySelector("#content_news li a")?.href;
if (!href) return;
res = await request(href);
res = res?.querySelector(".news a img[alt*='.th']").src.replace(".th", "");
if (!res || !(await request(res))) return;
return res;
};
fetchVideo = async ({ code, studio }) => {
code = code.toLowerCase();
if (studio) {
const matched = this.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") ||
""
);
};
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 = Array.from(new Set(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",
});
};
}
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] };
}
maxHei = 600;
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();
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("#search-input").focus();
}
});
});
};
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 - 50px);
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);
title.classList.add("line-4");
}
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}`;
}
},
}
);
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; }
.screencap { max-height: 600px; overflow: hidden; }
#avatar-waterfall, #sample-waterfall, #related-waterfall { margin: -5px !important; }
.photo-info { height: auto !important; }
.bigImage { display: block; }
`);
// insert dom
GM_addStyle(`
#exp { display: none; }
#expBtn {
position: absolute;
top: 0;
height: 40px;
line-height: 40px;
left: 0;
right: 0;
cursor: pointer;
background: rgba(0,0,0,.7);
color: #fff;
margin: 560px 15px 0 15px;
z-index: 99;
}
#expBtn::after { content: "展开"; }
#exp:checked + .screencap { max-height: none; }
#exp:checked + .screencap > #expBtn {
top: auto;
margin: 0 15px;
bottom: 0;
}
#exp:checked + .screencap > #expBtn::after { content: "收起"; }
#resBox a { color: #CC0000 !important; }
#smartOff { width: 100%; }
`);
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;
}
`);
}
DOC.addEventListener("DOMNodeInserted", ({ target: node }) => {
if (node?.nodeName.toLowerCase() !== "tr") return;
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);
});
},
contentLoaded() {
if (this.CK) {
this.handleClick("a.movie-box");
this.handleClick("a.avatar-box");
}
// insert copy
const 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);
};
handleCopy("h3");
handleCopy("span[style='color:#CC0000;']");
// expBtn
const screencap = DOC.querySelector(".screencap");
screencap.querySelector(".bigImage img").onload = () => this.load();
screencap.insertAdjacentHTML("beforebegin", `<input type="checkbox" id="exp">`);
screencap.insertAdjacentHTML("beforeend", `<label for="exp" id="expBtn"></label>`);
const info = DOC.querySelector(".col-md-3.info");
// resource
info.insertAdjacentHTML(
"beforeend",
`<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.handleResource(params);
this.handleStar(params);
this.handleImage(params);
this.handleVideo(params);
},
load() {
const maxHei = this.maxHei;
const img = DOC.querySelector(".screencap").children[0];
const styleHei = img.height || img.clientHeight || img.offsetHeight;
if (styleHei > maxHei) {
GM_addStyle(`.screencap { max-height: ${maxHei}px; } #expBtn { margin-top: ${maxHei - 40}px; }`);
} else {
GM_addStyle(
`.screencap { max-height: ${styleHei + 40}px; } #expBtn { margin-top: ${styleHei}px; }`
);
}
},
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),
};
},
async handleResource({ code }) {
// console.time("handleResource");
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>`,
""
)
);
// console.timeEnd("handleResource");
return resource;
},
async handleStar({ code, star, res }) {
// console.time("handleStar");
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
);
// console.timeEnd("handleStar");
},
async handleImage({ code, res, date }) {
// console.time("handleImage");
let image = res?.image ?? "";
if (!image) {
image = await this.fetchImage(code, date);
if (!image) return;
upItem(code, { image });
}
const img = DOC.create("img", { src: image, title: "点击收起", style: "cursor:pointer" });
img.addEventListener("click", () => {
if (getScrollTop() >= this.maxHei) window.scrollTo(0, 0);
DOC.querySelector("#exp").checked = false;
});
img.onload = () => DOC.querySelector(".screencap").appendChild(img);
// console.timeEnd("handleImage");
},
async handleVideo({ code, studio, res }) {
// console.time("handleVideo");
let video = res?.video ?? "";
if (!video) {
video = await this.fetchVideo({ code, studio });
if (!video) return;
upItem(code, { video });
}
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;
};
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);
const bigImage = DOC.querySelector(".bigImage");
const bImg = bigImage.querySelector("img");
const playBtn = DOC.create("div", { class: "playBtn", title });
playBtn.addEventListener("click", playVideo);
playBtn.appendChild(bImg);
bigImage.appendChild(playBtn);
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);
}
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);
// console.timeEnd("handleVideo");
},
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 = "http://115.com/?tab=offline&mode=wangpan";
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 {
const params = this.getParams();
let links = [];
const trs = DOC.querySelectorAll("#magnet-table tr");
for (let index = 1; index < trs.length; index++) {
const item = { link: "", zh: false, size: 0, date: 0 };
let [name, size, date] = trs[index].querySelectorAll("td");
if (!name || !size || !date) continue;
item.zh = !!name.querySelector("a.btn.btn-mini-new.btn-warning.disabled");
name = name.querySelector("a");
item.link = name.href;
size = size.querySelector("a").textContent.trim().replace(/gb/gi, "");
if (/mb/gi.test(size)) size = (parseFloat(size) / 1024).toFixed(2);
item.size = Number(size);
item.date = date.querySelector("a").textContent.trim().replace(/-/g, "");
links.push(item);
}
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;
}
});
}
for (let index = 0; index < links.length; index++) {
const item = links[index];
let res = await this.fetchOffLine(item.link);
if (!res || res?.errcode === 911) break;
if (res?.state) {
res = await this.checkResource(params);
if (res) {
this.afterAction({ zh: item.zh, ...params, 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);
});
});
}
})();