// ==UserScript==
// @name 搜索那个
// @description 日本影视行业内部搜索工具Pro Max Plus Ultra
// @author shopkeeperV
// @namespace https://greasyfork.org/zh-CN/users/150069
// @version 1.3.9
// @match *://*/*
// @connect *
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// ==/UserScript==
/*jshint esversion: 8*/
(async function () {
'use strict';
if (top !== window) {
//仅在iframe内执行,服务于分辨率获取功能,负责iframe内视频信息反馈
//注意广告插件或许会导致获取不到视频信息
if (!/(cloudflare)|(captcha)/.test(window.location.href)) {
//iframe获取父窗口消息,绕过iframe安全机制
window.addEventListener('message', e => {
//需要判断一下内容,部分网站自己也会通信,会触发到这个方法,JavSearch作为标志
if (typeof e.data === 'string' && e.data.includes("JavSearch")) {
//发送页面视频信息
window.top.postMessage(getVideosInfo(), '*');
}
});
}
//去部分iframe弹窗
let host = location.host;
if (/emturbovid/.test(host)) {
addCSS("div[id^='pop']{display:none;}");
} else if (/fc2stream/.test(host)) {
addCSS("div[style*='opacity:0']{display:none;}");
}
//7mm系列弹窗
else if (/mm.*xyz/.test(host)) {
addCSS("div[class*='pop']{display:none;}");
addCSS("div[style*='opacity:0']{display:none;}");
}
//为此iframe添加message事件后无需往后执行
return;
}
let isAndroid = /android/i.test(navigator.userAgent);
//设置样式,通知模态框要用
initStyle();
//通知模态框需要率先创建
let noticeDialogObj = new ModalDialog("<div id='jav_notice'></div>", {dialogPosition: "top"});
//执行个别网页的定制脚本
if (await adviceAndReturn()) return;
//最近一次的搜索词
let historyKeyword = "";
//待分词文本
let textContent = "";
//分辨获取功能的页面视频信息描述
let videosInfo = "";
//分词时特殊片商识别
let specialProducer = "";
//用于判断打开小球时是否使用历史搜索词
let isNewLinkWord = false;
//桌面端链接分词定时器
let timeoutTimer;
//功能是否初始化
let ready = false;
//灵魂搜索引擎们
let btnMap;
let cenMap;
let uncMap;
let sampleMap;
let searchDialogPosition, blingBlurRadius, blingSpreadRadius;
if (isAndroid) {
searchDialogPosition = "bottom";
blingBlurRadius = "50px";
blingSpreadRadius = "30px";
} else {
searchDialogPosition = "middle";
blingBlurRadius = "500px";
blingSpreadRadius = "60px";
}
buildBtnMap();
let searchDialogObj = new ModalDialog(searchDialogCode(), {
width: "310px",
dialogPosition: searchDialogPosition
});
let settingDialogObj;
let chooseDialogObj;
//大量使用,单独声明,注意需要模态框创建后才有,写在后面
let textInput = document.getElementById("jav_input");
//创建悬浮球
new FloatBall("jav_ball", {color: "#00cec9"});
//让屏幕周围亮起来,用于链接分词
let blingObj = new WindowBling({
color: "#00cec9",
blingPosition: ["left", "right"],
blurRadius: blingBlurRadius,
spreadRadius: blingSpreadRadius
});
//仅在初始化设置模态框时使用一次,其他都从存储中获取
let mosaic_reduce_first = getTmValue("mosaic_reduce_first");
if (mosaic_reduce_first == null) {
setTmValue("mosaic_reduce_first", false);
}
//第一阶段初始化,绑定必须的监听事件,小球,链接分词,划词等,其余的在首次点击小球后再初始化,以节约性能
init1();
function buildBtnMap() {
btnMap = new Map();
btnMap.set("avmoo有码情报", "https://avmoo.online/cn/search/%s");
btnMap.set("avsox无码情报", "https://avsox.click/cn/search/%s");
btnMap.set("javdb情报", "https://javdb.com/search?q=%s&f=all");
btnMap.set("google+jav", "https://www.google.com/search?q=%s%20jav");
}
function buildCenMap() {
cenMap = new Map();
let part1 = function () {
cenMap.set("supjav破解", {url: "https://supjav.com/zh/?s=%s", reqMethod: "get"});
}
let part2 = function () {
cenMap.set("missav字幕", {url: "https://missav.com/cn/search/%s", reqMethod: "get"});
cenMap.set("jable字幕", {url: "https://jable.tv/search/%s/", reqMethod: "get"});
cenMap.set("7mm字幕", {
url: "https://7mmtv.sx/zh/searchform_search/all/index.html",
reqMethod: "post",
payload: "search_keyword=%s&search_type=searchall&op=search"
});
cenMap.set("777字幕", {url: "https://www.jav777.xyz/?s=%s", reqMethod: "get"});
}
if (getTmValue("mosaic_reduce_first")) {
part1();
part2();
} else {
part2();
part1();
}
//搜索到分割线时,标志着破解版本和字幕版本不存在,打开之前保留的普通版本
cenMap.set("---", null);
//这后面可以添加其他搜索引擎,但似乎没有必要,一般要么前面搜索到了,要么就得谷歌来了
}
function buildUncMap() {
uncMap = new Map();
uncMap.set("missav无码", {url: "https://missav.com/cn/search/%s", reqMethod: "get"});
uncMap.set("guru", {url: "https://jav.guru/?s=%s", reqMethod: "get"});
uncMap.set("7mm无码", {
url: "https://7mmtv.sx/zh/searchform_search/all/index.html",
reqMethod: "post",
payload: "search_keyword=%s&search_type=uncensored&op=search"
});
uncMap.set("123av", {url: "https://123av.com/zh/search?keyword=%s", reqMethod: "get"});
uncMap.set("supjav无码", {url: "https://supjav.com/zh/?s=%s", reqMethod: "get"});
}
//为常规格式的(aaa-111)视频预览引擎也添加设置选项,防止他们抽风,其他格式的引擎不需要是因为他们都是独一无二的,抽风也没得换
function buildSampleMap() {
sampleMap = new Map();
sampleMap.set("trailer", {url: "https://javtrailers.com/search/%s"});
sampleMap.set("jav24", {url: "https://www.jav24.com/?q=%s"});
// sampleMap.set("javLand", {url: "https://jav.land/tw/id_search.php?keys=%s"});
sampleMap.set("javdb", {url: "https://javdb.com/search?q=%s&f=all"});
}
function searchDialogCode() {
return '<div id="jav_searchDialog">' +
'<div>' + getBtns() +
'<div id="jav_cen" class="bbtn llink">搜索有码</div>' +
'<div id="jav_unc" class="bbtn llink">搜索无码</div>' +
'</div>' +
'<div><input id="jav_input" type="text" value="" autocomplete="off"/></div>' +
'<div>' +
'<div id="jav_copy" class="bbtn ttool">复制到剪贴板</div>' +
'<div id="jav_clear" class="bbtn ttool">清空输入框</div>' +
'<div id="jav_analyze" class="bbtn ttool">析出番号</div>' +
'<div id="jav_sample" class="bbtn ttool">视频预览</div>' +
'<div id="jav_video_size" class="bbtn ttool">分辨率获取</div>' +
'<div id="jav_setting" class="bbtn ttool">搜索设置</div>' +
'<div id="jav_description" class="bbtn ttool">必看说明</div>' +
'</div>' +
'<div><div id="jav_close" class="bbtn cclose">关闭</div></div>' +
'</div>';
function getBtns() {
let btns = "";
btnMap.forEach(function (value, key) {
btns += `<div class='bbtn llink' id='${key}' jav_url='${value}'>${key}</div>`;
});
return btns;
}
}
function settingCode() {
return "<div id='jav_settingDialog'>" +
"<div class='jav_setting_text'>下列按钮设置效果永久保留。</div>" +
"<div id='mosaic_reduce_first' class='bbtn llink sset'>" +
"当前是:" + (mosaic_reduce_first ? "无码破解" : "中文字幕") + "优先</div>" +
"<div class='jav_setting_text' >下列开关决定相应引擎是否参与搜索,在搜索结果不理想时使用,仅在当前页面生效。</div>" +
"<div>" +
"<span class='jav_setting_text'>有码引擎</span><br/>" +
"<form class='jav_pick_form' id='cen_pick_form'>" +
getSettingBtns(cenMap) +
"</form>" +
"</div>" +
"<div>" +
"<span class='jav_setting_text'>无码引擎</span><br/>" +
"<form class='jav_pick_form' id='unc_pick_form'>" +
getSettingBtns(uncMap) +
"</form>" +
"</div>" +
"<div>" +
"<form class='jav_pick_form' id='sample_pick_form'>" +
"<b>预览引擎</b><br/>" +
getSettingBtns(sampleMap) +
"</form>" +
"</div>" +
"</div>";
}
function getSettingBtns(map) {
let result = "";
map.forEach((value, key) => {
if (/---/.test(key)) {
result += `<input type='checkbox' checked id='${key}'><label>${key}</label><div class='jav_go'>分隔</div><br/>`;
} else {
let urlPart = value.url.split("/");
result += `<input type='checkbox' checked id='${key}'><label for='${key}'>${key}</label>` +
`<div class='jav_go' jav_url='${urlPart[0]}//${urlPart[2]}'>前往</div>` +
"<br/>";
}
});
return result;
}
function initStyle() {
let globalCSS = ".ddialog .bbtn:focus,.ddialog .bbtn:active,.ddialog .bbtn:hover{-webkit-tap-highlight-color:transparent}" +
".ddialog .bbtn{box-sizing:border-box;display:inline-block;margin:3px;padding:0 8px;border-radius:4px;cursor:pointer;color:white;font:16px/1.8 sans-serif;min-width:140px;min-height:30px;letter-spacing:normal;user-select:none}" +
".ddialog .llink{background:#00baf8;}" +
".ddialog .ttool{background:#00baf8;}" +
".ddialog .cclose{background:#ff6666;}" +
"#jav_input{box-sizing:border-box;display:block;margin:3px auto;outline:none;border:3px solid #00baf8 !important;border-radius:6px;padding:6px;width:286px;height:35px;font:14px/1.8 sans-serif !important;text-align:center;color:#00baf8 !important;font-weight:bold !important;background-color:white !important;box-shadow:none;letter-spacing:normal;}" +
"#jav_input::placeholder{font:14px/1.8 sans-serif !important;color:#00baf8 !important;text-align:center;letter-spacing:normal;}" +
"#mmuted{position:fixed;box-sizing:border-box;z-index:999999;border-radius:4px;cursor:pointer;user-select: none;margin:auto;color:white;padding:15px 20px;text-align:center;font:18px/1.8 sans-serif;letter-spacing:normal;}" +
"#jav_notice,#jav_choose{font:18px/1.8 sans-serif;text-align:center;color:#00baf8;font-weight:bold;padding:10px;letter-spacing:normal;}" +
"#jav_notice .eerror{color:#ff6666;}" +
".jav_pick_form{text-align:center !important;}" +
".jav_pick_form input{display:none !important;}" +
".jav_pick_form label,.jav_go{border:1px solid #00baf8;padding:2px 5px 2px 5px;text-align:center;margin:5px;border-radius:5px;color:#00baf8;cursor:pointer;display:inline-block !important;font:14px/1.6 sans-serif;-webkit-tap-highlight-color:transparent;user-select:none;}" +
".jav_pick_form label{min-width:110px;}" +
".jav_pick_form input:checked + label{color:white;background:#00baf8;}" +
"#jav_searchDialog{margin-top:15px;margin-bottom:10px;}" +
"#jav_settingDialog{width:200px;margin:20px auto;font:14px/1.6 sans-serif;color:#00baf8;}" +
"#jav_settingDialog div:not(.jav_go){margin-bottom:10px;}" +
"#jav_settingDialog .sset{font:14px/2.3 sans-serif;}" +
".jav_setting_text{font-weight:bold;}";
addCSS(globalCSS);
//下面是特定网页的样式
let host = location.host;
let path = location.pathname;
if (/fc2.com/.test(host) && /article/.test(path)) {
addCSS('#fc2Div>div{background-color:#00baf8;border-radius:4px;color:white;padding:5px 8px;user-select:none;' +
'cursor: pointer;margin:5px 10px;text-align:center;display:inline-block;font:12px/1.6 sans-serif;}');
} else if (/7mmtv/.test(host)) {
//广告图,css隐藏比js快
addCSS(".ut_cg1_top{display:none;}");
//去视频悬浮层
addCSS(".mvspan_2_s_k_i_p_row{display:none;}");
addCSS("div[class*='pop']{display:none;}");
} else if (/jav777/.test(host)) {
addCSS("body{min-width:unset !important;}");
} else if (/javdb/.test(host)) {
addCSS("body{margin-right:0px !important;}");
} else if (/supjav/.test(host)) {
addCSS(".movv-ad{display:none;}");
}
}
function addCSS(css) {
let style = document.createElement("style");
//放body里是因为部分网页重写head标签
document.body.prepend(style);
style.textContent = css;
}
//此方法可以完成一些网站的自动点击、自动播放等,也对一些网站进行功能定制
async function adviceAndReturn() {
let voiceDiv;
let failTimes = 0;
let host = location.host;
let path = location.pathname;
let autoplay = getTmValue("autoplay");
setTmValue("autoplay", false);
let show_img = getTmValue("show_img");
setTmValue("show_img", false);
//7mm自定义播放源优先级
if (/7mmtv/.test(host)) {
//视频观看页面
if (document.getElementsByClassName("fullvideo-details").length === 1) {
//自动加载顺序:自定义数组>第一个源
let btns = document.getElementsByClassName("btn-server");
let sources = ["FL", "SW"];
if (btns.length > 0) {
let openFirst = true;
out:for (let source of sources) {
for (let btn of btns) {
let func = btn.onclick.toString();
if (func.includes(source)) {
autoClick(btn);
openFirst = false;
break out;
}
}
}
if (openFirst) autoClick(btns[0]);
}
}
}
//fc2,添加两个易用的按钮
else if (/fc2.com/.test(host) && /article/.test(path)) {
//该元素后添加按钮
let targetEle;
//用户主页
let home;
if (isAndroid) {
targetEle = document.getElementsByClassName("items_article_Mainitem")[0];
home = document.getElementsByClassName("items_article_seller")[0].href;
} else {
targetEle = document.getElementsByTagName("ul")[3];
home = targetEle.getElementsByTagName("a")[1].href;
}
let div = document.createElement("div");
div.id = "fc2Div";
div.innerHTML = "<div>该作者其他作品</div><div>搜索该作品</div>";
targetEle.after(div);
div.children[0].addEventListener("click", () => {
window.location.href = home + "articles?sort=date&order=desc";
});
div.children[1].addEventListener("click", () => {
textInput.value = window.location.href.match(/\/([0-9]+)\//)[1];
historyKeyword = textInput.value;
if (!ready) {
init2();
ready = true;
}
searchDialogObj.open();
});
}
//heyzo没有预览视频时将小图片替换高清图,无码片商中仅heyzo可以替换,因为他图片的url格式是固定的
else if (/heyzo\.com/.test(host)) {
//1秒后检测有没有视频元素,没有就展示预览大图
setTimeout(() => {
//没有视频才显示图片
if (document.getElementsByTagName("video").length === 0) {
//查找所有图片,替换url
let gallery = document.getElementById("section_gallery");
if (gallery) {
let reg = /\/contents\/[0-9]+\/[0-9]+\/gallery\/thumbnail.*?\.jpg/ig;
let template = '<img src="%s" style="width:inherit;">';
let imgs = gallery.innerHTML.match(reg);
//往.quality-btns后添加图片
let html = "";
for (let i = 0; i < imgs.length; i++) {
html += template.replace("%s", imgs[i].replace("thumbnail_", ""));
}
let div = document.createElement("div");
div.style.width = "inherit";
div.innerHTML = html;
document.getElementById("quality-btns").after(div);
}
}
}, 1000);
}
if (show_img) {
if (/trailer/.test(host)) {
addCSS(".videoWrapper{display:none;}");
let active = function () {
setTimeout(() => {
let gallery = document.getElementById("gallery-wrapper");
if (gallery) {
document.documentElement.scrollTop = gallery.offsetTop;
return;
} else active();
let picBtn = document.getElementById("description").getElementsByTagName("button")[0];
picBtn && autoClick(picBtn);
}, 500);
}
active();
} else if (/javdb/.test(host)) {
let image0 = document.getElementsByClassName("preview-images")[0];
if (image0) {
document.documentElement.scrollTop = image0.offsetTop;
}
}
} else if (autoplay) {
let videos = document.getElementsByTagName("video");
//TokyoHot,只有移动端需要自动点击
if (/tokyo-hot/.test(host) && isAndroid) {
let playDiv = document.getElementsByClassName("fp-ui")[0];
console.log("尝试点击");
//不一定有视频
playDiv && autoClick(playDiv);
console.log("点击成功");
} else if (/jav\.land/.test(host)) {
let sample = document.getElementById("play_sample");
sample && autoClick(sample);
} else if (/trailer/.test(host)) {
let loop = 0;
let btns = document.getElementsByClassName("vjs-big-play-button");
//只能按钮播放还得等按钮加载的网站
let handler = () => {
let video = videos[0];
loop++;
console.log("点击播放按钮。")
btns[0] && autoClick(btns[0]);
if (video) {
video.muted = true;
if (video.readyState === 0 && loop < 20) {
console.log("视频存在但未就绪。")
setTimeout(handler, 500);
}
} else {
console.log("视频不存在。")
setTimeout(handler, 500);
}
};
handler();
} else if (/javdb/.test(host)) {
let sample = document.getElementsByClassName("preview-video-container")[0];
sample && autoClick(sample);
} else if (/mgstage\.com/.test(host)) {
let btn;
if (isAndroid) {
btn = document.getElementById("sample-play");
} else {
btn = document.getElementsByClassName("button_sample")[0];
}
btn && autoClick(btn);
}
voiceDiv = document.createElement("div");
//给个通知,明确自动播放程序运行中
voiceDiv.textContent = "等待视频加载...";
voiceDiv.id = "mmuted";
let _width = 180;
voiceDiv.style.backgroundColor = "#ff6666";
voiceDiv.style.width = _width + "px";
voiceDiv.style.top = Math.round(screen.height / 3 * 2) + "px";
voiceDiv.style.left = Math.round(screen.width / 2 - _width / 2) + "px";
document.body.prepend(voiceDiv);
//部分网站通过js加载视频或需要点击,所以会出现延迟,需要不停尝试获取页面的视频播放
await forcePlay();
async function forcePlay() {
if (failTimes === 30) {
//15秒了还没播放
console.log("自动播放失败,视频不存在或视频异常。");
voiceDiv.innerHTML = "播放失败<br/>请尝试日本节点";
voiceDiv.addEventListener("click", () => {
voiceDiv.style.display = "none";
}, {once: true});
return;
}
console.log("自动播放:尝试强制播放,每0.5s一次。");
let video = videos[0];
if (!video) {
failTimes++;
setTimeout(forcePlay, 500);
return;
}
if (video.preload === "none") {
video.preload = "metadata";
}
//个别网站display不写在内联样式里,需要以这种方式获取所有的样式getComputedStyle
if (window.getComputedStyle(video).display === "none" || video.readyState === 0) {
failTimes++;
setTimeout(forcePlay, 500);
return;
}
console.log("自动播放:获取到视频元素,清除自动播放循环定时器。");
video.muted = true;
console.log("自动播放:设为无声。");
await video.play();
voiceDiv.style.backgroundColor = "#00cec9";
voiceDiv.textContent = "点我解除静音";
voiceDiv.addEventListener("click", () => {
video.muted = false;
voiceDiv.textContent = "点我3倍速播放";
voiceDiv.addEventListener("click", () => {
if (video.playbackRate === 1.0) {
video.playbackRate = 3.0;
voiceDiv.textContent = "点我恢复速度";
} else {
video.playbackRate = 1.0;
voiceDiv.textContent = "点我3倍速播放";
}
});
}, {once: true});
console.log("自动播放:强制播放成功。");
}
}
//默认情况不终止脚本执行
return false;
function autoClick(eventTarget) {
eventTarget.dispatchEvent(new Event("click", {bubbles: true}));
}
}
function init1() {
observeNodeIncrement();
//获取页面所有链接,添加事件获取文本内容,移动端滑动触发,桌面端鼠标悬浮一会触发
//getElementsByTagName、getElementsByClassName获取的集合是实时更新的,不需要重新获取,保留引用以提升性能
let links = document.getElementsByTagName("a");
//首次链接分词监听
addLinkListener();
//选词监听
document.addEventListener("selectionchange", getSelectedWord);
//记录小球位置,避免拖拽时触发点击
let startX;
let startY;
//为悬浮球添加鼠标按下事件,防止拖拽时触发点击
document.getElementById("jav_ball").addEventListener("mousedown", (e) => {
startX = e.clientX;
startY = e.clientY;
});
//为悬浮球添加点击事件
document.getElementById("jav_ball").addEventListener("click", (e) => {
//先判断是否拖拽过,误差5px
if (Math.abs(startX - e.clientX) > 5 || Math.abs(startY - e.clientY) > 5) {
return;
}
//首次初始化
if (!ready) {
init2();
ready = true;
}
//每次打开时重置placeholder
textInput.placeholder = "使用前请先看说明!";
//链接分词就不使用历史搜索词
if (!isNewLinkWord) {
//获取选词或历史
textInput.value = historyKeyword;
isNewLinkWord = false;
}
searchDialogObj.open();
});
//观察dom变化,因为个别网站会用xhr添加新的a标签,这部分链接将无法触发链接分词,dom增加节点时将刷新a标签的分词事件
function observeNodeIncrement() {
//延迟1秒重置标签,如果在一秒内又触发则取消执行,设置新的延迟任务,因为有时触发太频繁可能影响性能
let observerTimer;
new MutationObserver(nodeChangeCallback).observe(document.body, {childList: true, subtree: true});
console.log("搜索那个dom观察:开始观察dom变化。");
function nodeChangeCallback(mutationsList) {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
for (let node of mutation.addedNodes) {
if (node.nodeType === Node.ELEMENT_NODE && node.tagName.toLowerCase() === 'a') {
console.log('搜索那个dom观察:新增了一个a标签。');
if (observerTimer) {
clearTimeout(observerTimer);
console.log("搜索那个dom观察:清除定时任务。");
}
observerTimer = setTimeout(() => {
observerTimer = 0;
addLinkListener();
}, 1000);
console.log("搜索那个dom观察:一秒后重置所有a标签事件。");
return;
}
}
}
}
}
}
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
function addLinkListener() {
//链接分词监听,所有a标签
for (let i = 0; i < links.length; i++) {
//a标签有文字节点且有内容才添加事件
if (!links[i].getAttribute("jav_listen")) {
if (isAndroid) {
links[i].addEventListener("touchstart", getLinkText);
} else {
//桌面端鼠标悬浮触发
links[i].addEventListener("mousemove", rebuildLinkTimer/*鼠标移动的话不停重置定时器*/);
//离开时也清除定时器
links[i].addEventListener("mouseleave", clearLinkTimeout);
}
links[i].setAttribute("jav_listen", "true");
}
}
console.log("搜索那个dom观察:a标签事件添加完成。");
function rebuildLinkTimer(event) {
//在链接上移动鼠标需要重新设置定时器
timeoutTimer && clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
getLinkText(event);
}, 600);
}
function clearLinkTimeout() {
timeoutTimer && clearTimeout(timeoutTimer);
}
function getLinkText(event) {
let _textContent = event.target.textContent;
if (!/\S+/.test(_textContent)) return;
textContent = _textContent;
if (separate(true)) isNewLinkWord = true;
}
}
function getSelectedWord() {
let selectWords = window.getSelection().toString();
if (selectWords) {
//需要清除首尾空格
historyKeyword = removeSpaces(selectWords);
//有一种情况,触发链接分词没有打开悬浮球,而是又进行了选词,这时应该使用最新操作的选词文本
isNewLinkWord = false;
}
}
}
//更多功能的第二阶段初始化
function init2() {
buildCenMap();
buildUncMap();
buildSampleMap();
settingDialogObj = new ModalDialog(settingCode(), {
width: "250px",
dialogPosition: searchDialogPosition
});
chooseDialogObj = new ModalDialog("<div id='jav_choose'></div>", {
width: "320px",
dialogPosition: "middle",
canBeClose: false
});
//接收iframe发来的数据,绕过iframe安全机制
window.addEventListener('message', e => {
//需要判断一下内容,部分网站自己也会通信,会触发到这个方法
if (typeof e.data === 'string' && e.data.includes("JavFrame>")) {
appendNotice(e.data, false);
}
});
//给搜索按钮添加事件
btnMap.forEach((value, key) => {
let btn = document.getElementById(key);
btn.addEventListener("click", (event) => {
//获取btn元素中的jav_url属性,这是在创建搜索模态框时设置好的
let url = event.target.getAttribute("jav_url");
let keyword = removeSpaces(textInput.value);
//有搜索词
//使用油猴的GM_openInTab(),因为window.open()存在个别网站打不开的情况
if (keyword !== "" && !/^\s+$/.test(keyword)/*也不能全是空白符*/) {
openInTab(url.replace("%s", keyword), {insert: true, loadInBackground: false});
}
//没有搜索词
else {
//其余跳到主页
let parts = url.split("/");
openInTab(parts[0] + "//" + parts[2], {insert: true, loadInBackground: false});
}
});
});
let btnGoArray = document.getElementsByClassName("jav_go");
for (let go of btnGoArray) {
if (go.getAttribute("jav_url")) {
go.onclick = () => {
openInTab(go.getAttribute("jav_url"), {insert: true, loadInBackground: false});
};
}
}
//使用说明
document.getElementById("jav_description").addEventListener("click", () => {
openInTab("https://sleazyfork.org/zh-CN/scripts/470138#additional-info", {
insert: true,
loadInBackground: false
});
});
//视频预览
document.getElementById("jav_sample").addEventListener("click", sampleHandler);
//清除输入框内容
document.getElementById("jav_clear").addEventListener("click", () => {
textInput.value = "";
historyKeyword = "";
textInput.placeholder = "请输入";
});
//复制
document.getElementById("jav_copy").addEventListener("click", () => {
GM_setClipboard(removeSpaces(textInput.value), "text");
showNotice("已复制。");
setTimeout(() => {
hideNotice();
}, 500);
});
//析出番号,手动粘贴后,点击按钮获取文本中的番号
document.getElementById("jav_analyze").addEventListener("click", () => {
textContent = textInput.value;
separate(false);
});
//关闭按钮
document.getElementById("jav_close").addEventListener("click", () => {
searchDialogObj.close();
});
//获取页面中存在的视频的分辨率
document.getElementById("jav_video_size").addEventListener("click", () => {
//先获取当前页面视频信息
videosInfo = getVideosInfo();
searchDialogObj.close();
showNotice(videosInfo);
//发个空消息给所有子iframe,通知他们响应视频信息
let iframes = document.getElementsByTagName('iframe');
for (let i = 0; i < iframes.length; i++) {
let iframe = iframes[i];
if (iframe.contentWindow && iframe.contentWindow.postMessage) {
iframe.contentWindow.postMessage("JavSearch", '*');
}
}
});
document.getElementById("jav_setting").addEventListener("click", () => {
settingDialogObj.open();
});
document.getElementById("mosaic_reduce_first").addEventListener("click", async () => {
let mosaic_reduce_first = getTmValue("mosaic_reduce_first");
setTmValue("mosaic_reduce_first", !mosaic_reduce_first);
buildCenMap();
document.getElementById("cen_pick_form").innerHTML = getSettingBtns(cenMap);
document.getElementById("mosaic_reduce_first").textContent = "当前是:" + (!mosaic_reduce_first ? "无码破解" : "中文字幕") + "优先";
let btnGoArray = document.getElementById("cen_pick_form").getElementsByClassName("jav_go");
for (let go of btnGoArray) {
if (go.getAttribute("jav_url")) {
go.onclick = () => {
openInTab(go.getAttribute("jav_url"), {insert: true, loadInBackground: false});
};
}
}
});
document.getElementById("jav_cen").addEventListener("click", searchHandler);
document.getElementById("jav_unc").addEventListener("click", searchHandler);
//模态框内输入框添加离焦事件更新按钮事件
textInput.addEventListener("blur", () => {
//保留输入历史
historyKeyword = removeSpaces(textInput.value);
textInput.placeholder = "请输入";
});
}
//分词方法,blink表示是否分词后闪烁屏幕
function separate(blink) {
//输入框置空,显示placeholder的提示内容
textInput.value = "";
if (textContent === "") {
textInput.placeholder = "文本内容不存在";
return;
}
textContent = textContent.replaceAll("—", "-");
//匹配正则数组
let matchers = [
/((?<![a-z0-9-_])[a-z]*[0-9]*[a-z]*|[0-9]{5,})((?<=[a-z0-9])[_-])([a-z]*[0-9]{2,5}(?![0-9]))/i,
/(?<![a-z0-9])[a-z]+[0-9]{3,}(?![a-z0-9])/i,
/(?<![a-z0-9])[0-9]{4,}(?![a-z0-9])/i
];
let ids;
//特殊片商处理(片商名称像番号)
specialProducer = "";
let specialProducerList = ["H4610", "H0930", "C0930"];
for (let producer of specialProducerList) {
if (new RegExp(producer, "i").test(textContent)) {
//若视频预览功能匹配到响应格式的番号,将使用该片商的的预览地址
specialProducer = producer;
textContent = textContent.replace(producer, "").replace(producer.toLowerCase(), "");
break;
}
}
for (let i = 0; i < matchers.length; i++) {
ids = textContent.match(matchers[i]);
if (ids) {
break;
}
}
if (!ids) {
textInput.placeholder = "链接或输入框文本中未找到番号";
return false;
}
textInput.value = ids[0];
historyKeyword = ids[0];
if (blink) {
blingObj.blink();
}
return true;
}
//处理cloudflare拦截
async function checkCF(xhr) {
let finalUrl = xhr.finalUrl;
if (xhr.responseText.search(/you have been blocked/i) >= 0) {
//被cloudflare拉黑
appendNotice(finalUrl.split("/")[2] + "被cloudflare拉黑,请更换代理。", true);
//1代表出错并继续
return 1;
} else if (xhr.responseText.search(/Enable JavaScript and cookies to continue/i) >= 0) {
//需要真人验证
appendNotice(finalUrl.split("/")[2] + "需要真人验证。爬取此网站要求篡改猴5.2或以上,并排除chrome128、129版本。", true);
await sleep(1500);
openInTab(finalUrl, {insert: true, loadInBackground: false});
if (await choose("是否继续搜索?", "继续", "不用了")) {
appendNotice("继续搜索...");
//3代表正常并继续
return 3;
} else {
hideNotice();
//2代表停止
return 2;
}
}
//0代表未被拦截
else return 0;
}
function checkInput(text) {
if (text === "" || /^\s+$/.test(text)/*也不能全是空白符*/) {
showNotice("未输入。");
setTimeout(() => {
hideNotice();
}, 500);
return false;
}
if (!/^[a-z0-9]+[-_]?[a-z0-9]+$/i.test(text) || /^[a-z]+$/i.test(text)) {
showNotice("格式错误。");
setTimeout(() => {
hideNotice();
}, 500);
return false;
}
return true;
}
async function searchHandler(event) {
let keyword = removeSpaces(textInput.value).replaceAll("—", "-");
if (!checkInput(keyword)) {
return;
}
//用于判断是否在过程中出错,如果出错就不关闭通知
let error = false;
//三次超时强制中断就会停止搜索
let errorTimes = 0;
let selected = [];
let inputs;
let currentMap;
if (/jav_cen/.test(event.target.id)) {
showNotice("正在搜索...<br/>此按钮搜索如aaa-111、111aaaa-111等有码格式的番号,中文字幕和无码破解版本优先。");
inputs = document.getElementById("cen_pick_form").getElementsByTagName("input");
currentMap = cenMap;
} else {
showNotice("正在搜索...<br/>此按钮搜索如111111、111111-111、n1111、heyzo-111等无码格式的番号。");
inputs = document.getElementById("unc_pick_form").getElementsByTagName("input");
currentMap = uncMap;
}
for (let input of inputs) {
if (input.checked) {
selected.push(input.id);
}
}
let beFound = false;
//若没有字幕或无码破解版本但存在普通视频,将链接保存,在指定搜索结果都没找到到时使用
let savingUrl = "";
await checkUser();
for (let key of selected) {
if (/---/.test(key)) {
//指定引擎搜索不到后,如果前面有普通版本也打开
if (savingUrl) {
//打开特殊引擎的保留地址
hideNotice();
//凡是要隐藏通知然后打开新窗口的,都要等动画结束再打开,不然移动端回到页面会继续未完成动画,造成残影
//延迟时间与模态框淡出持续事件相等即可
setTimeout(() => {
openInTab(savingUrl, {insert: true, loadInBackground: false});
}, 300);
return;
}
//分割线,不执行搜索
continue;
}
beFound = await crawl(currentMap.get(key), keyword);
if (beFound) break;
}
if (!beFound) {
appendNotice("未找到结果,将尝试谷歌搜索。", true);
setTimeout(() => {
openInTab("https://www.google.com/search?q=%s%20jav".replace("%s", keyword), {
insert: true,
loadInBackground: false
});
}, 1000);
}
function crawl(engine, id) {
let url = engine.url;
let urlSplit = url.split("/");
let topLevelSite = urlSplit[0] + "//" + urlSplit[2];
if (/missav/.test(topLevelSite)) {
//需要将mgs格式改为常规格式
if (/^[0-9]+[a-z]+-[0-9]+$/i.test(id)) {
id = id.replace(/[0-9]*(?=[a-z])/i, "");
}
}
url = url.replace("%s", id);
let headers = {};
if (/7mmtv/.test(topLevelSite)) {
headers = {"content-type": "application/x-www-form-urlencoded"};
}
let timeout = 4000;
//已经超时强制中断的不再显示请求超时的通知
let aborted = false;
return new Promise((resolve) => {
let connectTimeout = true;
let promise = GM_xmlhttpRequest({
method: engine.reqMethod,
url: url,
//data为空时必须传null
data: engine.payload ? engine.payload.replace("%s", id) : null,
timeout: timeout,
headers: headers,
cookiePartition: {
topLevelSite: topLevelSite
},
onload: async function (xhr) {
connectTimeout = false;
let finalUrl = xhr.finalUrl;
let finalUrlSplit = finalUrl.split("/");
let currentHost = finalUrlSplit[0] + "//" + finalUrlSplit[2];
try {
let _id = id;
//不支持模糊搜索,需要完全匹配
if (/[a-z]/i.test(_id)) {
_id = `(?<![a-z])${_id}(?![0-9])`;
} else {
//如果是纯数字的番号,不要只匹配到几个数字
_id = `(?<![a-z0-9])${_id}(?![0-9])`;
}
//7mm
if (/7mmtv/.test(finalUrl)) {
//<a target="_top" href="https://7mmtv.sx/zh/chinese_content/37171/WAAA-167.html"
let urlReg = new RegExp(`${currentHost}/zh/[a-z]+_content/[^/]*/[^\\.]*${_id}.html`, "gi");
let matchArray = xhr.responseText.matchAll(urlReg);
for (let match of matchArray) {
if (/jav_unc/.test(event.target.id)) {
hideNoticeResolveAndOpen(match[0]);
return;
}
if (!savingUrl) {
savingUrl = match[0];
}
if (match[0].search(/chinese/) >= 0) {
hideNoticeResolveAndOpen(match[0]);
return;
}
}
resolve(false);
}
//jav777
else if (/jav777/.test(finalUrl)) {
//https://www.jav777.xyz/waaa-111-絕頂舔鮑狂熱-木下日葵有碼中文字幕.html
let regString = `/[^.]*${_id}[^.]*\\.html`;
let urls = xhr.responseText.match(new RegExp(regString, "i"));
if (urls) {
hideNoticeResolveAndOpen(currentHost + urls[0]);
} else {
resolve(false);
}
}
//supjav
else if (/supjav/.test(finalUrl)) {
switch (await checkCF(xhr)) {
case 1:
error = true;
resolve(false);
return;
case 2:
resolve(true);
return;
case 3:
resolve(false);
return;
}
//case为0时将继续运行
//href="https://supjav.com/zh/147335.html" title="FC2PPV 2753411 * Apricot...
let reg = new RegExp(`"(${currentHost}/zh/[0-9]+.html)" title="[^"]*${_id}[^"]*"`, "gi");
let matches = xhr.responseText.matchAll(reg);
for (let match of matches) {
if (/jav_unc/.test(event.target.id)) {
hideNoticeResolveAndOpen(match[1]);
return;
}
if (!savingUrl) {
savingUrl = match[1];
}
if (match[0].search(/无码破解|无码流出|無修正/) >= 0) {
hideNoticeResolveAndOpen(match[1]);
return;
}
}
//这网站的中文字幕只是顺便搜索的,并非主力搜索引擎,所以在字幕优先时并不一定搜索到这个
for (let match of matches) {
if (match[0].search(/中文字幕/) >= 0) {
hideNoticeResolveAndOpen(match[1]);
return;
}
}
resolve(false);
}
//missav
else if (/missav/.test(finalUrl)) {
if (/排序/.test(xhr.responseText)) {
//有排序选项表示存在结果
/*
<a href="https://missav.com/dm47/roe-142" alt="???-roe-142-???">
<span class="...">
中文字幕
</span>
*/
let reg = new RegExp(`<a href="([^"]*)" alt="[^"]*${_id}[^"]*"\\s?>\\s*<span[^>]*>[^<]*</span>`, "gi");
let matches = xhr.responseText.matchAll(reg);
for (let match of matches) {
if (/jav_cen/.test(event.target.id)) {
if (!savingUrl) {
savingUrl = match[1];
}
if (/中文字幕/.test(match[0])) {
hideNoticeResolveAndOpen(match[1]);
return;
}
} else {
hideNoticeResolveAndOpen(match[1]);
return;
}
}
resolve(false);
} else {
resolve(false);
}
}
//jable
else if (/jable/.test(finalUrl)) {
if (/search/.test(finalUrl)) {
//<a href="https://jable.tv/videos/miab-304/">MIAB-304...
let regString = `<a href="([^"]*)">[^<]*${_id}[^<]*<`;
let urls = xhr.responseText.match(new RegExp(regString, "i"));
if (urls) {
resolve(await crawl({url: urls[1], reqMethod: "get"}, ""));
} else {
resolve(false);
}
} else if (/videos/.test(finalUrl)) {
let cn = xhr.responseText.match(new RegExp("已更新至中文字幕版"));
if (cn) {
hideNoticeResolveAndOpen(finalUrl);
} else {
savingUrl = finalUrl;
resolve(false);
}
}
}
//guru
else if (/guru/.test(finalUrl)) {
/*
<a href="https://jav.guru/320706/101323-001-carib...">
<img src="..." alt="[101323-001-CARIB] Caribbeancom 101323-001 ...">
</a>
* */
let regString = `<a href="([^"]*${_id}[^"]*)">\\s<img`;
let urls = xhr.responseText.match(new RegExp(regString, "i"));
if (urls) {
hideNoticeResolveAndOpen(urls[1]);
} else {
resolve(false);
}
}
//123av
else if (/123av/.test(finalUrl)) {
switch (await checkCF(xhr)) {
case 1:
error = true;
resolve(false);
return;
case 2:
resolve(true);
return;
case 3:
resolve(false);
return;
}
/*
* <a href="v/caribbeancom-101323-001"
* */
let regString = `<a href="(v/[^"]*${_id}[^"]*)"`;
let urls = xhr.responseText.match(new RegExp(regString, "i"));
if (urls) {
hideNoticeResolveAndOpen(currentHost + "/zh/" + urls[1]);
} else {
resolve(false);
}
}
//没有匹配域名,被重定向到其他域名,搜索失败
else {
error = true;
appendNotice("错误:被重定向,请更换代理。原域名:"
+ urlSplit[2] + ",重定向域名:" + finalUrlSplit[2], true);
resolve(false);
}
} catch (e) {
error = true;
appendNotice(`错误:${url},请联系开发者。<br/>${e.name}:${e.message}`, true);
resolve(false);
}
},
ontimeout: function () {
error = true;
connectTimeout = false;
if (!aborted) {
appendNotice("超时:" + urlSplit[2], true);
}
resolve(false);
},
onerror: function () {
error = true;
connectTimeout = false;
appendNotice("错误:" + urlSplit[2] + ",机场不行或网站挂了。", true);
resolve(false);
}
});
setTimeout(() => {
promise.abort();
if (connectTimeout) {
error = true;
aborted = true;
appendNotice("超时强制中断:" + urlSplit[2], true);
errorTimes++;
if (errorTimes >= 3) {
appendNotice("你代理没开吧?", true);
resolve(true);
return;
}
resolve(false);
}
}, timeout);
//注意定义方法的位置,resolve方法需要包含在内
function hideNoticeResolveAndOpen(url) {
if (!error) {
hideNotice();
}
resolve(true);
setTimeout(() => {
openInTab(url, {insert: true, loadInBackground: false});
}, 300);
}
});
}
}
async function sampleHandler() {
let id = removeSpaces(textInput.value).toLowerCase().replaceAll("—", "-");
if (!checkInput(id)) {
return;
}
//正则
let common_reg = /^[a-z]+[0-9]*[a-z]*-[a-z]*[0-9]+$/i;
let heyzo_reg = /^heyzo-[0-9]+$/i;
let mgs_reg = /^[0-9]+[a-z]+-[0-9]+$/i;
let fc2_reg = /^[0-9]{6,7}$/;
let carib_reg = /^[0-9]{6}-[0-9]{3}$/;
let heyzo_k8_tokyoHot4_reg = /^[0-9]{4}$/;
let paco_1p_reg = /^[0-9]{6}_[0-9]{3}$/;
let _10m_reg = /^[0-9]{6}_[0-9]{2}$/;
let nyoshin_tokyoHotN_reg = /^n[0-9]{4}$/i;
let xxxav_reg = /^[0-9]{5}$/;
let other_reg = /^[a-z]+[0-9]+$/i;
let gachi_reg = /gachi/i;
//网址
/*
有些是搜索页,有些直接是详情页
基本涵盖所有番号的视频预览自动播放
全是get请求
*/
let jav24 = "https://www.jav24.com/watch/www.mgstage.com/product/product_detail/%s/";
let mgs = "https://www.mgstage.com/product/product_detail/%s/";
let fc2 = "https://javten.com/tw/search?kw=%s";//自动重定向,比fc2官网更快更稳
let carib = "https://en.caribbeancom.com/eng/moviepages/%s/index.html";
let caribpr = "https://en.caribbeancompr.com/moviepages/%s/index.html";
let _10musume = "https://en.10musume.com/movies/%s/";
let _1pondo = "https://en.1pondo.tv/movies/%s/";
let heyzo = "https://en.heyzo.com/moviepages/%s/index.html";
let pacopacomama = "https://en.pacopacomama.com/movies/%s/";
let nyoshin = "https://en.nyoshin.com/moviepages/%s/index.html";
let kin8tengoku = "https://en.kin8tengoku.com/moviepages/%s/index.html";
let xxxav = "https://www.xxx-av.com/mov/movie/%s/";
let tokyoHot_result = "https://my.tokyo-hot.com/product/?q=%s";
let tokyoHot_sample = "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4";
let ave = "https://www.aventertainments.com/search_Products.aspx?languageID=1&keyword=%s";
let specialProducers = {
"H4610": "https://en.h4610.com/moviepages/%s/index.html",
"H0930": "https://en.h0930.com/moviepages/%s/index.html",
"C0930": "https://en.c0930.com/moviepages/%s/index.html"
};
let notice_text = "正在搜索...<br/>搜索前请先确认番号的准确性,fc2的番号只搜索数字部分即可。";
//用于部分存在相同番号的片商,记录结果数量
let opened = 0;
//爬取过程中出错将不会关闭通知模态框
let error = false;
//三次超时强制中断就会停止搜索
let errorTimes = 0;
//用于记录常规番号的搜索结果中包含图片的首个结果,在没有预览视频时使用
let imgUrl;
//如abc-111
if (common_reg.test(id) && !heyzo_reg.test(id) && !gachi_reg.test(id)) {
await checkUser();
//ave常规格式番号前缀,优先爬取,提升搜索速度
const aveIds = ["sky", "skyhd", "cwp", "cwpbd", "lldv", "laf", "lafbd", "hey", "ccdv", "ssdv",
"smd", "smdv", "smbd", "pt", "bt", "s2m", "s2mbd", "mmdv", "mcdv", "mcbd", "mxx",
"drc", "drg", "drgbd", "dsam", "dsamd", "dsambd", "rhj", "jav", "pink"];
let prefix = id.split("-")[0];
showNotice(notice_text);
for (let aveId of aveIds) {
if (aveId === prefix.toLowerCase()) {
//前缀与aveId匹配,直接在ave搜索,然后终止方法
if (!await crawl(ave)) {
nothing();
}
return;
}
}
//常规有码的搜索引擎可以设置参不参与搜索
let brands = [];
let inputs = document.getElementById("sample_pick_form").getElementsByTagName("input");
for (let input of inputs) {
if (input.checked) {
brands.push(sampleMap.get(input.id).url);
}
}
brands.push(tokyoHot_result, ave/*没归纳的无码*/);
await crawlList(brands, true);
}
//如11aaa-11
else if (mgs_reg.test(id)) {
await checkUser();
showNotice(notice_text);
await crawlList([jav24, mgs/*需要日本ip*/]);
}
//6到7位纯数字
else if (fc2_reg.test(id)) {
//这种是没有同类番号,也不需要根据响应结果判断搜索结果是否存在,就直接打开
replaceAndOpen(fc2, false);
}
//6位-3位
else if (carib_reg.test(id)) {
replaceAndOpen(carib);
}
//6位_2位
else if (_10m_reg.test(id)) {
replaceAndOpen(_10musume);
}
//纯4位数字的heyzo等
else if (heyzo_k8_tokyoHot4_reg.test(id)) {
await checkUser();
showNotice(notice_text);
//每个有结果的都打开,
await crawl(heyzo);
await crawl(kin8tengoku);
await crawl(tokyoHot_sample);
if (opened === 0) {
//一个没找到
nothing();
}
}
//纯5位数字
else if (xxxav_reg.test(id)) {
await checkUser();
showNotice(notice_text);
if (!await crawl(xxxav)) {
nothing();
}
}
//带前缀heyzo-的
else if (heyzo_reg.test(id)) {
//取数字部分
id = id.split("-")[1];
replaceAndOpen(heyzo);
}
//6位_3位
else if (paco_1p_reg.test(id)) {
await checkUser();
showNotice("正在搜索...<br/>一本道存在结果将不爬取加勒比。");
await crawlList([_1pondo, pacopacomama, caribpr]);
}
//n开头加4位数字
else if (nyoshin_tokyoHotN_reg.test(id)) {
await checkUser();
showNotice(notice_text);
//nyoshin都没没什么源,番号却老重复,故优先TokyoHot,没结果再测nyoshin
await crawlList([tokyoHot_result, nyoshin]);
}
//其他字母加数字形式
else if (other_reg.test(id) && !gachi_reg.test(id)) {
//明确片商则无需重复打开
if (specialProducer) {
await replaceAndOpen(specialProducers[specialProducer]);
specialProducer = "";
return;
}
showNotice(notice_text);
await checkUser();
//TokyoHot的搜索结果会找不着,得在视频链接拼接试试
if (!await crawl(tokyoHot_result, {"user-agent": "windows"})) {
//即使找到视频也不知道详情页地址,直接播放即可
await crawl(tokyoHot_sample);
}
if (opened > 0) {
//如果在TokyoHot搜到视频就不再搜索了,番号一般不和下面的特殊片商重复
return;
}
//这三个片商番号极多重复
await crawl(specialProducers.H4610);
await crawl(specialProducers.H0930);
await crawl(specialProducers.C0930);
if (opened === 0) {
//一个没找到
nothing();
}
}
//其余形式未收录
else {
//其他番号尚未收录
if (gachi_reg.test(id)) {
showNotice("该片商官网已关闭。");
} else {
showNotice("输入错误或未收录该番号。");
}
}
function replaceAndOpen(url, autoplay = true) {
hideNotice();
setTmValue("autoplay", autoplay);
window.setTimeout(() => {
openInTab(url.replace("%s", id), {insert: true, loadInBackground: false});
}, 300);
}
async function crawlList(brands, needImg = false/*cen需要预览图,unc不需要*/) {
let beFound;
for (let brand of brands) {
beFound = await crawl(brand);
if (beFound) {
//找到一个就停
break;
}
}
if (!beFound) {
//一个视频都没找到就看预览图
if (needImg && imgUrl) {
setTmValue("show_img", true);
hideNoticeAndOpen(imgUrl, false);
} else {
nothing();
}
return false;
} else return true;
}
//爬取搜索结果或详情页中是否包含预览视频或图片
function crawl(url) {
let urlSplit = url.split("/");
let topLevelSite = urlSplit[0] + "//" + urlSplit[2];
let _id = id;
if (/mgstage\.com/.test(topLevelSite)) {
_id = _id.toUpperCase();
}
url = url.replace("%s", _id);
//默认3秒超时
let timeout = 3000;
if (/tokyo-hot.*samples/.test(url)) {
//视频链接,2秒内没404就表示存在
timeout = 2000;
} else if (/aventertainments.*search/.test(url)) {
//响应太慢了,实际上可以访问
timeout = 6000;
}
let headers = {};
if (/my.tokyo-hot.com\/product/.test(url)) {
headers = {"user-agent": "windows"};
}
let cookie = "";
if (/mgstage\.com/.test(topLevelSite)) {
cookie = "adc=1;";
}
//已经超时强制中断的不再显示请求超时的通知
let aborted = false;
return new Promise((resolve) => {
let connectTimeout = true;
let promise = GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: timeout,
headers: headers,
cookie: cookie,
cookiePartition: {
topLevelSite: topLevelSite
},
onload: async function (xhr) {
connectTimeout = false;
let finalUrl = xhr.finalUrl;
try {
/*
需要正则匹配的话,都得从开发者工具的网络工具中查看源码,不能在元素工具查看,不然会出现错误
注意id如cap-111和ap-11是不一样的
注意.不能替代[\\s\\S],同时浏览器有格式化功能,是否存在换行符需要看源码格式
*/
//ave结果,常规格式无码
if (/aventertainments/i.test(finalUrl)) {
/*
详情页https://www.aventertainments.com/40676/2/29/product_lists
商品番号 SMBD-18
源码 <span class="title">商品番号</span><span class="tag-title">SMBD-18</span>
*/
if (/lists/.test(finalUrl)) {
let searchWord = new RegExp(`<span class="tag-title">${_id}</span>`, "i");
if (xhr.responseText.search(searchWord) === -1) {
resolve(false);
} else {
hideNoticeAndOpen(finalUrl);
resolve(true);
}
}
/*搜索页 https://www.aventertainments.com/search_Products.aspx?keyword=SMBD-18
single-slider-product是存在结果时响应中存在的class*/
else if (xhr.responseText.search(/single-slider-product/i) === -1) {
resolve(false);
}
/*
即使有结果,该结果未必是搜索的番号,比如搜索bc会出现abc的结果
并且从响应中无法判断是否完全匹配,需要再到详情页比较避免搜索错误,这也是ave搜索慢的原因
搜索结果中的详情页url https://www.aventertainments.com/40676/2/29/product_lists
*/
else {
//爬取所有结果是否是与id匹配的
let matchArr = xhr.responseText.match(/https:\/\/www.aventertainments.com.*?product_lists/gi);
let urlSet = new Set();
for (let matchArrElement of matchArr) {
//很多有重复的
urlSet.add(matchArrElement);
}
let beFound;
for (let url of urlSet) {
beFound = await crawl(url);
if (beFound) break;
}
if (beFound) {
resolve(true);
} else resolve(false);
}
}
//trailer结果
else if (/trailer/i.test(finalUrl)) {
//详情页 https://javtrailers.com/video/ssis00411
if (/video/.test(finalUrl)) {
if (xhr.responseText.search("videoPlayerContainer") === -1) {
//image-container 图片的class,响应中有一个是css的选择器,所以超过一个就表示有图片
if (!imgUrl && xhr.responseText.search("gallery-button") > 0) {
//记录图片地址
imgUrl = finalUrl;
}
resolve(false);
} else {
hideNoticeAndOpen(finalUrl);
resolve(true);
}
}
/*结果页 https://javtrailers.com/search/SSIS-411
<img alt="SSIS-411 jav" class="card-img-top video-image"
"id空格jav在结果中只存在一个,开头双引号不能去掉,不然会有三个*/
else if (xhr.responseText.search(new RegExp(`"${_id} jav`, "i")) !== -1) {
//搜索到了,获取地址爬取 <div class="card-container">...href="/video/ssis00411"...没有换行符,是浏览器格式化了...alt="SSIS-411 jav"
let matchArray = xhr.responseText.match(new RegExp(`"(/video/[^"]*)"(.(?!card-container))*"${_id} jav"`, "i"));
if (matchArray) {
//使用日语网页
resolve(await crawl(topLevelSite + "/ja" + matchArray[1]));
}
} else {
resolve(false);
}
}
//land结果,不需要判断搜索结果,有会重定向
else if (/jav\.land/i.test(finalUrl)) {
switch (await checkCF(xhr)) {
case 1:
error = true;
resolve(false);
return;
case 2:
resolve(true);
return;
case 3:
resolve(false);
return;
}
if (/Too many connections/i.test(xhr.responseText)) {
error = true;
appendNotice("错误:jav.land请求过多被拦截。", true);
resolve(false);
return;
}
//jav.land搜索到会直接重定向到product页面,如果是请求地址没变代表没有搜索到 https://jav.land/tw/id_search.php?keys=WAAA-164
if (/id_search/i.test(finalUrl)) {
//没有搜索结果
resolve(false);
}
//如果请求被重定向表示有结果,https://jav.land/tw/movie/javw6eojvep.html
else if (/movie/i.test(finalUrl)) {
if (xhr.responseText.search("play_sample") !== -1) {
hideNoticeAndOpen(finalUrl);
resolve(true);
} else {
//<span id="waterfall"><a href="地址"><img src="地址" /></a>等等,还有一个waterfall字符串存在于<script>标签内
if (!imgUrl && xhr.responseText.match(new RegExp("waterfall", "gi")).length === 2) {
imgUrl = finalUrl;
}
resolve(false);
}
}
}
//jav24
else if (/jav24/i.test(finalUrl)) {
//mgs格式的视频预览
if (/mgstage/i.test(finalUrl)) {
if (xhr.status === 410) {
resolve(false);
} else {
resolve(true);
hideNoticeAndOpen(finalUrl);
}
return;
}
//https://www.jav24.com/?q=EROFV-130
//"/watch...."...my__product__code...EROFV-130<span
let _24reg = new RegExp(`"(/watch[^"]*)"(.(?!/watch/))*${_id}<span`, "i");
let match = xhr.responseText.match(_24reg);
if (match) {
hideNoticeAndOpen(topLevelSite + match[1]);
resolve(true);
} else {
resolve(false);
}
}
//mgstage
else if (/mgstage\.com/i.test(finalUrl)) {
if (/product/i.test(finalUrl)) {
//不存在会跳转到主页
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//javdb
else if (/javdb/i.test(finalUrl)) {
//搜索结果页 https://javdb.com/search?q=pts-437
if (/search/.test(finalUrl)) {
//日本ip会被拒绝,或者响应被限制的提示文本
if (xhr.status === 403 || xhr.responseText.search("prohibited"/*禁止*/) !== -1) {
error = true;
appendNotice("错误:javdb不能使用日本节点。", true);
resolve(false);
return;
}
//<a href="/v/ppARk"...包含换行符...<strong>PTS-437</strong>
let matchArr;
matchArr = xhr.responseText.match(new RegExp(`"(/v/[^"]*)"([\\s\\S](?!/v/))*>${_id}<`, "i"));
if (!matchArr) {
resolve(false);
return;
}
//找到了,去爬取详情页有没有视频存在
let uri = matchArr[1];
resolve(await crawl(topLevelSite + uri));
}
//详情页 https://javdb.com/v/DYvxa 可见地址中不包含id
else if (/\/v\//.test(finalUrl)) {
//详情页,存在视频时有个预告片a标签,preview-video-container是它的class
if (xhr.responseText.search("preview-video-container") !== -1) {
hideNoticeAndOpen(finalUrl);
resolve(true);
} else {
//data-caption是包含图片的a标签的特有属性,存在即有图片
if (!imgUrl && xhr.responseText.search(/data-caption/i) !== -1) {
//详情页存在图片,记录
imgUrl = finalUrl;
}
//往后搜索
resolve(false);
}
}
}
//TokyoHot结果,很多乱七八糟的番号需要搜索,地址中需要包含product,拼接番号访问sample的跳过
else if (/tokyo-hot.*q=/i.test(finalUrl)) {
//搜索页 https://my.tokyo-hot.com/product/?q=n1658
if (xhr.responseText.search(new RegExp("Product ID: " + _id, "i")) !== -1) {
//<a href="/product/n1658/" class="rm">...含有换行符...(Product ID: n1658)
let product_path = xhr.responseText.match(new RegExp(`(/product/[^/]*/)([\\s\\S](?!/product/))*ID: ${_id}`, "i"))[1];
hideNoticeAndOpen("https://my.tokyo-hot.com" + product_path);
opened++;
resolve(true);
} else {
resolve(false);
}
}
//nyoshin,拼接番号后请求,不存在结果会重定向到首页
else if (/nyoshin/i.test(finalUrl)) {
//https://en.nyoshin.com/moviepages/n2348/index.html
if (/moviepages/i.test(finalUrl)) {
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//xxxav,拼接方式请求,无结果重定向到首页
else if (/xxx-av/i.test(finalUrl)) {
//https://en.xxx-av.com/mov/movie/18059/
if (/movie/.test(finalUrl)) {
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//其他请求只要不是404就打开
else if (xhr.status !== 404) {
//这些片商番号重复的太多,搜索到也不能停,统计打开网页个数,因为最后要判断是否有找到结果
if (/heyzo|kin8tengoku|tokyo-hot|h4610|c0930|h0930/i.test(finalUrl)) {
opened++;
//不需要传参判断是否找到,都得测
resolve();
} else resolve(true);
hideNoticeAndOpen(url);
}
//404未找到
else {
resolve(false);
}
} catch (e) {
error = true;
appendNotice(`错误:${url},请联系开发者。<br/>${e.name}:${e.message}`, true);
resolve(false);
}
},
onerror: function () {
error = true;
connectTimeout = false;
appendNotice("错误:" + urlSplit[2] + ",机场不行或网站挂了。", true);
resolve(false);
},
ontimeout: function () {
error = true;
connectTimeout = false;
//访问的如果是视频资源,响应要很久才结束,所以两秒内没有响应404则默认存在该视频
if (/tokyo-hot.*samples/.test(url)) {
opened++;
hideNoticeAndOpen(url);
} else {
if (!aborted) {
appendNotice("超时:" + urlSplit[2], true);
}
resolve(false);
}
},
onprogress: function () {
connectTimeout = false;
}
});
setTimeout(() => {
promise.abort();
if (connectTimeout) {
error = true;
aborted = true;
appendNotice("超时强制中断:" + urlSplit[2], true);
errorTimes++;
if (errorTimes >= 3) {
appendNotice("你代理没开吧?", true);
resolve(true);
return;
}
resolve(false);
}
}, timeout);
});
}
function nothing() {
appendNotice("预览视频或图片皆未找到。", true);
}
//关闭模态框通知并打开指定页面
function hideNoticeAndOpen(url, autoplay = true) {
//db等需要自动点击,仅在第一次跳转时自动点击,再刷新就不用了
setTmValue("autoplay", autoplay);
if (!error) {
hideNotice();
}
window.setTimeout(() => {
openInTab(url, {insert: true, loadInBackground: false});
}, 300);
}
}
async function checkUser() {
//首次使用设置指引
if (!getTmValue("old_user")) {
showNotice("首次使用需要在弹出页面点选“总是允许全部域名”,将在3秒后弹出...");
await sleep(3000);
setTmValue("old_user", true);
}
}
//全局通知方法
function showNotice(notice) {
document.getElementById("jav_notice").innerHTML = notice;
noticeDialogObj.open();
}
function hideNotice() {
noticeDialogObj.close();
document.getElementById("jav_notice").innerHTML = "";
}
function appendNotice(notice, warning = false) {
let noticedDiv = document.getElementById("jav_notice");
if (warning) {
notice = "<span class='eerror'>" + notice + "</span>";
}
noticedDiv.innerHTML += (noticedDiv.innerHTML ? "<br/>" : "") + notice;
if (!noticeDialogObj.opened) {
noticeDialogObj.open();
}
}
//生成一个选择框,点击第一个按钮返回true,第二个为false
async function choose(words, name1, name2) {
document.getElementById("jav_choose").innerHTML = words + "<br/><div class='bbtn llink'>" + name1 +
"</div><div class='bbtn cclose'>" + name2 +
"</div>";
let result;
let btns = document.getElementById("jav_choose").getElementsByClassName("bbtn");
btns[0].addEventListener("click", () => {
chooseDialogObj.close();
result = true;
});
btns[1].addEventListener("click", () => {
chooseDialogObj.close();
result = false;
});
chooseDialogObj.open();
//阻塞,配合“点击背景无法关闭模态框”,等待点击完成才可以进行其他操作
while (true) {
if (result !== undefined) {
return result;
}
await sleep(500);
}
}
function removeSpaces(s) {
return s.replace(/^\s+/, '').replace(/\s+$/, '');
}
function getVideosInfo() {
let videos = document.getElementsByTagName("video");
let num = videos.length;
let info = `${top === window ? "Top>" : "JavFrame>"}${location.host}(${num})${num > 0 ? ":" : ""}`;
let count = 0;
for (let i = 0; i < num; i++) {
if (videos[i].readyState !== 0) {
let video = videos[i];
info += `<br/>video${i}:${video.videoWidth}*${video.videoHeight}`;
count++;
}
}
if (count === 0 && num > 0) {
info += "<br/>null";
}
return info;
}
//同步等待若干时间
function sleep(time) {
return new Promise((resolve) => {
setTimeout(() => resolve(), time);
});
}
function openInTab(url, option) {
GM_openInTab(url, option);
}
function getTmValue(key) {
return GM_getValue(key);
}
function setTmValue(key, value) {
GM_setValue(key, value);
}
function FloatBall(id, option) {
if (!id) {
throw new Error("FloatBall:创建悬浮球失败,至少需要指定id。");
}
if (document.getElementById(id)) {
throw new Error("FloatBall:创建悬浮球失败,指定id的元素已存在。");
}
if (!option) {
option = {};
}
let defaultOption = {
color: "red",
diameter: "40px",
opacity: "0.5"
};
for (let name in defaultOption) {
if (option[name] === undefined) {
option[name] = defaultOption[name];
// console.log(`FloatBall:未传入参数${name},将使用默认值${defaultOption[name]}。`);
}
}
console.log("FloatBall:配置完毕");
//添加默认的css,::-webkit-scrollbar只有id选择器可以用
//伪类中的highlight设置为透明,解决部分网站点击小球出现蓝色高光的问题
//类名首字母写两位,防止与被注入的网站冲突
let cssCode = '.bball{position:fixed;cursor:pointer;border-radius:99px;z-index:999999;}' +
'.bball:focus,.bball:active,.bball:hover{-webkit-tap-highlight-color:transparent;}';
addCSS(cssCode);
//使用div作为小球的元素,一方面div一般不会被加样式,一方面在移动端选词后点击可以直接取消选词,button就不行
//a标签也由于本身的意义不适合作为小球的元素
let ball = document.createElement("div");
ball.id = id;
ball.className = "bball";
ball.style.width = option.diameter;
ball.style.height = option.diameter;
ball.style.opacity = option.opacity;
ball.style.backgroundColor = option.color;
let ballX;
let ballY;
//从插件获取悬浮球上次的位置,不存在就使用默认位置
let lastPosition = getTmValue(ball.id);
if (lastPosition) {
let position = lastPosition.split(",");
//数值异常或越界使用默认位置
if (position[0] > window.innerWidth || position[1] > window.innerHeight) {
move(0, 0.5 * window.innerHeight);
} else move(position[0], position[1]);
} else {
move(0, 0.5 * window.innerHeight);
}
document.body.prepend(ball);
//鼠标与小球位置差值
let _x;
let _y;
//不同设备需要区别触发事件
let events;
if (isAndroid) {
events = ["touchstart", "touchmove", "touchend"];
} else events = ["mousedown", "mousemove", "mouseup"];
let moveListenerHandler = function (e) {
e.preventDefault();
//移动端多指触控处理
if (e.type === "touchmove") {
e = e.touches[0];
}
// console.log("touchmove/mousemove:移动小球。");
//移动小球时需要保持相对位置,同时处理屏幕左方与上方越界
move(e.pageX - _x < 0 ? 0 : e.pageX - _x, e.pageY - _y < 0 ? 0 : e.pageY - _y);
};
let freeHandler = function () {
//手指或鼠标放开,清除移动监听器,随后记录位置到插件
document.removeEventListener(events[1], moveListenerHandler);
console.log("mouseup/touchend:已清除移动事件监听。");
setTmValue(ball.id, ballX + "," + ballY);
//清除自身
document.removeEventListener(events[2], freeHandler);
console.log("mouseup/touchend:已清除抬起事件监听。");
};
//当小球被按下或触摸开始,添加移动监听器和抬起监视器
ball.addEventListener(events[0], (e) => {
//移动端多指触控处理
if (e.type === "touchstart") {
e = e.touches[0];
}
//记录小球位置与鼠标位置的差值
_x = e.pageX - ballX;
_y = e.pageY - ballY;
document.addEventListener(events[1], moveListenerHandler, {passive: false}/*解决移动端上下移动小球触发屏幕滑动*/);
console.log("mousedown/touchstart:已添加移动事件监听器。");
document.addEventListener(events[2], freeHandler);
console.log("mousedown/touchstart:已添加抬起事件监听器。");
});
function move(x, y) {
//获取直径数值
let diameter = Number(option.diameter.replace("px", ""));
//处理屏幕右方与下方越界
x = x > window.innerWidth - diameter ? window.innerWidth - diameter : x;
y = y > window.innerHeight - diameter ? window.innerHeight - diameter : y;
ballX = x;
ballY = y;
ball.style.left = x + "px";
ball.style.top = y + "px";
}
}
function ModalDialog(innerHTML, option) {
if (!innerHTML) {
innerHTML = "";
}
if (!option) {
option = {};
}
//参数校验
let defaultOption = {
dialogPosition: "middle",
width: "300px",
backgroundColor: "white",
backgroundOpacity: "0.8",
//是否可以通过点击背景关闭模态框
canBeClose: true
};
if (option.dialogPosition && !/top|middle|bottom/.test(option.dialogPosition)) {
option.dialogPosition = "middle";
console.log("ModalDialog:位置名称错误,将使用middle位置。");
}
for (let name in defaultOption) {
if (option[name] === undefined) {
option[name] = defaultOption[name];
// console.log("ModalDialog:未传入配置" + name + ",将使用默认配置" + defaultOption[name] + "。");
}
}
console.log("ModalDialog:配置完毕");
//添加默认的css,::-webkit-scrollbar不能把_下划线开头的选择器放在前面,会失效
//类名首字母写两位,防止与被注入的网站冲突
let cssCode = '.ddialogBackground{position:fixed;inset:0px;visibility:hidden;background:rgba(0,0,0,0);}' +
'.ddialog{overflow-y:overlay;box-sizing:border-box;margin:auto;border-radius:8px;visibility:hidden;text-align:center;opacity:0;}' +
'.ddialog::-webkit-scrollbar{display:none;}';
let transitionCss = '.ddialogBackground{transition:background 0.3s,visibility 0.3s;}' +
'.ddialog{transition:opacity 0.3s,visibility 0.3s,margin-top 0.3s linear,bottom 0.3s linear;}';
addCSS(cssCode);
if (!isAndroid) {
addCSS(transitionCss);
}
//模态框全屏容器,压暗
let dialogBackground = document.createElement("div");
dialogBackground.className = "ddialogBackground";
//添加点击事件,点击背景可以关闭当前模态框
if (option.canBeClose) {
dialogBackground.addEventListener("click", (e) => {
if (e.target !== dialogBackground) {
return;
}
this.close();
}, true);
}
document.body.prepend(dialogBackground);
//模态框
let dialog = document.createElement("div");
dialog.innerHTML = innerHTML;
dialog.className = "ddialog";
dialog.style.maxHeight = window.innerHeight - 90 + "px";
dialog.style.width = option.width;
dialog.style.backgroundColor = option.backgroundColor;
dialogBackground.prepend(dialog);
if (option.dialogPosition === "top") {
//移动端考虑到性能,不整动画
if (isAndroid) {
dialog.style.marginTop = "60px";
} else {
//60-20,给20做动画,动画结束后是60
dialog.style.marginTop = "40px";
}
} else if (option.dialogPosition === "middle") {
let a, b;
if (isAndroid) {
a = 0;
b = 60;
} else {
//-20,打开时会+20
a = 20;
b = 40;
}
let marginTop = Math.floor(innerHeight * 0.5 - dialog.scrollHeight / 2) - a;
dialog.style.marginTop = (marginTop < b ? b : marginTop) + 'px';
} else if (option.dialogPosition === "bottom") {
dialog.style.position = 'absolute';
dialog.style.left = '50%';
dialog.style.marginLeft = '-' + dialog.clientWidth / 2 + 'px';
if (isAndroid) {
dialog.style.bottom = '30px';
} else {
//动画结束后是30
dialog.style.bottom = '10px';
}
}
//开关状态
this.opened = false;
//定义打开与关闭方法
this.open = function () {
//打开时指定z-index,模态框依次叠加
if (!window.maxDialogZIndex) {
//7个9,基数
//并非每次都是回到这个数值,因为有允许先开的先关,此时最大值是不该递减的
//因为后开那个才是最上层,最终关完,提前关了几个,数值就会大几
window.maxDialogZIndex = 9999999;
}
dialogBackground.style.zIndex = window.maxDialogZIndex + 1;
window.maxDialogZIndex += 1;
//显示
//下面的样式书写书序没有影响
dialogBackground.style.background = "rgba(0,0,0," + option.backgroundOpacity + ")";
dialogBackground.style.visibility = "visible";
dialog.style.opacity = "1";
dialog.style.visibility = "visible";
if (!isAndroid) {
if (option.dialogPosition === "bottom") {
dialog.style.bottom = '30px';
} else {
let top = Number(dialog.style.marginTop.replace("px", "")) + 20;
dialog.style.marginTop = top + 'px';
}
}
this.opened = true;
};
this.close = function () {
//显示
dialogBackground.style.visibility = "hidden";
dialogBackground.style.background = "rgba(0,0,0,0)";
dialog.style.visibility = "hidden";
dialog.style.opacity = "0";
if (!isAndroid) {
if (option.dialogPosition === "bottom") {
dialog.style.bottom = '10px';
} else {
let top = Number(dialog.style.marginTop.replace("px", "")) - 20;
dialog.style.marginTop = top + 'px';
}
}
//修改最大z-index
//需要判断关闭的是不是最上层的再递减全局变量
if (dialogBackground.style.zIndex === String(window.maxDialogZIndex)) {
window.maxDialogZIndex -= 1;
}
this.opened = false;
};
}
function WindowBling(option) {
if (!option) {
option = {};
}
let defaultOption = {
color: "purple",
blingPosition: ["top", "bottom", "left", "right"],
blurRadius: "80px",
spreadRadius: "30px"
};
for (let name in defaultOption) {
if (option[name] === undefined) {
option[name] = defaultOption[name];
// console.log("WindowBling:未传入参数" + name + ",将使用默认值" + defaultOption[name] + "。");
}
}
console.log("WindowBling:配置完毕");
let blingArray = new Array(4);
//opacity,visibility需要改动,写在元素里
let topAndBottomStyle = "left:0px;right:0px;height:0px;opacity:0;visibility:hidden;";
let leftAndRightStyle = "top:0px;bottom:0px;width:0px;opacity:0;visibility:hidden;";
//通用css,使用选择器的方式方便调试
let cssCode = '.bbling{z-index:999998;position:fixed;transition:opacity 0.5s,visibility 0.5s;';
cssCode += "box-shadow:" + "0 0 " + option.blurRadius + " " + option.spreadRadius + " " + option.color + ";}";
addCSS(cssCode);
//给传入需要bling的位置创建元素并指定各自的样式
for (let position of option.blingPosition) {
let bling = document.createElement("div");
bling.className = "bbling";
bling.style[position] = "0px";
if (position === "top" || position === "bottom") {
bling.style.cssText += topAndBottomStyle;
} else if (position === "left" || position === "right") {
bling.style.cssText += leftAndRightStyle;
}
document.body.prepend(bling);
blingArray.push(bling);
}
this.blink = function () {
let handler = function () {
for (let bling of blingArray) {
if (bling) {
if (bling.style.opacity === "0") {
bling.style.opacity = "1";
} else {
bling.style.opacity = "0";
}
if (bling.style.visibility === "hidden") {
bling.style.visibility = "visible";
} else {
bling.style.visibility = "hidden";
}
}
}
};
//只闪一下
handler();
setTimeout(handler, 500);
};
}
})();