// ==UserScript==
// @name JAVBUS影片预告
// @namespace http://tampermonkey.net/
// @version 1.8
// @description JAVBUS自动显示预告片
// @author A9
// @supportURL https://sleazyfork.org/zh-CN/scripts/450740/feedback
// @source https://github.com/bigwolf9987/JavBusTrailer
// @match https://www.javbus.com/
// @include /^https?:\/\/(?:[A-Za-z0-9]+\.)*(?:javbus|busjav|busfan|fanbus|buscdn|cdnbus|dmmsee|seedmm|busdmm|dmmbus|javsee|seejav){1}(?:\.[A-Za-z0-9]+)?\/[\w_-]{1,}\/?$/
// @exclude /^https?:\/\/(?:[A-Za-z0-9]+\.)*(?:javbus|busjav|busfan|fanbus|buscdn|cdnbus|dmmsee|seedmm|busdmm|dmmbus|javsee|seejav){1}(?:\.[A-Za-z0-9]+)?\/(?:forum|actresses|uncensored|genre|series|studio|page){1,}\/?\S*$/
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @grant GM_getResourceText
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @connect r18.com
// @connect dmm.co.jp
// @connect javdb.com
// @connect mgstage.com
// @connect prestige-av.com
// @connect javspyl.eu.org
// @connect heyzo.com
// @connect avfantasy.com
// @connect tokyo-hot.com
// @connect caribbeancom.com
// @connect aventertainments.com
// @connect 10musume.com
// @connect pacopacomama.com
// @connect 1pondo.tv
// @connect cloudfront.net
// @connect workers.dev
// @connect supabase.co
// @connect xcity.jp
// @connect obox.jp
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAABFElEQVQ4ja2TMU4CQRSGvzfuKkQ2bqORRpKNV6CjIaGgovQK1LRs7R24AoECbrAHWE7glhQkJO4SEgt1GQsCBhmG1fjKee//8v/zZiSGJ+AZeOR3lQChxPDyB/EeIjFo28SuKSf6jk0sjoPXaHDh+6yjiDzLjmaUDaDKZe77fR4GAy5rNaNVK2DnQlwXEXOIs4Bz9f8AU85TG4CfW1AKv93mul7ndTjkfT4HETSgN5sCAK256XS47XZRlQrrKOIqCMjTlDxNjU7UoV6TTSZ8Lpfc9XoEoxFutUo6HvOxWBgdHL1EcRy8ZhOv1UKVSrzNZmTTKflqVQwA2wOBbX6trZeo2P6qQ+p3JqsYSBQQmiAFKgHCL3I+UIXeDJynAAAAAElFTkSuQmCC
// @require https://fastly.jsdelivr.net/npm/video.js@7.10.2/dist/video.min.js
// @require https://fastly.jsdelivr.net/npm/videojs-vr@1.10.1/dist/videojs-vr.min.js
// @resource video-js-css https://fastly.jsdelivr.net/npm/video.js@7.10.2/dist/video-js.min.css
// @resource video-vr-js-css https://fastly.jsdelivr.net/npm/videojs-vr@1.10.1/dist/videojs-vr.css
// @license MIT
// ==/UserScript==
(function () {
"use strict";
let player = null;
//脚本设置项
const settings = {
enable_vr_mode: 1, //是否使用VR模式,0 关闭;1 开启
enable_mute_play: 0, //是否开启静音播放,0 关闭;1 开启 (注:跨域页面无效,需手动控制播放与音量)
video_playback_speed: 1.0, //视频默认播放速度,建议设置范围 0.25~2.0(注:数值越大播放速度越快)
enable_debug_mode: 0,
video_quality: 720, //视频清晰度,可设置为下列值之一:1080;720;480;360;240;144;(注:数值越大越清晰,所需网络加载时间越长)
};
const corporations = {
stars: ["1"],
star: ["1"],
svdvd: ["1"],
sdde: ["1"],
mogi: ["1"],
dvdes: ["1"],
fset: ["1"],
rct: ["1"],
abp: ["118"],
ppt: ["118"],
dv: ["53"],
mds: ["84"],
gvg: ["13"],
haru: ["h_687"],
sdnm: ["1"],
scpx: ["84"],
dldss: ["1"],
spro: ["h_1594", "00"],
silkc: ["1", "00"],
hzgb: ["h_1100"],
awd: [""],
drpt: ["1"],
hz: ["h_113"],
pym: ["h_283"],
fone: ["h_491"],
stcv: ["h_1616", "00"],
ftht: ["1", "00"],
apns: [""],
dvaj: [""],
t28: ["55"],
lol: ["12", "00"],
vema: [""],
venx: [""],
skmj: ["h_1324"],
fsvr: ["h_955", "00"],
dtvr: ["24", "00"],
pydvr: ["h_1321", "00"],
hoks: [""],
sqis: [""],
real: [""],
urkk: [""],
bazx: [""],
mdbk: [""],
mdtm: [""],
mkmp: [""],
saba: [""],
scop: [""],
spz: ["h_254"],
udak: ["h_254"],
jukf: ["h_227"],
shind: ["h_1560", "00"],
ovg: [""],
shh: ["1", "00"],
shn: ["1", "00"],
dandy: ["1"],
sw: ["1"],
meko: ["h_1160", "00"],
apaa: [""],
ekdv: [""],
nhdtb: ["1"],
umd: ["125"],
sdab: ["1"],
sdjs: ["1"],
sdmf: ["1"],
sdmm: ["1"],
sdmua: ["1"],
rebd: ["h_346"],
sdth: ["1"],
aran: [""],
aed: [""],
anb: [""],
cmv: [""],
piyo: ["1"],
rctd: ["1"],
fgan: ["h_1440", "00"],
zex: ["h_720"],
fera: ["h_086", "00"],
fuga: ["h_086", "00"],
mtall: ["1", "00"],
fsdss: ["1"],
ofku: ["h_254"],
nsfs: ["", "00"],
sdmu: ["1"],
sinn: [""],
hkd: [""],
rvg: [""],
scd: [""],
ktra: ["h_094", "00"],
jrze: ["h_086", "00"],
mesu: ["h_086", "00"],
akdl: ["1", "00"],
wo: ["1"],
sun: ["1"],
gvh: [""],
vec: [""],
gma: [""],
supa: ["h_244"],
bobb: [""],
focs: [""],
tppn: [""],
abw: ["118"],
pkpd: [""],
adn: ["", "00"],
wkd: ["2", "00"],
aarm: [""],
sqte: [""],
flav: [""],
jsop: [""],
bda: [""],
tki: ["h_286"],
nacr: ["h_237"],
kire: ["1"],
docp: ["118"],
jksr: ["57"],
hgot: ["84"],
mcsr: ["57"],
};
const need_cors_domain = new Set(["smovie.1pondo.tv", "dy43ylo5q3vt8.cloudfront.net"]);
//Start running from here
const movieInfo = getMovieInfo();
if (movieInfo?.movieId && !movieInfo?.isEuropeOrAmerica) {
addPreviewVideoStyle();
log(JSON.stringify(movieInfo));
getVideoURL(movieInfo).then((videoURL) => {
if (!videoURL) return;
movieInfo.videoURL = videoURL;
//Save video url to local storage cache.
GM_setValue(movieInfo.movieId, videoURL);
addVideoPlayerElement(movieInfo);
});
}
/**
* get movie info object
* @returns {object}
*/
function getMovieInfo() {
const infoDom = document.querySelector(".container .info");
if (!infoDom) return;
let movieId = infoDom.innerText
.match(/(?<=(?:識別碼|识别码|ID|品番):.)[\w\-\.]+/)
?.at(0);
let corpName = infoDom.innerText
.match(/(?<=(?:製作商|Studio|메이커|メーカー):.)[\x20\S]+/)
?.at(0)
?.trim();
let isVR =
infoDom
.querySelector("#genre-toggle")
?.parentElement?.nextElementSibling.innerText.search(
/ハイクオリティVR|VR専用|カリVR|VR/
) > -1;
let isUncensored =
document
.querySelector("#navbar li.active")
?.innerText.search(/無碼|Uncensored|無修正|무수정/) > -1;
let isEuropeOrAmerica =
document
.querySelector("#navbar li.active")
?.innerText.search(/歐美|Western|外国人|서양의/) > -1;
let title = document.querySelector(".bigImage img")?.title;
let thumbnailURL = document.querySelector(".sample-box")?.href;
let titleKeyPhrase = getKeyPhrase(title);
//retry extract
if (titleKeyPhrase === title) titleKeyPhrase = getKeyPhrase(title, "!!");
log({
title,
movieId,
corpName,
isVR,
isUncensored,
isEuropeOrAmerica,
thumbnailURL,
titleKeyPhrase,
});
return {
title,
movieId,
corpName,
isVR,
isUncensored,
isEuropeOrAmerica,
thumbnailURL,
titleKeyPhrase,
};
}
/**
* Append player style
*/
function addPreviewVideoStyle() {
let containerStyle = `
#preview-video-container{
position: fixed;
height: 100%;
width: 100%;
background-color: rgba(0,0,0,0.8);
top: 0px;
z-index: 999;
display: none;
align-items: center;
justify-content: center;
}
#preview-video-container:before {
content: '\\2715';
font-size: 28px;
color: white;
opacity: 0.7;
right: 18px;
top: 8px;
position: absolute;
cursor: pointer;
pointer-events:none;
}
#preview-video-player{
height: 80%;
min-width: 70%;
max-width: 80%;
background-color: #000;
border-radius: 8px;
outline: none;
overflow: hidden;
}
.preview-video-img-container{
position: relative;
cursor:pointer;
}
.preview-video-img-container img{
max-width: 100%;
object-fit: cover;
width: 120px;
}
.preview-video-img-container:after {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 512 512'%3E%3Cpath d='M448 255c0-106-86-192-192-192S64 150 64 256s86 192 192 192 192-86 192-192z' fill='gold' fill-opacity='0.8' stroke='none'/%3E%3Cpath fill='white' d='M216.32 334.44l114.45-69.14a10.89 10.89 0 000-18.6l-114.45-69.14a10.78 10.78 0 00-16.32 9.31v138.26a10.78 10.78 0 0016.32 9.31z'/%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: 50%;
background-color: #0000005e;
background-size: 48px 48px;
bottom: 0;
content: "";
display: block;
left: 0;
position: absolute;
right: 0;
top: 0;
}
`;
GM_addStyle(containerStyle);
GM_addStyle(GM_getResourceText("video-js-css"));
GM_addStyle(GM_getResourceText("video-vr-js-css"));
}
/**
* Append Video Player Element to Page
* @param {object} movieInfo movieInfo
* @returns
*/
function addVideoPlayerElement(movieInfo) {
if (!movieInfo.videoURL) return;
let video;
let muted = settings.enable_mute_play == true ? "muted" : "";
//target video url need use iframe
if (needCORS(movieInfo.videoURL)) {
video = `
<iframe id="preview-video-iframe" name="preview-video-iframe" width="60%" height="80%" style="border:none;border-radius:8px;" srcdoc="<html><head><style>*{width:100%;height:100%;padding:0;margin:0;overflow:hidden;} video{background: #000;}</style></head><body><video controls><source src='${movieInfo.videoURL}' type='video/mp4'></video></body></html>">
</iframe>`;
} else {
video = `
<video id="preview-video-player" playsinline controls preload="none" ${muted} poster="${
document.querySelector("a.bigImage img")?.src
}">
<source src="${movieInfo.videoURL}" type="video/mp4" />
</video>`;
if (
videojs &&
((settings.enable_vr_mode == true && movieInfo.isVR) ||
isM3U8(movieInfo.videoURL))
) {
video = `
<video id="preview-video-player" class="video-js" playsinline controls preload="none"
${muted} poster="${
document.querySelector("a.bigImage img")?.src
}" crossorigin="anonymous">
</video>`;
}
}
let vContainer = document.createElement("div");
vContainer.id = "preview-video-container";
vContainer.innerHTML = video;
//onclick event -- hide and pause player
vContainer.addEventListener("click", (event) => {
if (event?.target != vContainer) return;
vContainer.style.display = "none";
if (player) {
player.pause();
} else {
//reset iframe to pause video
if (needCORS(movieInfo.videoURL)) {
const iframe = document.querySelector("#preview-video-iframe");
let originIframe = iframe.cloneNode(true);
iframe.parentElement.append(originIframe);
iframe.remove();
}
}
document.body.style.overflow = "auto";
});
document.body.append(vContainer);
initVideoPlayer(movieInfo);
addVideoPreviewImage();
}
function initVideoPlayer(movieInfo) {
if (needCORS(movieInfo.videoURL)) {
return;
}
if (
videojs &&
((settings.enable_vr_mode == true && movieInfo.isVR) || isM3U8(movieInfo.videoURL))
) {
player = videojs("preview-video-player", {
playbackRates: [0.5, 1, 1.5, 2],
});
player.mediainfo = player.mediainfo || {};
if (isM3U8(movieInfo.videoURL)) {
player.src({
type: "application/x-mpegURL",
src: movieInfo.videoURL,
});
}
//enabled vr plugin
if (movieInfo.isVR) {
player.src({
type: "video/mp4",
src: movieInfo.videoURL,
});
player.mediainfo.projection = "360";
player.vr({ projection: "AUTO" });
player.vr().on("initialized", () => {
player.vr().camera.position.x = -1.3981591081738982;
player.vr().camera.position.y = 0.035304011118944253;
player.vr().camera.position.z = -0.7904654323761686;
});
player.tech(false); //tech() will log warning without any argument
}
player.defaultPlaybackRate(settings.video_playback_speed);
} else {
player = document.querySelector("#preview-video-player");
player.playbackRate = settings.video_playback_speed;
}
}
/**
* Append video preview image to movie gallery
* @returns
*/
function addVideoPreviewImage() {
let imgWaterFall = document.querySelector("#sample-waterfall");
if (!imgWaterFall) {
imgWaterFall = document.createElement("div");
imgWaterFall.id = "sample-waterfall";
let heading = document.createElement("h4");
heading.innerText = "樣品圖像";
const clearfix = document.getElementsByClassName("clearfix")[0];
clearfix?.before(heading, imgWaterFall);
}
let previewImgSpan = document.createElement("span");
previewImgSpan.classList.add("sample-box");
let photoFrame = document.createElement("div");
photoFrame.classList.add("photo-frame", "preview-video-img-container");
let posterImg = document.querySelector("a.bigImage img");
if (posterImg) {
let img = document.createElement("img");
img.src = posterImg.src;
photoFrame.append(img);
}
previewImgSpan.append(photoFrame);
imgWaterFall.prepend(previewImgSpan);
//onclick event
previewImgSpan.addEventListener("click", () => {
let vContainer = document.querySelector("#preview-video-container");
if (vContainer) {
document.body.style.overflow = "hidden";
vContainer.style.display = "flex";
if (player) {
player.play();
player.focus();
}
}
});
}
async function getVideoURL(movieInfo) {
let videoURL = await queryLocalCacheDB(movieInfo)
.catch((e) => {
log(e);
return querySupaBase(movieInfo);
})
.catch((e) => {
log(e);
return queryCF(movieInfo);
})
.catch((e) => {
log(e);
return queryDMMVideoURL(movieInfo, undefined, false, "mhb");
})
.catch((e) => {
log(e);
return queryDMMVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryDMMVideoURL(movieInfo, undefined, true);
})
.catch((e) => {
log(e);
return queryPrestigeVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryMGStageVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryMGStageVideoURL(movieInfo, false, true);
})
.catch((e) => {
log(e);
//retry query (use movie title as keyword)
return queryMGStageVideoURL(movieInfo, true);
})
.catch((e) => {
log(e);
return queryXCityVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryBasicUncensoredVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryTokyoHotVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryAVFantasyVideoURL(movieInfo);
})
.catch((e) => {
log(e);
return queryAVFantasyVideoURL(movieInfo, true);
})
.catch((e) => {
log(e);
return queryJavSpylVideoURL(movieInfo);
})
// .catch((e) => {
// log(e);
// return queryJavDBVideoURL(movieInfo);
// })
.catch((e) => {
log(e);
});
return convertHTTPToHTTPS(videoURL);
}
async function queryBasicUncensoredVideoURL(movieInfo) {
if (!movieInfo.isUncensored)
return Promise.reject("Query basic uncensored only support uncensored movie.");
let videoURLs;
const qualityArr = ["720p.mp4", "1080p.mp4", "480p.mp4", "360p.mp4", "240p.mp4"];
if (
movieInfo.corpName === "カリビアンコム" ||
movieInfo.corpName.toUpperCase() === "CARIBBEANCOM"
) {
//create different quality video urls.
videoURLs = qualityArr.map(
(quality) =>
`https://smovie.caribbeancom.com/sample/movies/${movieInfo.movieId}/${quality}`
);
} else if (
movieInfo.corpName === "東京熱" ||
movieInfo.corpName.toUpperCase() === "TOKYOHOT"
) {
videoURLs = [`https://my.cdn.tokyo-hot.com/media/samples/${movieInfo.movieId}.mp4`];
} else if (
movieInfo.corpName === "天然むすめ" ||
movieInfo.corpName.toUpperCase() === "10MUSUME"
) {
videoURLs = qualityArr.map(
(quality) =>
`https://smovie.10musume.com/sample/movies/${movieInfo.movieId}/${quality}`
);
} else if (
movieInfo.corpName === "一本道" ||
movieInfo.corpName.toUpperCase() === "1PONDO"
) {
videoURLs = qualityArr.map(
(quality) =>
`https://smovie.1pondo.tv/sample/movies/${movieInfo.movieId}/${quality}`
);
videoURLs.push(
`https://ppvclips02.aventertainments.com/01m3u8/1pon_${movieInfo.movieId}/1pon_${movieInfo.movieId}.m3u8`
);
} else if (
movieInfo.corpName === "ワンピース" ||
movieInfo.corpName === "オリエンタルドリーム" ||
movieInfo.corpName.toUpperCase() === "ONEPIECEENTERTAINMENT" ||
movieInfo.corpName.toUpperCase() === "ORIENTALDREAM"
) {
videoURLs = [
`https://ppvclips02.aventertainments.com/${movieInfo.movieId}/ts/${movieInfo.movieId}-m3u8-aapl.ism/manifest(format=m3u8-aapl).m3u8`,
];
} else if (
movieInfo.corpName === "パコパコママ" ||
movieInfo.corpName.toUpperCase() === "PACOPACOMAMA"
) {
videoURLs = qualityArr.map(
(quality) =>
`https://fms.pacopacomama.com/hls/sample/pacopacomama.com/${movieInfo.movieId}/${quality}`
);
} else {
return Promise.reject(
"Query basic uncensored: This function not support this corporation movie."
);
}
for (const videoURL of videoURLs) {
log("Query basic uncensored:\r\n" + videoURL);
const validatedURL = await xFetch(videoURL, { method: "head" })
.then((resp) => {
if (resp?.status === 200) {
log("Query basic uncensored: server result video url: " + videoURL);
return videoURL;
}
return null;
})
.catch((e) => {
log(e);
return null;
});
if (validatedURL) {
return Promise.resolve(validatedURL);
}
}
return Promise.reject("Query basic uncensored Not found movie.");
}
async function queryJavSpylVideoURL(movieInfo) {
//Video links for these companies require cors, so ignore it.
// if (
// movieInfo.corpName === "HEYZO" ||
// movieInfo.corpName === "一本道" ||
// movieInfo.corpName === "1pondo"
// ) {
// return Promise.reject("JavSpyl server not support this corporation movie.");
// }
//see https://bit.ly/3RkgqSo
let serverURL = "https://api.javspyl.eu.org/api/";
return await xFetch(serverURL, {
headers: {
origin: "https://api.javspyl.eu.org",
"Content-Type": "application/x-www-form-urlencoded",
},
data: `ID=${movieInfo.movieId}`,
method: "POST",
})
.then((resp) => {
if (resp?.status !== 200 || !resp.responseText)
return Promise.reject("JavSpyl server not found movie.");
return JSON.parse(resp.responseText);
})
.then((video) => {
if (video?.info?.url?.length > 0) {
log("JavSpyl server result video url: https://" + video.info.url);
return "https://" + video.info.url;
} else {
return Promise.reject("JavSpyl server not found movie.");
}
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryAVFantasyVideoURL(movieInfo, isStandbyServer = false) {
if (!movieInfo.isUncensored)
return Promise.reject("AVFantasyDMM server only support uncensored movie.");
let notFound = () => Promise.reject("AVFantasy server not found movie.");
let keyword = movieInfo.movieId;
//Movie codes for these companies are not supported, so use movie titles to search.
if (movieInfo.corpName.toUpperCase() === "HEYZO") {
keyword = movieInfo.titleKeyPhrase;
}
let serverURL = `https://www.avfantasy.com/ppv/ppv_searchproducts.aspx?keyword=${keyword}`;
if (isStandbyServer) {
serverURL = `https://www.avfantasy.com/search_Products.aspx?keyword=${keyword}`;
}
log("AVFantasyDMM server query:\r\n" + serverURL);
return await xFetch(serverURL)
.then((response) => {
//AVFantasy search result, may contain multiple movies.
let resultMovies = extractAVFantasyMovieItem(response.responseText);
if (!resultMovies) return notFound();
let targetMovieEle = null;
for (const element of resultMovies) {
if (matchMovieByKeyword(element, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailURL = targetMovieEle.match(/<a href="(\S*?)"/s)?.at(1);
if (!avDetailURL) return notFound();
return xFetch(avDetailURL);
})
.then((response) => {
//AVFantasy movie detail page result.
let videoSource = response.responseText?.match(/<source.*?src="(\S*?)"/s)?.at(1);
if (videoSource) {
log("AVFantasy server result video url: " + videoSource);
return videoSource;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryJavDBVideoURL(movieInfo) {
let serverURL = `https://javdb.com/search?q=${movieInfo.movieId}&f=all`;
let notFound = () => Promise.reject("JavDB server not found movie.");
log("JavDB server query:\r\n" + serverURL);
return await fetch(serverURL)
.then((resp) => {
return resp.text();
})
.then((text) => {
//JavDB search result, may contain multiple movies.
//TODO
let doc = convertTextToDOM(text);
let resultMovies = doc.querySelectorAll(".item");
let targetMovieEle = null;
for (const element of resultMovies.values()) {
if (matchMovieByKeyword(element.innerHTML, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailPathName = targetMovieEle.querySelector("a")?.pathname;
if (!avDetailPathName) return notFound();
return fetch("https://javdb.com" + avDetailPathName);
})
.then((avDetailResp) => {
return avDetailResp.text();
})
.then((text) => {
//JavDB movie detail page result.
let doc = convertTextToDOM(text);
let videoSource = doc.querySelector("#preview-video source");
if (videoSource && videoSource?.src && videoSource.src != location.href) {
log("JavDB server result video url: " + videoSource.src);
return videoSource.src;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryDMMVideoURL(
movieInfo,
host = "cc3001.dmm.co.jp",
hasPrefix = false,
postfix = "_dmb_w"
) {
if (movieInfo.isUncensored)
return Promise.reject("DMM server not support uncensored movie.");
// if (movieInfo.thumbnailURL?.includes("mgstage.com"))
// return Promise.reject("DMM server not support MGStage movie.");
//see https://www.javbus.com/forum/forum.php?mod=viewthread&tid=63374
//see https://bit.ly/3wXLj6T
let infix = "litevideo/freepv";
//1500kbps = _dmb_w || 3000kbps = _mhb_w || vrlite || _sm_w.mp4 || _dm_w.mp4 || _dmb_s.mp4!!
// let postfix = "_dmb_w";
if (movieInfo.isVR) {
postfix = "vrlite";
infix = "vrsample";
}
let movieIdSplit = movieInfo.movieId.toLowerCase().split("-");
let corp = movieIdSplit[0];
let idNum = movieIdSplit[1];
let videoURL = `https://${host}/${infix}/${corp[0]}/${corp.substring(
0,
3
)}/${corp}${idNum}/${corp}${idNum}${postfix}.mp4`;
if (hasPrefix === false && movieInfo.thumbnailURL?.includes("pics.dmm.co.jp")) {
//extract keyword from thumbnail
//example: https://pics.dmm.co.jp/digital/video/mkmp00497/mkmp00497jp-2.jpg
//result keyword: mkmp00497
let keywordFromThumbnail = movieInfo.thumbnailURL
?.split("video/")[1]
?.split("/")[0];
//validation
if (keywordFromThumbnail?.includes(corp) && keywordFromThumbnail?.includes(idNum)) {
videoURL = `https://${host}/${infix}/${
keywordFromThumbnail[0]
}/${keywordFromThumbnail.substring(
0,
3
)}/${keywordFromThumbnail}/${keywordFromThumbnail}${postfix}.mp4`;
}
}
if (hasPrefix === true) {
if (corporations[corp]) {
//There must first get the idNum, and then get corp. Because corp will change.
idNum = corporations[corp][1] ? corporations[corp][1] + idNum : idNum;
corp = corporations[corp][0] + corp;
} else {
//if last time query is fail, this time try to add '00' prefix
if (!movieInfo.thumbnailURL?.includes("pics.dmm.co.jp")) {
idNum = "00" + idNum;
}
}
videoURL = `https://${host}/${infix}/${corp[0]}/${corp.substring(
0,
3
)}/${corp}${idNum}/${corp}${idNum}${postfix}.mp4`;
}
log("DMM server query:\r\n" + videoURL);
return await xFetch(videoURL, {
method: "head",
headers: {
"accept-language": "ja-JP",
cookie: "age_check_done=1;",
"referrer-policy": "no-referrer",
},
})
.then((resp) => {
if (resp.ok || resp.status === 200) {
log("DMM server result video url: " + videoURL);
return videoURL;
} else {
return Promise.reject("DMM server not found movie.");
}
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryDMMOfficialVideoURL(movieInfo, isUseTitle = false) {
if (movieInfo.isUncensored)
return Promise.reject("DMM Official server not support uncensored movie.");
let notFound = () => Promise.reject("DMM Official server not found movie.");
//Need ladder
let keyword = isUseTitle
? movieInfo.titleKeyPhrase
: movieInfo.movieId.replaceAll("-", "%20");
const host = "https://www.dmm.co.jp";
let serverURL = `${host}/search/=/searchstr=${keyword}/limit=3/sort=rankprofile/`;
log("DMM Official query:\r\n" + serverURL);
const headers = {
"accept-language": "ja-JP",
cookie: "age_check_done=1;",
"referrer-policy": "no-referrer",
};
return await xFetch(serverURL, {
headers: headers,
})
.then((resp) => {
//DMM Official search result, may contain multiple movies.
let resultMovies = extractDMMMovieItem(resp.responseText);
if (!resultMovies) return notFound();
let targetMovieEle = null;
for (const element of resultMovies) {
if (element.match(/dlsoft\.dmm\.co\.jp/)) continue;
if (matchMovieByKeyword(element, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailURL = targetMovieEle.match(/tmb.*?href="(\S*?)"/s)?.at(1);
if (!avDetailURL) return notFound();
//remove unuse params
return xFetch(avDetailURL.split("?")[0], {
headers: headers,
});
})
.then((avDetailResp) => {
//Different link format, get twice.
let videoIframeURL = avDetailResp.responseText
?.match(/data-video-url="(\S*?)"/s)
?.at(1);
if (!videoIframeURL) {
//try again
videoIframeURL = avDetailResp.responseText
?.match(/sampleplay\('(\S*?)'\)/s)
?.at(1);
}
if (!videoIframeURL) return notFound();
return xFetch(host + videoIframeURL, {
headers: headers,
});
})
.then((videoFrameResp) => {
let iframeURL = videoFrameResp.responseText?.match(/src="(\S*?)"/s)?.at(1);
if (!iframeURL) return notFound();
return xFetch(iframeURL, {
headers: headers,
});
})
.then((videoResp) => {
let videoURL = videoResp.responseText?.match(/args.*?src":"(\S*?)"/s)?.at(1);
//TODO get different quality
// bitrate.*?720.*?src":"(.*?)"
if (videoURL) {
videoURL = "https:" + videoURL.replaceAll("\\", "");
log("DMM Official server result video url: " + videoURL);
return videoURL;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryMGStageVideoURL(
movieInfo,
isUseTitle = false,
isUseThumbnail = false
) {
if (
movieInfo.isUncensored ||
(movieInfo.corpName.includes("プレステージ") === false &&
movieInfo.corpName.includes("ラグジュTV") === false &&
movieInfo.corpName.toUpperCase().includes("PRESTIGE") === false &&
!movieInfo.thumbnailURL?.includes("mgstage.com") &&
!movieInfo.thumbnailURL?.includes("prestige-av.com"))
)
return Promise.reject(
`MGStage server not support movieId: ${movieInfo.movieId}, CorpName: ${movieInfo.corpName}`
);
let notFound = () => Promise.reject("MGStage server not found movie.");
let keyword = isUseTitle ? movieInfo.titleKeyPhrase : movieInfo.movieId;
if (isUseThumbnail && movieInfo.thumbnailURL?.includes("image.mgstage.com")) {
//extract keyword from thumbnail
//example: https://image.mgstage.com/images/hmp/002aidv/0003/cap_e_1_002aidv-0003.jpg
//result keyword: 002aidv-0003
let keywordFromThumbnail = movieInfo.thumbnailURL
.match(new RegExp(`([\\da-zA-Z]*?${movieInfo.movieId})`, "i"))
?.at(1);
if (keywordFromThumbnail?.toUpperCase() === movieInfo.movieId.toUpperCase()) {
return Promise.reject("MGStage server : keywordFromThumbnail equals movieId.");
}
if (keywordFromThumbnail) {
keyword = keywordFromThumbnail;
}
}
//Need ladder
let serverURL = `https://www.mgstage.com/search/cSearch.php?search_word=${keyword}&list_cnt=30`;
log("MGStage server query:\r\n" + serverURL);
const headers = {
"accept-language": "ja-JP",
cookie: "coc=1;adc=1",
"referrer-policy": "no-referrer",
};
return await xFetch(serverURL, {
headers: headers,
})
.then((resp) => {
//MGStage search result, may contain multiple movies.
let resultMovies = extractMGStageMovieItem(resp.responseText);
if (!resultMovies) return notFound();
let targetMovieEle = null;
for (const element of resultMovies) {
if (matchMovieByKeyword(element, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailURL = targetMovieEle
.match(/<a href="(\S*?)"\s*?class="button_sample">/s)
?.at(1);
if (!avDetailURL) return notFound();
let pid = avDetailURL.split("/").at(-1);
return xFetch(
"https://www.mgstage.com/sampleplayer/sampleRespons.php?pid=" + pid,
{
headers: headers,
}
);
})
.then((avDetailResp) => {
//MGStage movie detail page result.
let videoURL = JSON.parse(avDetailResp.response)?.url?.split(".ism")[0] + ".mp4";
if (videoURL) {
log("MGStage server result video url: " + videoURL);
return videoURL;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryPrestigeVideoURL(movieInfo, isUseTitle = false) {
if (
movieInfo.isUncensored ||
(movieInfo.corpName.includes("プレステージ") === false &&
movieInfo.corpName.toUpperCase().includes("PRESTIGE") === false &&
!movieInfo.thumbnailURL?.includes("mgstage.com") &&
!movieInfo.thumbnailURL?.includes("prestige-av.com"))
)
return Promise.reject(
`Prestige server not support movieId: ${movieInfo.movieId}, CorpName: ${movieInfo.corpName}`
);
let notFound = () => Promise.reject("Prestige server not found movie.");
//Need ladder
let keyword = isUseTitle ? movieInfo.titleKeyPhrase : movieInfo.movieId;
let serverURL = `https://www.prestige-av.com/api/search?isEnabledQuery=true&searchText=${keyword}&isEnableAggregation=false&release=false&reservation=false&soldOut=false&from=0&aggregationTermsSize=0&size=20`;
log("Prestige server query:\r\n" + serverURL);
return await xFetch(serverURL)
.then((avDetailResp) => {
let resultMovies = JSON.parse(avDetailResp.response)?.hits?.hits;
if (!resultMovies) return notFound();
for (const movieDetail of resultMovies) {
let { deliveryItemId, productTitle, productMovie } = movieDetail["_source"];
if (
matchMovieByKeyword(deliveryItemId + productTitle, movieInfo) &&
productMovie?.path
) {
let videoURL = "https://www.prestige-av.com/api/media/" + productMovie.path;
log("Prestige server result video url: " + videoURL);
return videoURL;
}
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryTokyoHotVideoURL(movieInfo) {
if (
!movieInfo.isUncensored ||
(movieInfo.corpName != "東京熱" && movieInfo.corpName.toUpperCase() != "TOKYOHOT")
)
return Promise.reject(
`TokyoHot server not support movieId: ${movieInfo.movieId}, CorpName: ${movieInfo.corpName}`
);
let notFound = () => Promise.reject("TokyoHot server not found movie.");
//Need ladder
let serverURL = `https://my.cdn.tokyo-hot.com/product/?q=${movieInfo.movieId}`;
log("TokyoHot server query:\r\n" + serverURL);
return await xFetch(serverURL)
.then((resp) => {
//search result, may contain multiple movies.
let resultMovies = extractTokyoHotMovieItem(resp.responseText);
if (!resultMovies) return notFound();
let targetMovieEle = null;
for (const element of resultMovies) {
if (matchMovieByKeyword(element, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailURL = targetMovieEle.match(/<a href="(\S*?)" class="rm">/s)?.at(1);
if (!avDetailURL) return notFound();
return xFetch("https://my.cdn.tokyo-hot.com/" + avDetailURL);
})
.then((resp) => {
//detail page result
let videoSource = resp.responseText?.match(/<source.*?src="(\S*?)"/s)?.at(1);
// if (videoSource && videoSource?.src && videoSource.src != location.href)
if (videoSource) {
log("Tokyo server result video url: " + videoSource);
return videoSource;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryLocalCacheDB(movieInfo) {
let videoSource = GM_getValue(movieInfo.movieId, null);
if (!videoSource) {
return Promise.reject("Local cache storage not found movie.");
}
return await xFetch(videoSource, { method: "head" })
.then((resp) => {
if (resp?.status === 200) {
log(
"The video source URL in local cache validate successful. Video source: " +
videoSource
);
return Promise.resolve(videoSource);
}
//If validation fails,remove the url from the local cache.
GM_deleteValue(movieInfo.movieId);
return Promise.reject("The video source URL validate failed. Not found movie.");
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryCF(movieInfo) {
if (movieInfo.isUncensored)
return Promise.reject("CF server not support uncensored movie.");
let notFound = () => Promise.reject("CF server not found movie.");
let serverURL = `https://jav-trailer.breakwall9987.workers.dev/`;
return await xFetch(serverURL, {
data: JSON.stringify({
movieId: movieInfo.movieId,
title: movieInfo.title,
corpName: movieInfo.corpName,
isVR: movieInfo.isVR,
isUncensored: movieInfo.isUncensored,
thumbnailURL: movieInfo.thumbnailURL,
}),
method: "POST",
})
.then((resp) => {
if (resp?.status !== 200) return notFound();
let videoURL = resp.responseText;
if (!videoURL) return notFound();
log("CF server result video url: " + videoURL);
return videoURL;
})
.catch((e) => {
return Promise.reject(e);
});
}
async function querySupaBase(movieInfo) {
let notFound = () => Promise.reject("Supabase server not found movie.");
let serverURL = `https://pfragrqdbelauwiavsme.supabase.co/functions/v1/jav-trailer`;
return await xFetch(serverURL, {
data: JSON.stringify({
movieId: movieInfo.movieId,
title: movieInfo.title,
corpName: movieInfo.corpName,
isVR: movieInfo.isVR,
isUncensored: movieInfo.isUncensored,
thumbnailURL: movieInfo.thumbnailURL,
}),
method: "POST",
headers: {
"x-region": `ap-northeast-${Date.now() % 2 ? "1" : "2"}`,
},
})
.then((resp) => {
if (resp?.status !== 200) return notFound();
let videoURL = resp.responseText;
if (!videoURL) return notFound();
log("Supabase server result video url: " + videoURL);
return videoURL;
})
.catch((e) => {
return Promise.reject(e);
});
}
async function queryXCityVideoURL(movieInfo, isUseTitle = false) {
if (movieInfo.isUncensored)
return Promise.reject(
`XCity server not support movieId: ${movieInfo.movieId}, CorpName: ${movieInfo.corpName}`
);
let notFound = () => Promise.reject("XCity server not found movie.");
let keyword = isUseTitle
? movieInfo.titleKeyPhrase
: movieInfo.movieId.replaceAll("-", "");
//Need ladder
let serverURL = `https://xcity.jp/result_published/?genre=%2Fresult_published%2F&q=${keyword}&sg=main&num=30`;
log("XCity server query:\r\n" + serverURL);
const headers = {
"accept-language": "ja-JP",
cookie: "pagenum=30;",
Referer:
"https://xcity.jp/result_published/?genre=%2Fresult_published%2F&q=XCITY%E3%82%AA%E3%83%AA%E3%82%B8%E3%83%8A%E3%83%AB&sg=main&num=30",
"Sec-Fetch-Site": "same-origin",
};
return await xFetch(serverURL, {
headers: headers,
})
.then((resp) => {
//XCity search result, may contain multiple movies.
let resultMovies = resp.responseText?.match(
/<td>\s*?(<a href="\S*?id=\S*?">.*?<\/a>)\s*?<\/td>/gs
);
if (!resultMovies) return notFound();
let targetMovieEle = null;
for (const element of resultMovies) {
const innerText = element.match(/">(.*?)<\/a>/s)?.at(1);
if (matchMovieByKeyword(innerText, movieInfo)) {
targetMovieEle = element;
break;
}
}
if (!targetMovieEle) return notFound();
let avDetailURL = targetMovieEle.match(/href="(\S*?)"/s)?.at(1);
if (!avDetailURL) return notFound();
return xFetch(`https://xcity.jp${avDetailURL}`, {
headers: headers,
});
})
.then((avDetailResp) => {
//XCity movie detail page result.
let videoURL = avDetailResp.responseText
?.match(/name="src" value="(\S*?)"/s)
?.at(1);
if (videoURL) {
log("XCity server result video url: " + videoURL);
return videoURL;
}
return notFound();
})
.catch((e) => {
return Promise.reject(e);
});
}
//#region utility functions
async function xFetch(url, fetchInit = {}) {
const defaultFetchInit = { method: "get" };
const { headers, method, data, referrerPolicy } = {
...defaultFetchInit,
...fetchInit,
};
return new Promise((resolve, reject) => {
void GM_xmlhttpRequest({
url,
method,
headers,
referrerPolicy,
data,
onerror: reject,
onload: async (response) => resolve(response),
});
});
}
function log(msg) {
if (settings.enable_debug_mode == true) {
if (typeof msg === "object") {
console.dir(msg);
} else {
console.log(msg);
}
}
}
function isM3U8(url) {
return url?.endsWith("m3u8");
}
function needCORS(url) {
return need_cors_domain.has(new URL(url)?.host);
}
/**
* Convert HTTP protocol to HTTPS protocol
* @param {string} url
* @returns {string} url
*/
function convertHTTPToHTTPS(url) {
if (url?.startsWith("http:")) {
return url.replace("http:", "https:");
}
return url;
}
function extractDMMMovieItem(text) {
const matches = text.match(/<ul id="list">(.*?)<\/ul>/s);
if (!matches) return [];
return matches.at(1).match(/<li>(.*?)<\/li>/gs);
}
function extractMGStageMovieItem(text) {
const matches = text.match(/<div class="rank_list">\s*<ul>(.*?)<\/ul>/s);
if (!matches) return [];
return matches.at(1).match(/<li>(.*?)<\/li>/gs);
}
function extractTokyoHotMovieItem(text) {
return text.match(/<li class="detail">(.*?)<\/li>/gs);
}
function extractAVFantasyMovieItem(text) {
return text.match(/<div class="single-slider-product.*?>(.*?)<\/div>\s*?<\/div>/gs);
}
function matchMovieByKeyword(str, movieInfo) {
const st1 = str.toLowerCase();
const titleKeyPhrase = movieInfo.titleKeyPhrase.toLowerCase();
const movieId = movieInfo.movieId.toLowerCase();
const title = movieInfo.title.toLowerCase();
if (st1.indexOf(movieId) != -1 || st1.indexOf(title) != -1) {
return true;
}
if (jaroWinklerDistance(title, st1) >= 0.86) {
return true;
}
if (st1.indexOf(titleKeyPhrase) != -1) {
return true;
}
if (findDifference(titleKeyPhrase, removeWhitespace(st1)).length < 4) {
return true;
}
return false;
}
function getKeyPhrase(str, splitChar = " ") {
return str
.split(splitChar)
.reduce((pre, cur) => (cur?.length > pre.length ? cur : pre), "");
}
function findDifference(str1, str2) {
const len = Math.min(str1.length, str2.length);
const result = [];
for (let i = 0; i < len; i++) {
if (str1[i] !== str2[i]) {
result.push(str1[i]);
str1 = replaceChar(str1, i, str2[i]);
}
}
return result;
}
function replaceChar(str, index, newChar) {
const leftPart = str.slice(0, index);
const rightPart = str.slice(index + 1);
return leftPart + newChar + rightPart;
}
function removeWhitespace(str) {
return str.replace(/\s/g, "");
}
//Jaro-Winkler算法是一种专门用于计算短字符串相似度的算法,对于拼写错误更加容忍
//返回值在0到1之间,值越大表示两个字符串越相似。
function jaroWinklerDistance(str1, str2) {
const m = str1.length;
const n = str2.length;
if (m === 0 || n === 0) {
return 0;
}
const maxDistance = Math.floor(Math.max(m, n) / 2) - 1;
let match = 0;
const str1Matched = Array(m).fill(false);
const str2Matched = Array(n).fill(false);
// 计算匹配字符数
for (let i = 0; i < m; i++) {
const start = Math.max(0, i - maxDistance);
const end = Math.min(i + maxDistance + 1, n);
for (let j = start; j < end; j++) {
if (str2Matched[j] === false && str1[i] === str2[j]) {
str1Matched[i] = true;
str2Matched[j] = true;
match++;
break;
}
}
}
if (match === 0) {
return 0;
}
// 计算字符串的传输
let t = 0;
let point = 0;
for (let i = 0; i < m; i++) {
if (str1Matched[i]) {
while (str2Matched[point] === false) {
point++;
}
if (str1[i] !== str2[point]) {
t++;
}
point++;
}
}
t /= 2;
// 计算Jaro-Winkler距离
const jaroDistance = (match / m + match / n + (match - t) / match) / 3;
// 如果两个字符串前缀相同,增加前缀分数
let prefixLength = 0;
for (let i = 0; i < Math.min(m, n); i++) {
if (str1[i] === str2[i]) {
prefixLength++;
} else {
break;
}
}
prefixLength = Math.min(4, prefixLength);
const jaro_winkler = jaroDistance + 0.1 * prefixLength * (1 - jaroDistance);
return jaro_winkler;
}
//#endregion
})();