// ==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}>e_height=min<e_height=max>e_bust=min<e_bust=max>e_waist=min<e_waist=max>e_hip=min<e_hip=max>e_cup=min<e_cup=max>e_age=min<e_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);
}
})();