JavBus女优备注

在网页上的JAV女优名字后面添加信息备注。

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         JavBus女优备注
// @namespace    http://tampermonkey.net/
// @license      MIT
// @version      1.0
// @description  在网页上的JAV女优名字后面添加信息备注。
// @note         备注格式:[出生年份][出演年龄][身高][三围],如[1999][26岁][⭐172][B80-W58-H85]。
// @note         解释:
// @note         出生年份较早,拍摄时间较早的作品,不能因此就说这是“熟女片”。如果只备注出生年份,很容易忽视女优拍片时的实际年龄,因此需要额外备注出演年龄;
// @note         身高168cm及以上会⭐标显示。
// @note         由于日本的罩杯计算相比国内大一到两号,因此备注罩杯大小无意义。用胸围数据替代罩杯大小,更准确。而且腰围和臀围可以得到得到腰臀比;
// @note         多个数据来源,但偶尔也可能收录了错误的信息,哪个数据是正确的只能靠人工判断。当发现数据不对劲后,可对该女优信息自行补全更正。这份已知信息名单(预设数据),由使用者自行补全。即当查找的女优在这份名单里,会直接使用其数据,不再查询。
// @note         信息未知的部分用[?]备注。
// @note         实用功能:在素人片或共演片的“暫無出演者資訊”的下方显示实际出演女优及信息。如果搜寻结果出现 (≥o≤) 或 *** ,表示此女优信息暂未收录。
// @note         最后,不局限JavBus,有能力可自行扩展更多网站。
// @author       youziv6
// @include      /^https?:\/\/(?:[A-Za-z0-9]+\.)*(?:javbus|busjav|busfan|fanbus|buscdn|cdnbus|dmmsee|seedmm|busdmm|dmmbus|javsee|seejav){1}(?:\.[A-Za-z0-9]+)?\/*
// @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|genre|series|studio|page){1,}\/?\S*$/
// @icon         https://www.javbus.com/favicon.ico
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      av2ch.net
// @connect      av-wiki.net
// @connect      wikipedia.org
// @require      https://code.jquery.com/jquery-3.7.1.min.js
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    let actressName, ID = '';
    const host = window.location.host;
    const actressUrl = window.location.href;

    // 资料不全或数据不对,可在此手动添加预设数据
    const actressInfos = {
        '宮瀬リコ': '[1989][⭐173][B80-W59-H88]', // TokyoHot 官方数据
        '松本ななえ': '[1993][⭐172][B86-W57-H86]',
        '小谷舞花': '[1994][⭐170][?]',
        '高千穂すず': '[1996][⭐172][B87-W58-H89]',
        'みなと羽琉': '[1996][⭐175][B108-W62-H91]',
        '泉ゆり': '[1998][⭐175][B82-W54-H81]', // 出生年份由 https://xslist.org/ 的数据补全
        '深田えいみ': '[1998][158][B87.8-W63.5-H91.9]',
        '滝冬ひかり': '[2004][⭐170][B85-W56-H88]', // 根据 https://www.bilibili.com/opus/883159328946651157 推测
        '本庄鈴': '[1996][165][B80-W60-H86]', // 根据 https://www.south-plus.net/read.php?tid-2641300.html 分享的试镜资料
    }

    // 可自行扩展更多站点
    switch (true) {
        case /www\..*(jav|bus|dmm).*\..*/.test(host):
            if (actressUrl.includes('/star/')) {
                GM_addStyle(`.photo-info p {display: none;}`);
                actressName = $('.avatar-box .photo-info>span').text().trim();
                fetchData($('.avatar-box .photo-info>span'), actressName);
            } else {
                const releaseYear = $('.info p:nth-of-type(2)').text().match(/(\d{4})/)?.[1];
                $('.info .genre[onmouseover]>a').each(function () {
                    actressName = $(this).text().trim();
                    fetchData($(this), actressName, releaseYear);
                });
                // 对“暫無出演者資訊”作品的实际演员进行查询
                if (document.querySelector('.info .star-show+span').nextSibling.textContent.trim() === '暫無出演者資訊') {
                    ID = $('.info p:first span').text().trim().replace('識別碼:', '')
                    const targetElement = document.querySelector('.info .star-show + span');
                    const targetText = targetElement.nextSibling;
                    const newElement = document.createElement('p');
                    newElement.setAttribute('class', 'searched-star-show');
                    newElement.innerHTML = '<span><b>搜尋到出演女優:</b></span>';
                    targetElement.parentNode.insertBefore(newElement, targetText.nextSibling);
                    GM_xmlhttpRequest({
                        url: `https://av-wiki.net/?s=${ID}&post_type=product`,
                        method: 'GET',
                        onload: function (response) {
                            const parser = new DOMParser();
                            const doc = parser.parseFromString(response.responseText, 'text/html');
                            const xpath = `//li[text()='${ID}']`;
                            const result = doc.evaluate(
                                xpath,
                                doc,
                                null,
                                XPathResult.FIRST_ORDERED_NODE_TYPE,
                                null
                            );
                            const element = result.singleNodeValue;
                            if (element) {
                                $(element.previousSibling.previousSibling).find('a').each(function (index) {
                                    actressName = $(this).text().trim();
                                    $('.info .searched-star-show').after(`<p><span class="searched${index + 1}"><a href="https://www.javbus.com/searchstar/${actressName}" target="_blank">${actressName}</a></span></p>`);
                                    fetchData($(`.info .searched${index + 1}`), actressName, releaseYear);
                                })
                            }
                        }
                    })
                }
            }
            break;
    }

    function gmFetchAv2chInfo(actressName, releaseYear) {
        let matched = false;
        const pendingRequests = [];
        const targetNames = actressName.match(/[^()]+/g);
        return new Promise((resolve) => {
            targetNames.forEach((targetName) => {
                if (matched) return;
                const req = GM_xmlhttpRequest({
                    url: 'https://av2ch.net/avsearch/avs.php',
                    method: 'POST',
                    data: `keyword=${targetName}&gte_height=min&lte_height=max&gte_bust=min&lte_bust=max&gte_waist=min&lte_waist=max&gte_hip=min&lte_hip=max&gte_cup=min&lte_cup=max&gte_age=min&lte_age=max&genre_01=&genre_02=`,
                    headers: {
                        'Content-type': 'application/x-www-form-urlencoded',
                        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36'
                    },
                    onload: function (response) {
                        if (matched) return;
                        $(response.responseText).find('.box_actress>h2').each(function () {
                            const resultNames = $(this).text().trim().match(/[^()]+/g);
                            const text = $(this).nextAll('.text_actress').find('p').text();
                            resultNames.forEach((resultName) => {
                                if (resultName === targetName) {
                                    if (matched) return;
                                    const links = $(this).nextAll('.text_actress').find('p>.link_actress_genre_waku');
                                    if (links.length !== 0) {
                                        matched = true;
                                        pendingRequests.forEach(request => {
                                            if (request.readyState !== 4) {
                                                request.abort();
                                            }
                                        });
                                        pendingRequests.length = 0;
                                        const result = text.split('\n').slice(3, 5).join(' ');
                                        const info = formatPersonalInfo(result, releaseYear);
                                        resolve(info);
                                    }
                                }
                            })
                        })
                    },
                    onabort: function () {
                        console.log('请求被中止'); // 如果存在别名,会显示此信息
                    }
                });
                pendingRequests.push(req);
            })
        })
    }

    function gmFetchWikipediaInfo(actressName, releaseYear) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                url: `https://ja.wikipedia.org/wiki/${actressName}`,
                method: 'GET',
                headers: {
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
                },
                onload: function (response) {
                    const birthday = $(response.responseText).find('.infobox tbody tr:contains("生年月日")>td').text().trim();
                    const birthYear = parseInt(birthday.match(/(\d{4})年/)?.[1]);
                    // 出生年份小于1980年,直接返回undefined,防止碰巧重名
                    if (birthYear < 1980) {
                        const info = '[undefined][undefined][Bundefined-Wundefined-Hundefined]';
                        resolve(info);
                    }
                    const height = $(response.responseText).find('.infobox tbody tr:contains("身長 / 体重"):first>td').text().trim().split('/')[0].replace(/(\d{3}) cm/, 'T$1').trim();
                    const measurements = $(response.responseText).find('.infobox tbody tr:contains("スリーサイズ"):first>td').text().trim().replace(/(\d{2,3}) - (\d{2}) - (\d{2,3}) cm/, 'B$1-W$2-H$3');
                    const info = formatPersonalInfo([birthday, height, measurements].join(' '), releaseYear);
                    resolve(info);
                }
            });
        });
    }

    function gmFetchAvWikiInfo(actressName, releaseYear) {
        return new Promise((resolve) => {
            GM_xmlhttpRequest({
                url: `https://av-wiki.net/?s=${actressName}&post_type=product`,
                method: 'GET',
                headers: {
                    'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
                    'Accept-Encoding': 'gzip, deflate, br, zstd',
                    'Cookie': '_lscache_vary=5253ad7591e7079be24731efae29a1eb',
                    'Referer': 'https://av-wiki.net/',
                    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
                },
                onload: function (response) {
                    if ($(response.responseText).find(`a[rel='tag']:contains('${actressName}')`)[0] == undefined) {
                        const info = '[undefined][undefined][Bundefined-Wundefined-Hundefined]';
                        resolve(info);
                    }
                    const url = $(response.responseText).find(`a[rel='tag']:contains('${actressName}')`)[0].href;
                    GM_xmlhttpRequest({
                        url: url,
                        method: 'GET',
                        onload: function (response) {
                            let info = $(response.responseText).find('.actress-data dd:nth-of-type(n+3):nth-of-type(-n+4)').text();
                            info = formatPersonalInfo(info, releaseYear);
                            resolve(info);
                        }
                    });
                }
            });
        });
    }

    async function fetchData($element, actressName, releaseYear) {
        // 女优跳槽换事务所或引退后又复出改名不被识别,需要自行添加,用之前艺名检索
        switch (actressName) {
            case '河北彩伽':
                actressName = '河北彩花';
                break;
        }
        // 使用预设数据
        if (actressName in actressInfos) {
            if (actressUrl.includes('/star/')) {
                $element.append(`<br>${actressInfos[actressName]}`);
                return;
            }
            const year = actressInfos[actressName].match(/(\d{4})/)?.[1];
            const ageText = calculateAge(year, releaseYear);
            const result = actressInfos[actressName].replace(/^(\[[^\]]+\])/, '$1' + `[${ageText}]`);
            $element.append(`<br>${result}`);
            return;
        }
        try {
            if (actressName != undefined) showToast(`正在查找「${actressName}」...`, 3000);
            const response = await gmFetchAv2chInfo(actressName, releaseYear);
            console.log('「' + actressName + '」' + 'from Av2chInfo: ' + response);
            if (!response.includes('undefined')) {
                $element.append(`<br>${response}`);
                return;
            }
            const response2 = await gmFetchWikipediaInfo(actressName, releaseYear);
            console.log('「' + actressName + '」' + 'from WikipediaInfo: ' + response2);
            if (!response2.includes('undefined')) {
                $element.append(`<br>${response2}`);
                return;
            }
            const response3 = await gmFetchAvWikiInfo(actressName, releaseYear);
            console.log('「' + actressName + '」' + 'from AvWikiInfo: ' + response3);
            if (!response3.includes('undefined')) {
                $element.append(`<br>${response3}`);
                return;
            }
            const data = {
                av2chInfo: response.split('][').map(item => item.replace(/[\[\]]/g, '')),
                wikipediaInfo: response2.split('][').map(item => item.replace(/[\[\]]/g, '')),
                avWikiInfo: response3.split('][').map(item => item.replace(/[\[\]]/g, ''))
            };
            const formattedResult = processInfo(data);
            $element.append(`${formattedResult}<br>`);
        } finally {
            if (actressName != undefined) showToast(`✓ 已获取「${actressName}」的全部信息`, 3000);
        }
    }

    fetchData();

    function processInfo(data) {
        const mergedValues = [
            [data.av2chInfo[0], data.wikipediaInfo[0], data.avWikiInfo[0]], // 出生年份位置
            [data.av2chInfo[1], data.wikipediaInfo[1], data.avWikiInfo[1]], // 出演年龄位置
            [data.av2chInfo[2], data.wikipediaInfo[2], data.avWikiInfo[2]], // 身高位置
            [data.av2chInfo[3], data.wikipediaInfo[3], data.avWikiInfo[3]], // 三围位置
        ];
        const result = mergedValues.map(valueArray =>
            valueArray.find(value => value !== undefined && !value.includes('undefined') && value !== '?岁') || '?');
        console.table(result);
        if (actressUrl.includes('/star/')) return `[${result[0]}][${result[2]}][${result[3]}]`;
        return `[${result[0]}][${result[1]}][${result[2]}][${result[3]}]`;
    }

    function calculateAge(year, releaseYear) {
        let ageText = '?岁';
        if (year !== undefined && releaseYear) {
            const age = parseInt(releaseYear) - parseInt(year);
            ageText = `${age}岁`;
            return ageText;
        }
        return ageText;
    }

    function formatPersonalInfo(str, releaseYear) {
        const year = str.match(/(\d{4})年/)?.[1];
        const height = str.match(/[身長|T](\d{3})/)?.[1];
        const bust = str.match(/[B|B](\d{2,3})/)?.[1];
        const waist = str.match(/[W|W](\d{2,3})/)?.[1];
        const hips = str.match(/[H|H](\d{2,3})/)?.[1];
        if (actressUrl.includes('/star/')) return `[${year}][${height >= 168 ? '⭐' + height : height}][B${bust}-W${waist}-H${hips}]`;
        const ageText = calculateAge(year, releaseYear);
        return `[${year}][${ageText}][${height >= 168 ? '⭐' + height : height}][B${bust}-W${waist}-H${hips}]`;
    }

    let css = `
        .toast-container {
            position: fixed;
            top: 20px;
            right: 20px;
            z-index: 9999;
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        .toast-message {
            background: rgba(0, 0, 0, 0.7);
            color: white;
            padding: 10px 20px;
            border-radius: 4px;
            opacity: 0;
            transition: all 0.3s ease-in-out;
        }
    `;
    GM_addStyle(css);

    function showToast(message, duration = 3000) {
        let container = document.querySelector('.toast-container');
        if (!container) {
            container = document.createElement('div');
            container.className = 'toast-container';
            document.body.appendChild(container);
        }
        const toast = document.createElement('div');
        toast.className = 'toast-message';
        toast.textContent = message;
        container.appendChild(toast);
        requestAnimationFrame(() => {
            toast.style.opacity = '1';
        });
        setTimeout(() => {
            toast.style.opacity = '0';
            setTimeout(() => {
                container.removeChild(toast);
                if (container.children.length === 0) {
                    document.body.removeChild(container);
                }
            }, 300);
        }, duration);
    }

})();