- // ==UserScript==
- // @name Jav小司机
- // @namespace wddd
- // @version 1.1.5
- // @author wddd
- // @license MIT
- // @include http*://*javlibrary.com/*
- // @include http*://*javlib.com/*
- // @include http*://*m34z.com/*
- // @include http*://*j41g.com/*
- // @include http*://*h28o.com/*
- // @description Jav小司机。简单轻量速度快!
- // @grant GM_xmlhttpRequest
- // @grant GM_addStyle
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_log
- // @homepage https://github.com/wdwind/JavMiniDriver
- // ==/UserScript==
-
- // Credit to
- // * https://greasyfork.org/zh-CN/scripts/25781
- // * https://greasyfork.org/zh-CN/scripts/37122
-
- // Change log
- // 1.1.5
- /**
- * Add page selector in video detail page
- * Support filters by score and viewers
- */
- // 1.1.4
- /**
- * Add support for j41g.com and h28o.com
- * Block ad
- * Only load the full screenshot until user clicks the thumbnail
- */
- // 1.1.3
- /**
- * Issue: https://github.com/wdwind/JavMiniDriver/issues/1#issuecomment-521836751
- *
- * Update browser history when clicking "load more" button in video list page
- * Store the configuration of whether to show the page selector in local storage instead of cookies
- * Fix a screenshot bug to handle non-existing images gracefully
- * Temporarily remove video from sod.co.jp since it requires a Referer in http request header
- * ~~Add a iframe to bypass adult check and DDoS check of sod.co.jp~~
- * Other technical refactoring
- */
- // 1.1.2
- /**
- * Issue: https://greasyfork.org/zh-CN/forum/discussion/61213/x
- *
- * Minor updates
- * Add javbus torrent search
- * Add support for javlib.com and m34z.com
- */
- // 1.1.1
- /**
- * Issue: https://github.com/wdwind/JavMiniDriver/issues/1
- *
- * Change thumbnail font
- * Add page selector
- * Add japanese-bukkake as backup image screenshot source
- * Change image width to max-width when clicking the screenshot to prevent image being over zoomed
- * Add more data sources for the screenshots in reviews/comments
- */
- // 1.1.0
- /**
- * Simplify code by merging the functions for get more comments/reviews
- * Process screenshots in reviews/comments
- * Remove redirection
- * Get full image url
- * Add mouse click effect
- */
-
- // Utils
-
- function setCookie(cookieName, cookieValue, expireDays) {
- let expireDate =new Date();
- expireDate.setDate(expireDate.getDate() + expireDays);
- let expires = "expires=" + ((expireDays == null) ? '' : expireDate.toUTCString());
- document.cookie = cookieName + "=" + cookieValue + ";" + expires + ";path=/";
- }
-
- // Not used
- // function getCookie(cookieName) {
- // let value = "; " + document.cookie;
- // let parts = value.split("; " + cookieName + "=");
- // if (parts.length == 2) {
- // return parts.pop().split(";").shift();
- // }
- // }
-
- function insertAfter(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
- }
-
- function insertBefore(newNode, referenceNode) {
- referenceNode.parentNode.insertBefore(newNode, referenceNode);
- }
-
- function removeElementIfPresent(element) {
- if (element) {
- return element.parentNode.removeChild(element);
- }
- }
-
- function parseHTMLText(text) {
- try {
- let doc = document.implementation.createHTMLDocument('');
- doc.documentElement.innerHTML = text;
- return doc;
- } catch (e) {
- console.error('Parse error');
- }
- }
-
- // https://stackoverflow.com/questions/494143/creating-a-new-dom-element-from-an-html-string-using-built-in-dom-methods-or-pro
- function createElementFromHTML(html) {
- var template = document.createElement('template');
- html = html.trim(); // Never return a text node of whitespace as the result
- template.innerHTML = html;
- return template.content.firstChild;
- }
-
- // For the requests in different domains
- // GM_xmlhttpRequest is made from the background page, and as a result, it
- // doesn't have cookies in the request header
- function gmFetch(obj) {
- return new Promise((resolve, reject) => {
- GM_xmlhttpRequest({
- method: obj.method || 'GET',
- // timeout in ms
- timeout: obj.timeout,
- url: obj.url,
- headers: obj.headers ? obj.headers : {},
- data: obj.data,
- onload: (result) => {
- if (result.status >= 200 && result.status < 300) {
- resolve(result);
- } else {
- reject(result);
- }
- },
- onerror: reject,
- ontimeout: reject,
- });
- });
- }
-
- // For the requests in the same domain
- // XMLHttpRequest is made within the page, so it will send the cookies
- function xhrFetch(obj) {
- return fetch(obj.url, {
- method: obj.method || 'GET',
- headers: obj.headers || {},
- body: obj.data,
- credentials: 'include',
- timeout: obj.timeout,
- });
- }
-
- function xhrFetchWithRetry(obj) {
- let fun = (obj) => xhrFetch(obj).then(response => {
- if (response.status == 429) {
- throw new Error('429 (Too Many Requests)');
- } else {
- return response;
- }
- });
-
- return retry(fun, obj);
- }
-
- async function retry(fun, input, max_retries = 10, retries = 0, initial = 8000) {
- try {
- return await fun(input);
- } catch (e) {
- if (retries > max_retries) {
- throw e;
- }
-
- GM_log(`Retrying ${retries}, wait ${initial + (2 ** retries) * 1000} ms`);
-
- // wait
- await new Promise(_ => setTimeout(_, initial + (2 ** retries) * 1000));
-
- // retry
- return retry(fun, input, max_retries, retries + 1, initial);
- }
- }
-
- // Style
-
- function addStyle() {
- // social media
- GM_addStyle(`
- #toplogo {
- height: 55px;
- }
- .socialmedia {
- display: none !important;
- width: 0% !important;
- height: 0% !important;
- }
- .videothumblist .videos .video {
- height: 290px;
- }
- .thumbnailDetail {
- font-size: 14px;
- margin-top: 2.5em;
- color: #666666;
- }
- .page_selector {
- display: block;
- margin-bottom: 15px;
- }
- .load_more {
- text-align: center;
- }
- #load_next_page {
- margin-bottom: 10px;
- }
- #load_next_page_button {
- display: inline;
- }
- #togglePageSelector {
- margin-left: 20px;
- font-size: 14px;
- vertical-align: text-top;
- display: inline;
- }
- .toggle {
- cursor: pointer;
- color: blue;
- }
- .bottombanner2 {
- display: none !important;
- }
- table.displaymode {
- table-layout: fixed;
- }
- td.mid {
- text-align: left;
- font: bold 12px monospace;
- }
- input.slider {
- direction: rtl;
- height: 10px;
- margin-left: 10px;
- }
- .filter {
- display: inline-block;
- }
- .filterMinValue {
- display: inline-block;
- }
- `);
-
- // Homepage
- if (!window.location.href.includes('.php')) {
- GM_addStyle(`
- .videothumblist {
- height: 645px !important;
- }
- `);
- }
- }
-
- // Thumbnail
- class MiniDriverThumbnail {
-
- constructor() {
- this.filterKeys = ['minScore', 'minViewer'];
- this.filterConfigs = {'minScore': {'value' : GM_getValue('minScore', 0), 'max': 10},
- 'minViewer': {'value' : GM_getValue('minViewer', 0), 'max': 100}};
- this.videoStats = {};
- }
-
- execute() {
- let videos = document.getElementsByClassName('videos')[0];
- document.getElementsByClassName('videothumblist')[0].innerHTML = `<div class="videothumblist">
- <div class="videos"></div>
- </div>`;
- let pageSelector = document.getElementsByClassName('page_selector')[0];
- this.updatePageContent(videos, pageSelector);
- this.addFilters();
- }
-
- addFilters() {
- let filters = createElementFromHTML(
- `<td class="mid">
- <div class="filter">
- 显示 <label for="score">评分 > </label>
- <div class="filterMinValue">0</div>
- <!--<input type="number" id="minScore" min="0">-->
- <input type="range" id="minScore" min="0" class="slider">
- </div>
- <div class="filter">
- <label for="viewers">观看人数 > </label>
- <div class="filterMinValue">0</div>
- <!--<input type="number" id="minViewer" min="0">-->
- <input type="range" id="minViewer" min="0" class="slider">
- </div>
- </td>`);
-
-
- let sliders = filters.getElementsByClassName('slider');
- let valueDiv = filters.getElementsByClassName('filterMinValue');
-
- function round(num) {
- return Math.round(num * 100) / 100;
- }
- function getSliderValue(input, max) {
- return round(100 - input * (100 / max));
- }
- function getConfigValue(input, max) {
- return round(max - input / (100 / max));
- }
-
- for (let i = 0; i < sliders.length; i++) {
- let config = this.filterConfigs[this.filterKeys[i]];
-
- sliders[i].value = getSliderValue(config['value'], config['max']);
- valueDiv[i].innerText = config['value'];
-
- sliders[i].addEventListener('change', () => {
- let updatedConfig = getConfigValue(sliders[i].value, config['max']);
- valueDiv[i].innerText = updatedConfig;
- GM_setValue(this.filterKeys[i], updatedConfig);
-
- this.applyFilters();
- });
- }
-
- // Insert filter to the page
- let mode = document.getElementsByClassName('displaymode');
- if (mode.length > 0) {
- let leftMode = mode[0].getElementsByClassName('left');
- if (leftMode.length > 0) {
- insertAfter(filters, leftMode[0]);
- }
- }
- }
-
- applyFilters() {
- for (let key in this.videoStats) {
- this.applyFilterOn(key);
- }
- }
-
- applyFilterOn(videoKey) {
- let video = document.getElementById(videoKey);
- if (video) {
- let show = true;
-
- for (let filter of this.filterKeys) {
- let config = GM_getValue(filter, 0);
-
- if (config > 0) {
- if (!(filter in this.videoStats[videoKey])
- || !this.videoStats[videoKey][filter]
- || this.videoStats[videoKey][filter] == NaN
- || this.videoStats[videoKey][filter] < config) {
- show = false;
- if (!show) {
- break;
- }
- }
- }
- }
-
- video.style.display = show ? 'inline-block' : 'none';
- }
- }
-
- updatePageContent(videos, pageSelector) {
- // Add videos to the page
- let currentVideos = document.getElementsByClassName('videos')[0];
- if (videos) {
- Array.from(videos.children).forEach(video => {
- currentVideos.appendChild(video);
- this.updateVideoDetail(video);
- this.updateVideoEvents(video);
- });
- }
-
- // Replace page selector content
- let pageSelectorId = 'pageSelectorThumbnail';
- let showPageSelector = GM_getValue(pageSelectorId, 'none') != 'block' ? 'none' : 'block';
- document.getElementsByClassName('page_selector')[0].innerHTML = pageSelector.innerHTML;
- document.getElementsByClassName('page_selector')[0].id = pageSelectorId;
- document.getElementsByClassName('page_selector')[0].style.display = showPageSelector;
- }
-
- async updateVideoDetail(video) {
- if (video.id.includes('vid_')) {
- let request = {url: `/cn/?v=${video.id.substring(4)}`};
- let response = await xhrFetchWithRetry(request).catch(err => {GM_log(err); return;});
- let responseText = await response.text().catch(err => {GM_log(err); return;});
- let videoDetailsDoc = parseHTMLText(responseText);
-
- // Video date
- let videoDate = '';
- if (videoDetailsDoc.getElementById('video_date')) {
- videoDate = videoDetailsDoc.getElementById('video_date').getElementsByClassName('text')[0].innerText;
- }
-
- // Video score
- let videoScore = '';
- if (videoDetailsDoc.getElementById('video_review')) {
- let videoScoreStr = videoDetailsDoc.getElementById('video_review').getElementsByClassName('score')[0].innerText;
- videoScore = videoScoreStr.substring(1, videoScoreStr.length - 1);
- if (!(video.id in this.videoStats)) {
- this.videoStats[video.id] = {};
- }
- this.videoStats[video.id]['minScore'] = parseFloat(videoScore);
- }
-
- // Video watched
- let videoWatched = '0';
- if (videoDetailsDoc.getElementById('watched')) {
- videoWatched = videoDetailsDoc.getElementById('watched').getElementsByTagName('a')[0].innerText;
- if (!(video.id in this.videoStats)) {
- this.videoStats[video.id] = {};
- }
- this.videoStats[video.id]['minViewer'] = parseFloat(videoWatched);
- }
-
- let videoDetailsHtml = `
- <div class="thumbnailDetail">
- <span>${videoDate}</span> <span style='color:red;'>${videoScore}</span>
- <br/>
- <span>${videoWatched} 人看过</span>
- </div>
- `;
- let videoDetails = createElementFromHTML(videoDetailsHtml);
- video.insertBefore(videoDetails, video.getElementsByClassName('toolbar')[0]);
-
- // Apply filter
- this.applyFilterOn(video.id);
- }
- }
-
- updateVideoEvents(video) {
- if (video) {
- // Prevent existing listeners https://stackoverflow.com/a/46986927/4214478
- video.addEventListener('mouseout', (event) => {
- event.stopImmediatePropagation();
- video.getElementsByClassName('toolbar')[0].style.display = 'none';
- }, true);
- video.addEventListener('mouseover', (event) => {
- event.stopImmediatePropagation();
- video.getElementsByClassName('toolbar')[0].style.display = 'block';
- }, true);
- }
- }
-
- async getNextPage(url) {
- // Update page URL and history
- history.pushState(history.state, window.document.title, url);
-
- // Fetch next page
- let response = await xhrFetchWithRetry({url: url}).catch(err => {GM_log(err); return;});
- let responseText = await response.text().catch(err => {GM_log(err); return;});
- let nextPageDoc = parseHTMLText(responseText);
-
- // Update page content
- let videos = nextPageDoc.getElementsByClassName('videos')[0];
- let pageSelector = nextPageDoc.getElementsByClassName('page_selector')[0];
- this.updatePageContent(videos, pageSelector);
- }
- }
-
- class MiniDriver {
-
- execute() {
- let javUrl = new URL(window.location.href);
- this.javVideoId = javUrl.searchParams.get('v');
-
- // Video page
- if (this.javVideoId != null) {
- this.addStyle();
- this.setEditionNumber();
- this.updateTitle();
- this.addScreenshot();
- this.addTorrentLinks();
- this.updateReviews();
- this.updateComments();
- this.getPreview();
- }
- }
-
- addStyle() {
- // left menu
- GM_addStyle(`
- #leftmenu {
- display: none;
- width: 0%;
- }
- #rightcolumn {
- margin-left: 10px;
- }
- /*
- #video_title .post-title:hover {
- text-decoration: underline;
- text-decoration-color: #CCCCCC;
- }
- */
- #video_id .text {
- color: red;
- }
- #torrents > table {
- width:100%;
- text-align: center;
- border: 2px solid grey;
- }
- #torrents tr td + td {
- border-left: 2px solid grey;
- }
- #video_favorite_edit {
- margin-bottom: 20px;
- }
- #torrents {
- margin-bottom: 20px;
- }
- #preview {
- margin-bottom: 20px;
- }
- #preview video {
- max-width: 100%;
- max-height: 80vh;
- }
- .screenshot {
- cursor: pointer;
- max-width: 25%;
- display: block;
- }
- .clickToCopy {
- cursor: pointer;
- }
- `);
- }
-
- setEditionNumber() {
- let edition = document.getElementById('video_id').getElementsByClassName('text')[0];
- this.editionNumber = edition.innerText;
- }
-
- async updateTitle() {
- let videoTitle = document.getElementById('video_title');
- let postTitle = videoTitle.getElementsByClassName('post-title')[0];
- postTitle.innerText = postTitle.getElementsByTagName('a')[0].innerText;
-
- // Add English title
- if (!window.location.href.includes('/en/')) {
- let request = {url: `/en/?v=${this.javVideoId}`};
- let response = await xhrFetchWithRetry(request).catch(err => {GM_log(err); return;});
- let responseText = await response.text().catch(err => {GM_log(err); return;});
- let videoDetailsDoc = parseHTMLText(responseText);
- let englishTitle = videoDetailsDoc.getElementById('video_title')
- .getElementsByClassName('post-title')[0]
- .getElementsByTagName('a')[0].innerText;
- postTitle.innerHTML = `${postTitle.innerText}<br/>${englishTitle}`;
- }
- }
-
- scrollToTop(element) {
- let distanceToTop = element.getBoundingClientRect().top;
- if (distanceToTop < 0) {
-
- window.scrollBy(0, distanceToTop);
- }
- }
-
- screenShotOnclick(element) {
- if (element.style['max-width'] != '100%') {
- element.style['max-width'] = '100%';
- } else {
- element.style['max-width'] = '25%';
- }
- this.scrollToTop(element);
- }
-
- lazyScreenShotOnclick(element) {
- let currentSrc = element.src;
- element.src = element.dataset.src;
- element.dataset.src = currentSrc;
- element.style['max-width'] = '100%';
- this.scrollToTop(element);
- }
-
- async addScreenshot() {
- let javscreensUrl = `http://javscreens.com/images/${this.editionNumber}.jpg`;
- let videoDates = document.getElementById('video_date').getElementsByClassName('text')[0].innerText.split('-');
- let jbUrl = `http://img.japanese-bukkake.net/${videoDates[0]}/${videoDates[1]}/${this.editionNumber}_s.jpg`;
- for (let url of [javscreensUrl, jbUrl]) {
- let img = await this.loadImg(url).catch((img) => {return img;});
- if (img && img.naturalHeight > 200) {
- insertBefore(img, document.getElementById('rightcolumn').getElementsByClassName('socialmedia')[0]);
- img.addEventListener('click', () => this.screenShotOnclick(img));
- // Valid screenshot loaded, break the loop
- break;
- }
- removeElementIfPresent(img);
- }
- }
-
- loadImg(url) {
- return new Promise(function (resolve, reject) {
- GM_xmlhttpRequest({
- method: 'GET',
- responseType: 'blob',
- url: url,
- onload: (result) => {
- if (result.status >= 200 && result.status < 300) {
- let img = createElementFromHTML(`<img class="screenshot" title="">`);
- insertBefore(img, document.getElementById('rightcolumn').getElementsByClassName('socialmedia')[0]);
- img.src = window.URL.createObjectURL(result.response);
-
- img.onload = () => resolve(img);
- img.onerror = () => reject(img);
- } else {
- reject();
- }
- },
- onerror: reject,
- ontimeout: reject,
- });
- });
- }
-
- addTorrentLinks() {
- let sukebei = `https://sukebei.nyaa.si/?f=0&c=0_0&q=${this.editionNumber}`;
- let btsow = `https://btos.pw/search/${this.editionNumber}`;
- let javbus = `https://www.javbus.com/${this.editionNumber}`;
- let torrentKitty = `https://www.torrentkitty.tv/search/${this.editionNumber}`;
- let tokyotosho = `https://www.tokyotosho.info/search.php?terms=${this.editionNumber}`;
- let biedian = `https://biedian.me/search?source=%E7%A7%8D%E5%AD%90%E6%90%9C&s=time&p=1&k=${this.editionNumber}`;
- let btDigg = `http://btdig.com/search?q=${this.editionNumber}`;
- let idope = `https://idope.se/torrent-list/${this.editionNumber}/`;
-
- let torrentsHTML = `
- <div id="torrents">
- <!--
- <form id="form-btkitty" method="post" target="_blank" action="http://btkittyba.co/">
- <input type="hidden" name="keyword" value="${this.editionNumber}">
- <input type="hidden" name="hidden" value="true">
- </form>
- <form id="form-btdiggs" method="post" target="_blank" action="http://btdiggba.me/">
- <input type="hidden" name="keyword" value="${this.editionNumber}">
- </form>
- -->
- <table>
- <tr>
- <td><strong>种子:</strong></td>
- <td><a href="${sukebei}" target="_blank">sukebei</a></td>
- <td><a href="${btsow}" target="_blank">btsow</a></td>
- <td><a href="${javbus}" target="_blank">javbus</a></td>
- <td><a href="${torrentKitty}" target="_blank">torrentKitty</a></td>
- <td><a href="${tokyotosho}" target="_blank">tokyotosho</a></td>
- <td><a href="${biedian}" target="_blank">biedian</a></td>
- <td><a href="${btDigg}" target="_blank">btDigg</a></td>
- <td><a href="${idope}" target="_blank">idope</a></td>
- <!--
- <td><a id="btkitty" href="JavaScript:Void(0);" onclick="document.getElementById('form-btkitty').submit();">btkitty</a></td>
- <td><a id="btdiggs" href="JavaScript:Void(0);" onclick="document.getElementById('form-btdiggs').submit();">btdiggs</a></td>
- -->
-
- </tr>
- </table>
- </div>
- `;
-
- let torrents = createElementFromHTML(torrentsHTML);
- insertAfter(torrents, document.getElementById('video_favorite_edit'));
- }
-
- updateReviews() {
- // Remove existing reviews
- let videoReviews = document.getElementById('video_reviews');
- Array.from(videoReviews.children).forEach(child => {
- if (child.id.includes('review')) {
- child.parentNode.removeChild(child);
- }
- });
-
- // Add all reviews
- this.getNextPage(1, 'reviews');
- }
-
- async getNextPage(page, pageType) {
- let pageSelectorId = 'page_selector_' + pageType;
- let urlPath = 'video' + pageType;
- let elementsId = 'video_' + pageType;
-
- // Load more reviews
- let request = {url: `/cn/${urlPath}.php?v=${this.javVideoId}&mode=2&page=${page}`};
- let response = await xhrFetchWithRetry(request).catch(err => {GM_log(err); return;});
- let responseText = await response.text().catch(err => {GM_log(err); return;});
- let doc = parseHTMLText(responseText);
-
- // Remove the page selector div in current page
- let oldPageSelectorDiv = document.getElementById(pageSelectorId);
- if (oldPageSelectorDiv != null) {
- oldPageSelectorDiv.parentNode.removeChild(oldPageSelectorDiv);
- }
-
- // Get comments/reviews in the next page
- let elements = doc.getElementById(elementsId);
- if (!elements.getElementsByClassName('t')[0] || !doc.getElementsByClassName('page_selector')[0]) {
- return;
- }
-
- // Set element texts
- Array.from(elements.getElementsByClassName('t')).forEach(element => {
- let elementText = parseBBCode(escapeHtml(element.getElementsByTagName('textarea')[0].innerText));
- let elementHtml = createElementFromHTML(`<div>${parseHTMLText(elementText).body.innerHTML}</div>`);
- element.getElementsByClassName('text')[0].replaceWith(this.processUrls(elementHtml));
- });
-
- // Append elements to the page
- let currentElements = document.getElementById(elementsId);
- let bottomLine = currentElements.getElementsByClassName('grey')[0];
- Array.from(elements.children).forEach(element => {
- if (element.tagName == 'TABLE' || element.tagName == 'TD') {
- currentElements.insertBefore(element, bottomLine);
- }
- });
-
- // Append page selector
- let showPageSelector = GM_getValue(pageSelectorId, 'none') != 'block' ? 'none' : 'block';
- let pageSelector = doc.getElementsByClassName('page_selector')[0];
- if (pageSelector) {
- pageSelector.style.display = showPageSelector;
- pageSelector.id = pageSelectorId;
- let as = pageSelector.getElementsByTagName('a');
- for (let a of as) {
- let nextPage = (new URL(a.href, a.href.includes('https') ? undefined : 'https://www.javlibrary.com/')).searchParams.get('page');
- a.removeAttribute('href');
- a.style.cursor = 'pointer';
- a.addEventListener('click', async () => this.getNextPage(nextPage ? parseInt(nextPage) : 1, pageType));
- }
- insertAfter(pageSelector, currentElements);
- }
- }
-
- updateComments() {
- // Remove existing comments
- let videoComments = document.getElementById('video_comments');
- Array.from(videoComments.children).forEach(child => {
- if (child.id.includes('comment')) {
- child.parentNode.removeChild(child);
- }
- });
-
- // Add all comments
- this.getNextPage(1, 'comments');
- }
-
- processUrls(content) {
- Array.from(content.getElementsByTagName('a')).forEach(a => {
- if (a.href.includes('redirect.php?url=')) {
- let encodedRealUrl = a.href.replace('redirect.php?url=', '');
- let realUrl = decodeURIComponent(encodedRealUrl);
- if (realUrl.indexOf('&ver=') > 0) {
- realUrl = realUrl.substring(0, realUrl.indexOf('&ver='));
- }
- a.href = realUrl;
- }
- });
- return content;
- }
-
- getPreview() {
- let nativeDmm = async() => {
- let dmmCid = document.getElementsByClassName('btn_videoplayer')[0].getAttribute('attr-data');
-
- // let request = {url: `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${dmmCid}/mtype=AhRVShI_/service=litevideo/mode=/width=560/height=360/`};
- let request = {url: `https://www.dmm.co.jp/service/-/html5_player/=/cid=${dmmCid}/mtype=AhRVShI_/service=mono/floor=dvd/mode=/`}
-
- let result = await gmFetch(request).catch(err => {GM_log(err); return;});
- let doc = parseHTMLText(result.responseText);
-
- // Very hacky... Didn't find a way to parse the HTML with JS.
- for (let script of doc.getElementsByTagName('script')) {
- if (script.innerText != null && script.innerText.includes('.mp4')) {
- for (let line of script.innerText.split('\n')) {
- if (line.includes('.mp4')) {
- line = line.substring(line.indexOf('{'), line.lastIndexOf(';'));
- let videoSrc = JSON.parse(line).src;
- if (!videoSrc.startsWith('http')) {
- videoSrc = 'http:' + videoSrc;
- }
- return videoSrc;
- }
- }
- }
- }
- }
-
- // r18 site is shut down
- // let r18 = async () => {
- // let request = {url: `https://www.r18.com/common/search/order=match/searchword=${this.editionNumber}/`};
- // let result = await gmFetch(request).catch(err => {GM_log(err); return;});
- // let videoTag = parseHTMLText(result.responseText).querySelector('.js-view-sample');
- // let src = ['high', 'med', 'low']
- // .map(i => videoTag.getAttribute('data-video-' + i))
- // .find(i => i);
- // return src;
- // }
-
- let javTrailer = async () => {
- let searchRequest = {
- url: `https://javtrailers.com/api/autocomplete?query=${this.editionNumber}&searchtype=id&lang=en`,
- headers: {
- authorization: 'AELAbPQCh_fifd93wMvf_kxMD_fqkUAVf@BVgb2!md@TNW8bUEopFExyGCoKRcZX',
- // cookie: 'auth.strategy=local; user-country=US; searchterm=fset-411; searchtype=id',
- // referer: 'https://javtrailers.com/',
- }
- };
- let searchResult = await gmFetch(searchRequest).catch(err => {GM_log(err); return;});
-
- let results = JSON.parse(searchResult.responseText).results;
- for (let result of results) {
- if (this.editionNumber === result.dvdId) {
- let videoRequest = {
- url : `https://javtrailers.com/api/video/${result.contentId}`,
- headers: {
- authorization: 'AELAbPQCh_fifd93wMvf_kxMD_fqkUAVf@BVgb2!md@TNW8bUEopFExyGCoKRcZX',
- // cookie: 'auth.strategy=local; user-country=US; searchterm=fset-411; searchtype=id',
- // referer: 'https://javtrailers.com/video/1fset00411',
- }
- };
- let videoResult = await gmFetch(videoRequest).catch(err => {GM_log(err); return;});
- let trailerUrl = JSON.parse(videoResult.responseText).video.trailer;
- if (trailerUrl.includes('.m3u8')) {
- GM_log(trailerUrl);
- GM_log('.m3u8 is not supported by HTML video tag on some browsers.');
- return;
- } else {
- return trailerUrl;
- }
- }
- }
- }
-
- let dmm = async () => {
- let dmmCid = await this.getDmmCid();
-
- if (dmmCid == null || dmmCid == '') {
- return;
- }
-
- // let request = {url: `https://www.dmm.co.jp/service/digitalapi/-/html5_player/=/cid=${dmmCid}/mtype=AhRVShI_/service=litevideo/mode=/width=560/height=360/`};
- let request = {url: `https://www.dmm.co.jp/service/-/html5_player/=/cid=${dmmCid}/mtype=AhRVShI_/service=mono/floor=dvd/mode=/`}
-
- let result = await gmFetch(request).catch(err => {GM_log(err); return;});
- let doc = parseHTMLText(result.responseText);
-
- // Very hacky... Didn't find a way to parse the HTML with JS.
- for (let script of doc.getElementsByTagName('script')) {
- if (script.innerText != null && script.innerText.includes('.mp4')) {
- for (let line of script.innerText.split('\n')) {
- if (line.includes('.mp4')) {
- line = line.substring(line.indexOf('{'), line.lastIndexOf(';'));
- let videoSrc = JSON.parse(line).src;
- if (!videoSrc.startsWith('http')) {
- videoSrc = 'http:' + videoSrc;
- }
- return videoSrc;
- }
- }
- }
- }
- }
-
- // let sod = async () => {
- // let request = {url: `https://ec.sod.co.jp/prime/videos/sample.php?id=${this.editionNumber}`};
- // let result = await gmFetch(request).catch(err => {GM_log(err); return;});
- // let doc = parseHTMLText(result.responseText);
- // return doc.getElementsByTagName('source')[0].src;
- // }
-
- // Site closed?
- let jav321 = async () => {
- let request = {
- url: `https://www.jav321.com/search`,
- method: 'POST',
- data: `sn=${this.editionNumber}`,
- headers: {
- referer: 'https://www.jav321.com/',
- 'content-type': 'application/x-www-form-urlencoded',
- },
- };
-
- let result = await gmFetch(request).catch(err => {GM_log(err); return;});
- let doc = parseHTMLText(result.responseText);
- return doc.getElementsByTagName('source')[0].src;
- }
-
- let kv = async () => {
- if (this.editionNumber.includes('KV-')) {
- return `http://fskvsample.knights-visual.com/samplemov/${this.editionNumber.toLowerCase()}-samp-st.mp4`;
- }
-
- return;
- }
-
- // // Prepare for sod adult check and DDoS check
- // // iframe vs. embed vs. object https://stackoverflow.com/a/21115112/4214478
- // // ifrmae sandbox https://www.w3schools.com/tags/att_iframe_sandbox.asp
- // insertBefore(
- // createElementFromHTML(`<iframe src="https://ec.sod.co.jp/prime/_ontime.php"
- // style="display:none;" referrerpolicy="no-referrer" sandbox>
- // </iframe>`),
- // document.getElementById('topmenu'));
-
- let previewSearchSources = document.getElementsByClassName('btn_videoplayer').length > 0 ? [nativeDmm] : [javTrailer, dmm, jav321, kv];
- Promise.all(
- previewSearchSources.map(source => source().catch(err => {GM_log(err); return;}))
- ).then(responses => {
- GM_log(responses);
-
- let videoHtml = responses
- .filter(response => response != null
- && this.includesEditionNumber(response)
- && !response.includes('//_sample.mp4'))
- .map(response => `<source src="${response}">`)
- .join('');
- if (videoHtml != '') {
- let previewHtml = `
- <div id="preview">
- <video controls onloadstart="this.volume=0.5">
- <meta name="referrer" content="no-referrer">
- ${videoHtml}
- </video>
- </div>
- `;
- insertAfter(createElementFromHTML(previewHtml), document.getElementById('torrents'));
- }
- });
- }
-
- includesEditionNumber(str) {
- return str != null
- // && str.includes(this.editionNumber.toLowerCase().split('-')[0])
- && str.includes(this.editionNumber.toLowerCase().split('-')[1]);
- }
-
- async getDmmCid() {
- let getCidFromUrl = (url) => {
- if (url.includes('dmm.co.jp') && this.includesEditionNumber(url)) {
- let cid = url.split('/').at(-2);
- return cid;
- }
- }
-
- let profileImageUrl = document.getElementById('video_jacket_img').src;
- let cid = getCidFromUrl(profileImageUrl);
- if (cid !== null) {
- return cid;
- }
-
- let urlPattern = /(http|https):\/\/[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/g;
- for (let url of document.body.innerHTML.match(urlPattern)) {
- cid = getCidFromUrl(url);
- if (cid != null) {
- return cid;
- }
- }
-
- let getCidFromSearchEngine = async (searchUrl) => {
- let request = {url: searchUrl};
- let response = await gmFetch(request).catch(err => {GM_log(err); return;});
- let pattern = /(cid=[\w]+|pid=[\w]+)/g;
- for (let match of response.responseText.match(pattern)) {
- if (this.includesEditionNumber(match)) {
- return match.replace(/(cid=|pid=)/, '');
- }
- }
- }
-
- // Find dmm cid from search engines
- let bingCid = getCidFromSearchEngine(`https://www.bing.com/search?q=${this.editionNumber.toLowerCase()}+site%3awww.dmm.co.jp`);
- if (bingCid != null) {
- return bingCid;
- }
-
- let googleCid = await getCidFromSearchEngine(`https://www.google.com/search?q=${this.editionNumber}+site%3Awww.dmm.co.jp`);
- if (googleCid != null) {
- return googleCid;
- }
- }
- }
-
- // Need `// @run-at document-start` to override the default addEventListener
- // Check https://stackoverflow.com/a/26269087/4214478 and https://stackoverflow.com/a/57437878/4214478
- // EventTarget.prototype.addEventListenerBase = EventTarget.prototype.addEventListener;
- // EventTarget.prototype.addEventListener = function(type, listener) {
- // if (this == document && type == 'click') {
- // GM_log('Prevent adding click event on "document" element. Event listener: ' + listener.toString());
- // return;
- // }
- // this.addEventListenerBase(type, listener);
- // };
-
- function blockAds() {
- // Not open ad url
- // https://stackoverflow.com/a/9172526
- // https://stackoverflow.com/a/4658196
-
- let adSites = ['yuanmengbi', 'zhaijv', 'henanlvyi'];
-
- let scope = (typeof unsafeWindow === "undefined") ? window : unsafeWindow;
- scope.open = function(open) {
- return function(url, name, features) {
- if (adSites.some(site => url.includes(site))) {
- return;
- }
- return open.call(scope, url, name, features);
- };
- }(scope.open);
- }
-
- // Block ad
- blockAds();
-
- // Adult check
- setCookie('over18', 18);
-
- // Style change
- addStyle();
- if (!window.location.href.includes('.php')
- && (window.location.href.includes('?v=') || window.location.href.includes('&v='))) {
- new MiniDriver().execute();
- } else {
- new MiniDriverThumbnail().execute();
- }