// ==UserScript==
// @name 搜索那个
// @description 日本影视行业内部搜索工具Pro Max Plus Ultra
// @author shopkeeperV
// @namespace https://greasyfork.org/zh-CN/users/150069
// @version 1.1.6
// @match *://*/*
// @resource css https://greasyfork.org/scripts/470136-javsearch-css/code/JavSearch-CSS.user.css
// @connect *
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// @grant GM_registerMenuCommand
// ==/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 (e.data.includes("JavSearch")) {
//发送页面视频信息
window.parent.postMessage(getVideosInfo(), '*');
//通知iframe里面的子iframe
noticeSubIframe();
}
//收到子iframe的信息再往上层传递
else if (e.data.includes("JavFrame>")) {
//发送页面视频信息
window.parent.postMessage(e.data, '*');
}
});
}
//为此iframe添加message事件后无需往后执行
return;
}
//设置样式,通知模态框要用
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 = buildBtnMap();
let cenMap;
let uncMap;
let sampleMap;
let dialogPosition, blurRadius, spreadRadius;
if (/android/i.test(navigator.userAgent)) {
dialogPosition = "bottom";
blurRadius = "50px";
spreadRadius = "30px";
} else {
dialogPosition = "middle";
blurRadius = "500px";
spreadRadius = "60px";
}
let searchDialogObj = new ModalDialog(searchDialogCode(), {
width: "310px",
dialogPosition: dialogPosition
});
let settingDialogObj;
//大量使用,单独声明,注意需要模态框创建后才有,写在后面
let textInput = document.getElementById("jav_input");
//创建悬浮球
new FloatBall("jav_ball", {color: "#00cec9"});
//让屏幕周围亮起来,用于链接分词
let blingObj = new WindowBling({
color: "#00cec9",
blingPosition: ["left", "right"],
blurRadius: blurRadius,
spreadRadius: spreadRadius
});
//全局设置
if (GM_getValue("sample_with_sound") == null) {
GM_setValue("sample_with_sound", true);
}
if (GM_getValue("mosaic_reduce_first") == null) {
GM_setValue("mosaic_reduce_first", false);
}
//第一阶段初始化,绑定必须的监听事件,小球,链接分词,划词等,其余的在首次点击小球后再初始化,以节约性能
init1();
function buildBtnMap() {
let map = new Map();
map.set("avmoo有码情报", "https://avmoo.online/cn/search/%s#info");
map.set("avsox无码情报", "https://avsox.click/cn/search/%s#info");
map.set("javdb情报", "https://javdb.com/search?q=%s&f=all#info");
map.set("google+jav", "https://www.google.com/search?q=%s%20jav");
//搜索集合
map.set("搜索有码", "censoredSet");
map.set("搜索无码", "uncensoredSet");
return map;
}
function buildCenMap() {
let map = new Map();
let part1 = function () {
map.set("supjav破解", "https://supjav.com/zh/?s=%s#get");
}
let part2 = function () {
map.set("7mm字幕", "https://7mmtv.sx/zh/searchform_search/all/index.html#post#search_keyword=%s&search_type=searchall&op=search");
map.set("missav字幕", "https://missav.com/cn/search/%s#get");
map.set("ggjav字幕", "https://ggjav.com/main/search?string=%s#get");
map.set("netflav字幕", "https://netflav5.com/search?type=title&keyword=%s#get");
map.set("777字幕", "https://www.jav777.xyz/?s=%s#get");
}
if (GM_getValue("mosaic_reduce_first")) {
part1();
part2();
} else {
part2();
part1();
}
//搜索到#pointcut时,标志着破解版本和字幕版本不存在,打开之前保留的普通版本
map.set("-----", "#pointcut")
map.set("today", "https://javhd.today/search/video/?s=%s#get");
return map;
}
function buildUncMap() {
let map = new Map();
map.set("tasexy", "https://www.tasexy.com/?module=tags&action=keyword#post#keyword=%s");
map.set("ggjav无码", "https://ggjav.com/main/search?string=%s#get");
map.set("netflav无码", "https://netflav5.com/search?type=title&keyword=%s#get");
map.set("7mm无码", "https://7mmtv.sx/zh/searchform_search/all/index.html#post#search_keyword=%s&search_type=uncensored&op=search");
//主要搜索fc2
map.set("missav无码", "https://missav.com/cn/fc2-ppv-%s#get");
map.set("supjav无码", "https://supjav.com/zh/?s=%s#get");
return map;
}
//为常规格式的(aaa-111)视频预览引擎也添加设置选项,防止他们抽风,其他格式的引擎不需要是因为他们都是独一无二的,抽风也没得换
function buildSampleMap() {
let map = new Map();
map.set("trailer", "https://javtrailers.com/search/%s");
map.set("jav24", "https://www.jav24.com/?q=%s");
map.set("javLand", "https://jav.land/tw/id_search.php?keys=%s");
map.set("javdb", "https://javdb.com/search?q=%s&f=all");
return map;
}
function searchDialogCode() {
return '<div id="jav_dialogContent" style="margin-top:15px;margin-bottom:10px;">' +
'<div>' + getBtns() + '</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 style='width:200px;margin:20px auto;font:14px/1.6 sans-serif;color:#00baf8;'>" +
"<div style='font-weight:bold;margin-bottom:10px;'>下列按钮设置效果永久保留。</div>" +
"<div id='sample_with_sound' class='jav_global_setting'>预览视频尝试有声播放</div>" +
"<div id='reducing_mosaic_first' class='jav_global_setting'>优先搜索无码破解版本</div>" +
"<div style='font-weight:bold;margin-top:10px;'>下列开关决定相应引擎是否参与搜索,在搜索结果不理想时使用,仅在当前页面生效。</div>" +
"<form id='jav_pick_form' style='text-align:center;'>" +
"<div style='margin-top:10px;'>" +
"<b>有码引擎</b><br/>" +
getBtns("censoredSet") +
"<div style='margin-top:10px;'>" +
"<b>无码引擎</b><br/>" +
getBtns("uncensoredSet") +
"<div style='margin-top:10px;'>" +
"<b>预览引擎</b><br/>" +
getBtns("sampleSet") +
"</div>" +
"</form>" +
"</div>" +
"</div>";
function getBtns(type) {
let result = "";
let map;
switch (type) {
case "censoredSet":
map = cenMap;
break;
case "uncensoredSet":
map = uncMap;
break;
case "sampleSet":
map = sampleMap;
}
map.forEach((value, key) => {
let parts = value.split("/");
result += "<input type='checkbox' name='" + type + "' value='" + value + "' checked id='" + key + "'><label for='" + key + "'>" + key + "</label>" +
"<div class='jav_go' jav_url='" + parts[0] + "//" + parts[2] + "'>前往</div>" +
"<br/>";
});
return result;
}
}
function noticeSubIframe() {
let iframes = document.getElementsByTagName("iframe");
for (let iframe of iframes) {
iframe.contentWindow.postMessage("JavSearch"/*JavSearch作为标志标明是此脚本发送的信息*/, '*');
}
}
function initStyle() {
//部分通用的需要用到选择器的样式或是太长的样式,定义在外部
addCSS(GM_getResourceText("css"));
//下面是特定网页的样式
let host = location.host;
let path = location.pathname;
//javdb
if (/javdb/.test(host)) {
addCSS("body{margin-right:0px !important;}");
}
//fc2
else if (/fc2.com/.test(host) && /article/.test(path)) {
addCSS('#fc2Div>div{background-color:#00cec9;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;}');
}
//7mm
else if (/7mmtv/.test(host)) {
//广告图,css隐藏比js快
addCSS("img[src$='.gif']{display:none;}img[src*='gif.7mmtv.sx']{display:none;}");
//去视频悬浮层
addCSS(".mvspan_2_s_k_i_p_row{display:none;}");
}
}
function addCSS(css) {
let style = document.createElement("style");
//放body里是因为部分网页重写head标签
document.body.prepend(style);
style.textContent = css;
}
//此方法可以完成一些网站的自动点击、自动播放等,也对一些网站进行功能定制
async function adviceAndReturn() {
//需要强制播放的窗口,有时是iframe所以需要改变的
let videoWindow = window;
//强制播放定时器
let forcePlayTimer;
let voiceDiv;
let failTimes = 0;
let host = location.host;
let hash = location.hash;
let path = location.pathname;
//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 (/android/i.test(navigator.userAgent)) {
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();
});
}
//jav.land
else if (/jav\.land/.test(host) && /#autoclick/.test(hash) && GM_getValue("is_new_popup")) {
GM_setValue("is_new_popup", false);
let sample = document.getElementById("play_sample");
autoClick(sample);
}
//mgs
else if (/mgstage\.com/.test(host) && /#autoclick/.test(hash) && GM_getValue("is_new_popup")) {
GM_setValue("is_new_popup", false);
let btn;
if (/android/i.test(navigator.userAgent)) {
btn = document.getElementById("sample-play");
} else {
btn = document.getElementsByClassName("button_sample")[0];
}
btn && autoClick(btn);
}
//javdb
else if (/javdb/.test(host)) {
let video = document.getElementsByTagName("video")[0];
if (GM_getValue("sample_with_sound")) {
if (video) video.muted = false;
}
if (/#autoclick/.test(hash) && GM_getValue("is_new_popup")) {
GM_setValue("is_new_popup", false);
let sample = document.getElementsByClassName("preview-video-container")[0];
autoClick(sample);
}
if (/#autoscroll/.test(hash)) {
let images = document.getElementsByClassName("preview-images")[0];
document.documentElement.scrollTop = images.offsetTop;
}
}
//heyzo没有预览视频时将小图片替换高清图,无码片商中仅heyzo可以替换,因为他图片的url格式是固定的
else if (/heyzo\.com/.test(host)) {
//1秒后检测有没有视频元素,没有就展示预览大图
setTimeout(showImg, 1000);
function showImg() {
//没有视频才显示图片
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);
}
}
}
}
//trailer,没视频的打开相册
else if (/trailer/.test(host) && /#autoclick/.test(hash) && GM_getValue("is_new_popup")) {
GM_setValue("is_new_popup", false);
document.getElementsByClassName("videoWrapper")[0].remove();
let picBtn = document.getElementById("thumbnailContainer").getElementsByTagName("button")[0];
autoClick(picBtn);
}
//TokyoHot,只有移动端需要自动点击
else if (/tokyo-hot/.test(host) && /#autoclick/.test(hash) && /android/i.test(navigator.userAgent)) {
let playDiv = document.getElementsByClassName("fp-ui")[0];
console.log("尝试点击");
//不一定有视频
playDiv && autoClick(playDiv);
console.log("点击成功");
}
if (/#autoplay/.test(hash)) {
voiceDiv = document.createElement("div");
//给个通知,明确自动播放程序运行中
voiceDiv.textContent = "等待视频加载...";
voiceDiv.id = "mmuted";
let _width = 180;
voiceDiv.style.backgroundColor = "#ff4b4b";
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);
voiceDiv.addEventListener("click", () => {
voiceDiv.style.display = "none";
});
//部分网站通过js加载视频或需要点击,所以会出现延迟,需要不停尝试获取页面的视频播放
forcePlayTimer = window.setInterval(forcePlay, 500);
}
//默认情况不终止脚本执行
return false;
async function forcePlay() {
if (failTimes > 10) {
//10秒了还没播放
console.log("自动播放失败,视频不存在或视频异常。");
voiceDiv.textContent = "播放失败</br>请尝试日本节点";
window.clearInterval(forcePlayTimer);
}
console.log("自动播放:尝试强制播放,每0.5s一次。");
if (!videoWindow) return;
let video = videoWindow.document.getElementsByTagName("video")[0];
if (!video) {
failTimes++;
return;
}
if (video.preload === "none") {
video.preload = "metadata";
}
//个别网站display不写在内联样式里,需要以这种方式获取所有的样式getComputedStyle
if (window.getComputedStyle(video).display === "none" || video.readyState === 0) {
failTimes++;
return;
}
window.clearInterval(forcePlayTimer);
console.log("自动播放:获取到视频元素,清除自动播放循环定时器。");
/*
默认先试试有声播放,可以修改该设置
浏览器以没有用户操作为由拒绝,桌面端有个浏览器参数控制,用久了可以有声播放,移动端不行
失败后尝试无声播放
*/
let mutedPlay = async function () {
video.muted = true;
console.log("自动播放:设为无声。");
await video.play();
voiceDiv.addEventListener("click", () => {
video.muted = false;
});
//无声播放将通知改为按钮
voiceDiv.style.backgroundColor = "#00cec9";
voiceDiv.textContent = "点我解除静音";
console.log("自动播放:强制播放成功。");
}
if (GM_getValue("sample_with_sound")) {
video.muted = false;
console.log("自动播放:设为有声,尝试执行play方法。");
try {
console.log("自动播放:等待play方法返回。");
await video.play();
voiceDiv.style.display = "none";
console.log("自动播放:强制播放成功。");
} catch (e) {
console.log("自动播放:执行play方法失败。" + e);
await mutedPlay();
}
} else await mutedPlay();
}
function autoClick(eventTarget) {
let e = document.createEvent("MouseEvents");
e.initEvent("click", true, true);
eventTarget.dispatchEvent(e);
}
}
function init1() {
observeNodeIncrement();
//首次链接分词监听
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() {
//观察document所有后代节点
//Node是Element的父类,注意节点node和元素element是不同对象,node无法转成element
const config = {childList: true, subtree: true};
let observer = new MutationObserver(nodeChangeCallback);
observer.observe(document, config);
console.log("dom观察:开始观察dom变化。");
function nodeChangeCallback(mutationsList) {
for (let mutation of mutationsList) {
if (mutation.addedNodes.length > 0) {
console.log("dom观察:检测到dom变化,并且是增加了节点。");
} else continue;
//只需要检测增加的节点,因为增加的a标签才需要重新绑定事件
for (let node of mutation.addedNodes) {
if (node.nodeType === 1 && node.nodeName.toLowerCase() !== "br"/*排除换行符,我们的notice常用到*/) {
//是元素增减,触发a标签事件重置,没办法判断node是不是含有a标签,只能这么粗暴了
addLinkListener();
console.log("dom观察:增加的是元素节点,已重置所有a标签事件。");
return;
}
}
}
}
}
//https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/EventLoop
function addLinkListener() {
//获取页面所有链接,添加事件获取文本内容,移动端滑动触发,桌面端鼠标悬浮一会触发
let links = document.getElementsByTagName("a");
//链接分词监听,所有a标签
for (let i = 0; i < links.length; i++) {
//a标签有文字节点且有内容才添加事件
if (!links[i].getAttribute("jav_listen") &&
links[i].textContent.search(/\S+?/i) >= 0) {
//为了node的增减,都需要先清除原先的事件,再添加
if (/android/i.test(navigator.userAgent)) {
links[i].addEventListener("touchstart", getLinkText);
} else {
//桌面端鼠标悬浮触发
links[i].addEventListener("mousemove", rebuildLinkTimer/*鼠标移动的话不停重置定时器*/);
//离开时也清除定时器
links[i].addEventListener("mouseleave", clearLinkTimeout);
}
links[i].setAttribute("jav_listen", "true");
}
}
function rebuildLinkTimer(event) {
//在链接上移动鼠标需要重新设置定时器
timeoutTimer && clearTimeout(timeoutTimer);
timeoutTimer = setTimeout(() => {
getLinkText(event);
}, 600);
}
function clearLinkTimeout() {
timeoutTimer && clearTimeout(timeoutTimer);
}
function getLinkText(event) {
textContent = event.target.textContent;
if (separate(true)) isNewLinkWord = true;
}
}
function getSelectedWord() {
let selectWords = window.getSelection().toString();
if (selectWords) {
//需要清除首尾空格
historyKeyword = removeSpaces(selectWords);
//有一种情况,触发链接分词没有打开悬浮球,而是又进行了选词,这时应该使用最新操作的选词文本
isNewLinkWord = false;
}
}
}
//更多功能的第二阶段初始化
function init2() {
cenMap = buildCenMap();
uncMap = buildUncMap();
sampleMap = buildSampleMap();
settingDialogObj = new ModalDialog(settingCode(), {
width: "250px",
dialogPosition: dialogPosition
});
//接收iframe发来的数据,绕过iframe安全机制
window.addEventListener('message', e => {
//需要判断一下内容,部分网站自己也会通信,会触发到这个方法
if (e.data.includes("JavFrame>")) {
appendNotice(e.data, false);
}
});
//给搜索按钮添加事件
btnMap.forEach((value, key) => {
let btn = document.getElementById(key);
btn.addEventListener("click", btnHandler);
});
let btnGoArray = document.getElementsByClassName("jav_go");
for (let go of btnGoArray) {
go.onclick = () => {
GM_openInTab(go.getAttribute("jav_url"), {insert: true, loadInBackground: false});
};
}
//使用说明
document.getElementById("jav_description").addEventListener("click", () => {
GM_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,通知他们响应视频信息
noticeSubIframe();
});
document.getElementById("jav_setting").addEventListener("click", () => {
settingDialogObj.open();
});
document.getElementById("sample_with_sound").addEventListener("click", () => {
let sample_with_sound = GM_getValue("sample_with_sound");
if (sample_with_sound) {
if (confirm("目前【预览视频尝试有声播放】已开启,是否要关闭?")) {
GM_setValue("sample_with_sound", !sample_with_sound);
}
} else {
if (confirm("目前【预览视频尝试有声播放】已关闭,是否要开启?")) {
GM_setValue("sample_with_sound", !sample_with_sound);
}
}
});
document.getElementById("reducing_mosaic_first").addEventListener("click", () => {
let mosaic_reduce_first = GM_getValue("mosaic_reduce_first");
if (mosaic_reduce_first) {
if (confirm("目前【优先搜索无码破解版本】已开启,是否要关闭?")) {
GM_setValue("mosaic_reduce_first", !mosaic_reduce_first);
document.location.reload();
}
} else {
if (confirm("目前【优先搜索无码破解版本】已关闭,是否要开启?")) {
GM_setValue("mosaic_reduce_first", !mosaic_reduce_first);
document.location.reload();
}
}
});
//模态框内输入框添加离焦事件更新按钮事件
textInput.addEventListener("blur", () => {
//保留输入历史
historyKeyword = removeSpaces(textInput.value);
textInput.placeholder = "请输入";
});
}
//分词方法,blink表示是否分词后闪烁屏幕
function separate(blink) {
//输入框置空,显示placeholder的提示内容
textInput.value = "";
if (textContent === "") {
textInput.placeholder = "文本内容不存在";
return;
}
//匹配正则数组
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 (!specialProducer && new RegExp(producer, "i").test(textContent)) {
//若视频预览功能匹配到响应格式的番号,将使用该片商的的预览地址
specialProducer = producer;
}
textContent = textContent.replace(producer, "").replace(producer.toLowerCase(), "");
}
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) {
if (xhr.responseText.search(/you have been blocked/i) >= 0) {
//被cloudflare拉黑
appendNotice("错误:" + xhr.finalUrl.split("/")[2] + "访问失败,请更换代理。", true);
return 1;
} else if (xhr.responseText.search(/Enable JavaScript and cookies to continue/i) >= 0) {
//需要真人验证
changeNotice(xhr.finalUrl.split("/")[2] + "访问失败,请在弹出页面完成真人验证。", true);
await sleep(1000);
GM_openInTab(xhr.finalUrl, {insert: true, loadInBackground: false});
if (confirm("是否完成了真人认证?"))
return 2;
else return 1;
} else return 0;
}
//搜索按钮处理器
async function btnHandler(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)/*也不能全是空白符*/) {
//判断输入内容是否规范,有些按钮不是简单的拼接打开,存在业务逻辑
if (await searchAdviceAndReturn(url, _keyword)) return;
//给自动点击的网站识别是否是首次访问,防止刷新或历史记录问题又自动点击
GM_setValue("is_new_popup", true);
GM_openInTab(url.replace("%s", removeSpaces(_keyword)), {insert: true, loadInBackground: false});
}
//没有搜索词
else {
if (/censoredSet/.test(url)) {
showNotice("请输入正确的番号。");
} else {
//其余跳到主页
let parts = url.split("/");
GM_openInTab(parts[0] + "//" + parts[2], {insert: true, loadInBackground: false});
}
}
async function searchAdviceAndReturn(sign, _keyword) {
//用于判断是否在过程中出错,如果出错就不关闭通知
let error = false;
//情报站不要求输入的是番号
if (!/#info/.test(sign) && !/^[a-z0-9]+[-_]?[a-z0-9]+$/i.test(_keyword)) {
showNotice("请输入正确的番号。");
return true;
}
await checkUser();
//不是聚合搜索,直接替换关键词打开
if (/^censoredSet/.test(sign)) {
showNotice("正在搜索...</br>此按钮搜索如fanza的aaa-111、mgs的111aaaa-111等常见有码格式的番号,默认优先搜索中文字幕版本,可更改为无码破解优先。");
} else if (/^uncensoredSet/.test(sign)) {
showNotice("正在搜索...</br>此按钮搜索如fc2的111111、一本道111111-111、东京热的n1111、heyzo-111等常见无码格式的番号。");
} else {
return false;
}
let selected = [];
//定义网址#第一个参数,提交方式,#第二个参数,如果是post方式就是请求体
let inputs = document.getElementById("jav_pick_form").getElementsByTagName("input");
for (let input of inputs) {
if (input.name === sign && input.checked) {
selected.push(input.value);
}
}
let beFound = false;
//若没有字幕或无码破解版本但存在普通视频,将链接保存,在指定搜索结果都没找到到时使用
let savingUrl = "";
for (let url of selected) {
if (/#pointcut/.test(url)) {
//指定引擎搜索不到后,如果前面有普通版本也打开
if (savingUrl) {
//打开特殊引擎的保留地址
hideNotice();
//凡是要隐藏通知然后打开新窗口的,都要等动画结束再打开,不然移动端回到页面会继续未完成动画,造成残影
//延迟时间与模态框淡出持续事件相等即可
setTimeout(() => {
GM_openInTab(savingUrl, {insert: true, loadInBackground: false});
}, 300);
return true;
}
//分割线,不执行搜索
continue;
}
beFound = await crawl(url, _keyword);
if (beFound) break;
}
if (!beFound) {
appendNotice("未找到结果,将尝试谷歌搜索。", true);
setTimeout(() => {
GM_openInTab("https://www.google.com/search?q=%s%20jav".replace("%s", _keyword), {
insert: true,
loadInBackground: false
});
}, 1000);
}
//默认不会再执行后续的“弹窗到搜索引擎主页”
return true;
function crawl(url, id) {
let reqWay;
let payload;
let params = url.split("#");
reqWay = params[1];
if (/missav/.test(url)) {
//需要将mgs格式改为常规格式
if (/^[0-9]+[a-z]+-[0-9]+$/i.test(id)) {
id = id.replace(/[0-9]*(?=[a-z])/i, "");
}
}
if (/#post/.test(url)) {
payload = params[2].replace("%s", id);
}
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: reqWay,
url: url.replace("%s", id),
data: payload,
timeout: 4000,
headers: payload ? {"content-type": "application/x-www-form-urlencoded"} : {},
onload: async function (xhr) {
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(xhr.finalUrl)) {
//<a target="_top" href="https://7mmtv.sx/zh/chinese_content/37171/WAAA-167.html"
let urlReg = new RegExp("https://7mmtv\\.[a-z]+/zh/[a-z]+_content/[^/]*?/[^\\.]*?" + _id + "\\.html", "gi");
let matchArray = xhr.responseText.matchAll(urlReg);
for (let match of matchArray) {
if (sign === "uncensoredSet") {
hideNoticeResolveAndOpen(match[0]);
return;
}
if (!savingUrl) {
savingUrl = match[0];
}
if (match[0].search(/chinese/) >= 0) {
hideNoticeResolveAndOpen(match[0]);
return;
}
}
resolve(false);
}
//netflav
else if (/netflav/.test(xhr.finalUrl)) {
//结果页
if (/search/.test(xhr.finalUrl)) {
//<a href="/video?id=QKY3TWlejJ">空白符<div class="grid_0_title">DVDMS-962 大...</div>
let reg = new RegExp("(/video\\?id=[a-z0-9]+)\">\\s*<div class=\"[^\"]+?\">[^<]*?" + _id
+ ".*?</div>", "gi");
let matches = xhr.responseText.matchAll(reg);
for (let match of matches) {
let targetUrl = "https://netflav5.com" + match[1];
if (sign === "censoredSet") {
if (!savingUrl) {
savingUrl = targetUrl;
}
if (await crawl(targetUrl + "#get", id)) {
hideNoticeResolveAndOpen(targetUrl);
return;
}
} else {
hideNoticeResolveAndOpen(targetUrl);
return;
}
}
resolve(false);
}
//详情页
else {
if (xhr.responseText.search(new RegExp("\\[中文字幕\\]" + id, "i")) > 0) {
resolve(true);
} else resolve(false);
}
}
//jav777
else if (/jav777/.test(xhr.finalUrl)) {
//https://www.jav777.xyz/waaa-111-絕頂舔鮑狂熱-木下日葵有碼中文字幕.html
let regString = "https://www\\.jav777\\.xyz/[^\\.]*?" + _id + ".*?\\.html";
let url = xhr.responseText.match(new RegExp(regString, "i"));
if (url) {
hideNoticeResolveAndOpen(url[0]);
} else {
resolve(false);
}
}
//ta-sexy
else if (/tasexy/.test(xhr.finalUrl)) {
//这个网站搜索结果都是重定向的
//存在结果https://www.tasexy.com/tags/一串数字.html
//不存在结果https://www.tasexy.com/?module=tags&action=keyword
if (/module/.test(xhr.finalUrl)) {
//失败
resolve(false);
} else {
//<a title="Caribbean-060611-717-タイ
let reg = new RegExp('<a title="[^"]*?' + _id, "i");
if (xhr.responseText.search(reg) !== -1) {
hideNoticeResolveAndOpen(xhr.finalUrl);
} else {
resolve(false);
}
}
}
//ggjav
else if (/ggjav/.test(xhr.finalUrl)) {
//<div class="item_title"><a class="gray_a" href="/main/video?id=5907">Carib 120311-877 朝倉ことみ 痴漢電車</a></div>
let reg = new RegExp("(/main/video\\?id=[0-9]+).*?" + _id + ".*?</a></div>", "ig");
let matches = xhr.responseText.matchAll(reg);
for (let match of matches) {
if (!savingUrl) {
savingUrl = "https://ggjav.com" + match[1];
}
if (match[0].search(/中文字幕/) >= 0) {
hideNoticeResolveAndOpen("https://ggjav.com" + match[1]);
return;
}
}
resolve(false);
}
//supjav
else if (/supjav/.test(xhr.finalUrl)) {
switch (await checkCF(xhr)) {
case 1:
error = true;
resolve(false);
return;
case 2:
resolve(crawl(xhr.finalUrl + "#get", id));
return;
}
//href="https://supjav.com/zh/147335.html" title="FC2PPV 2753411 * Apricot...
let reg = new RegExp("\"(https://supjav.com/zh/[0-9]+.html)\" title=\"[^\"]*?" + _id + "[^\"]*?\"", "gi");
let matches = xhr.responseText.matchAll(reg);
for (let match of matches) {
if (sign === "uncensoredSet") {
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(xhr.finalUrl)) {
if (/排序/.test(xhr.responseText)) {
//有排序选项表示存在结果
/*
<a href="https://missav.com/dm26/roe-142-uncensored-leak" alt="roe-142-uncensored-leak">
<span class="...">
2:19:31
</span>
*/
let match1 = xhr.responseText.match(/<a href="(.*?)".*?>[\s]<span.*?>[\s].*?:[0-9]+[\s]<\/span>/i);
if (!savingUrl) {
savingUrl = match1[1];
}
/*
<a href="https://missav.com/dm47/roe-142" alt="roe-142">
<span class="...">
中文字幕
</span>
*/
let match2 = xhr.responseText.match(/<a href="(.*?)".*?>[\s]<span.*?>[\s].*?中文字幕[\s]<\/span>/i);
if (match2) {
hideNoticeResolveAndOpen(match2[1]);
} else {
resolve(false);
}
} else {
resolve(false);
}
}
//today
else if (/\.today/.test(xhr.finalUrl)) {
//https://javhd.today/search/video/?s=SKMJ-073
//href="/59014/skmj-073-studio.../" title="SKMJ-073 Studio
let reg = new RegExp("href=\"(.*?)\" title=\"[^\"]*?" + _id, "i");
let matchResult = xhr.responseText.match(reg);
if (matchResult) {
hideNoticeResolveAndOpen("https://javhd.today" + matchResult[1]);
} else {
resolve(false);
}
}
//没有匹配域名,被重定向到其他域名,搜索失败
else {
error = true;
appendNotice("错误:访问地址被重定向,搜索失败,请尝试更换代理。原域名:"
+ url.split("/")[2] + ",重定向域名:" + xhr.finalUrl.split("/")[2], true);
resolve(false);
}
} catch (e) {
changeNotice("聚合搜索" + url.replace("%s", id) + "错误,请联系开发者。<br/>" + e.name + ":" + e.message);
reject(e);
}
},
ontimeout: function () {
changeNotice("网络超时,超时地址:" + url.split("/")[2]);
},
onerror: function () {
changeNotice("网络异常,请检查代理服务器。");
}
});
//注意定义方法的位置,resolve方法需要包含在内
function hideNoticeResolveAndOpen(url) {
if (!error) {
hideNotice();
}
resolve(true);
setTimeout(() => {
GM_openInTab(url, {insert: true, loadInBackground: false});
}, 300);
}
});
}
}
}
//复杂方法,视频预览
async function sampleHandler() {
let id = removeSpaces(textInput.value).toLowerCase();
if (id) {
//正则
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;
//网址
/*
有些是搜索页,有些直接是详情页
所以有些地址携带自动播放标记,没有的将在crawl方法内拼接到最终的目标地址
基本涵盖所有番号的视频预览自动播放
全是get请求
*/
let jav24 = "https://www.jav24.com/watch/www.mgstage.com/product/product_detail/%s/#autoplay";
let mgs = "https://www.mgstage.com/product/product_detail/%s/#autoclick#autoplay";
let fc2 = "https://fc2hub.com/search?kw=%s";//自动重定向,比fc2官网更快更稳
let carib = "https://en.caribbeancom.com/eng/moviepages/%s/index.html#autoplay";
let caribpr = "https://en.caribbeancompr.com/moviepages/%s/index.html#autoplay";
let _10musume = "https://en.10musume.com/movies/%s/#autoplay";
let _1pondo = "https://en.1pondo.tv/movies/%s/#autoplay";
let heyzo = "https://en.heyzo.com/moviepages/%s/index.html#autoplay";
let pacopacomama = "https://en.pacopacomama.com/movies/%s/#autoplay";
let nyoshin = "https://en.nyoshin.com/moviepages/%s/index.html#autoplay";
let kin8tengoku = "https://en.kin8tengoku.com/moviepages/%s/index.html#autoplay";
let xxxav = "https://www.xxx-av.com/mov/movie/%s/#autoplay";
let tokyoHot_result = "https://my.tokyo-hot.com/product/?q=%s";
let tokyoHot_sample = "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4#autoplay";
let ave = "https://www.aventertainments.com/search_Products.aspx?languageID=1&keyword=%s";
let specialProducers = {
"H4610": "https://en.h4610.com/moviepages/%s/index.html#autoplay",
"H0930": "https://en.h0930.com/moviepages/%s/index.html#autoplay",
"C0930": "https://en.c0930.com/moviepages/%s/index.html#autoplay"
};
let notice_text = "正在搜索...<br/>搜索前请先确认番号的准确性,fc2的番号只搜索数字部分即可。";
//用于部分存在相同番号的片商,记录结果数量
let opened = 0;
//用于记录常规番号的搜索结果中包含图片的首个结果,在没有预览视频时使用
let imgUrl;
//关闭模态框,展开提示
searchDialogObj.close();
await checkUser();
//如abc-111
if (common_reg.test(id) && !heyzo_reg.test(id) && !gachi_reg.test(id)) {
//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("jav_pick_form").getElementsByTagName("input");
for (let input of inputs) {
if (input.name === "sampleSet" && input.checked) {
brands.push(input.value);
}
}
brands.push({
url: tokyoHot_result,
//TokyoHot官网如果是移动端ua搜索不到任何内容
headers: {"user-agent": "windows"}
}, ave/*没归纳的无码*/);
await crawlList(brands, true);
}
//如11aaa-11
else if (mgs_reg.test(id)) {
showNotice(notice_text);
await crawlList([jav24, mgs], false);
}
//6到7位纯数字
else if (fc2_reg.test(id)) {
//这种是没有同类番号,也不需要根据响应结果判断搜索结果是否存在,就直接打开
replaceAndOpen(fc2);
}
//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)) {
showNotice(notice_text);
//每个有结果的都打开,
await crawl(heyzo);
await crawl(kin8tengoku);
await crawl(tokyoHot_sample);
if (opened === 0) {
//一个没找到
nothing();
}
}
//纯5位数字
else if (xxxav_reg.test(id)) {
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)) {
showNotice("正在搜索...</br>一本道存在结果将不爬取加勒比。");
await crawlList([_1pondo, pacopacomama, caribpr], false);
}
//n开头加4位数字
else if (nyoshin_tokyoHotN_reg.test(id)) {
showNotice(notice_text);
//nyoshin都没没什么源,番号却老重复,故优先TokyoHot,没结果再测nyoshin
await crawlList([{
url: tokyoHot_result,
headers: {"user-agent": "windows"}
}, nyoshin], false);
}
//其他字母加数字形式
else if (other_reg.test(id) && !gachi_reg.test(id)) {
//明确片商则无需重复打开
if (specialProducer) {
await replaceAndOpen(specialProducers[specialProducer]);
specialProducer = "";
return;
}
showNotice(notice_text);
//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) {
hideNotice();
GM_setValue("is_new_popup", true);
window.setTimeout(() => {
GM_openInTab(url.replace("%s", id), {insert: true, loadInBackground: false});
}, 300);
}
async function crawlList(brands, needImg/*cen需要预览图,unc不需要*/) {
let beFound;
for (let brand of brands) {
if (typeof brand === "string") {
beFound = await crawl(brand);
} else {
beFound = await crawl(brand["url"], brand["headers"]);
}
if (beFound) {
//找到一个就停
break;
}
}
if (!beFound) {
//一个视频都没找到就看预览图
if (needImg && imgUrl) {
hideNoticeAndOpen(imgUrl);
} else {
nothing();
}
return false;
} else return true;
}
//爬取搜索结果或详情页中是否包含预览视频或图片
function crawl(url, headers) {
return new Promise((resolve, reject) => {
let _id = id;
if (/mgstage\.com/.test(url)) {
_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;
}
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: _timeout,
headers: headers ? headers : {},
onload: async function (xhr) {
try {
/*
需要正则匹配的话,都得从开发者工具的网络工具中查看源码,不能在元素工具查看,不然会出现错误
注意id如cap-111和ap-11是不一样的
*/
//ave结果,常规格式无码
if (/aventertainments/i.test(xhr.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(xhr.finalUrl)) {
let searchWord = new RegExp("<span class=\"tag-title\">" + _id + "</span>", "i");
if (xhr.responseText.search(searchWord) === -1) {
resolve(false);
} else {
hideNoticeAndOpen(xhr.finalUrl + "#autoplay");
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(xhr.finalUrl)) {
//详情页 https://javtrailers.com/video/ssis00411
if (/video/.test(xhr.finalUrl)) {
if (xhr.responseText.search("videoPlayerContainer") === -1) {
//image-container 图片的class,响应中有一个是css的选择器,所以超过一个就表示有图片
if (!imgUrl && xhr.responseText.match(new RegExp("image-container", "gi")).length > 1) {
//记录图片地址
imgUrl = xhr.finalUrl + "#autoclick";
}
resolve(false);
} else {
hideNoticeAndOpen(xhr.finalUrl + "#autoplay");
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) {
//搜索到了,获取地址爬取 href="/video/ssis00411"...包括换行符...alt="SSIS-411 jav"
let uri = xhr.responseText.match(new RegExp("(/video/[^\"]*?)\"([\\s\\S](?!/video/))*?\"" + _id + "[a-z]* jav\"", "i"))[1];
//使用日语网页
if (await crawl("https://javtrailers.com/ja" + uri)) {
resolve(true);
} else resolve(false);
} else {
resolve(false);
}
}
//land结果,不需要判断搜索结果,有会重定向
else if (/jav\.land/i.test(xhr.finalUrl)) {
switch (await checkCF(xhr)) {
case 1:
resolve(false);
return;
case 2:
resolve(crawl(xhr.finalUrl));
return;
}
//jav.land搜索到会直接重定向到product页面,如果是请求地址没变代表没有搜索到 https://jav.land/tw/id_search.php?keys=WAAA-164
if (/id_search/i.test(xhr.finalUrl)) {
//没有搜索结果
resolve(false);
}
//如果请求被重定向表示有结果,https://jav.land/tw/movie/javw6eojvep.html
else if (/movie/i.test(xhr.finalUrl)) {
if (xhr.responseText.search("play_sample") !== -1) {
hideNoticeAndOpen(xhr.finalUrl + "#autoclick#autoplay");
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 = xhr.finalUrl;
}
resolve(false);
}
}
}
//jav24
else if (/jav24/i.test(xhr.finalUrl)) {
//mgs格式的视频预览
if (/mgstage/i.test(xhr.finalUrl)) {
if (xhr.status === 410) {
resolve(false);
} else {
resolve(true);
hideNoticeAndOpen(xhr.finalUrl + "#autoplay");
}
return;
}
//https://www.jav24.com/?q=MFCS-050
//href="/watch/www.mgstage.com/product/product_detail/435MFCS-050/"
let _24host = "https://www.jav24.com";
//https://www.jav24.com/?q=EROFV-130
//"/watch...."...my__product__code...EROFV-130<
let _24reg = new RegExp("(/watch[^\"]*?)\"(.(?!/watch/))*?(?<![a-z])class=\"my__product__code badge\">[0-9]*" + _id + "[a-z]*<", "i");
let match = xhr.responseText.match(_24reg);
if (match) {
hideNoticeAndOpen(_24host + match[1] + "#autoplay");
resolve(true);
} else {
resolve(false);
}
}
//mgstage
else if (/mgstage\.com/i.test(xhr.finalUrl)) {
if (/product/i.test(xhr.finalUrl)) {
//不存在会跳转到主页
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//javdb
else if (/javdb/i.test(xhr.finalUrl)) {
//搜索结果页 https://javdb.com/search?q=pts-437
if (/search/.test(xhr.finalUrl)) {
//日本ip会被拒绝,或者响应被限制的提示文本
if (xhr.status === 403 || xhr.responseText.search("prohibited"/*禁止*/) !== -1) {
appendNotice("错误:javdb访问失败,请更换为除日本以外的节点。", true);
resolve(false);
return;
}
//桌面端href="/v/DYvxa" class="box" title="PTS-437
//移动端<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];
if (await crawl("https://javdb.com" + uri)) {
resolve(true);
} else {
resolve(false);
}
}
//详情页 https://javdb.com/v/DYvxa 可见地址中不包含id
else if (/\/v\//.test(xhr.finalUrl)) {
//详情页,存在视频时有个预告片a标签,preview-video-container是它的class
if (xhr.responseText.search("preview-video-container") !== -1) {
hideNoticeAndOpen(xhr.finalUrl + "#autoclick#autoplay");
resolve(true);
} else {
//data-caption是包含图片的a标签的特有属性,存在即有图片
if (!imgUrl && xhr.responseText.search(/data-caption/i) !== -1) {
//详情页存在图片,记录
imgUrl = xhr.finalUrl + "#autoscroll";
}
//往后搜索
resolve(false);
}
}
}
//TokyoHot结果,很多乱七八糟的番号需要搜索,地址中需要包含product,拼接番号访问sample的跳过
else if (/tokyo-hot.*q=/i.test(xhr.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 + "#autoclick#autoplay");
opened++;
resolve(true);
} else {
resolve(false);
}
}
//nyoshin,拼接番号后请求,不存在结果会重定向到首页
else if (/nyoshin/i.test(xhr.finalUrl)) {
//https://en.nyoshin.com/moviepages/n2348/index.html
if (/moviepages/i.test(xhr.finalUrl)) {
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//xxxav,拼接方式请求,无结果重定向到首页
else if (/xxx-av/i.test(xhr.finalUrl)) {
//https://en.xxx-av.com/mov/movie/18059/
if (/movie/.test(xhr.finalUrl)) {
hideNoticeAndOpen(url);
resolve(true);
} else {
resolve(false);
}
}
//其他请求只要不是404就打开
else if (xhr.status !== 404) {
//这些片商番号重复的太多,搜索到也不能停,统计打开网页个数,因为最后要判断是否有找到结果
if (/heyzo|kin8tengoku|tokyo-hot|h4610|c0930|h0930/i.test(xhr.finalUrl)) {
opened++;
//不需要传参判断是否找到,都得测
resolve();
} else resolve(true);
hideNoticeAndOpen(url);
}
//404未找到
else {
resolve(false);
}
} catch (e) {
changeNotice("获取" + _id + "预览视频错误,请联系开发者。<br/>" + e.name + ":" + e.message);
reject(e);
}
},
onerror: function () {
changeNotice("网络错误,请尝试更换代理服务器。");
},
ontimeout: function () {
//访问的如果是视频资源,响应要很久才结束,所以两秒内没有响应404则默认存在该视频
if (/tokyo-hot.*samples/.test(url)) {
opened++;
hideNoticeAndOpen(url);
} else {
changeNotice("网络超时,请尝试更换代理服务器,超时地址:" + url.split("/")[2]);
}
}
});
});
}
function nothing() {
appendNotice("预览视频或图片皆未找到。", true);
}
//关闭模态框通知并打开指定页面
function hideNoticeAndOpen(url) {
//db等需要自动点击,仅在第一次跳转时自动点击,再刷新就不用了
GM_setValue("is_new_popup", true);
hideNotice();
window.setTimeout(() => {
GM_openInTab(url, {insert: true, loadInBackground: false});
}, 300);
}
} else {
//番号为空
if (textInput.value === "") textInput.placeholder = "未输入内容";
else showNotice("输入的全是空格。");
}
}
async function checkUser() {
//首次使用设置指引
if (!GM_getValue("old_user")) {
showNotice("首次使用需要在弹出页面点选“总是允许全部域名”,将在5秒后弹出...");
await sleep(5000);
GM_setValue("old_user", true);
}
}
//全局通知方法
function showNotice(notice) {
document.getElementById("jav_notice").innerHTML = notice;
noticeDialogObj.open();
}
function hideNotice() {
noticeDialogObj.close();
}
function changeNotice(notice) {
document.getElementById("jav_notice").innerHTML = "<span style='color:#ff4b4b'>" + notice + "</span>";
//有时用户会关闭之前的通知模态框,那么更改模态框将给不到通知,需要判断是不是被关了
if (!noticeDialogObj.opened) {
noticeDialogObj.open();
}
}
function appendNotice(notice, warning) {
let noticedDiv = document.getElementById("jav_notice");
if (warning) {
notice = "<span style='color:#ff4b4b'>" + notice + "</span>";
}
noticedDiv.innerHTML += (noticedDiv.innerHTML ? "<br/>" : "") + notice;
if (!noticeDialogObj.opened) {
noticeDialogObj.open();
}
}
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 FloatBall(id, option) {
let defaultOption;
if (id) {
defaultOption = {
color: "red",
diameter: "40px",
opacity: "0.5"
};
if (!document.getElementById(id)) {
for (let name in defaultOption) {
if (option[name]) {
defaultOption[name] = option[name];
} else {
console.log("FloatBall:未传入参数" + name + ",将使用默认值" + defaultOption[name] + "。");
}
}
} else {
throw new Error("FloatBall:创建悬浮球失败,指定id的元素已存在。");
}
} else {
throw new Error("FloatBall:创建悬浮球失败,至少需要指定id。");
}
//添加默认的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 = defaultOption.diameter;
ball.style.height = defaultOption.diameter;
ball.style.opacity = defaultOption.opacity;
ball.style.backgroundColor = defaultOption.color;
let ballX;
let ballY;
//从插件获取悬浮球上次的位置,不存在就使用默认位置
let lastPosition = GM_getValue(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 (/android/i.test(navigator.userAgent)) {
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:已清除移动事件监听。");
GM_setValue(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(defaultOption.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) {
//参数校验
let defaultOption;
if (innerHTML) {
defaultOption = {
dialogPosition: "middle",
width: "300px",
backgroundColor: "white",
backgroundOpacity: "0.8"
};
if (option) {
if (option.dialogPosition && !/top|middle|bottom/.test(option.dialogPosition)) {
option.dialogPosition = "middle";
console.log("ModalDialog:位置名称错误,将使用middle位置。");
}
for (let name in defaultOption) {
if (option[name]) {
defaultOption[name] = option[name];
} else {
console.log("ModalDialog:未传入配置" + name + ",将使用默认配置" + defaultOption[name] + "。");
}
}
} else {
console.log("ModalDialog:未传入option,将使用默认配置。");
}
} else {
throw new Error("ModalDialog:未传入innerHTML!");
}
//添加默认的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 (!/android/i.test(navigator.userAgent)) {
addCSS(transitionCss);
}
//模态框全屏容器,压暗
let dialogBackground = document.createElement("div");
dialogBackground.className = "ddialogBackground";
//添加点击事件,点击背景可以关闭当前模态框
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 = defaultOption.width;
dialog.style.backgroundColor = defaultOption.backgroundColor;
dialogBackground.prepend(dialog);
if (defaultOption.dialogPosition === "top") {
//移动端考虑到性能,不整动画
if (/android/i.test(navigator.userAgent)) {
dialog.style.marginTop = "60px";
} else {
//60-20,给20做动画,动画结束后是60
dialog.style.marginTop = "40px";
}
} else if (defaultOption.dialogPosition === "middle") {
let a, b;
if (/android/i.test(navigator.userAgent)) {
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 (defaultOption.dialogPosition === "bottom") {
dialog.style.position = 'absolute';
dialog.style.left = '50%';
dialog.style.marginLeft = '-' + dialog.clientWidth / 2 + 'px';
if (/android/i.test(navigator.userAgent)) {
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," + defaultOption.backgroundOpacity + ")";
dialogBackground.style.visibility = "visible";
dialog.style.opacity = "1";
dialog.style.visibility = "visible";
if (!/android/i.test(navigator.userAgent)) {
if (defaultOption.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 (!/android/i.test(navigator.userAgent)) {
if (defaultOption.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) {
let defaultOption = {
color: "purple",
blingPosition: ["top", "bottom", "left", "right"],
blurRadius: "80px",
spreadRadius: "30px"
};
for (let name in defaultOption) {
if (option[name]) {
defaultOption[name] = option[name];
} else {
console.log("WindowBling:未传入参数" + name + ",将使用默认值" + defaultOption[name] + "。");
}
}
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 " + defaultOption.blurRadius + " " + defaultOption.spreadRadius + " " + defaultOption.color + ";}";
addCSS(cssCode);
//给传入需要bling的位置创建元素并指定各自的样式
for (let position of defaultOption.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);
};
}
})();