// ==UserScript==
// @name 搜索那个
// @description 日本影视行业内部搜索工具Pro Max Plus Ultra
// @author shopkeeperV
// @namespace https://greasyfork.org/zh-CN/users/150069
// @version 2.1
// @match *://*/*
// @exclude *://localhost*/*
// @exclude *://192.168*/*
// @connect *
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_openInTab
// @grant GM_xmlhttpRequest
// @grant GM_setClipboard
// ==/UserScript==
/*jshint esversion: 8*/
(async function () {
'use strict';
let iframes = document.getElementsByTagName('iframe');
if (top !== window) {
let host = location.host;
//仅在iframe内执行,服务于分辨率获取功能,负责iframe内视频信息反馈
//注意广告插件或许会导致获取不到视频信息
if (!/(cloudflare)|(captcha)/.test(window.location.href)) {
console.log(`iframe>${host}开始监听消息。`);
//iframe获取父窗口消息,绕过iframe安全机制
window.addEventListener('message', e => {
//需要判断一下内容,部分网站自己也会通信,会触发到这个方法,JavSearch作为标志
if (typeof e.data === 'string' && e.data.includes("JavSearch")) {
console.log(`收到父窗口通知,发送iframe>${host}内视频信息。`);
//发送页面视频信息
window.top.postMessage(getVideosInfo(), '*');
//通知iframe里面的子iframe
noticeSubIframe();
}
});
}
//去部分iframe弹窗
if (/emturbovid/.test(host)) {
addCSS("div[id^='pop']{display:none;}");
}
//https://supjav.com/zh/112670.html FST源
else if (host === "fc2stream.tv") {
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);
let modalStyleReady = false;
let ballStyleReady = false;
let blingStyleReady = false;
//设置样式,通知模态框要用
initStyle();
//通知模态框需要率先创建
let noticeDialogObj = new ModalDialog("<div id='jav_notice'></div>", {
width: "310px",
dialogPosition: "top",
}, () => {
//模态框关闭后视频还在播放,特意添加一个方法来清空内容
document.getElementById("jav_notice").innerHTML = "";
});
//需要声明在noticeDialogObj创建后
let noticeContainer = document.getElementById("jav_notice");
//执行个别网页的定制脚本
if (await adviceAndReturn()) return;
//分辨获取功能的页面视频信息描述
let videosInfo = "";
//分词时特殊片商识别
let currentSP = "";
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 timeoutTimer;
//功能是否初始化
let ready = false;
let oldUser = getTmValue("old_user");
//灵魂搜索引擎们
let btnMap;
let cenMap;
let uncMap;
let mosaic_reduce_first = getTmValue("mosaic_reduce_first");
if (mosaic_reduce_first == null) {
setTmValue("mosaic_reduce_first", false);
}
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 textInput = document.getElementById("jav_input");
//创建悬浮球
new FloatBall("jav_ball", {color: "#00cec9"});
//让屏幕周围亮起来,用于链接分词
let blingObj = new WindowBling({
color: "#00cec9",
blingPosition: ["left", "right"],
blurRadius: blingBlurRadius,
spreadRadius: blingSpreadRadius
});
//监听变量,多线程请求完成个数,个数符合预期代表本次搜索结束
const obj = {value: 0};
let complete = 0;
let completeHandler;
const count = new Proxy(obj, {
set(target, prop, newValue) {
console.log(`搜索完成计数从 ${target[prop]} 变为 ${newValue}`);
if (newValue !== 0 && newValue === complete) {
completeHandler();
}
target[prop] = newValue;
return true;
}
});
let startNewCount = function (_complete, _completeHandler) {
count.value = 0;
complete = _complete;
console.log("请求完成目标总数:" + complete);
completeHandler = _completeHandler;
};
let hlsJsLoaded = false;
let hlsSupported = false;
//第一阶段初始化,绑定必须的监听事件,小球,链接分词,划词等,其余的在首次点击小球后再初始化,以节约性能
init1();
function noticeSubIframe() {
for (let iframe of iframes) {
if (iframe.contentWindow && iframe.contentWindow.postMessage) {
const url = new URL(iframe.src);
const iframeHost = url.host;
console.log(`${location.host}给iframe>${iframeHost}发送信号。`);
iframe.contentWindow.postMessage("JavSearch"/*JavSearch作为标志标明是此脚本发送的信息*/, '*');
}
}
}
function buildBtnMap() {
btnMap = new Map();
btnMap.set("trailers情报", {
url: "https://javtrailers.com/ja/search/%s",
//当搜索内容为空时,没有back属性的引擎将打开首页,有back属性的引擎会打开指定的页面
back: "https://javtrailers.com/shorts"
});
btnMap.set("avsox无码情报", {url: "https://avsox.click/cn/search/%s", back: "https://avsox.click/cn"});
btnMap.set("javdb情报", {url: "https://javdb.com/search?q=%s&f=all"});
btnMap.set("google+jav", {url: "https://www.google.com/search?q=%s%20jav"});
}
//排序对应结果优先级
function buildCenMap() {
cenMap = new Map();
cenMap.set("missav字幕", {url: "https://missav.ai/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("supjav破解", {url: "https://supjav.com/zh/?s=%s", reqMethod: "get"});
cenMap.set("777字幕", {url: "https://www.jav777.xyz/?s=%s", reqMethod: "get"});
}
function buildUncMap() {
uncMap = new Map();
uncMap.set("missav无码", {url: "https://missav.ai/cn/search/%s", reqMethod: "get"});
uncMap.set("123av", {url: "https://123av.com/zh/search?keyword=%s", reqMethod: "get"});
uncMap.set("7mm无码", {
url: "https://7mmtv.sx/zh/searchform_search/all/index.html",
reqMethod: "post",
payload: "search_keyword=%s&search_type=searchall&op=search"
});
uncMap.set("guru", {url: "https://jav.guru/?s=%s", reqMethod: "get"});
uncMap.set("supjav无码", {url: "https://supjav.com/zh/?s=%s", reqMethod: "get"});
}
function searchDialogCode() {
return '<div id="jav_searchDialog">' +
'<div>' + getBtns() +
`<div id="jav_cen" class="bbtn llink">搜索有码${mosaic_reduce_first ? "[破]" : "[字]"}</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_switch" 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}'>${key}</div>`;
});
return btns;
}
}
function settingCode() {
return "<div id='jav_settingDialog'>" +
"<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>";
}
function getSettingBtns(map) {
let result = "";
map.forEach((value, key) => {
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,#ssearch{position:fixed;box-sizing:border-box;z-index:999997;border-radius:4px;cursor:pointer;user-select: none;margin:auto;color:white;text-align:center;letter-spacing:normal;}" +
"#jav_notice,#jav_video{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 (host === "adult.contents.fc2.com" && /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 (host === "www.jav777.xyz") {
addCSS("body{min-width:unset !important;}");
} else if (host === "javdb.com") {
addCSS(".columns{margin-right:0px !important;}");
} else if (host === "supjav.com") {
addCSS(".movv-ad{display:none;}");
}
}
function addCSS(css) {
let style = document.createElement("style");
//放body里是因为部分网页重写head标签,外面套了一层div是因为bing换搜索词会删掉body的子style
let styleDiv = document.querySelector("#jav_style");
if (!styleDiv) {
styleDiv = document.createElement("div");
styleDiv.id = "jav_style";
document.body.prepend(styleDiv);
}
style.textContent = css;
styleDiv.appendChild(style);
}
//此方法可以完成一些网站的自动点击、自动播放等,也对一些网站进行功能定制
async function adviceAndReturn() {
let host = location.host;
let path = location.pathname;
let hash = location.hash;
//7mm自定义播放源优先级
if (/^7mmtv\./.test(host)) {
if (/searchform_search/.test(path)) {
let match = hash.match(/#(.*)/);
if (match) {
let formElement = document.querySelector("form");
let inputElement = formElement.querySelector('input[type="text"]');
inputElement.value = match[1];
formElement.submit();
}
}
//视频观看页面
else if (document.querySelectorAll(".fullvideo-details").length === 1) {
chooseSource(".btn-server", ["FL", "SW", "VH", "US"],
(btn, source) => btn.onclick.toString().includes(source));
}
}
//supjav自定义播放源优先级
else if (host === "supjav.com") {
if (path.includes(".html")) {
chooseSource(".btnst>a", ["FST", "VOE", "DS"],
(btn, source) => btn.textContent === source,
() => document.querySelectorAll("#dz_video>iframe").length > 0);
}
}
//fc2,添加两个易用的按钮
else if (host === "adult.contents.fc2.com" && /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];
if (!ready) {
init2();
ready = true;
}
searchDialogObj.open();
});
}
//heyzo没有预览视频时将小图片替换高清图,无码片商中仅heyzo可以替换,因为他图片的url格式是固定的
else if (/\.heyzo\.com/.test(host)) {
//查找所有图片,替换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);
}
}
//missav分辨中文字幕、无码流出视频
else if (/^missav\./.test(host)) {
let selector = document.querySelector(".space-y-2");
if (selector) {
let match = selector.textContent.match(/中文字幕|无码流出/);
if (match) {
let title = document.querySelector(".mt-4>h1");
if (title) {
title.innerText = `【${match[0]}】${title.innerText}`;
}
}
}
}
//给trailers的短视频页面记录浏览历史
else if (host === "javtrailers.com") {
//单页应用
const originalPushState = history.pushState;
history.pushState = function (state, title, url) {
originalPushState.apply(history, arguments);
console.log('pushState被调用,新地址:', url);
if (/shorts/.test(location.pathname/*必须重新获取*/)) {
waitContainer();
}
};
let waitContainer = () => {
let container = document.querySelector(".video-container");
if (!container) {
setTimeout(waitContainer, 500);
return;
}
let videos = container.getElementsByTagName("video");
if (isAndroid) {
//添加搜索按钮,仅在移动端shorts页面显示,所以随着container的存在而存在
let div = document.createElement("div");
div.style.cssText = "left:0;top:70%;background-color:#00cec9;padding:5px 8px;font:14px/1.8 sans-serif;";
div.id = "ssearch";
div.innerText = "获取此页番号";
container.appendChild(div);
div.addEventListener("click", () => {
showNotice("获取番号中...");
let noPlaying = true;
for (let video of videos) {
if (!video.paused) {
let a = video.parentElement.parentElement.querySelector(".icon-buttons-container>a:nth-child(2)");
if (!a) {
appendNotice("非短视频页面。", true);
return;
}
noPlaying = false;
let url = a.href;
GM_xmlhttpRequest({
method: "GET",
url: url,
timeout: 3000,
onload: (res) => {
let match = res.responseText.match("DVD ID:</span> ([^<]+)<");
if (match) {
textInput.value = match[1];
if (!ready) {
init2();
ready = true;
}
hideNotice();
searchDialogObj.open();
} else {
appendNotice("所请求页面未找到番号。", true);
}
},
onerror: (err) => {
hideNotice();
openInTab(url);
},
ontimeout: (err) => {
hideNotice();
openInTab(url);
}
});
break;
}
}
if (noPlaying) {
appendNotice("没有视频在播放。", true);
}
});
}
let count = 0;
new MutationObserver(() => {
count++;
//短时间内不重复执行
if (count > 1) {
return;
}
console.log("video-container内dom变化");
setTimeout(() => {
count = 0;
}, 300);
waitCamera();
}).observe(container, {childList: true});
let waitCamera = () => {
let camera = container.querySelector(".flicking-camera");
if (!camera) {
setTimeout(waitCamera, 500);
return;
}
new MutationObserver(() => {
console.log("flicking-camera内dom变化");
for (let video of videos) {
if (!video.getAttribute("mmark")) {
video.addEventListener("play", () => {
console.log("playing");
let target = video.parentElement.parentElement.querySelector(".icon-buttons-container>a:nth-child(2)");
if (target) {
history.pushState(null, "", target.href);
}
});
video.setAttribute("mmark", "true");
}
}
}).observe(camera, {childList: true});
};
waitCamera();
};
if (/shorts/.test(path)) {
waitContainer();
}
}
//自动播放
if (hash === "#autoplay") {
let failTimes = 0;
let videos = document.getElementsByTagName("video");
let voiceDiv = document.createElement("div");
//给个通知,明确自动播放程序运行中
voiceDiv.textContent = "等待视频加载...";
voiceDiv.id = "mmuted";
let _width = 180;
voiceDiv.style.cssText = "background-color:#ff6666;padding:15px 20px;font:18px/1.8 sans-serif;";
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);
if (host === "javtrailers.com") {
let posters = document.getElementsByClassName("vjs-poster");
let clickPoster = function () {
if (failTimes > 20) {
finalFailHandler();
return;
}
if (posters.length === 0 || videos.length === 0) {
setTimeout(clickPoster, 500);
failTimes++;
return;
}
let video = videos[0];
if (video.paused) {
//想要播放得静音
video.muted = true;
autoClick(posters[0]);
}
if (video.readyState === 0) {
setTimeout(clickPoster, 500);
failTimes++;
} else {
failTimes = 0;
forcePlay();
}
};
clickPoster();
} else {
forcePlay();
}
function finalFailHandler() {
voiceDiv.innerHTML = "播放失败<br/>请尝试日本节点";
voiceDiv.addEventListener("click", () => {
voiceDiv.style.display = "none";
}, {once: true});
}
async function forcePlay() {
if (failTimes === 15) {
finalFailHandler();
return;
}
console.log("自动播放:尝试强制播放,每0.5s一次。");
let video = videos[0];
if (!video) {
failTimes++;
setTimeout(forcePlay, 500);
console.log("没有视频元素。");
return;
}
//个别网站display不写在内联样式里,需要以这种方式获取所有的样式getComputedStyle
if (window.getComputedStyle(video).display === "none") {
failTimes++;
setTimeout(forcePlay, 500);
console.log('video.style.display="none"');
return;
}
//视频已就绪但是播放不了的话
setTimeout(() => {
if (video.readyState === 0) {
finalFailHandler();
}
}, 8000);
video.muted = true;
console.log("自动播放:设为无声。");
try {
await video.play();
} catch (e) {
console.log("视频未就绪:" + e.message);
failTimes++;
setTimeout(forcePlay, 500);
return;
}
//刷新不再自动播放
location.hash = "played";
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) {
console.log("尝试点击中...");
eventTarget.dispatchEvent(new Event("click", {bubbles: true}));
}
function chooseSource(selector, sources, matchFunc, checkFunc = () => true) {
//自动加载顺序:自定义数组>第一个源
let btns = document.querySelectorAll(selector);
if (btns.length > 0) {
let openFirst = true;
out: for (let source of sources) {
for (let btn of btns) {
if (matchFunc(btn, source)) {
autoClick(btn);
openFirst = false;
break out;
}
}
}
if (openFirst) autoClick(btns[0]);
if (!checkFunc()) {
setTimeout(() => chooseSource(selector, sources, matchFunc, checkFunc), 500);
}
} else {
setTimeout(() => chooseSource(selector, sources, matchFunc, checkFunc), 500);
}
}
}
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 = "使用前请先看说明!";
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) {
console.log('搜索那个dom观察:发现dom变化。');
if (observerTimer) {
return;
}
observerTimer = setTimeout(() => {
observerTimer = 0;
addLinkListener();
}, 1000);
console.log("搜索那个dom观察:一秒后重置所有a标签事件。");
}
}
//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;
separate(_textContent, true);
}
}
function getSelectedWord(event) {
//排除自己的输入框
if (textInput.contains(event.target)) {
return;
}
let selectWords = window.getSelection().toString();
if (selectWords) {
//需要清除首尾空格
textInput.value = removeSpaces(selectWords);
}
}
}
//更多功能的第二阶段初始化
function init2() {
buildCenMap();
buildUncMap();
settingDialogObj = new ModalDialog(settingCode(), {
width: "220px",
dialogPosition: searchDialogPosition
});
//接收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) => {
let url = value.url;
let keyword = removeSpaces(textInput.value);
//有搜索词
//使用油猴的GM_openInTab(),因为window.open()存在个别网站打不开的情况
if (keyword !== "" && !/^\s+$/.test(keyword)/*也不能全是空白符*/) {
openInTab(url.replace("%s", keyword));
}
//没有搜索词
else {
if (value.back) {
openInTab(value.back);
} else {
//其余跳到主页
let parts = url.split("/");
openInTab(parts[0] + "//" + parts[2]);
}
}
});
});
let btnGoArray = document.getElementsByClassName("jav_go");
for (let go of btnGoArray) {
if (go.getAttribute("jav_url")) {
go.onclick = () => {
openInTab(go.getAttribute("jav_url"));
};
}
}
//使用说明
document.getElementById("jav_description").addEventListener("click", () => {
openInTab("https://sleazyfork.org/zh-CN/scripts/470138#additional-info");
});
//视频预览
document.getElementById("jav_sample").addEventListener("click", sampleHandler);
//清除输入框内容
document.getElementById("jav_clear").addEventListener("click", () => {
textInput.value = "";
textInput.placeholder = "请输入";
});
//复制
document.getElementById("jav_copy").addEventListener("click", () => {
GM_setClipboard(removeSpaces(textInput.value), "text");
showNotice("已复制。");
setTimeout(() => {
hideNotice();
}, 500);
});
//析出番号,手动粘贴后,点击按钮获取文本中的番号
document.getElementById("jav_analyze").addEventListener("click", () => {
separate(textInput.value, 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("jav_switch").addEventListener("click", async () => {
mosaic_reduce_first = !mosaic_reduce_first;
setTmValue("mosaic_reduce_first", mosaic_reduce_first);
let cen = document.getElementById("jav_cen");
cen.textContent = cen.textContent.replace(/\[.\]/, mosaic_reduce_first ? "[破]" : "[字]");
});
document.getElementById("jav_cen").addEventListener("click", searchHandler);
document.getElementById("jav_unc").addEventListener("click", searchHandler);
//模态框内输入框添加离焦事件更新按钮事件
textInput.addEventListener("blur", () => {
currentSP = "";
console.log("blur清除特殊片商");
textInput.placeholder = "请输入";
});
}
//分词方法,blink表示是否分词后闪烁屏幕
function separate(textContent, blink, search = false/*如果是搜索调用,不要修改特殊片商的信息*/) {
//输入框置空,显示placeholder的提示内容
textInput.value = "";
if (textContent === "") {
textInput.placeholder = "文本内容不存在";
return;
}
textContent = textContent.replaceAll("—"/*中文连接符*/, "-");
//匹配正则数组
let matchers = [
//部分文本胡乱使用连接符和下划线,只能尽量识别,小部分需要手动删改
//可能出现carib-11111-11,所以先获取该格式,日期格式1111-11-11只有最高四位数不用管
/(?<![a-z0-9])[0-9]{5,}[_-][0-9]{2,5}(?![a-z0-9])/i,
//可能出现fc2-ppv-111111,但数字部分6位起步不用管
/((?<![a-z0-9])([0-9]*[a-z]+|[a-z]+[0-9]+[a-z]*))[_-]([a-z]*[0-9]{2,5}(?![0-9]))/i,/*后面可以有字母,因为有些番号以abc分p*/
/(?<![a-z0-9])[a-z]+[0-9]{3,}(?![a-z0-9])/i,
//常见日期格式1111-11-11,所以末尾带-或_的排除
/(?<![a-z0-9])[0-9]{4,}(?![a-z0-9-_])/i
];
let ids;
if (!search) {
//特殊片商处理(片商名称像番号)
currentSP = "";
console.log("separate清除特殊片商");
for (let sp of Object.keys(specialProducers)) {
if (new RegExp(sp, "i").test(textContent)) {
console.log("获取到特殊片商:" + sp);
//若视频预览功能匹配到响应格式的番号,将使用该片商的的预览地址
currentSP = sp;
textContent = textContent.replace(sp, "").replace(sp.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];
if (blink) {
blingObj.blink();
}
return true;
}
//处理cloudflare拦截
async function checkCF(xhr) {
let finalUrl = xhr.finalUrl;
let finalHost = finalUrl.split("/")[2];
//从响应码判断可以避免每次请求都搜索字符串,但是cloudflare拦截后响应码是否一定不是200待验证
if (xhr.status === 200) {
console.log(`${finalHost}的响应码是200,跳过cloudflare检查`);
return 0;
}
if (xhr.responseText.search(/you have been blocked/i) >= 0) {
//被cloudflare拉黑
appendNotice(finalHost + "被cloudflare拉黑,请更换代理。响应码:" + xhr.status, true);
//1代表出错
return 1;
} else if (xhr.responseText.search(/Enable JavaScript and cookies to continue/i) >= 0) {
//需要真人验证
return 2;
}
//0代表未被拦截
else return 0;
}
function addTitle(text) {
let div = document.createElement("div");
div.innerText = text + "↓";
noticeContainer.appendChild(div);
}
function addBtn(innerText, url, warning = false) {
const btn = document.createElement("div");
btn.className = warning ? "bbtn cclose"/*只是红色,并非关闭*/ : "bbtn llink";
btn.innerText = innerText;
btn.addEventListener("click", () => openInTab(url));
noticeContainer.appendChild(document.createElement("div")).appendChild(btn);
}
function addCloseBtn() {
const btn = document.createElement("div");
btn.className = "bbtn cclose";
btn.innerText = "关闭";
btn.addEventListener("click", () => noticeDialogObj.close());
noticeContainer.appendChild(document.createElement("div")).appendChild(btn);
}
async function searchHandler(event) {
separate(textInput.value, false, true);
let keyword = textInput.value;
if (keyword === "") {
return;
}
let selected = [];
let inputs;
let currentMap;
let type = event.target.id;
if ("jav_cen" === type) {
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);
}
}
if (selected.length === 0) {
appendNotice("未至少选择一个搜索引擎。", true);
return;
}
//用于判断是否在过程中出错,如果出错就不关闭通知
let error = false;
//下面三种无码番号的结果需要展示所有结果,其他的都是打开第一个
let heyzo_k8_tokyoHot4_reg = /^[0-9]{4}$/;
let paco_1p_caribpr_reg = /^[0-9]{6}_[0-9]{3}$/;
let other_reg = /^[a-z]+[0-9]+$/i;/*包括n1111*/
let blockedList = [];
//无码结果集,二维数组
let uncResults = [];
let addUncResults = (index, itemPage, itemInfo) => {
if (!uncResults[index]) {
uncResults[index] = [];
}
uncResults[index].push({itemPage: itemPage, itemInfo: itemInfo});
};
//无码破解版本的结果集
let results0 = [];
//中文字幕版本的结果集
let results1 = [];
//有结果就放进去
let results2 = [];
startNewCount(selected.length, async () => {
let foundResult = false;
let haveBlocked = false;
if (blockedList.length > 0) {
haveBlocked = true;
for (let i = 0; i < blockedList.length; i++) {
if (blockedList[i]) {
let resultPage = currentMap.get(selected[i]).url;
addBtn(resultPage.split("/")[2] + "被拦截,点我去人机验证"
, resultPage.replace("%s", keyword)
, true);
}
}
}
if ("jav_cen" === type) {
const order = mosaic_reduce_first
? [results0, results1, results2]
: [results1, results0, results2];
console.log(results0);
console.log(results1);
console.log(results2);
if (haveBlocked) {
let getTypeText = function (index) {
switch (index) {
case 0:
return mosaic_reduce_first ? "无码破解" : "中文字幕";
case 1:
return mosaic_reduce_first ? "中文字幕" : "无码破解";
case 2:
return "普通视频";
}
};
//按往网站分类
for (let i = 0; i < selected.length; i++) {
let haveTitle = false;
//获取每种类型的结果
for (let j = 0; j < order.length; j++) {
let results = order[j];
if (results[i]) {
if (!haveTitle) {
haveTitle = true;
addTitle(currentMap.get(selected[i]).url.split("/")[2]);
}
foundResult = true;
addBtn(getTypeText(j), results[i]);
}
}
}
if (foundResult) {
addCloseBtn();
}
} else {
for (const results of order) {
if (results.length > 0/*添加过一次就会大于0,即使很多都是undefined*/) {
for (let result of results) {
if (result) {
if (!error) {
hideNotice();
//动画结束再打开新标签
await sleep(300);
}
openInTab(result);
break;
}
}
foundResult = true;
break;
}
}
}
} else {
if (uncResults.length > 0) {
console.log(uncResults);
foundResult = true;
let btnView = false;
if (haveBlocked) {
btnView = true;
}
if (heyzo_k8_tokyoHot4_reg.test(keyword)
|| paco_1p_caribpr_reg.test(keyword)
|| other_reg.test(keyword)) {
btnView = true;
}
for (let uncResultsF2 of uncResults) {
if (!uncResultsF2) {
continue;
}
if (btnView) {
let haveTitle = false;
for (let result of uncResultsF2) {
if (!haveTitle) {
haveTitle = true;
addTitle(result.itemPage.split("/")[2]);
}
addBtn(result.itemInfo, result.itemPage);
}
} else {
if (!error) {
hideNotice();
//动画结束再打开新标签
await sleep(300);
}
openInTab(uncResultsF2[0].itemPage);
break;
}
}
btnView && addCloseBtn();
}
}
//如果未找到结果,则尝试谷歌搜索
if (!foundResult) {
appendNotice("未找到结果,可以尝试谷歌搜索。", true);
addBtn(`去谷歌搜索${keyword}`
, `https://www.google.com/search?q=${keyword}%20jav`, true);
}
});
for (let i = 0; i < selected.length; i++) {
/*多线程
checkCF并不会阻碍其他搜索引擎,只是不触发最终结果汇总(count不加到预期值)*/
crawl(currentMap.get(selected[i]), keyword, i);
}
//在一次搜索结束后不再通知授权
setTmValue("old_user", true);
oldUser = true;
async function crawl(engine, id, index = 0/*用于结果排序*/, once = false) {
//在爬取过程需要嵌套爬取时,跳过请求完成计数
let url = engine.url;
let urlSplit = url.split("/");
let host = urlSplit[2];
let topLevelSite = urlSplit[0] + "//" + host;
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);
console.log("正在爬取:" + url);
let headers = {Host: host, Origin: topLevelSite};
if (/7mmtv/.test(topLevelSite)) {
headers["content-type"] = "application/x-www-form-urlencoded";
}
let timeout = 5000;
//给新用户授权的时间
if (!oldUser && location.origin !== topLevelSite) {
await checkUser();
timeout = 15000;
}
//已经超时强制中断的不再显示请求超时的通知
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) {
if (aborted) {
return;
}
const meetCF = async function () {
switch (await checkCF(xhr)) {
case 0:
return false;
case 2:
blockedList[index] = true;
case 1:
error = true;
!once && count.value++;
resolve(false);
return true;
}
};
connectTimeout = false;
let finalUrl = xhr.finalUrl;
let finalUrlSplit = finalUrl.split("/");
let finalHost = finalUrlSplit[2];
let finalOrigin = finalUrlSplit[0] + "//" + finalHost;
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 cne unc
if (/7mmtv/.test(finalUrl)) {
//<a target="_top" href="https://7mmtv.sx/zh/chinese_content/56350/VEC-697.html">[中字]VEC-697 与一个美丽的...</a>
let urlReg = new RegExp(`"(${finalOrigin}/zh/[^/]+_content/[^/]*/[^\\.]*${_id}.html)">([^<]*)</a>`, "gi");
let matchArray = xhr.responseText.matchAll(urlReg);
let found = false;
for (let match of matchArray) {
found = true;
if ("jav_unc" === type) {
addUncResults(index, match[1], match[2]);
} else {
results2[index] = match[1];
if (match[1].search(/chinese/) >= 0) {
results1[index] = match[1];
}
if (match[1].search(/reducing/) >= 0) {
results0[index] = match[1];
}
}
}
found ? resolve(true) : resolve(false);
}
//jav777 cen
else if (/jav777/.test(finalUrl)) {
//搜索页
if (/\?s=/.test(finalUrl)) {
//<h2 class="post-title"><a href="https://..."
let regString = `post-title"><a href="([^"]*)"`;
let matches = xhr.responseText.matchAll(new RegExp(regString, "gi"));
let found = false;
for (let match of matches) {
console.log(`嵌套爬取:${match[1].replace("%s", id)}`);
if (await crawl({
url: match[1],
reqMethod: "get"
}, id/*需要还没添加规则的id,避免重复添加*/, index, true)) {
results1[index] = match[1];
found = true;
break;
}
}
found ? resolve(true) : resolve(false);
}
//详情页
else if (finalUrl.includes(".html")) {
if (xhr.responseText.search(`【番號】︰[^<]*${_id}<br />`) > 0) {
resolve(true);
} else {
resolve(false);
}
}
}
//supjav cen unc
else if (/supjav/.test(finalUrl)) {
if (await meetCF()) return;
//case为0时将继续运行
//href="https://supjav.com/zh/147335.html" title="FC2PPV 2753411 * Apricot...
let reg = new RegExp(`"(${finalOrigin}/zh/[0-9]+.html)" title="([^"]*${_id}[^"]*)"`, "gi");
let matches = xhr.responseText.matchAll(reg);
let found = false;
for (let match of matches) {
found = true;
if ("jav_unc" === type) {
addUncResults(index, match[1], match[2]);
} else {
results2[index] = match[1];
if (match[0].search(/无码破解|无码流出|無修正/) >= 0) {
results0[index] = match[1];
}
if (match[0].search(/中文字幕/) >= 0) {
results1[index] = match[1];
}
}
}
found ? resolve(true) : resolve(false);
}
//missav cen unc
else if (/missav/.test(finalUrl)) {
if (await meetCF()) return;
if (/排序/.test(xhr.responseText)) {
//有排序选项表示存在结果
/*
<img
x-cloak
:class="{ hidden: showPreview === '01ae1d7c-e420-46c0-b1d6-b66b70fac0e1' || holdPreviews.includes('01ae1d7c-e420-46c0-b1d6-b66b70fac0e1') }"
class="lozad w-full"
data-src="https://fourhoi.com/seven-016-uncensored-leak/cover-t.jpg"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN09omrBwADNQFuUCqPAwAAAABJRU5ErkJggg=="
alt="一对在酒吧被抓住的女孩。这个故事讲述了一个肉食爱好者长者在性爱后醒来,被跨坐在他身上的谦虚短小者所吸引。文乃五月"
>
</a>
<a href="https://missav.ai/cn/seven-016-uncensored-leak" alt="seven-016-uncensored-leak">
<span class="absolute bottom-1 left-1 rounded-lg px-2 py-1 text-xs text-nord5 bg-blue-800 bg-opacity-75">
无码影片
</span>
</a>
<a href="https://missav.ai/cn/seven-016-uncensored-leak" alt="seven-016-uncensored-leak" >
<span class="absolute bottom-1 right-1 rounded-lg px-2 py-1 text-xs text-nord5 bg-gray-800 bg-opacity-75">
1:11:58
</span>
</a>
</div>
<div class="my-2 text-sm text-nord4 truncate">
<a
class="text-secondary group-hover:text-primary"
href="https://missav.ai/cn/seven-016-uncensored-leak"
alt="seven-016-uncensored-leak"
>
SEVEN-016 一对在酒吧被抓住的女孩。这个故事讲述了一个肉食爱好者长者在性爱后醒来,被跨坐在他身上的谦虚短小者所吸引。文乃五月 - 沙月ふみの
</a>
</div>
</div>
*/
let reg = new RegExp(`<img([\\s\\S](?!<img))*truncate">\\s*<a[^>]*href="([^"]*)"[^>]*>\\s*([^<]*${_id}[^<]*)\\s*</a>`, "ig");
let matches = xhr.responseText.matchAll(reg);
let found = false;
for (let match of matches) {
found = true;
if ("jav_cen" === type) {
results2[index] = match[2];
if (/中文字幕/.test(match[0])) {
results1[index] = match[2];
}
if (/无码影片/.test(match[0])) {
results0[index] = match[2];
}
} else {
addUncResults(index, match[2], match[3]);
}
}
found ? resolve(true) : resolve(false);
} else {
resolve(false);
}
}
//jable cen
else if (/jable/.test(finalUrl)) {
if (await meetCF()) return;
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) {
results2[index] = finalUrl;
console.log(`嵌套爬取:${urls[1]}`);
await crawl({url: urls[1], reqMethod: "get"}, "", index, true);
resolve(true);
} else {
resolve(false);
}
} else if (/videos/.test(finalUrl)) {
let cn = xhr.responseText.match(new RegExp("已更新至中文字幕版"));
if (cn) {
results1[index] = finalUrl;
resolve(true);
} else {
resolve(false);
}
}
}
//guru unc
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[^>]*alt="([^"]*)"`;
let matches = xhr.responseText.matchAll(new RegExp(regString, "gi"));
let found = false;
for (let match of matches) {
found = true;
addUncResults(index, match[1], match[2]);
resolve(true);
}
found ? resolve(true) : resolve(false);
}
//123av unc
else if (/123av/.test(finalUrl)) {
if (await meetCF()) return;
/*
<div class="detail">
<a href="dm14/v/1pondo-062822_001">1Pondo-062822_001 - 款待〜为男人提供深喉口交的女性〜</a>
</div>
* */
let regString = `<div class="detail">\\s<a href="([^"]*${_id}[^"]*)">([^<]*)</a>`;
let matches = xhr.responseText.matchAll(new RegExp(regString, "gi"));
let found = false;
for (let match of matches) {
found = true;
addUncResults(index, finalOrigin + "/zh/" + match[1], match[2]);
}
found ? resolve(true) : resolve(false);
}
//没有匹配域名,被重定向到其他域名,搜索失败
else {
error = true;
appendNotice("错误:被重定向,请更换代理。原域名:"
+ host + ",重定向域名:" + finalHost, true);
resolve(false);
}
} catch (e) {
error = true;
appendNotice(`错误:${url},请联系开发者。<br/>${e.name}:${e.message}`, true);
resolve(false);
throw e;
}
console.log(`${host}爬取完成。`);
!once && count.value++;
},
ontimeout: function () {
connectTimeout = false;
if (!aborted) {
error = true;
appendNotice("超时:" + host, true);
console.log("超时:" + host);
!once && count.value++;
resolve(false);
}
},
onerror: function () {
error = true;
connectTimeout = false;
appendNotice("错误:" + host + ",机场不行或网站挂了。", true);
console.log("错误:" + host + ",机场不行或网站挂了。");
!once && count.value++;
resolve(false);
}
});
setTimeout(() => {
promise.abort();
if (connectTimeout) {
error = true;
aborted = true;
appendNotice("超时强制中断:" + host, true);
console.log("超时强制中断:" + host);
!once && count.value++;
resolve(false);
}
}, timeout);
});
}
}
async function sampleHandler() {
if (!separate(textInput.value, false, true)) {
return;
}
let id = textInput.value.toLowerCase();
//正则
let common_reg = /^[a-z]+[0-9]*[a-z]*-[a-z]*[0-9]+$/i;
let heyzo_reg = /^heyzo-?[0-9]+$/i;
let gachi_reg = /^gachi[a-z]*-?[a-z]*[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_caribpr_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;
//网址
/*
有些是搜索页,有些直接是详情页
基本涵盖所有番号的视频预览自动播放
全是get请求
*/
let trailer = "https://javtrailers.com/search/%s";
let jav24 = "https://www.jav24.com/?q=%s";
let mgs_player = "https://www.mgstage.com/sampleplayer/sampleRespons.php?pid=";/*这里不能加%s,不需要替换id*/
let mgs = "https://www.mgstage.com/product/product_detail/%s/";
let javten = "https://javten.com/tw/search?kw=%s";//自动重定向,比fc2官网更快更稳
let fc2 = "https://adult.contents.fc2.com/article/%s/";
let carib = "https://en.caribbeancom.com/eng/moviepages/%s/index.html";
let caribpr = "https://en.caribbeancompr.com/moviepages/%s/index.html";
let heyzo = "https://en.heyzo.com/moviepages/%s/index.html";
let paco_1p_10_template = "/movies/%s/";
let _1pondo_data = "https://en.1pondo.tv/dyn/phpauto/movie_details/movie_id/%s.json";
let pacopacomama_data = "https://en.pacopacomama.com/dyn/phpauto/movie_details/movie_id/%s.json";
let _10musume_data = "https://en.10musume.com/dyn/phpauto/movie_details/movie_id/%s.json";
let nyoshin = "https://en.nyoshin.com/moviepages/%s/index.html";
let kin8tengoku = "https://en.kin8tengoku.com/moviepages/%s/index.html";
let xxxav = "https://en.xxx-av.com/mov/movie/%s/";
let tokyoHot_result = "https://my.tokyo-hot.com/product/?q=%s";
let tokyoHot_product = "https://my.tokyo-hot.com/product/%s/";
let tokyoHot_sample = "https://my.cdn.tokyo-hot.com/media/samples/%s.mp4";
//只搜索dvd部分,ppv没一个能看的
let ave = "https://www.aventertainments.com/search_Products.aspx?languageID=1&keyword=%s";
let javdb = "https://javdb.com/search?q=%s&f=all";
//无声视频预览,官网经常没有预览视频或者需要日本ip,所以用盗版网站的自制预览作为替代
/*url: "https://7mmtv.sx/zh/searchform_search/all/index.html",
reqMethod: "post",
payload: "search_keyword=%s&search_type=searchall&op=search"*/
let _7mmtv = "https://7mmtv.sx/zh/searchform_search/all/index.html";
let _123av = "https://123av.com/zh/search?keyword=%s";
//miss在搜索时如果没有-会自动添加,而且是在服务器添加的,这导致部分存在的结果,一搜索却不存在了
let missav = "https://missav.ai/cn/search/%s";
let silentSamples = [_7mmtv, _123av, missav];
let needSilentSample = true;
let notice_text = "正在搜索...<br/>搜索前请先确认番号的准确性。<br/>部分预览视频需要日本ip。";
//爬取过程中出错将不会关闭通知模态框
let error = false;
let blockedList = [];
//爬取结果集,有序的,获取结果需要遍历判断undefined
let results = [];
let silentResults = [];
let addResult = function (index, productPage, videoSrc = "") {
results[index] = {productPage: productPage, videoSrc: videoSrc};
};
let addSilentResult = function (index, resultPage, videoSrc, videoText) {
!silentResults[index] && (silentResults[index] = {resultPage: resultPage, videoInfoArray: []});
silentResults[index].videoInfoArray.push({src: videoSrc, text: videoText});
};
let dealOneResult = function () {
addTitle("官网/资讯站");
if (results[0].videoSrc) {
showVideo(results[0].videoSrc, true);
}
let pg = results[0].productPage;
addBtn(pg.split("/")[2], pg);
addCloseBtn();
}
showNotice(notice_text);
//SKYHD-156(ave) NATR-749 MKD-015(ave列表外) RED-183(tokyo-hot大小写敏感,小写没有视频)
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].toLowerCase();
if (aveIds.includes(prefix)) {
//前缀与aveId匹配,直接在ave搜索,然后终止方法
if (await crawl(ave)) {
dealOneResult();
} else {
nothing();
}
} else if (/red/i.test(prefix)) {
id = id.toUpperCase();
if (await crawl(tokyoHot_product)) {
dealOneResult();
} else {
nothing();
}
} else {
await crawlList([trailer, jav24, ave/*没归纳的无码*/, javdb/*锁ip,有无码内容,放后面补充*/]);
}
}
//200GANA-3166
else if (mgs_reg.test(id)) {
await crawlList([jav24, mgs/*需要日本ip*/]);
}
//6到7位纯数字 4668750
else if (fc2_reg.test(id)) {
needSilentSample = false;
crawlSilentSamples(() => {
replaceAndOpen(javten);
}, () => {
addBtn("去javten查看", javten.replace("%s", id));
addBtn("跳转到官网", fc2.replace("%s", id));
});
}
//6位-3位 041525-001
else if (carib_reg.test(id)) {
if (await crawl(carib)) {
dealOneResult();
} else {
nothing();
}
}
//6位_2位 040825_01
else if (_10m_reg.test(id)) {
if (await crawl(_10musume_data)) {
dealOneResult();
} else {
nothing();
}
}
//纯4位数字的heyzo等 1234 5271(tokyo-hot实际是n0274,链接却是四位数)
else if (heyzo_k8_tokyoHot4_reg.test(id)) {
needSilentSample = false;
await crawlList([heyzo, kin8tengoku, tokyoHot_product]);
}
//纯5位数字 24293
else if (xxxav_reg.test(id)) {
if (await crawl(xxxav)) {
dealOneResult();
} else nothing();
}
//没有官网,直接搜索无声预览
else if (gachi_reg.test(id)) {
appendNotice("这片商官网没了。", true);
//有连接符的获取连接符后的内容,没有的不修改,这是为了匹配无声预览那些结果的写法
if (id.includes("-")) {
id = "gachi" + id.match(/-(.*)/)[1];
}
//由无声预览搜索此番号
}
//带前缀heyzo的 HEYZO-3566 heyzo3566
else if (heyzo_reg.test(id)) {
//没有-的补上,取数字部分在crawl方法内进行不然,无声预览也会使用4位数字搜索
id = "heyzo-" + id.match(/[0-9]+/)[0];
if (await crawl(heyzo)) {
dealOneResult();
} else nothing();
}
//6位_3位 060624_001 041525_100 040525_001
else if (paco_1p_caribpr_reg.test(id)) {
await crawlList([_1pondo_data, pacopacomama_data, caribpr]);
}
//n开头加4位数字 n2348(nyoshin) n0274(product拼接不行)
else if (nyoshin_tokyoHotN_reg.test(id)) {
await crawlList([tokyoHot_result, nyoshin]);
}
//其他字母加数字形式 lb0017(tokyo-hot但是搜索不到,拼链接也不行) kb1737(tokyo-hot) ki250311(specialProducers)
else if (other_reg.test(id) && !nyoshin_tokyoHotN_reg.test(id) && !heyzo_reg.test(id) && !gachi_reg.test(id)) {
//明确片商则无需重复打开
//H4610-ki250304 伊瓦基·希恩(Iwaki Shion),21岁
if (currentSP) {
if (await crawl(specialProducers[currentSP])) {
dealOneResult();
}
} else {
//tokyoHot存在一个有另一个没有的情况
let list = [tokyoHot_result, tokyoHot_product, nyoshin].concat(Object.values(specialProducers));
await crawlList(list);
}
}
//其余形式未收录
else {
needSilentSample = false;
showNotice("输入错误或未收录该番号。");
}
if (needSilentSample) {
appendNotice("正在搜索无声预览视频...");
crawlSilentSamples();
}
//在一次搜索结束后不再通知授权,前面如果有使用crawl(),不能在使用crawl后还return,此处必须执行
setTmValue("old_user", true);
oldUser = true;
function showVideo(url, first) {
let video = document.createElement("video");
video.controls = true;
video.loop = true;
video.style.width = "100%";
video.style.marginBottom = "1px";
video.style.marginTop = "10px";
first && (video.autoplay = true);
// 检查是否是 HLS 流
let isHlsStream = url.toLowerCase().endsWith('.m3u8');
// 如果是首次加载且是 HLS 流并且 hls.js 尚未加载,则动态加载 hls.js
if (first && isHlsStream && !hlsJsLoaded) {
let script = document.createElement('script');
script.src = 'https://cdn.jsdelivr.net/npm/[email protected]';
script.type = 'text/javascript';
script.onload = function () {
console.log('hls.js 加载完成');
if (Hls.isSupported()) {
console.log("首次加载m3u8视频。");
hlsSupported = true;
loadHlsVideo(url, video);
} else {
console.log("此浏览器不支持 hls.js");
}
hlsJsLoaded = true; // 设置标志表示 hls.js 已加载
};
noticeContainer.parentElement.prepend(script);
} else {
// 对于非首次加载或非 HLS 流的情况
if (isHlsStream && hlsSupported) {
console.log("加载m3u8视频中...");
loadHlsVideo(url, video);
} else {
// 直接设置 src 属性播放普通视频
video.src = url;
}
}
let div = document.createElement('div');
div.style.position = "relative";
div.style.display = "flex";
noticeContainer.appendChild(div).appendChild(video);
function loadHlsVideo(url, video) {
let hls = new Hls();
hls.loadSource(url);
hls.attachMedia(video);
}
}
function replaceAndOpen(url) {
!error && hideNotice();
window.setTimeout(() => {
openInTab(url.replace("%s", id));
}, 300);
}
//除了常规格式番号,无声预览会展示同一个网站下的多个同番号结果,因为有些番号一样但内容不一样
//另外mgs格式会被转为常规格式搜索
//060624_001
function crawlSilentSamples(noResultHandler, finishHandler) {
startNewCount(silentSamples.length, () => {
if (blockedList.length > 0) {
for (let i = 0; i < blockedList.length; i++) {
if (blockedList[i]) {
addBtn(silentSamples[i].split("/")[2] + "被拦截,点我去人机验证"
, silentSamples[i].replace("%s", id)
, true);
}
}
}
if (silentResults.length === 0) {
noResultHandler && noResultHandler();
if (noticeContainer.innerHTML) {
appendNotice("未找到无声预览视频。", true);
} else {
nothing();
}
} else {
console.log(silentResults);
for (let i = 0, open = 0; i < silentResults.length; i++) {
if (silentResults[i]) {
let haveTitle = false;
for (let videoInfo of silentResults[i].videoInfoArray) {
let pageHost = silentResults[i].resultPage.split("/")[2];
if (/7mmtv/.test(pageHost)) {
//此页面需要form表单搜索,给链接传一个id,在弹窗内用js提交表单
silentResults[i].resultPage += `#${id}`;
}
if (!haveTitle) {
addTitle(pageHost);
haveTitle = true;
}
showVideo(videoInfo.src, (open === 0 && !noticeContainer.querySelector("video")));
if (heyzo_k8_tokyoHot4_reg.test(id)
|| paco_1p_caribpr_reg.test(id)
|| other_reg.test(id)) {
addBtn(videoInfo.text, silentResults[i].resultPage);
}
addCloseBtn();
open++;
}
}
}
finishHandler && finishHandler();
}
});
if (mgs_reg.test(id)) {
id = id.match(/[a-z]+-[0-9]+/i);
}
for (let i = 0; i < silentSamples.length; i++) {
crawl(silentSamples[i], i, false);
}
}
async function crawlList(brands) {
return await new Promise((resolve) => {
startNewCount(brands.length, () => {
if (results.length > 0) {
addTitle("官网/资讯站");
console.log(results);
//需要fori用于判断是不是第一个视频自动播放
for (let i = 0, open = 0; i < results.length; i++) {
if (results[i]) {
let videoSrc = results[i].videoSrc;
let productPage = results[i].productPage;
let btnText = productPage.split("/")[2];
if (/javtrailer/.test(productPage)) {
btnText = "点我跳转到javtrailers.com,原网页播放无需日本ip";
}
if (videoSrc) {
showVideo(videoSrc, open === 0);
open++;
}
productPage && addBtn(btnText, productPage);
videoSrc && addCloseBtn();
}
}
resolve(true);
} else {
nothing();
resolve(false);
}
});
for (let i = 0; i < brands.length; i++) {
crawl(brands[i], i/*对应数组索引*/, false);
}
});
}
//爬取搜索结果或详情页中是否包含预览视频或图片
async function crawl(url, index = 0, once = true) {
//在爬取过程需要嵌套爬取时,跳过请求完成计数
let ignore = false;
let urlSplit = url.split("/");
let host = urlSplit[2];
let topLevelSite = urlSplit[0] + "//" + host;
let _id = id;
if (/mgstage\.com/.test(topLevelSite)) {
_id = _id.toUpperCase();
} else if (/jav24/.test(topLevelSite)) {
if (mgs_reg.test(id)) {
_id = _id.match(/[a-z]+-[0-9]+/i)[0];
}
} else if (/heyzo/.test(topLevelSite)) {
_id = _id.match(/[[0-9]+/i)[0];
}
url = url.replace("%s", _id);
console.log("正在爬取:" + url);
let payload = null;
if (/7mmtv/.test(url)) {
payload = "search_keyword=%s&search_type=searchall&op=search".replace("%s", _id);
}
let method = "get";
if (/tokyo-hot.*samples/.test(url)) {
//head请求可以不下载视频,直接返回响应头信息
method = "head";
} else if (/7mmtv/.test(url)) {
method = "post";
}
//默认超时时间
let timeout = 5000;
if (/aventertainments.*search/.test(url)) {
//响应太慢了,实际上可以访问
timeout = 7000;
} else if (/tokyo-hot.*product/.test(url)) {
timeout = 7000;
}
//给新用户授权的时间
if (!oldUser && location.origin !== topLevelSite) {
await checkUser();
timeout = 15000;
}
let headers = {Host: host, Origin: topLevelSite};
if (/tokyo-hot.*q=/.test(url)) {
headers["user-agent"] = "windows";
} else if (/mgstage\.com/.test(url)) {
//移动端的响应不一样
headers["user-agent"] = "windows";
} else if (/7mmtv/.test(url)) {
headers["content-type"] = "application/x-www-form-urlencoded";
}
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: method,
url: url,
data: payload,
timeout: timeout,
headers: headers,
cookie: cookie,
cookiePartition: {
topLevelSite: topLevelSite
},
onload: async function (xhr) {
if (aborted) {
return;
}
let meetCF = async function () {
switch (await checkCF(xhr)) {
case 0:
return false;
case 2:
blockedList[index] = true;
case 1:
error = true;
!once && count.value++;
resolve(false);
return true;
}
};
let dealSilentResult = function (reg, srcIndex, infoIndex) {
let matches = xhr.responseText.matchAll(new RegExp(reg, "gi"));
let haveMatch = false;
if (/7mmtv/.test(finalUrl) && common_reg.test(id)) {
let isMgs = false;
let firstMatch;
//修复7mm的mgs番号删减数字部分时,预览链接错误 SGKI-038
for (const match of matches) {
haveMatch = true;
if (!firstMatch) {
firstMatch = match;
}
if (new RegExp(`[0-9]+${_id}`, "i").test(match[srcIndex])) {
isMgs = true;
addSilentResult(index, finalUrl, match[srcIndex], match[infoIndex]);
break;
}
}
if (!isMgs && firstMatch) {
addSilentResult(index, finalUrl, firstMatch[srcIndex], firstMatch[infoIndex]);
}
} else {
for (const match of matches) {
haveMatch = true;
addSilentResult(index, finalUrl, match[srcIndex], match[infoIndex]);
if (common_reg.test(id)) {
//常规格式的番号结果有些是字幕和无码版本,对预览来说无区别,保存一个即可
break;
}
}
}
if (haveMatch) {
resolve(true);
} else {
//爬搜索引擎的无声视频,没有视频什么都不用放
resolve(false);
}
};
connectTimeout = false;
let finalUrl = xhr.finalUrl;
let finalHost = finalUrl.split("/")[2];
try {
/*
需要正则匹配的话,都得从开发者工具的网络工具中查看源码,不能在元素工具查看,不然会出现错误
注意id如cap-111和ap-11是不一样的
注意.不能替代[\\s\\S],同时浏览器有格式化功能,是否存在换行符需要看源码格式
*/
//404未找到
if (xhr.status === 404) {
//404并不是错误,而是没有结果的意思
console.log(`404:${xhr.finalUrl}`);
resolve(false);
}
//caribbeancom和caribbeancompr,会404
else if (/caribbeancom/.test(finalUrl)) {
//https://en.caribbeancompr.com/moviepages/040525_001/index.html
//"sample_flash_url":"https:\/\/smovie.caribbeancompr.com\/sample\/movies\/040525_001\/480p.mp4"
//https://en.caribbeancom.com/eng/moviepages/041525-001/index.html
//"sample_flash_url":"https:\/\/smovie.caribbeancom.com\/sample\/movies\/041525-001\/480p.mp4"
//"sample_m_flash_url":"https:\/\/smovie.caribbeancom.com\/sample\/movies\/041525-001\/sample_m.mp4"
let nothing = false;
if (/com\./.test(finalUrl)) {
//即使胡编乱造的id也不会404,而是写着过期了,判断标题为空的就是这种
if (xhr.responseText.search(/<title>\s+-\s+Caribbeancom.com<\/title>/i) > 0) {
nothing = true;
resolve(false);
}
}
if (!nothing) {
let reg = new RegExp(`flash_url":"([^"]+mp4)"`, "i");
let match = xhr.responseText.match(reg);
addResult(index, finalUrl, match ? match[1].replaceAll("\\", "") : "");
resolve(true);
}
}
//pacopacomama|1pondo|10musume,会404
else if (/pacopacomama|1pondo|10musume/.test(finalUrl)) {
/*
"SampleFiles": [
{
...
"URL": "https://smovie.1pondo.tv/sample/movies/060624_001/240p.mp4"
* */
let reg = new RegExp(`SampleFiles[\\s\\S]*? "URL": "([^"]+mp4)"`/*最低清晰度*/, "i");
let match = xhr.responseText.match(reg);
let page = topLevelSite + paco_1p_10_template.replace("%s", id);
addResult(index, page, match ? match[1] : "");
resolve(true);
}
//ave结果,常规格式无码
else if (/aventertainments/.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 {
//<source src="... .m3u8"
let reg = /<source src="([^"]*)"/i;
let match = xhr.responseText.match(reg);
addResult(index, finalUrl, match ? match[1] : "");
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 matches = xhr.responseText.match(/https:\/\/www.aventertainments.com.*?product_lists/gi);
let urlSet = new Set();
for (let m of matches) {
//很多有重复的
urlSet.add(m);
}
let beFound;
for (let url of urlSet) {
console.log(`嵌套爬取:${url}`);
beFound = await crawl(url, index, true);
if (beFound) break;
}
if (beFound) {
resolve(true);
} else resolve(false);
}
}
//trailer结果
else if (/javtrailers/.test(finalUrl)) {
//详情页 https://javtrailers.com/video/ssis00411
if (/video/.test(finalUrl)) {
//"https://cc3001.dmm.co.jp/hlsvideo/freepv/s/son/sone00638/playlist.m3u8"
//这个视频需要日本ip另一个视频链接不能跨域
let reg = /"([^"]+\.(mp4|m3u8))"/i;
let match = xhr.responseText.match(reg);
addResult(index, finalUrl + "#autoplay", match ? match[1] : "");
resolve(true);
}
/*结果页 https://javtrailers.com/search/SSIS-411
"id空格jav在结果中只存在一个,开头双引号不能去掉,不然会有三个*/
else if (/search/.test(finalUrl)) {
//<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) {
//使用日语网页
let url = topLevelSite + "/ja" + matchArray[1];
console.log(`嵌套爬取:${url}`);
await crawl(url, index, true);
resolve(true);
} else {
resolve(false);
}
}
}
//jav24
else if (/jav24/.test(finalUrl)) {
//结果页
if (/q=/.test(finalUrl)) {
//https://www.jav24.com/?q=EROFV-130
//他响应的网页就没有换行
//<a class="my__product__summary__link" href="/watch/www.dmm.co.jp/digital/videoc/-/detail/=/cid=bskj001/"... BSKJ-001<span
let _24reg = new RegExp(`"(/watch[^"]*)"(.(?!/watch/))*(?<![a-z])${_id}<span`, "i");
let match = xhr.responseText.match(_24reg);
if (match) {
await crawl(topLevelSite + match[1], index, true);
resolve(true);
} else {
resolve(false);
}
}
//产品页
else if (/watch/.test(finalUrl)) {
let reg = /<source src="([^"]*)"/i;
let match = xhr.responseText.match(reg);
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
}
}
//mgstage
else if (/mgstage\.com/.test(finalHost)) {
//https://www.mgstage.com/product/product_detail/200GANA-3166/
if (xhr.status === 403) {
error = true;
appendNotice("错误:mgstage需要日本ip。", true);
resolve(false);
} else if (/product/.test(finalUrl)) {
//不存在会跳转到主页
//https://www.mgstage.com/product/product_detail/200GANA-3166/
//网页中按钮<a href="/sampleplayer/sampleplayer.html/bb7109fc-d1dd-48a9-95da-3851c188527a" class="button_sample">
//视频所在地址https://www.mgstage.com/sampleplayer/sampleRespons.php?pid=bb7109fc-d1dd-48a9-95da-3851c188527a
/*"url":"https:\/\/sample.mgstage.com\/sample\/nanpatv\/200gana\/3166\/200gana-3166_20250307T185502.ism
太长了其实未换行\/request?uid=10000000-0000-0000-0000-00000000000a&pid=bb7109fc-d1dd-48a9-95da-3851c188527a"*/
let reg = /"[^"]+html\/([^"]+)" class="button_sample"/i;
let match = xhr.responseText.match(reg);
if (match) {
console.log(`嵌套爬取:${mgs_player + match[1]}`);
await crawl(mgs_player + match[1], index, true);
} else {
addResult(index, finalUrl);
}
resolve(true);
} else if (/sampleplayer/.test(finalUrl)) {
let reg = /"url":"(.*ism).*"/i;
let match = xhr.responseText.match(reg);
addResult(index, mgs.replace("%s", _id),
match ?
match[1].replaceAll("\\", "").replace("ism", "mp4") :
"");
resolve(true);
} else {
resolve(false);
}
}
//javdb
else if (/javdb/.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);
} else {
//<a href="/v/ppARk"...包含换行符...<strong>PTS-437</strong>
let matchArr = xhr.responseText.match(new RegExp(`"(/v/[^"]*)"([\\s\\S](?!/v/))*>${_id}<`, "i"));
if (!matchArr) {
resolve(false);
} else {
//找到了,去爬取详情页有没有视频存在
let uri = matchArr[1];
console.log(`嵌套爬取:${topLevelSite + uri}`);
await crawl(topLevelSite + uri, index/*嵌套爬取要携带原先的index*/, true);
resolve(true);
}
}
}
//详情页 https://javdb.com/v/DYvxa 可见地址中不包含id
else if (/\/v\//.test(finalUrl)) {
let reg = /"([^"]+\.(mp4|m3u8))"/i;
let match = xhr.responseText.match(reg);
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
}
}
//tokyo-hot,会404
else if (/tokyo-hot/.test(finalUrl)) {
//搜索页 https://my.tokyo-hot.com/product/?q=n0274
//<div class="actor"> (Product ID: n0274)</div>
if (/q=/.test(finalUrl)) {
//<a href="/product/5271/" class="rm">...含有换行符...<div class="actor"> (Product ID: n0274)</div> 不用翻译不一样
//单个英文括号也是要被转义的
let reg = new RegExp(`"/product/([^/]+)/"([\\s\\S](?!/product/))*<div class="actor">[^<]*(?<![a-z])${_id}\\)<`, "i");
let match = xhr.responseText.match(reg);
if (match) {
let sample_url = tokyoHot_sample.replace("%s", match[1]);
let product_url = tokyoHot_product.replace("%s", match[1]);
//sample_url这里id变了,将没有%s被替换
console.log(`嵌套爬取:${sample_url}`);
if (await crawl(sample_url, index, true)) {
addResult(index, product_url, sample_url);
} else {
addResult(index, product_url);
}
resolve(true);
} else {
resolve(false);
}
}
//产品页https://my.tokyo-hot.com/product/kb1738/ 没404就是有
else if (/product\/[^?]/.test(finalUrl)) {
console.log(`嵌套爬取:${tokyoHot_sample}`);
if (await crawl(tokyoHot_sample, index)) {
addResult(index, finalUrl, tokyoHot_sample.replace("%s", id));
} else {
addResult(index, finalUrl);
}
resolve(true);
}
//视频文件 https://my.cdn.tokyo-hot.com/media/samples/kb1738.mp4
else if (/samples/.test(finalUrl)) {
resolve(true);
}
}
//nyoshin,拼接番号后请求,不存在结果会重定向到首页
else if (/nyoshin/.test(finalUrl)) {
//https://en.nyoshin.com/moviepages/n2348/index.html
//stream_url = '//smovie.nyoshin.com/contents/2348/sample.mp4'
if (/moviepages/.test(finalUrl)) {
let regString = `stream_url = '([^']*)'`;
let match = xhr.responseText.match(new RegExp(regString, "i"));
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
} else {
resolve(false);
}
}
//xxxav,拼接方式请求,无结果重定向到首页
else if (/xxx-av/.test(finalUrl)) {
//https://en.xxx-av.com/mov/movie/18059/
if (/movie/.test(finalUrl)) {
addResult(index, finalUrl);
resolve(true);
} else {
resolve(false);
}
}
//7mmtv
else if (/7mmtv/.test(finalUrl)) {
/*
<div class='video'>
<figure class='video-preview'>
<a target="_top" href="https://7mmtv.sx/zh/chinese_content/56350/VEC-697.html"><img class='lazyload' data-src='https://99avcdn.org/censored/s/389380_VEC-697.jpg' alt='VEC-697 与一个美丽的屁股妻子进行的蹲式训练会严格禁止插入屁股!她无法握住它,她经历了女牛仔的位置,并在自己的体内暨!我无法摆脱活塞的乐趣! Nogami Shiori'><video loop='true' muted='true' playsinline='true' muted='' poster='' data-src='https://video2.98avcdn.xyz/censored/389380_VEC-697.mp4' src='' autoplay='true'></video><div class='video-loader'><div class='spinner-border text-pink'></div></div></a>
</figure>
<h3 class='video-title'>
<a target="_top" href="https://7mmtv.sx/zh/chinese_content/56350/VEC-697.html">[中字]VEC-697 与一个美丽的屁股妻子进行的蹲式训练会严格禁止插入屁股!她无法握住它,她经历了女牛仔的位置,并在自己的体内暨!我无法摆脱活塞的乐趣! Nogami Shiori</a>
</h3>
<div class='video-info'>
<div class='row justify-content-between'>
<div class='col-auto'>
<div class='video-channel'>goubi</div>
</div>
<div class='col-auto'>
<span class='small text-muted'> 2025-04-18 14:50:54 </span>
</div>
</div>
</div>
*/
let reg = `<video[^>]+data-src='([^']*)'[\\s\\S]*?<a.*?>([^<]*(?<![a-z])${_id}(?![0-9])[^<]*)</a>`;
dealSilentResult(reg, 1, 2);
}
//123av
else if (/123av/.test(finalUrl)) {
if (await meetCF()) {
return;
}
/*
<div class="box-item">
<div class="thumb" v-scope="Preview()" data-preview="https://cdn.123av.me/preview/5/45/maan-1066/preview.png?t=1744536876" @vue:mounted="init($el)">
<a href="v/maan-1066" title="MAAN-1066">
<img class="lazyload" data-src="https://cdn.123av.me/resize/s360/5/45/maan-1066/cover.jpg?t=1744536876" title="MAAN-1066" alt="MAAN-1066" />
</a>
<div class="favourite"
v-scope="Favourite('movie', 270783, 0)"
data-code="MAAN-1066"
@click="handle" :class="{active: state == 1}">
<i class="fas fa-heart"></i>
</div>
<div class="duration">01:18:47</div>
</div>
<div class="detail">
<a href="v/maan-1066">MAAN-1066 - “ [敏感的托儿所老师被浸透和生长]一个超级可爱的Crybaby Girl刺入了赌注!当她进入室内时,她进入了顽皮的角色扮演者的回合!她完全炫耀了自己的屁股!她从后面从后面从后面开始高潮...</a>
</div>
</div>
* */
let reg = `data-preview="([^"]*)"[\\s\\S]*?data-code[\\s\\S]*?<a.*?>([^<]*(?<![a-z])${_id}(?![0-9])[^<]*)</a>`;
dealSilentResult(reg, 1, 2);
}
//missav
else if (/missav/.test(finalUrl)) {
if (await meetCF()) {
return;
}
/*
<div
@mouseenter="setPreview('094e105f-a18b-44c7-bab2-583f44a9a56b')"
@mouseleave="setPreview()"
@click="clickPreview('094e105f-a18b-44c7-bab2-583f44a9a56b')"
class="thumbnail group"
>
<div class="relative aspect-w-16 aspect-h-9 rounded overflow-hidden shadow-lg">
<a href="https://missav.ai/dm315/cn/060624_001" alt="060624_001" >
<video
x-cloak
:class="{ hidden: showPreview !== '094e105f-a18b-44c7-bab2-583f44a9a56b' && ! holdPreviews.includes('094e105f-a18b-44c7-bab2-583f44a9a56b') }"
id="preview-094e105f-a18b-44c7-bab2-583f44a9a56b"
class="preview hidden"
loop
muted
playsinline
data-src="https://fourhoi.com/060624_001/preview.mp4"
></video>
<img
x-cloak
:class="{ hidden: showPreview === '094e105f-a18b-44c7-bab2-583f44a9a56b' || holdPreviews.includes('094e105f-a18b-44c7-bab2-583f44a9a56b') }"
class="lozad w-full"
data-src="https://fourhoi.com/060624_001/cover-t.jpg"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN09omrBwADNQFuUCqPAwAAAABJRU5ErkJggg=="
alt="欲望家政府小泉真希的清洁与清洁!"
>
</a>
<a href="https://missav.ai/dm315/cn/060624_001" alt="060624_001" >
<span class="absolute bottom-1 right-1 rounded-lg px-2 py-1 text-xs text-nord5 bg-gray-800 bg-opacity-75">
0:55:38
</span>
</a>
</div>
<div class="my-2 text-sm text-nord4 truncate">
<a
class="text-secondary group-hover:text-primary"
href="https://missav.ai/dm315/cn/060624_001"
alt="060624_001"
>
060624_001 欲望家政府小泉真希的清洁与清洁!
</a>
</div>
</div>
* */
let reg = `<video[^>]*data-src="([^"]*)"[\\s\\S]*?<span[\\s\\S]*?<a[^>]*>\\s([^<]*(?<![a-z])${_id}(?![0-9])[^<]*)\\s</a>`;
dealSilentResult(reg, 1, 2);
}
//heyzo,会404
else if (/heyzo/.test(finalUrl)) {
//"contentUrl": "https://sample.heyzo.com/contents/3000/3565/sample.mp4"
let regString = `"contentUrl": "([^"]*${_id}[^"]*)"`;
let match = xhr.responseText.match(new RegExp(regString, "i"));
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
}
//即使搜索到了特殊片商也会用js跳转而不是重定向,直接获取视频链接播放
else if (/h4610|c0930|h0930/.test(finalUrl)) {
//https://en.h4610.com/moviepages/ki250306/index.html
//"contentUrl": "https://smovie.h4610.com/moviepages/ki250306/sample.mp4"
if (/moviepages/.test(finalUrl)) {
let regString = `"contentUrl": "([^"]*${_id}[^"]*)"`;
let match = xhr.responseText.match(new RegExp(regString, "i"));
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
} else resolve(false);
}
//kin8tengoku
else if (/kin8tengoku/.test(finalUrl)) {
if (/moviepages/.test(finalUrl)) {
//'//en.kin8tengoku.com/1111/pht/sample_en.mp4' 有四个结果,画质不一样,以防万一只用第一个
let regString = `'([^']+mp4)'`;
let match = xhr.responseText.match(new RegExp(regString, "i"));
addResult(index, finalUrl, match ? match[1] : "");
resolve(true);
} else resolve(false);
}
} catch (e) {
error = true;
appendNotice(`错误:${url},请联系开发者。<br/>${e.name}:${e.message}`, true);
console.log(`错误:${url},请联系开发者。<br/>${e.name}:${e.message}`);
resolve(false);
throw e;
}
console.log(`${host}爬取完成。`);
!once && count.value++;
},
onerror: function () {
error = true;
connectTimeout = false;
appendNotice("错误:" + host + ",机场不行或网站挂了。", true);
console.log("错误:" + host + ",机场不行或网站挂了。");
!once && count.value++;
resolve(false);
},
ontimeout: function () {
connectTimeout = false;
if (!aborted) {
error = true;
appendNotice("超时:" + host, true);
console.log("超时:" + host);
!once && count.value++;
resolve(false);
}
}
});
setTimeout(() => {
promise.abort();
if (connectTimeout) {
error = true;
aborted = true;
appendNotice("超时强制中断:" + host, true);
console.log("超时强制中断:" + host);
!once && count.value++;
resolve(false);
}
}, timeout);
});
}
function nothing() {
appendNotice("预览视频或图片皆未找到。", true);
}
}
async function checkUser() {
//首次使用设置指引
//多线程,其实刷新了数次这个通知
showNotice("请在弹出页面点选【总是允许全部域名】。");
await sleep(4000);
}
//全局通知方法
function showNotice(notice) {
document.getElementById("jav_notice").innerHTML = notice;
noticeDialogObj.open();
}
function hideNotice() {
if (!noticeDialogObj.opened) return;
noticeDialogObj.close();
document.getElementById("jav_notice").innerHTML = "";
}
function appendNotice(notice, warning = false) {
let noticedDiv = document.getElementById("jav_notice");
let div = document.createElement("div");
div.className = warning ? "eerror" : "";
div.innerText = notice;
noticedDiv.appendChild(div);
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 openInTab(url, option = {insert: true, loadInBackground: false}) {
GM_openInTab(url, option);
}
function getTmValue(key) {
return GM_getValue(key);
}
function setTmValue(key, value) {
try {
GM_setValue(key, value);
} catch (e) {
appendNotice("GM_setValue调用失败,浏览器bug捕获。" + e.message, true);
}
}
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;}';
if (!ballStyleReady) {
addCSS(cssCode);
ballStyleReady = true;
}
//使用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, Number(ballX).toFixed(1) + "," + Number(ballY).toFixed(1));
};
//当小球被按下或触摸开始,添加移动监听器和抬起监视器
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, {once: true});
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, closeHandler = null) {
if (!innerHTML) {
innerHTML = "";
}
if (!option) {
option = {};
}
//参数校验
let defaultOption = {
dialogPosition: "middle",
width: "300px",
backgroundColor: "white",
backgroundOpacity: "0.9",
//是否可以通过点击背景关闭模态框
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;}';
if (!modalStyleReady) {
addCSS(cssCode);
if (!isAndroid) {
addCSS(transitionCss);
}
modalStyleReady = true;
}
//模态框全屏容器,压暗
let dialogBackground = document.createElement("div");
dialogBackground.className = "ddialogBackground";
//添加点击事件,点击背景可以关闭当前模态框
if (option.canBeClose) {
dialogBackground.addEventListener("click", (e) => {
if (e.target !== dialogBackground) {
return;
}
this.close();
});
}
document.body.prepend(dialogBackground);
//模态框
let dialog = document.createElement("div");
dialog.innerHTML = innerHTML;
dialog.className = "ddialog";
dialog.style.width = option.width;
dialog.style.backgroundColor = option.backgroundColor;
dialogBackground.prepend(dialog);
const thisModal = this;
let keepDialogMiddle = function () {
//-20,打开时会+20
let marginTop = Math.floor(innerHeight * 0.5 - dialog.clientHeight * 0.5)
- ((isAndroid || thisModal.opened/*打开后的尺寸变化不要偏移*/) ? 0 : 20/*-20,打开时会+20*/);
dialog.style.marginTop = marginTop + 'px';
};
//模态框上下留白
let boundary;
//动画偏移量
let offset = 20;
if (option.dialogPosition === "top") {
boundary = 60;
//移动端考虑到性能,不整动画
if (isAndroid) {
dialog.style.marginTop = boundary + "px";
} else {
//给20做动画,动画结束后是boundary的值
dialog.style.marginTop = boundary - offset + "px";
}
} else if (option.dialogPosition === "middle") {
boundary = 60;
let observer = new ResizeObserver(() => {
console.log("middle-dialog尺寸变化");
keepDialogMiddle();
});
observer.observe(dialog);
keepDialogMiddle();
} else if (option.dialogPosition === "bottom") {
boundary = 30;
dialog.style.position = 'absolute';
dialog.style.left = '50%';
dialog.style.marginLeft = '-' + dialog.clientWidth / 2 + 'px';
if (isAndroid) {
dialog.style.bottom = boundary + 'px';
} else {
//动画结束后是30
dialog.style.bottom = boundary - offset + 'px';
}
}
dialog.style.maxHeight = window.innerHeight - boundary * 2 + "px";
//开关状态
this.opened = false;
//定义打开与关闭方法
this.open = function () {
if (this.opened) {
return;
}
//打开时指定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 = boundary + 'px';
} else {
let top = Number(dialog.style.marginTop.replace("px", "")) + offset;
dialog.style.marginTop = top + 'px';
}
}
this.opened = true;
};
this.close = function () {
if (!this.opened) {
return;
}
if (closeHandler) {
closeHandler();
}
//显示
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 = boundary - offset + 'px';
} else {
let top = Number(dialog.style.marginTop.replace("px", "")) - offset;
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 + ";}";
if (!blingStyleReady) {
addCSS(cssCode);
blingStyleReady = true;
}
//给传入需要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);
};
}
})();