// ==UserScript==
// @name PSN中文网功能增强
// @namespace https://swsoyee.github.io
// @version 1.0.18
// @description 数折价格走势图,显示人民币价格,奖杯统计和筛选,发帖字数统计和即时预览,楼主高亮,自动翻页,屏蔽黑名单用户发言,被@用户的发言内容显示等多项功能优化P9体验
// eslint-disable-next-line max-len
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAAMFBMVEVHcEw0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNs0mNuEOyNSAAAAD3RSTlMAQMAQ4PCApCBQcDBg0JD74B98AAABN0lEQVRIx+2WQRaDIAxECSACWLn/bdsCIkNQ2XXT2bTyHEx+glGIv4STU3KNRccp6dNh4qTM4VDLrGVRxbLGaa3ZQSVQulVJl5JFlh3cLdNyk/xe2IXz4DqYLhZ4mWtHd4/SLY/QQwKmWmGcmUfHb4O1mu8BIPGw4Hg1TEvySQGWoBcItgxndmsbhtJd6baukIKnt525W4anygNECVc1UD8uVbRNbumZNl6UmkagHeRJfX0BdM5NXgA+ZKESpiJ9tRFftZEvue2cS6cKOrGk/IOLTLUcaXuZHrZDq3FB2IonOBCHIy8Bs1Zzo1MxVH+m8fQ+nFeCQM3MWwEsWsy8e8Di7meA5Bb5MDYCt4SnUbP3lv1xOuWuOi3j5kJ5tPiZKahbi54anNRaaG7YElFKQBHR/9PjN3oD6fkt9WKF9rgAAAAASUVORK5CYII=
// @author InfinityLoop, mordom0404, Nathaniel_Wu, JayusTree
// @include *psnine.com/*
// @include *d7vg.com/*
// @require http://cdn.staticfile.org/jquery/2.1.4/jquery.min.js
// @require https://code.highcharts.com/highcharts.js
// @require https://code.highcharts.com/modules/histogram-bellcurve.js
// @require https://unpkg.com/tippy.js@3/dist/tippy.all.min.js
// @license MIT
// @supportURL https://github.com/swsoyee/psnine-night-mode-CSS/issues/new
// @compatible chrome
// @compatible firefox
// @compatible edge
// @grant GM_addStyle
// @grant GM_setValue
// @grant GM_getValue
// @run-at document-start
// ==/UserScript==
/* globals $, Highcharts, tippy */
(function () {
const settings = {
// 功能0-3设置:鼠标滑过黑条即可显示内容
hoverUnmark: true, // 设置为false则选中才显示
// 功能0-5设置:是否开启自动签到
autoCheckIn: true,
// 功能0-6: 自动翻页
autoPaging: 0, // 自动往后翻的页数
// 功能0-7:个人主页下显示所有游戏
autoPagingInHomepage: true,
// 功能1-4:回复内容回溯
replyTraceback: true,
// 功能1-1设置:高亮发帖楼主功能
highlightBack: '#3890ff', // 高亮背景色
highlightFront: '#ffffff', // 高亮字体颜色
// 功能1-2设置:高亮具体ID功能(默认管理员id)
// 注:此部分功能源于@mordom0404的P9工具包:
// https://greasyfork.org/zh-CN/scripts/29343-p9%E5%B7%A5%E5%85%B7%E5%8C%85
highlightSpecificID: ['mechille', 'sai8808', 'jimmyleo', 'jimmyleohk', 'monica_zjl', 'yinssk'], // 需要高亮的ID数组
highlightSpecificBack: '#d9534f', // 高亮背景色
highlightSpecificFront: '#ffffff', // 高亮字体颜色
// 功能1-6设置:屏蔽黑名单中的用户发言内容
blockList: [], // 请在左侧输入用户ID,用逗号进行分割,如: ['use_a', 'user_b', 'user_c'] 以此类推
// 屏蔽词,
blockWordsList: [],
// 问答页面状态UI优化
newQaStatus: true,
// 功能1-11设置:鼠标悬浮于头像显示个人奖杯卡
hoverHomepage: true,
// 功能4-3设置:汇总以获得和未获得奖杯是否默认折叠
foldTrophySummary: false, // true则默认折叠,false则默认展开
// 功能5-1设置:是否在`游戏`页面启用降低无白金游戏的图标透明度
filterNonePlatinumAlpha: 0.2, // 透密 [0, 1] 不透明,如果设置为1则关闭该功能
// 设置热门标签阈值
hotTagThreshold: 20,
// 夜间模式
nightMode: false,
// 自动夜间模式
autoNightMode: {
value: 'SYSTEM',
enum: ['SYSTEM', 'TIME', 'OFF'], // options in settings panel have to be in the same order
},
// 约战页面去掉发起人头像
removeHeaderInBattle: false,
// 机因、问答页面按最新排序
listPostsByNew: false,
// 载入全部问答答案
showAllQAAnswers: false,
// 答案按最新排列
listQAAnswersByNew: false,
// 答案显示隐藏回复
showHiddenQASubReply: false,
// 检测纯文本中的链接
fixTextLinks: true,
// 修复D7VG链接
fixD7VGLinks: true,
// 站内使用HTTPS链接
fixHTTPLinks: false,
// 尝试关联不同版本的游戏
referGameVariants: true,
// 查询游戏版本优先使用搜索
preferSearchForFindingVariants: false,
// 展开隐藏的子评论
expandCollapsedSubcomments: true,
};
if (window.localStorage) {
if (window.localStorage['psnine-night-mode-CSS-settings']) {
const localSettings = JSON.parse(window.localStorage['psnine-night-mode-CSS-settings']);
let settingTypeUpdated = false;
Object.keys(settings).forEach((key) => {
if (typeof settings[key] !== typeof localSettings[key]) {
localSettings[key] = settings[key];
settingTypeUpdated = true;
}
});
$.extend(settings, localSettings); // 用storage中的配置项覆盖默认设置
if (settingTypeUpdated) localStorage['psnine-night-mode-CSS-settings'] = JSON.stringify(localSettings);
}
} else {
console.log('浏览器不支持localStorage,使用默认配置项');
}
// 获取自己的PSN ID
const psnidCookie = document.cookie.match(/__Psnine_psnid=(\w+);/);
// 全局优化
function onDocumentStart() { // run before anything is downloaded
// 站内使用HTTPS链接
if (settings.fixHTTPLinks && /^http:\/\//.test(window.location.href)) window.location.href = window.location.href.replace('http://', 'https://');
// 机因、问答页面按最新排序
if (settings.listPostsByNew && /\/((gene)|(qa))($|(\/$))/.test(window.location.href)) {
window.location.href += '?ob=date';
}
// 功能0-2:夜间模式
const toggleNightMode = () => {
if (settings.nightMode) {
const node = document.createElement('style');
node.id = 'nightModeStyle';
node.type = 'text/css';
node.appendChild(document.createTextNode(`
li[style="background:#f5faec"]{background:#344836 !important;}li[style="background:#fdf7f7"]{background:#4f3945 !important;}li[style="background:#faf8f0"]{background:#4e4c39 !important;}li[style="background:#f4f8fa"]{background:#505050 !important;}span[style="color:blue;"]{color:#64a5ff !important;}span[style="color:red;"],span[style="color:#a10000"]{color:#ff6464 !important;}span[style="color:brown;"]{color:#ff8864 !important;}.tit3{color:white !important;}.mark{background:#bbb !important;color:#bbb;}body.bg{background:#2b2b2b !important;}.list li,.box .post,td,th{border-bottom:1px solid #333;}.psnnode{background:#656565;}.box{background:#3d3d3d !important;}.title a{color:#bbb;}.text-strong,strong,.storeinfo,.content{color:#bbb !important;}.alert-warning,.alert-error,.alert-success,.alert-info{background:#4b4b4b !important;}h1,.title2{color:#ffffff !important;}.twoge{color:#ffffff !important;}.inav{background:#3d3d3d !important;}.inav li.current{background:#4b4b4b !important;}.ml100 p{color:#ffffff !important;}.t1{background:#657caf !important;}.t2{background:#845e2f !important;}.t3{background:#707070 !important;}.t4{background:#8b4d2d !important;}blockquote{background:#bababa !important;}.text-gray{color:#bbb !important;}.tradelist li{color:white !important;}.tbl{background:#3c3c3c !important;}.genelist li:hover,.touchclick:hover{background:#333 !important;}.showbar{background:radial-gradient(at center top,#7B8492,#3c3c3c);}.darklist,.cloud{background-color:#3c3c3c;}.side .hd3,.header,.dropdown ul{background-color:#222;}.list li .sonlist li{background-color:#333;}.node{background-color:#3b4861;}.rep{background-color:#3b4861;}.btn-gray{background-color:#666;}.dropmenu .o_btn{margin-right:0;color:#bbb;border-color:#bbb;}.dropmenu .o_btn.select{margin-right:0;color:#fff;border-color:#3498db;background-color:#3498db;}`));
const heads = document.getElementsByTagName('head');
if (heads.length > 0) {
heads[0].appendChild(node);
} else { // no head yet, stick it whereever
document.documentElement.appendChild(node);
}
} else {
$('#nightModeStyle').remove();
}
};
const setNightMode = (isOn) => {
settings.nightMode = isOn;
toggleNightMode();
};
switch (settings.autoNightMode.value) {
case 'SYSTEM':
if (window.matchMedia) { // if the browser/os supports system-level color scheme
setNightMode(window.matchMedia('(prefers-color-scheme: dark)').matches);
const darkThemeQuery = window.matchMedia('(prefers-color-scheme: dark)');
if (darkThemeQuery.addEventListener) darkThemeQuery.addEventListener('change', (e) => setNightMode(e.matches));
else darkThemeQuery.addListener((e) => setNightMode(e.matches)); // deprecated
break;
}
// eslint-disable-next-line no-fallthrough
case 'TIME': {
const hour = (new Date()).getHours();
setNightMode(hour > 18 || hour < 7);// TODO: time selector in settings panel
break;
}
default:
toggleNightMode();
}
/*
* 功能:黑条文字鼠标悬浮显示
* param: isOn 是否开启功能
*/
const showMarkMessage = (isOn) => {
if (isOn) {
$(document).on('mouseenter', '.mark', function () {
$(this).css({ color: settings.nightMode ? 'rgb(0,0,0)' : 'rgb(255,255,255)' });
});
$(document).on('mouseleave', '.mark', function () {
$(this).css({ color: '' });
});
}
};
showMarkMessage(settings.hoverUnmark);
}
onDocumentStart();
function onDOMContentReady() { // run when DOM is loaded
let numberOfHttpCSS = 0;
let numberOfHttpsCSSLoaded = 0;
const httpCSSFixed = () => numberOfHttpsCSSLoaded === numberOfHttpCSS;
const fixLinksOnThePage = () => {
// 检测纯文本中的链接
const duplicatedSchemeRegex1 = /((href|src)=")((https?:\/\/)+)/g;
const duplicatedSchemeRegex2 = /(<a( [^<]+?)?>)((https?:\/\/)+)/g;
const untaggedUrlRegex = /(?<!((href|src)="|<a( [^<]+?)?>))(https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([A-Za-z0-9\-._~:/?#[\]@!$&'()*+,;=%]*))(?!("|<\/a>))/g;// https://stackoverflow.com/a/3809435 & https://stackoverflow.com/a/1547940
const fixTextLinksOnThePage = (isOn) => {
if (isOn && /(\/(topic|gene|qa|battle|trade)\/\d+)|(\/psnid\/.+?\/comment)|(\/my\/notice)|(\/psngame\/\d+\/comment)|(\/trophy\/\d+)/.test(window.location.href)) $('div.content').each((i, e) => { e.innerHTML = e.innerHTML.replace(duplicatedSchemeRegex1, '$1$4').replace(duplicatedSchemeRegex2, '$1$4').replace(untaggedUrlRegex, '<a href="$4" target="_blank">$4</a>'); });
};
// 修复D7VG链接
const linkReplace = (link, substr, newSubstr) => {
if (link.href) {
link.href = (link.href === link.innerText)
? (link.innerText = link.innerText.replace(substr, newSubstr))
: link.href.replace(substr, newSubstr);
} else if (link.src) link.src = link.src.replace(substr, newSubstr);
};
const fixD7VGLinksOnThePage = (isOn) => {
if (isOn) {
$("a[href*='//d7vg.com'], a[href*='//www.d7vg.com']").each((i, a) => {
if (!/d7vg\.com($|\/$)/.test(a.href)) { // 排除可能特意指向d7vg.com的链接
linkReplace(a, 'd7vg.com', 'psnine.com');
}
});
}
};
// 站内使用HTTPS链接
const fixHTTPLinksOnThePage = (isOn) => {
if (isOn) {
const httpCSS = $("link[href*='http://psnine.com'], link[href*='http://www.psnine.com']");
numberOfHttpCSS = httpCSS.length;
httpCSS.each((i, l) => {
const replacement = document.createElement('link');
replacement.addEventListener('load', () => { numberOfHttpsCSSLoaded += 1; }, false);
replacement.type = 'text/css';
replacement.rel = 'stylesheet';
replacement.href = l.href.replace('http://', 'https://');
l.remove();
document.head.appendChild(replacement);
});
$("a[href*='http://psnine.com'], a[href*='http://www.psnine.com'], img[src*='http://psnine.com'], img[src*='http://www.psnine.com'], iframe[src*='http://player.bilibili.com']").each((i, a) => linkReplace(a, 'http://', 'https://'));
const scriptSources = [];
$("script[src*='http://psnine.com'], script[src*='http://www.psnine.com']").each((i, s) => {
scriptSources.push(s.src.replace('http://', 'https://'));
s.remove();
});
$('head').find('script').each((i, s) => {
if (/^\s*var u\s*=\s*'http:\/\/(www\.)?psnine\.com';\s*$/.test(s.text)) {
s.remove();
const replacement = document.createElement('script');
replacement.type = 'text/javascript';
replacement.text = `var u = '${window.location.href.match(/^.+?\.com/)[0]}'`;
document.head.appendChild(replacement);
return false;
}
return true;
});
const scripts = [];
scriptSources.forEach((src) => {
$.ajax({ method: 'GET', dataType: 'text', url: src }).then((data) => {
const replacement = document.createElement('script');
replacement.type = 'text/javascript';
replacement.text = data;
scripts.push({
source: src,
script: replacement,
});
if (scripts.length === scriptSources.length) {
scriptSources.forEach((originalSrc) => {
const index = scripts.findIndex((s) => originalSrc.replace('http://', 'https://') === s.source);
document.head.appendChild(scripts[index].script);
scripts.splice(index, 1);
});
}
});
});
}
};
fixTextLinksOnThePage(settings.fixTextLinks);
fixD7VGLinksOnThePage(settings.fixD7VGLinks);
fixHTTPLinksOnThePage(settings.fixHTTPLinks);
};
fixLinksOnThePage();
Highcharts.setOptions({
lang: {
contextButtonTitle: '图表导出菜单',
decimalPoint: '.',
downloadJPEG: '下载JPEG图片',
downloadPDF: '下载PDF文件',
downloadPNG: '下载PNG文件',
downloadSVG: '下载SVG文件',
drillUpText: '返回 {series.name}',
loading: '加载中',
months: [
'一月',
'二月',
'三月',
'四月',
'五月',
'六月',
'七月',
'八月',
'九月',
'十月',
'十一月',
'十二月',
],
noData: '没有数据',
numericSymbols: ['千', '兆', 'G', 'T', 'P', 'E'],
printChart: '打印图表',
resetZoom: '恢复缩放',
resetZoomTitle: '恢复图表',
shortMonths: [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月',
],
thousandsSep: ',',
weekdays: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
},
});
// 暴力猴中已经删掉了GM_addStyle函数,因此需要自己定义
// eslint-disable-next-line camelcase
function GM_addStyle(css) {
const style = document.getElementById('GM_addStyleBy8626') || (function () {
// eslint-disable-next-line no-shadow
const style = document.createElement('style');
style.type = 'text/css';
style.id = 'GM_addStyleBy8626';
document.head.appendChild(style);
return style;
}());
const { sheet } = style;
sheet.insertRule(css, (sheet.rules || sheet.cssRules || []).length);
}
// 增加图标
GM_addStyle(`
.fa-check-circle {
width: 15px; height: 15px;
float: left;
margin-top: 3px;
margin-right: 3px;
background: url('data:image/svg+xml;utf8,<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="check-circle" class="svg-inline--fa fa-check-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="%23659f13" d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z"></path></svg>') no-repeat center;
}`);
GM_addStyle(`
.fa-question-circle {
width: 15px; height: 15px;
float: left;
margin-top: 3px;
margin-right: 3px;
background: url('data:image/svg+xml;utf8,<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="question-circle" class="svg-inline--fa fa-question-circle fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="%23c09853" d="M504 256c0 136.997-111.043 248-248 248S8 392.997 8 256C8 119.083 119.043 8 256 8s248 111.083 248 248zM262.655 90c-54.497 0-89.255 22.957-116.549 63.758-3.536 5.286-2.353 12.415 2.715 16.258l34.699 26.31c5.205 3.947 12.621 3.008 16.665-2.122 17.864-22.658 30.113-35.797 57.303-35.797 20.429 0 45.698 13.148 45.698 32.958 0 14.976-12.363 22.667-32.534 33.976C247.128 238.528 216 254.941 216 296v4c0 6.627 5.373 12 12 12h56c6.627 0 12-5.373 12-12v-1.333c0-28.462 83.186-29.647 83.186-106.667 0-58.002-60.165-102-116.531-102zM256 338c-25.365 0-46 20.635-46 46 0 25.364 20.635 46 46 46s46-20.636 46-46c0-25.365-20.635-46-46-46z"></path></svg>') no-repeat center;
}`);
GM_addStyle(`
.fa-comments {
width: 15px; height: 15px;
float: left;
margin-top: 3px;
margin-right: 3px;
background: url('data:image/svg+xml;utf8,<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="comments" class="svg-inline--fa fa-comments fa-w-18" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="%233a87ad" d="M416 192c0-88.4-93.1-160-208-160S0 103.6 0 192c0 34.3 14.1 65.9 38 92-13.4 30.2-35.5 54.2-35.8 54.5-2.2 2.3-2.8 5.7-1.5 8.7S4.8 352 8 352c36.6 0 66.9-12.3 88.7-25 32.2 15.7 70.3 25 111.3 25 114.9 0 208-71.6 208-160zm122 220c23.9-26 38-57.7 38-92 0-66.9-53.5-124.2-129.3-148.1.9 6.6 1.3 13.3 1.3 20.1 0 105.9-107.7 192-240 192-10.8 0-21.3-.8-31.7-1.9C207.8 439.6 281.8 480 368 480c41 0 79.1-9.2 111.3-25 21.8 12.7 52.1 25 88.7 25 3.2 0 6.1-1.9 7.3-4.8 1.3-2.9.7-6.3-1.5-8.7-.3-.3-22.4-24.2-35.8-54.5z"></path></svg>') no-repeat center;
}`);
GM_addStyle(`
.fa-coins {
width: 15px; height: 15px;
float: left;
background: url('data:image/svg+xml;utf8,<svg aria-hidden="true" focusable="false" data-prefix="fas" data-icon="coins" class="svg-inline--fa fa-coins fa-w-16" role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="%23bf6a3a" d="M0 405.3V448c0 35.3 86 64 192 64s192-28.7 192-64v-42.7C342.7 434.4 267.2 448 192 448S41.3 434.4 0 405.3zM320 128c106 0 192-28.7 192-64S426 0 320 0 128 28.7 128 64s86 64 192 64zM0 300.4V352c0 35.3 86 64 192 64s192-28.7 192-64v-51.6c-41.3 34-116.9 51.6-192 51.6S41.3 334.4 0 300.4zm416 11c57.3-11.1 96-31.7 96-55.4v-42.7c-23.2 16.4-57.3 27.6-96 34.5v63.6zM192 160C86 160 0 195.8 0 240s86 80 192 80 192-35.8 192-80-86-80-192-80zm219.3 56.3c60-10.8 100.7-32 100.7-56.3v-42.7c-35.5 25.1-96.5 38.6-160.7 41.8 29.5 14.3 51.2 33.5 60 57.2z"></path></svg>') no-repeat center;
}`);
/*
* 页面右下角追加点击跳转到页面底部按钮
*/
const toPageBottom = () => {
$('.bottombar').append("<a id='scrollbottom' class='yuan mt10'>B</a>");
$('#scrollbottom').click(() => {
$('body,html').animate({
scrollTop: document.body.clientHeight,
},
500);
}).css({
cursor: 'pointer',
});
};
// 功能0-2:夜间模式
const nightModeStyle = document.getElementById('nightModeStyle');
// ensures that night mode css is after native psnine css
if (nightModeStyle) {
document.head.appendChild(nightModeStyle);
}
/*
* 自动签到功能
* @param isOn 是否开启功能
*/
const repeatUntilSuccessful = (functionPtr, interval) => {
if (!functionPtr()) {
setTimeout(() => {
repeatUntilSuccessful(functionPtr, interval);
}, interval);
}
};
const automaticSignIn = (isOn) => {
// 如果签到按钮存在页面上
if (isOn && $('[class$=yuan]').length > 0) {
repeatUntilSuccessful(() => {
if (typeof qidao !== 'function') return false;
let signed = false;
$('[class$=yuan]').each((i, e) => {
if (!signed && /^\s*签\s*$/.test(e.innerText)) {
e.click();
signed = true;
}
});
return true;
}, 200);
}
};
automaticSignIn(settings.autoCheckIn);
/*
* 获取当前页面的后一页页码和链接
* @return nextPage 后一页页码
* @return nextPageLink 后一页的链接
*/
const getNextPageInfo = () => {
// 获取下一页页码
const nextPage = Number($('.page > ul > .current:last').text()) + 1;
// 如果地址已经有地址信息
let nextPageLink = '';
if (/page/.test(window.location.href)) {
nextPageLink = window.location.href.replace(
/page=.+/,
`page=${nextPage}`,
);
} else {
nextPageLink = `${window.location.href}&page=${nextPage}`;
}
return { nextPage, nextPageLink };
};
GM_addStyle(
`#loadingMessage {
position : absolute;
bottom : 0px;
position : fixed;
right : 1px !important;
display : none;
color : white;
}`,
);
if (
/psnid\/[A-Za-z0-9_-]+\/?$/.test(window.location.href)
&& $('tbody').length > 2
) {
const windowLocationHref = window.location.href.replace(/\/$/g, '');
// 功能0-7:个人主页下显示所有游戏
if (settings.autoPagingInHomepage) {
let isbool2 = true; // 触发开关,防止多次调用事件
// 插入加载提示信息
$('body').append("<div id='loadingMessage'/>");
let gamePageIndex = 2;
$(window).scroll(function () {
if (
$(this).scrollTop() + $(window).height() + 700
>= $(document).height()
&& $(this).scrollTop() > 700
&& isbool2 === true
) {
isbool2 = false;
const gamePage = `${windowLocationHref}/psngame?page=${gamePageIndex}`;
// 加载页面并且插入
$('#loadingMessage').text(`加载第${gamePageIndex}页...`).show();
$.get(
gamePage,
{},
(data) => {
const $response = $('<div />').html(data);
const nextGameContent = $response.find('tbody > tr');
if (nextGameContent.length > 0) {
$('tbody > tr:last').after(nextGameContent);
isbool2 = true;
gamePageIndex += 1;
} else {
$('#loadingMessage').text('没有更多游戏了...');
}
},
'html',
);
setTimeout(() => {
$('#loadingMessage').fadeOut();
}, 2000);
}
});
}
// 功能:未注册用户的PSN主页添加更新按钮
const updateButtonForm = $('div.psnzz > div.inner > div.psnbtn.psnbtnright > form');
if (updateButtonForm.find('a').length === 0) {
const upbase = `<a href="${windowLocationHref}/upbase">等级同步</a>`;
const upgame = `<a href="${windowLocationHref}/upgame">游戏同步</a>`;
updateButtonForm.append(upbase, upgame);
}
}
// 帖子优化
/*
* 功能:对发帖楼主增加“楼主”标志
* @param userId 用户(楼主)ID
*/
const addOPBadge = (userId) => {
$('.psnnode').each((i, n) => {
// 匹配楼主ID,变更CSS
if ($(n).text() === userId) {
$(n).after('<span class="badge badge-1">楼主</span>');
}
});
};
/*
* AJAX获取页面
*/
const fetchOtherPage = (url, successFunction) => {
let resultSet;
$.ajax({
type: 'GET',
url,
dataType: 'html',
async: true,
success(data, status) {
if (status === 'success') {
resultSet = successFunction(data);
$('.imgbgnb').parent().each((index, el) => {
resultSet.forEach((element) => {
if (element.trophy === $(el).attr('href')) {
$(el).next().find('a').slice(0, 1)
.append(`<div class="fa-check-circle"></div> <em class="alert-success pd5" style="border-radius: 3px;">${element.earned}</em>`);
}
});
});
}
},
error: () => {
console.log('无法获取页面信息');
},
});
};
const getEarnedTrophiesInfo = (data) => {
const reg = /[\s\S]*<\/body>/g;
const html = reg.exec(data)[0];
const resultSet = [];
$(html).find('tbody>tr[id]').find('.imgbg.earned').parent()
.parent()
.parent()
.each((index, el) => {
const earnedTime = $(el).find('em.lh180.alert-success.pd5.r');
const earnedTimeCopy = earnedTime.clone();
earnedTimeCopy.find('br').replaceWith(' ');
resultSet.push({
trophy: $(el).find('a').attr('href'),
earned: `${earnedTime.attr('tips').trim()} ${earnedTimeCopy.text().trim()}`,
});
});
return resultSet;
};
if (/topic\//.test(window.location.href) && psnidCookie) {
const games = {};
$('.imgbgnb').parent().each((index, el) => {
if (!/(^| |")(pd10|t3)($| |")/.test($(el).parent().get()[0].className)) return;
const href = $(el).attr('href');
const gameId = href.slice(href.lastIndexOf('/') + 1, -3);
// 根据具体游戏获取对应自己页面的信息
if (!Object.prototype.hasOwnProperty.call(games, gameId)) {
const gamePageUrl = `${document.URL.match(/^.+?\.com/)[0]}/psngame/${gameId}?psnid=${psnidCookie[1]}`;
fetchOtherPage(gamePageUrl, getEarnedTrophiesInfo);
games[gameId] = true;
}
});
}
if (
/(gene|trade|topic)\//.test(window.location.href)
&& !/comment/.test(window.location.href)
) {
// 获取楼主ID
const authorId = $('.title2').text();
addOPBadge(authorId);
}
/*
* 功能:对关注用户进行ID高亮功能函数
*/
const addHighlightOnID = () => {
settings.highlightSpecificID.forEach((i) => {
$(`.meta>[href="${window.location.href.match('(.*)\\.com')[0]}/psnid/${i}"]`).css({
'background-color': settings.highlightSpecificBack,
color: settings.highlightSpecificFront,
});
});
};
addHighlightOnID();
/*
* 功能:根据纯文本的长度截断DOM
* @param elem 需要截断的DOM
* @param length 需要保留的纯文本长度
* @return 截断后的 html 文本
*/
const truncateHtml = (elem, length) => {
// 递归获取 DOM 里的纯文本
const truncateElem = (e, reqCount) => {
let grabText = '';
let missCount = reqCount;
$(e).contents().each(function () {
switch (this.nodeType) {
case Node.TEXT_NODE: {
// Get node text, limited to missCount.
grabText += this.data.substr(0, missCount);
missCount -= Math.min(this.data.length, missCount);
break;
}
case Node.ELEMENT_NODE: {
// Explore current child:
const childPart = truncateElem(this, missCount);
grabText += childPart.text;
missCount -= childPart.count;
break;
}
default: {
break;
}
}
if (missCount === 0) {
// We got text enough, stop looping.
return false;
}
return true;
});
return {
// Wrap text using current elem tag.
text: `${e.outerHTML.match(/^<[^>]+>/m)[0] + grabText}</${e.localName}>`,
count: reqCount - missCount,
};
};
return truncateElem(elem, length).text;
};
/*
* 功能:回复内容回溯,仅支持机因、主题
* @param isOn 是否开启功能
*/
const showReplyContent = (isOn) => {
if (isOn) {
// 每一层楼的回复框
const allSource = $('.post .ml64 > .content');
if (allSource.length <= 0) return;
GM_addStyle(
`.replyTraceback {
background-color: rgb(0, 0, 0, 0.05) !important;
padding: 10px !important;
color: rgb(160, 160, 160, 1) !important;
border-bottom: 1px solid !important;
}`,
);
// 悬浮框内容左对齐样式
GM_addStyle(`
.tippy-content {
text-align: left;
overflow-wrap: break-word;
}`);
// 每一层楼的回复者用户名
const userId = $('.post > .ml64 > [class$=meta]');
// 每一层的头像
const avator = $('.post > a.l');
for (let floor = allSource.length - 1; floor > 0; floor -= 1) {
// 层内内容里包含链接(B的发言中是否有A)
const content = allSource.eq(floor).find('a');
if (content.length > 0) {
for (let userNum = 0; userNum < content.length; userNum += 1) {
// 对每一个链接的文本内容判断
const linkContent = content.eq(userNum).text().match('@(.+)');
// 链接里是@用户生成的链接, linkContent为用户名(B的发言中有A)
if (linkContent !== null) {
// 从本层的上一层开始,回溯所@的用户的最近回复(找最近的一条A的发言)
let traceIdFirst = -1;
let traceIdTrue = -1;
for (let traceId = floor - 1; traceId >= 0; traceId -= 1) {
// 如果回溯到了的话,选取内容
// 回溯层用户名
const thisUserID = userId.eq(traceId).find('.psnnode:eq(0)').text();
if (thisUserID.toLowerCase() === linkContent[1].toLowerCase()) {
// 判断回溯中的@(A的发言中有是否有B)
if (
allSource.eq(traceId).text() === userId.eq(floor).find('.psnnode:eq(0)').text()
) {
traceIdTrue = traceId;
break;
} else if (traceIdFirst === -1) {
traceIdFirst = traceId;
}
}
}
let outputID = -1;
if (traceIdTrue !== -1) {
outputID = traceIdTrue;
} else if (traceIdFirst !== -1) {
outputID = traceIdFirst;
}
// 输出
if (outputID !== -1) {
const replyContentObjectOriginal = allSource.eq(outputID);
const replyContentObject = replyContentObjectOriginal.clone();
const replyContentPlainText = replyContentObject.text();
replyContentObject.find('.mark').text((index, text) => `<span class="mark">${text}</span>`);
const replyContentText = replyContentObject.text();
let replyContentTruncatedText = $(truncateHtml($('<p></p>').html(replyContentText)[0], 45)).html();
if (replyContentPlainText.length > 45) {
replyContentTruncatedText += '......';
}
const avatorImg = avator.eq(outputID).find('img:eq(0)').attr('src');
allSource.eq(floor).before(
`<div class=replyTraceback>
<span class="badge">
<img src="${avatorImg}" height="15" width="15" style="margin-right: 5px; border-radius: 8px;vertical-align:sub;"/>
${linkContent[1]}
</span>
<span class="responserContent_${floor}_${outputID}" style="padding-left: 10px;">
${replyContentTruncatedText}
</span>
</div>`,
);
// 如果内容超过45个字符,则增加悬浮显示全文内容功能
if (replyContentPlainText.length > 45) {
tippy(`.responserContent_${floor}_${outputID}`, {
content: replyContentText,
animateFill: false,
maxWidth: '500px',
});
}
// 增加点击回复内容跳转功能
const responserContent = $(`.responserContent_${floor}_${outputID}`);
responserContent.click(() => {
const targetBounds = replyContentObjectOriginal.get(0).getBoundingClientRect();
const currentBoundsTop = responserContent.get(0).getBoundingClientRect().top;
if (targetBounds.top < 0) { // 回复内容顶部不在窗口内
// 回复内容顶部滚动到当前元素顶部处无法完整显示时
if (currentBoundsTop + targetBounds.height > window.innerHeight) {
// 回复内容比窗口高度更长时,回复内容顶部滚动至窗口顶部,否则回复内容底部滚动至窗口底部
if (targetBounds.height > window.innerHeight) window.scrollBy({ top: targetBounds.top, behavior: 'smooth' });
else window.scrollBy({ top: targetBounds.bottom - window.innerHeight, behavior: 'smooth' });
} else if (currentBoundsTop < 0) window.scrollBy({ top: targetBounds.top, behavior: 'smooth' }); // 当前元素顶部在窗口外时
else window.scrollBy({ top: targetBounds.top - currentBoundsTop, behavior: 'smooth' }); // 默认滚动至当前元素顶部
}
$(replyContentObjectOriginal)
.fadeOut(500)
.fadeIn(500)
.fadeOut(500)
.fadeIn(500);
});
// 鼠标悬浮变手形样式
responserContent.css('cursor', 'pointer');
}
}
}
}
}
}
};
/*
* 功能:增加帖子楼层信息
*/
const addFloorIndex = () => {
let baseFloorIndex = 0;
let subFloorIndex = -1;
$('span[class^=r]').each((i, el) => {
if (i > 0) {
if ($(el).attr('class') === 'r') {
$(el).children('a:last')
.after(`  <span>#${baseFloorIndex}</span>`);
baseFloorIndex += 1;
subFloorIndex = -1;
} else {
$(el).children('a:last')
.after(
`  <span>#${baseFloorIndex}${subFloorIndex}</span>`,
);
subFloorIndex -= 1;
}
}
});
};
/*
* 功能:热门帖子增加 热门 标签
*/
const addHotTag = () => {
$('div.meta').each((index, element) => {
const replyCount = $(element).text().split(/(\d+)/);
if (Number(replyCount[replyCount.length - 2]) > settings.hotTagThreshold
&& replyCount[replyCount.length - 1].match('评论|答案|回复')
&& replyCount[replyCount.length - 1].match('评论|答案|回复').index > -1
&& $(element).children('a#hot').length === 0
) {
const tagBackgroundColor = $('body.bg').css('background-color');
$(element)
.append(` <a class="psnnode" id="hot" style="background-color: ${tagBackgroundColor === 'rgb(43, 43, 43)'
? 'rgb(125 69 67)' // 暗红色
: 'rgb(217, 83, 79)' // 鲜红色
};color: rgb(255, 255, 255);">🔥热门 </a>`);
}
});
};
addHotTag();
/*
* 功能:层内逆序显示
* @param isOn 是否开启该功能
*/
const reverseSubReply = (isOn) => {
if (!isOn || !/(\/trophy\/\d+)|(\/psngame\/\d+\/comment)|(\/psnid\/.+?\/comment)/.test(window.location.href)) return;
repeatUntilSuccessful(() => {
try {
$('div.btn.btn-white.font12').click();
const blocks = $('div.sonlistmark.ml64.mt10:not([style="display:none;"])');
blocks.each((index, block) => {
const reversedBlock = $($(block).find('li').get().reverse());
$(block).find('.sonlist').remove();
$(block).append('<ul class="sonlist">');
reversedBlock.each((i, li) => {
if (i === 0) {
$(li).attr({ style: 'border-top:none;' });
} else {
$(li).attr({ style: '' });
}
$(block).find('.sonlist').append(li);
});
});
return true;
} catch (e) { return false; }
}, 200);
};
const enhanceQAPage = (loadAll, reverseOrder, allSubReply) => {
if (!(loadAll || reverseOrder || allSubReply) || !/\/qa\/\d+($|(\/$))/.test(window.location.href)) return;
/*
* 功能:答案按时间顺序排列
* @param isOn 是否开启该功能
*/
const reverseAnwsers = (isOn) => {
if (!isOn) return;
const answerList = $('body > div.inner.mt40 > div.main > div.box.mt20 > ul.list');
const answers = answerList.find('> li');
answers.remove();
answers.get().reverse().forEach((answer) => { answerList.append(answer); });
};
/*
* 功能:展开隐藏的二级回复
* @param isOn 是否开启该功能
*/
const showHiddenSubReply = (isOn) => {
repeatUntilSuccessful(() => {
try {
if (isOn) $('body > div.inner.mt40 > div.main > div.box.mt20 > ul.list div.btn.btn-white.font12').click();
return true;
} catch (e) { return false; }
}, 200);
};
/*
* 功能:载入全部问答答案
* @param isOn 是否开启该功能
*/
const showAllAnsers = (isOn, isReverseOrder, isAllSubReply) => {
if (!isOn) return 0;
const answerList = $('body > div.inner.mt40 > div.main > div.box.mt20 > ul.list');
const pageList = $('body > div.inner.mt40 > div.main > div.box.mt20 > div.page > ul');
const lastPageUrlElement = pageList.find('> li:not(.current):not(.disabled.h-p) > a:last()');
if (lastPageUrlElement.length === 0) {
pageList.remove();
return 0;
}
const lastPageUrl = lastPageUrlElement.get()[0].href;
const lastPageNumber = Number(lastPageUrl.match(/\?page=\d+/)[0].replace('?page=', ''));
let qaPagesToLoad = lastPageNumber - 1;
let lastAppendedPage = 1;
const qaPageData = new Array(qaPagesToLoad);
qaPageData.fill(null);
const loadQaPage = (pageNumber) => {
const appendAnswers = () => {
let latestReadyPage = lastAppendedPage;
for (let i = lastAppendedPage + 1; i <= lastPageNumber; i += 1) {
if (qaPageData[i - 2]) latestReadyPage = i;
else break;
}
if (latestReadyPage > lastAppendedPage) {
for (let i = lastAppendedPage + 1; i <= latestReadyPage; i += 1) {
qaPageData[i - 2].find('div.inner.mt40 > div.main > div.box.mt20 > ul.list > li').each((index, answer) => {
answerList.append(answer);
});
qaPageData[i - 2].remove();
qaPageData[i - 2] = null;
}
lastAppendedPage = latestReadyPage;
}
};
const pageUrl = lastPageUrl.replace(`?page=${lastPageNumber}`, `?page=${pageNumber}`);
$.get(
pageUrl,
{ retryLimit: 3 },
(data) => {
qaPageData[pageNumber - 2] = $('<div />').html(data);
appendAnswers();
qaPagesToLoad -= 1;
if (qaPagesToLoad === 0) { // 在载入全部答案之后运行
reverseAnwsers(isReverseOrder);
showHiddenSubReply(isAllSubReply);
pageList.remove();
}
},
'html',
);
};
for (let i = 2; i <= lastPageNumber; i += 1) loadQaPage(i);
return lastPageNumber - 1;
};
if (showAllAnsers(loadAll, reverseOrder, allSubReply) === 0) {
reverseAnwsers(reverseOrder);
showHiddenSubReply(allSubReply);
}
};
enhanceQAPage(
settings.showAllQAAnswers,
settings.listQAAnswersByNew,
settings.showHiddenQASubReply,
);
reverseSubReply(true);
addFloorIndex();
// 功能1-6:屏蔽黑名单中的用户发言内容
const Filter = (psnnode, parent, userListLowerCase, psnInfoGetter, userNameChecker) => {
let psnInfo = '';
const userNameCheckerFinal = (user) => userNameChecker(user, psnInfo);
const remover = parent.replace(/\s/g, '') === 'ul.sonlist>li' ? (el) => {
const parentElements = el.parents(parent);
const sonlistmark = parentElements.parents('div.sonlistmark.ml64.mt10');
parentElements.remove();
if (sonlistmark[0].querySelector('ul.sonlist>li') === null) {
sonlistmark.hide();
}
} : (el) => el.parents(parent).remove();
let removed = 0;
$(psnnode).each((i, el) => {
psnInfo = psnInfoGetter($(el)).trim();
if (userListLowerCase.find(userNameCheckerFinal) !== undefined) {
remover($(el));
removed += 1;
}
});
return removed;
};
let filteredCriticPost = false;
const filterUserPost = () => {
if (settings.blockList.length > 0) {
const windowHref = window.location.href;
const userListLowerCase = [];
settings.blockList.forEach((user) => { userListLowerCase.push(user.toLowerCase()); });
const FilterRegular = (psnnode, parent) => {
Filter(psnnode, parent, userListLowerCase,
(el) => el.html().toLowerCase(),
(user, psnid) => user === psnid);
};
if (windowHref.match(/\/gen(e\/|e)$/)) {
FilterRegular('.touchclick .psnnode', '.touchclick'); // 机因一览
} else if (windowHref.indexOf('gene') > -1) {
FilterRegular('div.post .psnnode', 'div.post'); // 机因回复
} else if (windowHref.match(/\.co(m\/|m)$/) !== null || windowHref.indexOf('node') > -1 || windowHref.indexOf('qa') > -1 || windowHref.match(/\/trad(e\/|e)$/) !== null) {
FilterRegular('div.ml64>.meta>.psnnode', 'li'); // 主页一览、问答一览、问答回复、交易一览
} else if (windowHref.indexOf('topic') > -1 || windowHref.indexOf('trade') > -1 || windowHref.match(/\/battle\/[1-9][0-9]+/) !== null) {
FilterRegular('div.ml64>.meta>.psnnode', 'div.post'); // 主页帖回复、交易帖回复、约战帖回复
} else if (windowHref.match(/\/my\/notice/)) {
FilterRegular('.psnnode', 'li'); // 消息通知
} else if (windowHref.indexOf('trophy') > -1 || windowHref.match(/\/psnid\/[^/]+\/comment/) !== null) {
FilterRegular('div.ml64>.meta.pb10>.psnnode', 'li'); // 奖杯TIPS、个人主页留言
FilterRegular('ul.sonlist .content>.psnnode', 'ul.sonlist>li'); // 奖杯TIPS二级回复、个人主页留言二级回复
} else if (windowHref.match(/\/psngame\/[1-9][0-9]+\/comment/) !== null) {
filteredCriticPost = FilterRegular('div.ml64>.meta.pb10>.psnnode', 'li') > 0; // 游戏测评
FilterRegular('ul.sonlist .content>.psnnode', 'ul.sonlist>li'); // 游戏测评二级回复
} else if (windowHref.indexOf('battle') > -1) {
Filter('table.list td.pdd15.h-p>a', 'tr', userListLowerCase, (el) => el[0].href, (user, elementHref) => elementHref.indexOf(`psnid/${user}`) > -1); // 约战一览
}
if (windowHref.match(/\/qa\/[1-9][0-9]*/)) {
FilterRegular('ul.sonlist .content>.psnnode', 'ul.sonlist>li'); // 问答二级回复
}
}
};
// 屏蔽词
// const blockWordsList = ['日版', '顶', '第一句', '股票', '安排', '技能点', '刀剑'];
const FilterWordRegular = (postSelector, width) => {
const posts = $(postSelector);
if (posts.length > 0) {
posts.each((index, post) => {
settings.blockWordsList.forEach((word) => {
if ($(post).text().match(word)) {
$(post).parent().parent().after(`
<div onclick="$(this).prev().show();$(this).hide();" class="btn btn-gray font12" style="margin-bottom:2px;${width && `width:${width}%;`}">====== 内容包含您的屏蔽词,点击查看屏蔽内容 ======</div>
`);
$(post).parent().parent().hide();
}
});
});
}
};
const filterBlockWorld = () => {
const windowHref = window.location.href;
if (windowHref.indexOf('gene') > -1 // 机因回复
|| windowHref.indexOf('topic') > -1 // 主帖回复
|| windowHref.indexOf('trophy') > -1 // 奖杯TIPS
|| windowHref.indexOf('qa') > -1 // 问答回复
|| windowHref.indexOf('trade') > -1 // 交易回复
|| windowHref.match(/\/battle\/[1-9][0-9]+/) !== null // 约战回复
|| windowHref.match(/\/psnid\/[^/]+\/comment/) !== null // 个人主页留言
) {
FilterWordRegular('div.ml64>div.content.pb10');
}
};
filterBlockWorld();
filterUserPost();
// 功能1-8:回复按钮hover触发显示
/*
* 回复按钮hover触发显示功能函数
* @param div 标签
*/
const hoverShowReply = (div) => {
const subClass = "span[class='r']";
$(`${div} ${subClass}`).css({
opacity: 0,
transition: 'all 0.2s ease',
});
$(div).hover(
function () {
$(this).find(subClass).css({
opacity: 1,
});
},
function () {
$(this).find(subClass).css({
opacity: 0,
});
},
);
};
hoverShowReply('.post');
if (/^(?!.*trade|.*qa(\?(ob|title)=.*)?$)/.test(window.location.href)) {
hoverShowReply("div[class$='ml64']");
}
/* 将BBCode替换成对应html代码
* @param str 原始BBCode输入
* @return str 转换后的html代码
*/
const replaceAll = (str, mapObj) => {
let newStr = str;
Object.keys(mapObj).forEach((i) => {
const re = new RegExp(i, 'g');
newStr = str.replace(re, mapObj[i]);
});
return newStr;
};
/*
* BBCode和html标签对应表
*/
const bbcode = {
'\\[quote\\](.+?)\\[/quote\\]': '<blockquote>$1</blockquote>',
'\\[mark\\](.+?)\\[/mark\\]': '<span class="mark">$1</span>',
'\\[img\\](.+?)\\[/img\\]': '<img src="$1"></img>',
'\\[b\\](.+?)\\[/b\\]': '<b>$1</b>',
'\\[s\\](.+?)\\[/s\\]': '<s>$1</s>',
'\\[center\\](.+?)\\[/center\\]': '<center>$1</center>',
'\\[\\](.+?)\\[/b\\]': '<b>$1</b>',
'\\[color=(.+?)\\](.+?)\\[/color\\]': '<span style="color:$1;">$2</span>',
'\\[url\\](.+)\\[/url\\]': '<a href="$1">$1</a>',
'\\[url=(.+)\\](.+)\\[/url\\]': '<a href="$1">$2</a>',
// '\\[trophy=(.+)\\]\\[/trophy\\]': '<a href="$1">$2</a>',
// '\\[trophy=(.+)\\](.+)\\[/trophy\\]': '<a href="$1">$2</a>',
'\\n': '<br/>',
};
/*
* 功能:在输入框下方追加输入内容预览框
* @param tag 可定位到输入框的标签名
*/
const addInputPreview = (tag) => {
$(tag).after(
"<div class='content' style='padding: 0px 10px; word-wrap: break-word; word-break:break-all;' id='preview' />",
);
$(tag).keyup(() => {
$('#preview').html(replaceAll($(tag).val(), bbcode));
});
};
/*
* 功能:实时统计创建机因时候的文字数
*/
const countInputLength = () => {
$(".pr20 > textarea[name='content']").before(
`<div class='text-warning'>
<p>字数:<span class='wordCount'>0</span>/600</p>
</div>`,
);
$(".pr20 > textarea[name='content']").keyup(() => {
const wordCount = $('.wordCount').text(
$("textarea[name='content']").val().replace(/\n|\r/gi, '').length,
);
if (Number($('.wordCount').text()) > 600) {
$('button.btn.btn-large.btn-banner')
.prop('disabled', true)
.css('background-color', '#aaa')
.text('内容字数超过上限!');
} else {
$('button.btn.btn-large.btn-banner')
.prop('disabled', false)
.css('background-color', '#3890ff')
.text('提交');
}
return (wordCount);
});
};
// 评论框预览功能(等追加自定义设置后开放)
// addInputPreview("textarea#comment[name='content']");
/*
* 问答标题根据回答状况着色
* @param isOn 是否开启功能
*/
const changeQaStatus = (isOn) => {
if (isOn) {
// 替换文字状态为图标形式
$('.list>li').each((i, node) => {
const el = $(node).find('div.meta > .r > span:nth-child(2)');
const status = $(el).text();
// 替换文字状态为图标形式
const selector = 'div.ml64>p.title.font16>a';
switch (status) {
case '已解决': $(node).find(selector).prepend('<div class="fa-check-circle"></div>'); break;
case '未回答': $(node).find(selector).prepend('<div class="fa-question-circle"></div>'); break;
case '解决中': $(node).find(selector).prepend('<div class="fa-comments"></div>'); break;
default: return;
}
const elReward = $(node).find('div.meta > .r > span:nth-child(1)');
const rewardNum = $(elReward).text();
// 替换文字状态为图标形式
const reward = rewardNum.match(/悬赏(\d+)铜/);
if (reward && reward.length > 0) {
const number = Number(reward[1]);
let textType;
if (number > 30) {
textType = 'text-gold';
} else {
textType = number === 10 ? 'text-bronze' : 'text-silver';
}
$(elReward).replaceWith(`<div class="fa-coins"></div> <span class="${textType}" style="font-weight:bold;"}">${number}</span>`);
}
});
}
};
/*
* 通过Ajax获取自己的该游戏页面的奖杯数目
* @param data Ajax获取的数据
* @param tip Tippy对象
*/
const getTrophyContentByAjax = (data, tip) => {
const reg = /[\s\S]*<\/body>/g;
const html = reg.exec(data)[0];
const inner = $(html).find('td>em>.text-strong');
tip.setContent(inner.length > 0
? `你的奖杯完成度:${inner.text()}`
: '你还没有获得该游戏的任何奖杯');
};
/*
* 通过Ajax获取用户名片
* @param data Ajax获取的数据
* @param tip Tippy对象
*/
const getUserCardByAjax = (data, tip) => {
const reg = /[\s\S]*<\/body>/g;
const html = reg.exec(data)[0];
const inner = $(html).find('.psnzz').parent().get(0);
$(inner).find('.inner').css('max-width', '400px');
tip.setContent(inner);
};
/*
* 使用Tippy的OnShow部分函数
* @param url Ajax获取目标地址
* @param tip Tippy对象
* @param successFunction 获取数据时调用函数
*/
const tippyOnShow = (url, tip, successFunction) => {
if (!tip.state.ajax) {
tip.state.ajax = {
isFetching: false,
canFetch: true,
};
}
if (tip.state.ajax.isFetching || !tip.state.ajax.canFetch) {
return;
}
tip.state.ajax.isFetching = true;
tip.state.ajax.canFetch = false;
try {
$.ajax({
type: 'GET',
url,
dataType: 'html',
success: (data) => {
successFunction(data, tip);
},
error: () => {
tip.setContent('无法获取页面信息');
},
});
} catch (e) {
tip.setContent(`获取失败:${e}`);
} finally {
tip.state.ajax.isFetching = false;
}
};
/*
* 功能:悬浮于头像显示个人界面
*/
const addHoverProfile = () => {
if (settings.hoverHomepage) {
$("a[href*='psnid/'] > img").parent().each(function (i) {
const url = $(this).attr('href');
$(this).attr('id', `profile${i}`);
tippy(`#profile${i}`, {
content: '加载中...',
delay: 700,
maxWidth: '500px',
animateFill: false,
interactive: true,
placement: 'left',
async onShow(tip) {
tippyOnShow(url, tip, getUserCardByAjax);
},
onHidden(tip) {
tip.state.ajax.canFetch = true;
tip.setContent('加载中...');
},
});
});
}
};
addHoverProfile();
/*
* 功能:自动翻页
* @param pagingSetting 自动翻页的页数
*/
const autoPaging = (pagingSetting) => {
if (pagingSetting > 0) {
let isbool = true; // 触发开关,防止多次调用事件
let autoPagingLimitCount = 0;
$(window).scroll(function () {
// 当内容滚动到底部时加载新的内容
if (
$(this).scrollTop() + $(window).height() + 700
>= $(document).height()
&& $(this).scrollTop() > 700
&& isbool === true
&& autoPagingLimitCount < settings.autoPaging
) {
isbool = false;
// 获取下一页页码和链接
const { nextPage, nextPageLink } = getNextPageInfo();
// 加载页面并且插入
$('#loadingMessage').text(`加载第${nextPage}页...`).show();
$('.page:last').after(`<div class='loadPage${nextPage}'></div>`);
$.get(
nextPageLink,
{},
(data) => {
const $response = $('<div />').html(data);
$(`.loadPage${nextPage}`)
.append($response.find('.list'))
.append($response.find('.page'));
isbool = true;
autoPagingLimitCount += 1;
// 各个页面的功能追加
if (/\/qa/.test(window.location.href)) {
changeQaStatus(settings.newQaStatus);
}
addHighlightOnID();
filterUserPost();
addHoverProfile();
addHotTag();
},
'html',
);
setTimeout(() => {
$('#loadingMessage').fadeOut();
}, 2000);
}
});
}
};
/*
* 日期转换函数,将(XX年XX月XX日)形式切割成UTC时间
* @param value XX年XX月XX日 形式的字符串
* @return {object} UTC时间对象
*/
const converntTime = (value) => {
const time = value.replace(/年|月|日/g, '-').split('-');
return Date.UTC(`20${time[0]}`, Number(time[1]) - 1, time[2]);
};
/*
* 获取当前页面的价格变动时间,构建绘图曲线X轴数据集
* @return xValue 价格变动时间X数据
*/
const priceLineDataX = () => {
// 获取X轴的日期
const xContents = $('p.dd_text');
let xValue = [];
for (let index = 3; index < xContents.length; index += 4) {
const tamp = xContents[index].innerText.split(' ~ ').map((item) => converntTime(item));
xValue = [tamp[0], tamp[0], tamp[1], tamp[1], ...xValue];
}
return xValue;
};
/*
* 获取当前页面的价格情况,构建绘图曲线Y轴数据集
* @return yNormal 普通会员价格Y数据
* @return yPlus plus会员价格Y数据
*/
const priceLineDataY = () => {
const div = $('.dd_price');
let yNormal = [];
let yPlus = [];
div.each((i, el) => {
const yOld = $(el).children('.dd_price_old').eq(0).text();
const yPriceNormal = $(el).children('.dd_price_off').eq(0).text();
// 普通会员价格曲线值
yNormal = [yOld, yPriceNormal, yPriceNormal, yOld, ...yNormal];
// PS+会员价格曲线值
const yPricePlus = $(el).children('.dd_price_plus').eq(0);
const pricePlusTamp = yPricePlus.length === 0 ? yPriceNormal : yPricePlus.text();
yPlus = [yOld, pricePlusTamp, pricePlusTamp, yOld, ...yPlus];
});
return { yNormal, yPlus };
};
/*
* 修正数据集的最后一组数据函数。如果当前日期在最后一次促销结束前,
* 则修改最后一组数据为当前日期,如在以后,则将最后一次促销的原始
* 价格作为最后一组数据的当前价格。
* @param [dataArray] 包含[datetime, price]的原始数据
* @return [dataArray] 修改后的[datetime, price]数据
*/
const fixTheLastElement = (data) => {
const newData = data;
const today = new Date();
const todayArray = Date.UTC(
today.getYear() + 1900,
today.getMonth(),
today.getDate(),
);
if (newData[newData.length - 1][0] > todayArray) {
newData.pop();
newData[newData.length - 1][0] = todayArray;
} else {
newData.push([todayArray, newData[newData.length - 1][1]]);
}
return newData;
};
/*
* 传入时间和一般、Plus会员价格数组,生成绘图用数据集
* @param xValue 价格变动时间数组
* @param yNormal 一般会员价格数组
* @param yPlus Plus会员价格数组
* @return normalData 一般会员价格绘图用数组
* @return plusData Plus会员价格绘图用数组
* @return region 地区货币符
*/
const createPriceLineData = (xValue, yNormal, yPlus) => {
// 用于保存绘图数据的变量
let normalData = [];
let plusData = [];
// 判断地区
const prefix = yNormal[0].substring(0, 1);
const region = prefix === 'H' ? 'HK$' : prefix;
xValue.forEach((item, i) => {
normalData.push([item, Number(yNormal[i].replace(region, ''))]);
plusData.push([item, Number(yPlus[i].replace(region, ''))]);
});
// 最后一组数组的处理,生成最终数据绘图数据集
normalData = fixTheLastElement(normalData);
plusData = fixTheLastElement(plusData);
return { normalData, plusData, region };
};
/* 根据数据绘制价格变动走势图
* @param normalData 一般会员价格绘图用数组
* @param plusData Plus会员价格绘图用数组
* @param region 地区货币符
*
* @return priceLinePlot highChart对象
*/
const createPriceLinePlot = (normalData, plusData, region) => {
const priceLineChart = {
type: 'areaspline',
backgroundColor: 'rgba(0,0,0,0)',
};
const priceLineTitle = {
text: '价格变动走势图',
style: {
color: '#808080',
},
};
const priceLineXAxis = {
type: 'datetime',
dateTimeLabelFormats: {
year: '%y年',
day: '%y年<br/>%b%e日',
week: '%y年<br/>%b%e日',
month: '%y年<br/>%b',
},
title: {
text: '日期',
},
};
const priceLineYAxis = {
title: {
text: '价格',
},
plotLines: [
{
value: 0,
width: 1,
color: '#808080',
},
],
};
const priceLineTooltip = {
headerFormat: '<b>{series.name}</b><br>',
pointFormat: `{point.x:%y年%b%e日}: ${region}{point.y:.2f}`,
};
const priceLinePlotOptions = {
areaspline: {
fillOpacity: 0.25,
},
};
const priceLineSeries = [
{
name: '普通会员价',
color: '#00a2ff',
data: normalData,
},
{
name: 'PS+会员价',
color: '#ffd633',
data: plusData,
},
];
const priceLineCredits = {
enabled: false,
};
const priceLineLegend = {
itemStyle: {
color: '#808080',
},
itemHoverStyle: {
color: '#3890ff',
},
};
const priceLinePlot = {
chart: priceLineChart,
title: priceLineTitle,
tooltip: priceLineTooltip,
xAxis: priceLineXAxis,
yAxis: priceLineYAxis,
series: priceLineSeries,
plotOptions: priceLinePlotOptions,
credits: priceLineCredits,
legend: priceLineLegend,
};
return priceLinePlot;
};
/*
* 功能:在页面中插入价格变动走势图
*/
const addPriceLinePlot = () => {
// 构建绘图数据
const xValue = priceLineDataX();
const { yNormal, yPlus } = priceLineDataY();
const { normalData, plusData, region } = createPriceLineData(xValue, yNormal, yPlus);
const priceLinePlot = createPriceLinePlot(normalData, plusData, region);
// 插入页面
$('.dd_ul').before('<div id="container"></div>');
Highcharts.chart('container', priceLinePlot);
};
/*
* 增加单个价格或文字展示标签
* @param value 展示数值或字符串
* @param className 样式名
* @param styleString 额外追加的样式
* @return {string} 展示内容标签
*/
const priceSpan = (value, className, styleString = null) => {
let text = value;
if (typeof value === 'number') {
if (value > 0) {
text = `¥${value.toFixed(2)}`;
}
}
return `<span class=${className} style="float:right;${styleString}">${text}</span>`;
};
/*
* 功能:在当前页面上添加外币转人民币的价格展示
*/
const retrieveRealTimeExchangeRate = (callbackSuccess, callbackFailure) => {
// 默认汇率 latest exchange rate as of 2020/09/30/00:00 AM (GMT+8)
const exchangeRate = {
HKD: 0.8796572978575602,
USD: 6.817381644163391,
GBP: 8.770269230346404,
JPY: 0.06453927675754388,
};
try { // 获取实时汇率
const httpReq = new XMLHttpRequest();
httpReq.open('GET', 'https://api.exchangeratesapi.io/latest', false);
httpReq.send(null);
const startTime = Date.now();
repeatUntilSuccessful(() => {
// Wait until HTTP GET SUCCESSFULL or TIMEOUT
if ((httpReq.status !== 200)
&& (httpReq.readyState !== XMLHttpRequest.DONE)
&& (Date.now() - startTime) < 3000) {
return false;
}
let rawExchangeRate = null;
if ((httpReq.status === 200)
&& (httpReq.readyState === XMLHttpRequest.DONE)) {
rawExchangeRate = JSON.parse(httpReq.response);
}
if (rawExchangeRate) { // HTTP GET SUCCESSFULL
['HKD', 'USD', 'GBP', 'JPY'].forEach((currency) => {
exchangeRate[currency] = rawExchangeRate.rates.CNY / rawExchangeRate.rates[currency];
});
}
callbackSuccess(exchangeRate);
return true;
}, 50);
} catch (e) {
console.log('实时汇率获取失败,使用默认汇率');
callbackFailure(exchangeRate);
}
};
const foreignCurrencyConversion = () => {
const insertConvertedPriceTags = (exchangeRate) => {
$('.dd_price').each((i, el) => {
// 一览页面和单商品页面不同位置偏移
const offset = /dd\//.test(window.location.href) ? 2 : 3;
const region = $(`.dd_info p:nth-child(${offset})`).eq(i).text();
if (region === '国服') return;
const price = [
$(el).children().eq(0).text(), // 原始价格
$(el).children().eq(1).text(), // 优惠价格
$(el).children().eq(2).text(), // 会员优惠价格
];
// 根据地区转换原始价格
const regionCurrency = {
港服: ['HK$', exchangeRate.HKD],
美服: ['$', exchangeRate.USD],
日服: ['¥', exchangeRate.JPY],
英服: ['£', exchangeRate.GBP],
};
const CNY = price.map((item) => (
Number(item.replace(regionCurrency[region][0], ''))
* regionCurrency[region][1]
));
// 整块增加的价格表示
const addCNYPriceBlock = [
priceSpan(CNY[2], 'dd_price_plus'),
priceSpan(CNY[1], 'dd_price_off'),
priceSpan(CNY[0], 'dd_price_old', 'text-decoration:line-through'),
priceSpan('CNY:', 'dd_price_off', 'font-size:12px;'),
].filter(Boolean).join('');
// 添加到页面中
$('.dd_price span:last-child').eq(i).after(addCNYPriceBlock);
});
};
retrieveRealTimeExchangeRate(insertConvertedPriceTags, insertConvertedPriceTags);
};
const foreignCurrencyConversionSalesPage = () => {
const changeToConvertedPriceTags = (exchangeRate) => {
$('.store_box>.store_price').each((i, el) => {
// 一览页面和单商品页面不同位置偏移
const region = window.location.href.match(/region=.+?(&|$)/)[0].replace(/(region=|&)/g, '').toLowerCase();
if (region === 'cn') return;
// 根据地区转换原始价格
const regionCurrency = {
hk: ['HK$', exchangeRate.HKD],
us: ['$', exchangeRate.USD],
jp: ['¥', exchangeRate.JPY],
gb: ['£', exchangeRate.GBP],
};
$(el).children().each((j, priceTag) => {
$(priceTag).attr('original-price', $(priceTag).text());
$(priceTag).attr('converted-price', `CN¥${(Number($(priceTag).text().replace(regionCurrency[region][0], '')) * regionCurrency[region][1]).toFixed(2)}`);
$(priceTag).text($(priceTag).attr('converted-price'));
});
});
};
retrieveRealTimeExchangeRate(changeToConvertedPriceTags, changeToConvertedPriceTags);
};
/*
* 功能:根据降价幅度变更标题颜色
*/
const changeGameTitleColor = () => {
// 设定不同降价范围的标题颜色
const priceTitleColorDict = {
100: 'rgb(220,53,69)',
80: 'rgb(253,126,20)',
50: 'rgb(255,193,7)',
20: 'rgb(40,167,69)',
};
// 着色
$('.dd_box').each((i, el) => {
const offPercent = Number(
$(el).find('.dd_pic > div[class^="dd_tag"] ').text()
.match('省(.+)%')[1],
);
$('.dd_title.mb10>a').eq(i).css({ color: priceTitleColorDict[offPercent] });
});
};
/*
* 增加按键样式
* @param id 标签ID
* @param backgroundColor 按键背景色
* @param padding padding
* @param margin margin
*/
const addButtonStyle = (id, backgroundColor, padding = '0px 5px', margin = '0 0 0 10px') => {
GM_addStyle(
`#${id} {
padding : ${padding};
margin : ${margin};
border-radius : 2px;
display : inline-block;
color : white;
background-color : ${backgroundColor};
cursor : pointer;
line-height : 24px;
}`,
);
};
const colorAddedButtonReady = '#d9534f';
addButtonStyle('selectOriginalPrice', colorAddedButtonReady); // 原币种价格
GM_addStyle(`
.switch {
position: relative;
display: inline-block;
margin-left: 10px;
width: 40px;
height: 18px;
}`);
GM_addStyle(`
.switch input {
opacity: 0;
width: 0;
height: 0;
}`);
GM_addStyle(`
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
-webkit-transition: .4s;
transition: .4s;
}`);
GM_addStyle(`
.slider:before {
position: absolute;
content: "";
height: 16px;
width: 16px;
left: 2px;
bottom: 1px;
background-color: white;
-webkit-transition: .4s;
transition: .4s;
}`);
GM_addStyle(`
input:checked + .slider {
background-color: #2196F3;
}`);
GM_addStyle(`
input:focus + .slider {
box-shadow: 0 0 1px #2196F3;
}`);
GM_addStyle(`
input:checked + .slider:before {
-webkit-transform: translateX(20px);
-ms-transform: translateX(20px);
transform: translateX(20px);
}`);
GM_addStyle(`
.slider.round {
border-radius: 34px;
}`);
GM_addStyle(`
.slider.round:before {
border-radius: 50%;
}
`);
/*
* 功能:页面上追加“只看史低”功能按键,点击显示史低,再次点击恢复显示所有游戏(数折页面)
*/
const showBest = () => {
// 追加只看史低按键
$('.dropmenu').append('<li style="color:#B8C4CE;padding: 0px 0px 0px 10px;">只看史低</li><label class="switch" style="line-height:1.3;"><input id="showBest" type="checkbox"><span class="slider round"></span></label>');
// 点击按钮隐藏或者显示
const toggle = $('#showBest');
toggle[0].checked = false;
toggle.change(() => {
$('li.dd_box').each((i, el) => {
if ($(el).children('.dd_status.dd_status_best').length === 0) {
if (toggle[0].checked === true) {
$(el).hide();
} else {
$(el).show();
}
}
});
});
};
/*
* 功能:页面上追加“只看史低”功能按键,点击显示史低,再次点击恢复显示所有游戏(活动页面)
*/
const onlyLowestSalesPage = () => {
// 追加只看史低按键
$('.disabled.h-p').eq(0).after('<li style="color:white;padding: 2px 5px;">只看史低<label class="switch"><input id="hideGamebox" type="checkbox"><span class="slider round"></span></label></li>');
const toggle = $('#hideGamebox');
toggle[0].checked = false;
toggle.change(() => {
$(document.querySelectorAll('li.store_box')).each((i, el) => {
if ((el).querySelector('.store_tag_best') === null) {
$(el).css('display', toggle[0].checked === true ? 'none' : 'block');
}
});
});
};
/*
* 功能:页面上追加“显示人民币”功能按键(活动页面)
*/
const showOriginalPrice = () => {
if (window.location.href.match(/region=.+?(&|$)/)[0].replace(/(region=|&)/g, '').toLowerCase() === 'cn') return;
$('.disabled.h-p').eq(0).after('<li style="color:white;padding: 2px 5px;">显示人民币<label class="switch"><input id="selectOriginalPrice" type="checkbox"><span class="slider round"></span></label></li>');
const toggle = $('#selectOriginalPrice');
toggle[0].checked = true;
toggle.change(() => {
$('.store_box>.store_price').children().each((i, priceTag) => {
$(priceTag).text($(priceTag).attr(toggle[0].checked === true ? 'converted-price' : 'original-price'));
});
});
};
// 综合页面:一览
if (/((gene|qa|topic|trade)($|\?))/.test(window.location.href)) {
autoPaging(settings.autoPaging);
}
// 页面:机因 > 发机因
if (/set\/gene/.test(window.location.href)) {
// 实时统计创建机因时候的文字数
countInputLength();
// 发基因时可实时预览结果内容
addInputPreview("textarea[name='content']");
}
// 页面:机因、主题
if (/\/((gene|topic|trade|battle)\/)|(psngame\/\d+\/comment\/?$)/.test(window.location.href)) {
showReplyContent(settings.replyTraceback);
}
// 页面:问答
if (/\/qa/.test(window.location.href)) {
changeQaStatus(settings.newQaStatus);
}
// 页面:数折
if (/\/dd($|\?)/.test(window.location.href)) {
// 外币转人民币
foreignCurrencyConversion();
// 根据降价幅度变更标题颜色
changeGameTitleColor();
// 只看史低
showBest();
}
// 页面:数折 > 商品页
if (
/\/dd\//.test(window.location.href)
|| /game\/[0-9]+\/dd$/.test(window.location.href)
) {
repeatUntilSuccessful(() => {
if (httpCSSFixed()) {
addPriceLinePlot();
// 外币转人民币
foreignCurrencyConversion();
return true;
} return false;
}, 100);
}
// 页面:活动
if (/huodong/.test(window.location.href)) {
// 外币转人民币
foreignCurrencyConversionSalesPage();
// 只看史低
onlyLowestSalesPage();
// 原币种价格
showOriginalPrice();
}
// 页面:全局
// 跳转至底部按钮
toPageBottom();
/*
* 获取奖杯各种类个数
* @param className 用于识别的类名
* @param name 奖杯种类名
* @param color 色块所用颜色码
* @return {object} 用于绘扇形图的单个数据块
*/
const getTrophyCategory = (className, name, color) => {
const trophyCount = $(className).eq(0).text().replace(name, '');
return { name, y: Number(trophyCount), color };
};
/*
* 获取奖杯各稀有度个数
* @return {object} 用于绘扇形图的数据块
*/
const getTrophyRarity = () => {
const rareArray = [0, 0, 0, 0, 0]; // 个数统计
const rareStandard = [0, 5, 10, 20, 50]; // 区间定义
[1, 2, 3, 4].forEach((rareIndex) => {
$(`.twoge.t${rareIndex}.h-p`).each((i, ev) => {
// 获取稀有度
const rarity = Number($(ev).eq(0).text().split('%')[0].replace('%', ''));
// 计算该稀有度的奖杯数量
rareArray[[...rareStandard, rarity].sort((a, b) => a - b).indexOf(rarity) - 1] += 1;
});
});
return rareArray;
};
/*
* 功能:在单独游戏页面上方追加奖杯统计扇形图
*/
const addTrophyPieChart = () => {
// 奖杯稀有度统计数据
const rareArray = getTrophyRarity();
const trophyRatioSeriesRarityData = [
{ name: '极度珍贵', y: rareArray[0], color: 'rgb(160, 217, 255)' },
{ name: '非常珍贵', y: rareArray[1], color: 'rgb(124, 181, 236)' },
{ name: '珍贵', y: rareArray[2], color: 'rgb(88, 145, 200)' },
{ name: '罕见', y: rareArray[3], color: 'rgb(52, 109, 164)' },
{ name: '一般', y: rareArray[4], color: 'rgb(40, 97, 152)' },
];
// 奖杯个数统计数据
const trophyRatioSeriesCategoryData = [
getTrophyCategory('.text-platinum', '白', '#7a96d1'),
getTrophyCategory('.text-gold', '金', '#cd9a46'),
getTrophyCategory('.text-silver', '银', '#a6a6a6'),
getTrophyCategory('.text-bronze', '铜', '#bf6a3a'),
];
// 背景设置
const trophyRatioChart = {
backgroundColor: 'rgba(0,0,0,0)',
};
// 悬浮内容设置
const trophyRatioTooltip = {
pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>',
};
// 绘图设置
const trophyRatioPlotOptions = {
pie: {
allowPointSelect: true,
cursor: 'pointer',
dataLabels: {
enabled: true,
distance: -20,
style: {
fontWeight: 'bold',
color: 'white',
textOutline: '0px contrast',
},
formatter() {
return this.point.y;
},
},
},
};
// 绘图数据
const trophyRatioSeries = [
{
type: 'pie',
name: '比例',
data: trophyRatioSeriesCategoryData,
center: [50, 30],
size: 130,
},
{
type: 'pie',
name: '比例',
data: trophyRatioSeriesRarityData,
center: [200, 30],
size: 130,
},
];
// 标题设置
const trophyRatioTitle = {
text: '奖杯统计',
style: {
color: '#808080',
},
};
// 构建绘图对象
const trophyRatio = {
chart: trophyRatioChart,
tooltip: trophyRatioTooltip,
title: trophyRatioTitle,
series: trophyRatioSeries,
plotOptions: trophyRatioPlotOptions,
credits: { enabled: false },
};
// 插入页面
$('#trophyChartContainer').append(
'<div id="trophyRatioChart" align="left"></div>',
);
Highcharts.chart('trophyRatioChart', trophyRatio);
};
/*
* 增加绘图框架样式
* @param id 标签ID
* @param width 宽度
*/
const addPlotFrame = (id, width) => {
GM_addStyle(
`#${id} {
width : ${width}px;
height : 200px;
margin : 0 0;
display : inline-block;
}`,
);
};
addPlotFrame('trophyRatioChart', 320);
addPlotFrame('trophyGetTimeChart', 460);
/*
* 构建奖杯获得时间绘图数据集
* @param className 用于识别的类名
* @return {object} 用于绘线形图的数据集
*/
const trophyGetTimeElementParser = (timeElement) => {
// 奖杯时间丢失部分处理
const dayTime = $(timeElement).text().trim();
if (dayTime === '时间丢失') return 0;
// 从页面上获取奖杯时间,生成时间对象并且放入数组中保存
const timeArray = [
$(timeElement).attr('tips').replace('年', ''), // 年
Number(dayTime.substr(0, 2)) - 1, // 月
dayTime.substr(3, 2), // 日
dayTime.substr(5, 2), // 时
dayTime.substr(8, 2), // 分
].map((x) => Number(x));
return Date.UTC(...timeArray);
};
const createTrophyGetTimeData = (className) => {
const timeElements = $(className);
if (timeElements.length === 0) return null;
const getTimeArray = [];
timeElements.each((i, el) => {
const xTime = trophyGetTimeElementParser(el);
getTimeArray.push([xTime, el.parentElement.parentElement]);
});
getTimeArray.sort((t1, t2) => t1[0] - t2[0]);
const earliestValidTimeIndex = getTimeArray.findIndex((t) => t[0] !== 0);
if (earliestValidTimeIndex >= 0) {
getTimeArray.forEach((t) => {
if (t[0] === 0) {
const earliestValidTime = getTimeArray[earliestValidTimeIndex][0];
t[0] = earliestValidTime;
}
});
} else {
getTimeArray[0] = Number.NaN;
}
const data = getTimeArray.map((x, y) => [x[0], y + 1]);
// 调整最终数据点
// data[data.length - 1][1] -= 1;
const trophyElements = getTimeArray.map((x) => x[1]);
return { data, trophyElements };
};
/*
* 功能:在单独游戏页面上方追加奖杯获得时间线形图
*/
let trophyGetTimeData;
const addTrophyGetTimeLineChart = () => {
// 奖杯获得时间年月统计
trophyGetTimeData = createTrophyGetTimeData('em.lh180.alert-success.pd5.r');
const data = trophyGetTimeData === null ? [] : trophyGetTimeData.data;
const totalTrophyCount = Number($('div.main>.box.pd10>em>.text-strong')
.text().replace('总', ''));
const receivedTrophyCount = data.length;
// 悬浮内容设置
const trophyGetTimeTooltip = {
pointFormat: '{series.name}<b>{point.y}</b>个奖杯',
};
// 日期格式设置
const trophyGetTimeXaxis = {
type: 'datetime',
dateTimeLabelFormats: {
second: '%Y-%m-%d<br/>%H:%M:%S',
minute: '%Y-%m-%d<br/>%H:%M',
hour: '%Y-%m-%d<br/>%H:%M',
day: '%Y<br/>%m-%d',
week: '%Y<br/>%m-%d',
month: '%Y-%m',
year: '%Y',
},
title: {
display: false,
},
labels: {
style: { color: '#808080' },
},
};
// 绘图数据
const trophyGetTimeSeries = [
{
name: '第',
data,
showInLegend: false,
},
];
// 标题设置
const trophyGetRatio = ((receivedTrophyCount / totalTrophyCount) * 100).toFixed(2);
const trophyGetTimeTitleText = `奖杯获得时间(完成率:${trophyGetRatio}%)`;
const trophyGetTimeTitle = {
text: trophyGetTimeTitleText,
style: {
color: '#808080',
},
};
const trophyGetTimeSubtitle = {
text: $('div.ml100>p').eq(0).text(), // 游戏名称
};
// Y轴设置
const trophyGetTimeYAxis = {
title: {
text: '获得个数',
},
min: 0,
max: totalTrophyCount,
endOnTick: false,
tickInterval: Math.floor(totalTrophyCount / 4),
labels: {
style: { color: '#808080' },
},
};
// 绘图设置
const trophyGetTimeChart = {
backgroundColor: 'rgba(0,0,0,0)',
type: 'area',
};
// 图形设置
const trophyGetTimePlotOptions = {
areaspline: {
fillOpacity: 0.5,
},
};
// Credits设置
const trophyGetTimeCreditsText = [];
$('div.main>.box.pd10>em:eq(0)>span').each((i, el) => {
trophyGetTimeCreditsText.push($(el).text());
});
const trophyGetTimeCredits = {
text: trophyGetTimeCreditsText.join(' '),
href: undefined,
};
const trophyGetTime = {
chart: trophyGetTimeChart,
tooltip: trophyGetTimeTooltip,
xAxis: trophyGetTimeXaxis,
yAxis: trophyGetTimeYAxis,
title: trophyGetTimeTitle,
subtitle: trophyGetTimeSubtitle,
series: trophyGetTimeSeries,
plotOptions: trophyGetTimePlotOptions,
credits: trophyGetTimeCredits,
};
// 插入页面
$('#trophyChartContainer').append(
'<div id="trophyGetTimeChart" align="left"></div>',
);
Highcharts.chart('trophyGetTimeChart', trophyGetTime);
};
const sortTrophiesByTimestamp = () => {
const trophyTableEntries = $('table.list').eq(0).children().find('tr');
const trophies = trophyTableEntries.filter((i, e) => e.id !== '');
if (trophies.eq(0).hasClass('t1')) { // Platinum
trophyTableEntries.filter((i, e) => e.id === '').eq(0).after(trophyGetTimeData.trophyElements);
} else {
trophies.eq(0).after(trophyGetTimeData.trophyElements);
}
};
const addTrophySortByTimestamp = () => {
$('div.main ul.dropmenu > li.dropdown > ul').eq(0).append('<li id="sortTrophiesByTimestamp"><a>获得时间</a></li>');
$('#sortTrophiesByTimestamp').click(() => {
sortTrophiesByTimestamp();
$('#sortTrophiesByTimestamp').remove();
$('div.main ul.dropmenu > li.dropdown').removeClass('hover');
});
};
/*
* 功能:汇总以获得和未获得奖杯
*/
const addEarnedTrophiesSummary = () => {
const trophyTitleStyle = `border-radius: 2px; padding:5px; background-color:${$('li.current').css('background-color')};`;
// tippy弹出框的样式
GM_addStyle(`.tippy-tooltip.psnine-theme {background-color: ${$('.box').css('background-color')};}`);
// 奖杯tips颜色
let tipColor = '';
// 创建奖杯汇总框架函数
const createTrophyContainer = (object, className, title) => {
// 添加标题框在汇总图下
$('#trophyChartContainer').append(
`<div class='${className}'><p class='trophyCount' style='${trophyTitleStyle}'></p><div class='trophyContainer' style='padding:5px;'></div></div>`,
);
object.each(function (i) {
// 如果这个奖杯有Tips,就设置左边框为绿色,否则就为底色(边框颜色和底色一致)
if (
$(this).parent().parent().next()
.find('.alert-success.pd5')
.length > 0
) {
tipColor = '#8cc14c';
} else {
tipColor = $('.box').css('background-color');
}
// 添加奖杯图标
$(`.${className}> .trophyContainer`).append(
`<span id='${className}Small${i}' style='padding:3px; border-left: 3px solid ${tipColor};'><a href='${$(this).parent().attr('href')}'><img src='${$(this).attr('src')}' width='30px'></img><a></span>`,
);
// 添加鼠标悬浮弹出消息
tippy(`#${className}Small${i}`, {
content: `<td>${$(this).parent().parent().html()}</td><p></p><td>${$(this).parent().parent().next()
.html()}</td>`,
theme: 'psnine',
animateFill: false,
});
});
// 给奖杯汇总标题填充文字
const summaryTrophyDict = {
'.t1': ['text-platinum', '白'],
'.t2': ['text-gold', '金'],
'.t3': ['text-silver', '银'],
'.t4': ['text-bronze', '铜'],
};
let trophySubText = '';
Object.keys(summaryTrophyDict).forEach((i) => {
trophySubText += `<span class=${summaryTrophyDict[i][0]}> ${summaryTrophyDict[i][1]}${object.parent().parent(i).length}</span>`;
});
$(`.${className}> .trophyCount`).append(
`<span style='color:#808080;'>${title}:${trophySubText}<span class='text-strong'> 总${object.length}</span></span>`,
);
};
// 创建已获得奖杯汇总框
createTrophyContainer($('.imgbg.earned'), 'earnedTrophy', '已获得奖杯');
// 创建未获得奖杯汇总框
createTrophyContainer($("img[class$='imgbg']"), 'notEarnedTrophy', '未获得奖杯');
// 未获得奖杯变黑白
$('span[id^="notEarnedTrophySmall"] > a > img').css({ filter: 'grayscale(100%)' });
// 折叠奖杯汇总
// 奖杯图标设置为不可见
if (settings.foldTrophySummary) {
$('.trophyContainer').css('display', 'none');
}
// 单击奖杯汇总标题后展开奖杯图标
$('.trophyCount').click(function () {
$(this).next().slideToggle();
});
};
// 奖杯系统优化
// 功能3-1:游戏奖杯界面可视化
if (
/psngame\//.test(window.location.href)
&& /^(?!.*comment|.*rank|.*battle|.*gamelist|.*topic|.*qa)/.test(
window.location.href,
)
) {
$('.box.pd10').append('<div id="trophyChartContainer" style="float: left"></div>');
repeatUntilSuccessful(() => {
if (httpCSSFixed()) {
// 追加奖杯统计扇形图
addTrophyPieChart();
// 追加奖杯获得时间线形图
addTrophyGetTimeLineChart();
// 追加奖杯获得时间排序
addTrophySortByTimestamp();
// 汇总以获得和未获得奖杯
addEarnedTrophiesSummary();
return true;
} return false;
}, 100);
}
/*
* 功能:降低没有白金的游戏的图标亮度
* @param alpha 无白金游戏图标透明度
*/
const filterNonePlatinum = (alpha) => {
if (alpha < 1) {
$('tr').each((i, el) => {
// 读取白金数量
const platinumNum = $(el)
.find('.pd1015.title.lh180 > em > .text-platinum').eq(0)
.text()
.replace('白', '');
if (platinumNum === '0') {
$(el).find('.pdd15 > a > img').eq(0)
.css({ opacity: alpha });
}
});
}
};
/*
* 功能:悬浮图标显示自己的游戏的完成度
*/
const getMyCompletion = () => {
$('.imgbgnb').each((i, el) => {
$(el).attr('id', `game${i}`);
if (psnidCookie) {
const psnid = psnidCookie[1];
let myGameUrl = $(el).parent().attr('href');
if (myGameUrl !== undefined) {
myGameUrl += `?psnid=${psnid}`;
tippy(`#game${i}`, {
content: '加载中...',
animateFill: false,
placement: 'left',
delay: 500,
async onShow(tip) {
tippyOnShow(myGameUrl, tip, getTrophyContentByAjax);
},
onHidden(tip) {
tip.state.ajax.canFetch = true;
tip.setContent('加载中...');
},
});
}
}
});
};
// 游戏页面优化
if (
/psngame/.test(window.location.href) && !/psnid/.test(window.location.href)
) {
// 降低没有白金的游戏的图标亮度
filterNonePlatinum(settings.filterNonePlatinumAlpha);
// 悬浮图标显示自己的游戏的完成度
getMyCompletion();
}
// 约战页面可以选择去掉发起人头像
if (settings.removeHeaderInBattle) {
if (/battle$/.test(window.location.href)) {
$('.pdd15.h-p').hide();
}
}
// 机因、问答页面按最新排序
if (settings.listPostsByNew) {
$('#pcmenu a').each((i, obj) => {
if (/\/((gene)|(qa))($|(\/$))/.test(obj.href)) obj.href += '?ob=date';
});
}
// 进入游戏页默认查看我自己的奖杯
if (
window.location.href.match(/psngame\/\d+($|\/$)|(#\d+($|\/$))/)
&& !/psnid/.test(window.location.href)
) {
// 检查游戏页
window.onpageshow = (e) => {
const backTrigger = e || window.event;
if (!backTrigger.persisted && psnidCookie) {
if (window.location.href.match(/psngame\/\d+#\d+/)) window.location.href = window.location.href.replace(/#(\d+)($|\/$)/, `?psnid=${psnidCookie[1]}#$1`);
else window.location.href = window.location.href.replace(/($|\/$)/, `?psnid=${psnidCookie[1]}`);
}
};
}
// 匹配游戏的不同版本
const referGameVariants = (on, searchFirst) => {
if (!on) return;
const fetchPageAndProcess = (url, func) => {
$.ajax({
method: 'GET',
dataType: 'html',
url,
async: true,
}).then((data) => {
const page = document.createElement('html');
page.innerHTML = data;
func(page);
});
};
const gameIdFromPsngameUrl = (url) => {
const idMatch = url.match(/\/psngame\/\d+/);
if (idMatch.length > 0) return Number.parseInt(idMatch[0].replace('/psngame/', ''), 10);
return -1;
};
const gameIdFromTrophyUrl = (url) => {
const idMatch = url.match(/\/trophy\/\d+\/?$/);
// 奖杯ID除去后三位即为游戏ID
if (idMatch.length > 0) return Number.parseInt(idMatch[0].replace(/\/trophy\/(\d+)\d{3}/, '$1'), 10);
return -1;
};
// 创建包含多个游戏版本链接的板块
const createReferenceDiv = (text, style = '') => {
const referenceDiv = document.createElement('div');
referenceDiv.className = 'box';
referenceDiv.style.cssText = style;
const innerTextEm = document.createElement('em');
innerTextEm.innerText = text;
referenceDiv.appendChild(innerTextEm);
return referenceDiv;
};
const createReferenceA = (referenceDiv, url, text) => {
const referenceA = document.createElement('a');
referenceA.href = url;
referenceA.innerText = text;
referenceA.target = '_blank';
referenceDiv.appendChild(referenceA);
};
// 适用于奖杯列表页面
const referVariantsOnTrophyList = (gameId, gameIds) => {
const psngameNavBar = $('div.main > ul.inav');
const referenceDiv = createReferenceDiv('查看该游戏的其他版本:', 'margin-bottom: 20px; padding:12px 10px;');
gameIds.forEach((otherGameId) => {
if (gameId === otherGameId) return;
createReferenceA(referenceDiv, `https://psnine.com/psngame/${otherGameId}`, ` #${otherGameId}`);
});
psngameNavBar.after(referenceDiv);
};
// 适用于奖杯TIPS页面
const referVariantsOnTrophyTips = (gameId, gameIds) => {
const trophy = $('body > div.min-inner.mt40 > div.box.pd5');
const trophyIdStr = window.location.href.match(/\/trophy\/\d+/)[0].replace(`/trophy/${gameId}`, '');
const referenceDiv = createReferenceDiv('查看该游戏其他版本的奖杯Tips:', 'margin-bottom: 20px; margin-top: 20px; padding:12px 10px;');
gameIds.forEach((otherGameId) => {
if (gameId === otherGameId) return;
createReferenceA(referenceDiv, `https://psnine.com/trophy/${otherGameId}${trophyIdStr}`, ` #${otherGameId}${trophyIdStr}`);
});
trophy.after(referenceDiv);
};
// 适用于其他游戏子页面
const referVariantsOnRankThroughGamelist = (gameId, gameIds) => {
const psngameNavBar = $('body > div.min-inner.mt40 > ul.inav');
const referenceDiv = createReferenceDiv('查看该游戏的其他版本:', 'margin-bottom: 20px; margin-top: 20px; padding:12px 10px;');
gameIds.forEach((otherGameId) => {
if (gameId === otherGameId) return;
createReferenceA(referenceDiv, window.location.href.replace(`/psngame/${gameId}/`, `/psngame/${otherGameId}/`), ` #${otherGameId}`);
});
psngameNavBar.after(referenceDiv);
};
const psngameTrophyListUrlRegex = /\/psngame\/\d+\/?($|\?)/;
const referVariantsDelegate = (gameId, gameIds) => {
if (gameIds.length === 1) return;
if (psngameTrophyListUrlRegex.test(window.location.href)) {
referVariantsOnTrophyList(gameId, gameIds);
} else if (/\/trophy\/\d+\/?$/.test(window.location.href)) {
referVariantsOnTrophyTips(gameId, gameIds);
} else if (/\/(rank|comment|qa|topic|battle|gamelist)\/?$/.test(window.location.href)) {
referVariantsOnRankThroughGamelist(gameId, gameIds);
}
};
// 缓存游戏的多版本信息
const gameVariantCacheID = (gameId) => `psngame-variants-${gameId}`;
const gameVariantCacheEncode = (gameId, gameIds) => {
const cache = { timestamp: Date.now(), variants: gameIds };
return JSON.stringify(cache);
};
const gameVariantCacheDecode = (cacheText) => {
if (!cacheText) return null;
const cache = JSON.parse(cacheText);
// 缓存有效时间24小时
if (Date.now() - cache.timestamp > 24 * 60 * 60 * 1000) return null;
return cache.variants;
};
const gameVariantCacheStore = (gameIds) => {
gameIds.forEach((gameId) => {
// eslint-disable-next-line no-undef
GM_setValue(gameVariantCacheID(gameId),
gameVariantCacheEncode(gameId, gameIds));
});
};
// 查询尚未由管理员关联的游戏是否存在多版本
const gameTitleTrim = (title) => title.replace(/(^(\s*《\s*)+)|((\s*》\s*)+$)/g, '')
.replace(/\s*[((]VR2?(\s*可选)?[))]\s*$/gi, '')
.replace(/\s*Trophies\s*$/gi, '');
function findGameVariantsBySearch(gameId, gameTitle, tryGameMeta = false) {
const searchUrl = `https://psnine.com/psngame?title=${encodeURIComponent(gameTitle).replaceAll('%20', '+')}`;
fetchPageAndProcess(searchUrl, (page) => {
const psngameMatches = $(page).find('div.min-inner.mt40 > div.box > table > tbody > tr > td.pd1015.title.lh180 > a');
if (psngameMatches.length <= 0) return;
let gameIds = [gameId];
psngameMatches.each((i, a) => {
if (gameTitleTrim(a.innerText) !== gameTitle) return;
gameIds.push(gameIdFromPsngameUrl(a.href));
});
gameIds = gameIds.filter((value, index, array) => array.indexOf(value) === index);
gameIds.sort((a, b) => a - b);
gameVariantCacheStore(gameIds);
referVariantsDelegate(gameId, gameIds);
if (gameIds.length === 1 && tryGameMeta) {
if (psngameTrophyListUrlRegex.test(window.location.href)) {
// 无缓存、当前页面为奖杯列表,直接查询
// eslint-disable-next-line no-use-before-define
findGameVariantsByMeta(gameId, document.body);
} else {
// 无缓存、当前页面并非奖杯列表,抓取奖杯列表页面再查询
// eslint-disable-next-line no-use-before-define
fetchPageAndProcess(`https://psnine.com/psngame/${gameId}`, (_page) => { findGameVariantsByMeta(gameId, _page); });
}
}
});
}
// 在不同页面查找游戏标题
const findGameTitle = () => {
const gameTitleExtaction = (title) => title.replace(/(^[^《]*)|([^》]*$)/g, '');
if (psngameTrophyListUrlRegex.test(window.location.href)) {
return gameTitleTrim(gameTitleExtaction($('div.inner.mt40 > div.main > div.box.pd10 > h1')[0].innerText));
}
if (/\/trophy\/\d+\/?$/.test(window.location.href)) {
return gameTitleTrim($('div.min-inner.mt40 > ul > li > div.ml100 > p > a')[0].innerText);
}
if (/\/(rank|comment|qa|topic|battle|gamelist)\/?$/.test(window.location.href)) {
return gameTitleTrim(gameTitleExtaction($('div.min-inner.mt40 > div.box.pd10 > h1')[0].innerText));
}
return null;
};
// 查询游戏多版本
function findGameVariantsByMeta(gameId, page, trySearch = false) {
// 查询已由管理员关联的游戏的多版本
const fetchGameMetaPage = (url, _gameId) => {
fetchPageAndProcess(url, (_page) => {
const psngameMatches = $(_page).find('div.min-inner.mt20 > ul > li > a[href*="https://psnine.com/psngame/"]').slice(1);
const gameIds = [];
psngameMatches.each((i, a) => { gameIds.push(gameIdFromPsngameUrl(a.href)); });
gameIds.sort();
gameVariantCacheStore(gameIds);
referVariantsDelegate(_gameId, gameIds);
if (gameIds.length === 1 && trySearch) {
findGameVariantsBySearch(_gameId, findGameTitle());
}
});
};
// 在参数的奖杯列表页面查找游戏是否已经被关联
const gameMetaUrl = $(page).find('div.side > div.hd3:contains("关联游戏")').length > 0 ? $(page).find('div.side > ul > center > a')[0].href : null;
if (gameMetaUrl) {
fetchGameMetaPage(gameMetaUrl, gameId);
} else if (trySearch) {
findGameVariantsBySearch(gameId, findGameTitle());
}
}
const referVariants = (gameId) => {
// 查找缓存
// eslint-disable-next-line no-undef
const gameIds = gameVariantCacheDecode(GM_getValue(gameVariantCacheID(gameId), null));
if (gameIds) {
// 有缓存时直接链接
referVariantsDelegate(gameId, gameIds);
console.log('游戏关联版本信息已使用缓存');
} else if (psngameTrophyListUrlRegex.test(window.location.href) && !searchFirst) {
// 无缓存、当前页面为奖杯列表、非搜索优先
findGameVariantsByMeta(gameId, document.body, true);
} else if (searchFirst) {
// 无缓存、搜索优先
findGameVariantsBySearch(gameId, findGameTitle(), true);
} else {
// 无缓存、当前页面并非奖杯列表、非搜索优先,抓取奖杯列表页面再查询
fetchPageAndProcess(`https://psnine.com/psngame/${gameId}`, (page) => { findGameVariantsByMeta(gameId, page, true); });
}
};
if (/\/psngame\//g.test(window.location.href)) {
referVariants(gameIdFromPsngameUrl(window.location.href));
} else if (/\/trophy\//g.test(window.location.href)) {
referVariants(gameIdFromTrophyUrl(window.location.href));
}
};
referGameVariants(settings.referGameVariants, settings.preferSearchForFindingVariants);
// 奖杯心得页面加入Tips排序选项、输入框加入可缩放大小
const trophyTipsEnhancement = () => {
const content = $('div.min-inner.mt40 > div.box.mt20');
const oldTips = content.children('div.post');
const newTipsContainer = content.children('ul.list');
const newTips = newTipsContainer.children('li');
if (window.location.href.match(/trophy\/\d+($|\/$)/) && (newTips.length || oldTips.length)) {
// 加入Tips排序选项
let isSorted = false;
const sortButtonHTML = '<div id="sortTipsByLikes">将Tips按热门排序</div>';
const tipsContainerStyle = {
position: 'relative',
display: 'flex',
flexDirection: 'column',
};
let tips;
if (newTips.length) {
newTipsContainer.before(sortButtonHTML).css(tipsContainerStyle);
tips = newTips;
} else {
// 将旧版Tips收入单独的容器以在不影响其他元素的情况下排序
$(oldTips[0]).before(sortButtonHTML);
$(oldTips[0]).before('<div id="tipsContainer" />');
const oldTipsContainer = content.children('#tipsContainer').css(tipsContainerStyle);
oldTips.each((i, e) => {
$(e).detach();
oldTipsContainer.append(e);
});
tips = oldTips;
}
const sortTipsByLikes = () => {
tips.each((index, tip) => {
if (!isSorted) {
// 获取顶元素
const likesText = $(tip).find('.text-success')[0].innerHTML.replace(/[^\d]/g, '');
$(tip).css({ order: likesText ? 9999 - likesText : 9999 });
} else {
$(tip).css({ order: 0 });
}
});
};
content.children('#sortTipsByLikes').css({
padding: '8px 6px',
'font-size': '12px',
'text-align': 'center',
'background-color': '#3890ff',
color: '#FFFFFF',
cursor: 'pointer',
margin: '10px',
}).click((event) => {
sortTipsByLikes();
if (isSorted) {
$(event.target).text('将Tips按热门排序').css({
'background-color': '#3890ff',
color: '#FFFFFF',
});
} else {
$(event.target).text('将Tips按时间排序').css({
'background-color': '#E7EBEE',
color: '#99A1A7',
});
}
isSorted = !isSorted;
});
// 输入框允许大小缩放
content.find('#comment').css({
resize: 'vertical',
minHeight: 200,
});
}
};
trophyTipsEnhancement();
// 奖杯心得及游戏评论页面展开二级评论
const expandCollapsedSubcomments = (on) => {
if (!on) return;
let pageType;
if (/trophy\/\d+\/?$/.test(window.location.href)) pageType = 'trophy tips';
else if (/psngame\/\d+\/comment\/?$/.test(window.location.href)) pageType = 'game comment';
else return;
let commentMetas;
switch (pageType) {
case 'trophy tips':
commentMetas = document.querySelectorAll('div.min-inner.mt40 > div.box.mt20 > ul.list > li > div.ml64 > div.meta:not(.pb10)');
break;
case 'game comment':
commentMetas = document.querySelectorAll('div.min-inner.mt40 > div.box > ul.list > li > div.ml64 > div.meta:not(.pb10)');
break;
default:
return;
}
const subcommentAlreadyExpanded = (subcommentLink) => Boolean($(subcommentLink).parents('li')[0].querySelector('div.sonlistmark.ml64.mt10 > ul.sonlist > li'));
const subcommentLinks = [];
commentMetas.forEach((commentMeta) => {
const subcommentLink = commentMeta.querySelector('span.r > a:nth-child(2)');
if (/评论\(\d+\)/.test(subcommentLink.innerText.trim()) && !subcommentAlreadyExpanded(subcommentLink)) subcommentLinks.push(subcommentLink);
});
if (subcommentLinks.length === 0) return;
const clickedLinks = [];
const links = [];
let activeIntersectionEvents = 0;
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const link = entries[0].target;
if (clickedLinks.indexOf(link) < 0 && links.indexOf(link) < 0) links.push(link);
}
});
activeIntersectionEvents += 1;
if (activeIntersectionEvents > 1) {
activeIntersectionEvents -= 1;
return;
}
let linkClickStatus = false;
let linkFails;
repeatUntilSuccessful(() => {
if (links.length === 0) {
activeIntersectionEvents -= 1;
return true;
}
const link = links[0];
if (!linkClickStatus) {
link.click();
$(link).parents('li')[0].querySelector('div.sonlistmark.ml64.mt10 > form.comson').style = 'display: none';
// console.log(`clicked subcomment link #${subcommentLinks.indexOf(link)}`);
linkClickStatus = true;
linkFails = 0;
}
if (subcommentAlreadyExpanded(link)) {
linkClickStatus = false;
clickedLinks.push(links.shift());
} else {
linkFails += 1;
if (linkFails % 5 === 0) {
link.click();
// console.log(`re-clicked subcomment link #${subcommentLinks.indexOf(link)}`);
}
}
return false;
}, 200);
}, { rootMargin: '0px', threshold: [0.5] });
subcommentLinks.forEach((subcommentLink) => {
observer.observe(subcommentLink);
});
};
expandCollapsedSubcomments(settings.expandCollapsedSubcomments);
// P9时间格式转换函数
function p9TimeTextParser(timestampText) { // returns UTC time
let array = null;
// 1小时
const unitTimeHour = 60 * 60 * 1000;
const relativeDescriptionToOffset = (prunePattern, unitTime) => -parseInt(timestampText.replace(prunePattern, ''), 10) * unitTime;
const relativeTimestamp = (offset, replacePattern) => {
if (replacePattern) {
return (
(new Date((new Date()).getTime() + 8 * unitTimeHour + offset))
.toLocaleDateString('en-CA', { timeZone: 'Asia/Shanghai' })
.split('-')
.concat(timestampText.replace(replacePattern, '').split(/:/))
);
}
const timeArrayConverted = (new Date((new Date()).getTime() + offset))
.toLocaleString('en-CA', { timeZone: 'Asia/Shanghai', hour12: false })
.split(/-|, |:/);
timeArrayConverted.pop();
return timeArrayConverted;
};
const dateStringToArray = (dateString) => dateString.split(/-|\s|:/);
if (timestampText.match(/[0-9]{4}-[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}/)) {
array = dateStringToArray(timestampText);
} else if (timestampText.match(/[0-9]{2}-[0-9]{2}\s[0-9]{2}:[0-9]{2}/)) {
array = dateStringToArray(timestampText);
array.unshift((new Date()).getFullYear());
} else {
// if time were not offset by 8 hours,
// date calculation would be incorrect when description involves '[0-9]+天前'
// eslint-disable-next-line no-lonely-if
if (timestampText.match(/[0-9]+天前\s[0-9]{2}:[0-9]{2}/)) {
array = relativeTimestamp(relativeDescriptionToOffset(/天前.+$/g, unitTimeHour * 24), /[0-9]+天前\s/g);
} else if (timestampText.match(/前天\s[0-9]{2}:[0-9]{2}/)) {
array = relativeTimestamp(-2 * unitTimeHour * 24, /前天\s/g);
} else if (timestampText.match(/昨天\s[0-9]{2}:[0-9]{2}/)) {
array = relativeTimestamp(-unitTimeHour * 24, /昨天\s/g);
} else if (timestampText.match(/今天\s[0-9]{2}:[0-9]{2}/)) {
array = relativeTimestamp(0, /今天\s/g);
} else if (timestampText.match(/[0-9]+小时前/)) {
array = relativeTimestamp(relativeDescriptionToOffset(/小时.+$/g, unitTimeHour));
} else if (timestampText.match(/[0-9]+分钟前/)) {
array = relativeTimestamp(relativeDescriptionToOffset(/分钟.+$/g, 60 * 1000));
} else if (timestampText.match(/刚刚/)) {
array = relativeTimestamp(0);
}
}
if (array) {
for (let i = array.length - 1; i >= 0; i -= 1) {
if (array[i] !== '') {
array[i] = parseInt(array[i], 10);
if (i === 1) { // Everything else is normal except month starts from 0
array[i] -= 1;
}
} else {
array.splice(i, 1);
}
}
return Date.UTC(...array) - 8 * unitTimeHour;
}
console.log(`not parsed: ${timestampText}`);
return null;
}
// 游戏评论页面计算平均分
function showCriticAverage() {
if (window.location.href.match(/psngame\/[1-9][0-9]+\/comment/)) {
let scoreParser; let scoreElements; let
scoreParentReview;
const selectScoreElements = () => {
scoreElements = $('div.min-inner.mt40 div.box ul.list li div.ml64 div.meta.pb10 span.alert-success.pd5:contains(评分 )');
if (scoreElements.length > 0) {
scoreParser = (element) => parseInt(element.text().replace('评分 ', ''), 10);
scoreParentReview = 'li';
} else {
scoreElements = $('div.min-inner.mt40 div.box div.ml64 p.text-success:contains(评分 ) b');
if (scoreElements.length > 0) {
scoreParser = (element) => parseInt(element.text(), 10);
scoreParentReview = 'div.post';
} else {
return false;
}
}
return true;
};
if (!selectScoreElements()) return;
let reviewsNoScore = null; let
reviewsNoScoreHidden = false;
const selectReviewsNoScore = () => {
if (reviewsNoScore === null) reviewsNoScore = $('div.min-inner.mt40 div.box ul.list li div.ml64 div.meta.pb10:not(:has(span.alert-success.pd5))').parents('li');
if (reviewsNoScore.length === 0) reviewsNoScore = $('div.min-inner.mt40 div.box div.ml64:not(:has(p.text-success))').parents('div.post');
};
const hideReviewsNoScore = () => {
if (reviewsNoScoreHidden) return;
selectReviewsNoScore();
reviewsNoScore.hide();
reviewsNoScoreHidden = true;
};
const showReviewsNoScore = () => {
if (!reviewsNoScoreHidden) return;
selectReviewsNoScore();
reviewsNoScore.show();
reviewsNoScoreHidden = false;
};
const hiddenScores = [];
const hideSpecificScore = (score) => {
if (hiddenScores.indexOf(score) > -1) return;
let hidden = 0;
scoreElements.each(function () {
if (scoreParser($(this)) === score) {
$(this).parents(scoreParentReview).hide();
hidden += 1;
}
});
if (hidden > 0) {
hideReviewsNoScore();
hiddenScores.push(score);
}
};
const showSpecificScore = (score) => {
const hiddenScoreIndex = hiddenScores.indexOf(score);
if (hiddenScoreIndex >= 0) {
scoreElements.each(function () {
if (scoreParser($(this)) === score) $(this).parents(scoreParentReview).show();
});
hiddenScores.splice(hiddenScoreIndex, 1);
if (hiddenScores.length === 0) showReviewsNoScore();
}
};
const scoreOnclick = (chart, seriesEntry, score) => {
if (filteredCriticPost) {
selectScoreElements();
filteredCriticPost = false;
}
switch (seriesEntry.color.length) {
case 7:// no alpha, score is being shown
seriesEntry.color += '1f';
hideSpecificScore(score);
break;
case 9:// has alpha, score is being hidden
seriesEntry.color = seriesEntry.color.substring(0, 7);
showSpecificScore(score);
break;
default:
break;
}
chart.redraw();
};
let gaussianOn = true;
let scoreDataBarchart; let scoreDataBarchartNoGaussian; let
scoreDataGaussian;
let scoreAxis; let
scoreAxisNoGaussian;
const scoreBarChartAddLabelOnclick = (chart) => {
chart.xAxis[0].labelGroup.element.childNodes.forEach((label) => {
label.onclick = function () {
const value = parseInt(this.innerHTML, 10);
const pos = chart.series[0].data.find((e) => e.category === value).index;
scoreOnclick(chart, chart.series[0].data[pos], value);
};
});
};
const createScoreBarChart = (criticsCount, scoreCountMin, scoreCountMax) => {
const scoreChart = {
type: 'column',
backgroundColor: 'rgba(0,0,0,0)',
events: {
click() {
gaussianOn = !gaussianOn;
const chart = Highcharts.chart('scoreBarChart', createScoreBarChart(criticsCount, scoreCountMin, scoreCountMax));
scoreBarChartAddLabelOnclick(chart);
hiddenScores.forEach((s) => {
scoreOnclick(
chart,
chart.series[0].data[chart.xAxis[0].categories.indexOf(s)],
s,
);
});
},
},
};
const scoreTitle = {
text: '评论分数分布',
style: { color: '#808080' },
};
const scoreSubtitle = {
text: '点击分数柱或横坐标数字隐藏相应评论',
style: { fontSize: '9px', color: '#808080' },
};
const scoreXaxis = [{
categories: gaussianOn ? scoreAxis : scoreAxisNoGaussian,
crosshair: true,
labels: {
style: { color: '#808080' },
},
}];
const scoreYaxis = [{
min: 0,
max: 5 * Math.ceil(scoreCountMax / 5),
tickInterval: Math.ceil(scoreCountMax / 5),
title: { text: '点评人数' },
labels: {
style: { color: '#808080' },
},
}];
const scoreTooltip = {
formatter() {
switch (this.series.index) {
case 0:
return `<b>${this.y}人</b>`;
case 1:
return `<b>${(this.y * 100).toFixed(2)}%</b>`;
default:
return this.y;
}
},
pointFormat: '{point.y}',
};
const scorePlotOptions = {
column: {
pointPadding: 0,
borderWidth: 0,
},
bellcurve: {
color: '#8080807f',
fillColor: '#00000000',
},
series: { point: { events: { click() { if (this.series.name === '评分计数') scoreOnclick(this.series.chart, this, this.category); } } } },
};
const scoreSeries = [{
name: '评分计数',
xAxis: 0,
yAxis: 0,
zIndex: 1,
baseSeries: 0,
data: gaussianOn ? scoreDataBarchart : scoreDataBarchartNoGaussian,
}];
const scoreCredits = {
text: `点评总人数:${criticsCount}`,
};
if (gaussianOn) {
scoreXaxis.push({
min: 0.5,
max: 10.5,
alignTicks: true,
opposite: true,
visible: false,
});
scoreYaxis.push({
min: 0,
title: { text: '正态分布' },
opposite: true,
labels: {
formatter() {
return `${this.value * 100}%`;
},
style: { color: '#808080' },
},
});
scoreSeries.push({
type: 'bellcurve',
xAxis: 1,
yAxis: 1,
zIndex: 0,
baseSeries: 1,
data: scoreDataGaussian,
enableMouseTracking: false,
});
}
const scoreBarChart = {
chart: scoreChart,
title: scoreTitle,
subtitle: scoreSubtitle,
xAxis: scoreXaxis,
yAxis: scoreYaxis,
tooltip: scoreTooltip,
plotOptions: scorePlotOptions,
series: scoreSeries,
legend: { enabled: false },
credits: scoreCredits,
};
return scoreBarChart;
};
const weekOfYear = (date) => { // https://stackoverflow.com/a/6117889
const startOfDay = new Date(
Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()),
);
const dayOfWeek = startOfDay.getUTCDay() || 7;
startOfDay.setUTCDate(startOfDay.getUTCDate() + 4 - dayOfWeek);
const startOfYear = new Date(Date.UTC(startOfDay.getUTCFullYear(), 0, 1));
return Math.ceil(
(((startOfDay - startOfYear) / 86400000/* milliseconds of a day */) + 1) / 7,
);
};
const weeksOfYearCache = {};
const weeksOfYear = (year) => {
let weeks = weeksOfYearCache[year];
if (weeks === undefined) {
const lastWeek = weekOfYear(new Date(year, 11, 31));
if (lastWeek === 1) weeks = weekOfYear(new Date(year, 11, 24));
else weeks = lastWeek;
weeksOfYearCache[year] = weeks;
}
return weeks;
};
const yearOfWeek = (date, week = null) => {
const realYear = date.getUTCFullYear();
let newWeek = week;
if (week === null) {
newWeek = weekOfYear(date);
}
if (date.getUTCMonth() === 0) {
if (newWeek > 5) {
return realYear - 1;
}
} else if (newWeek === 1) {
return realYear + 1;
}
return realYear;
};
const weekToTimestamp = (year, week, day = 4) => {
let startOfYear = new Date(Date.UTC(year, 0, 1));
if (weekOfYear(startOfYear) > 1) startOfYear = new Date(Date.UTC(year, 0, 8));
return (
startOfYear.getTime()
+ (-((startOfYear.getUTCDay() || 7) - 1)
+ (7 * (week - 1) + (day - 1))) * 86400000/* milliseconds of a day */
);
};
const weekDifference = (date1, date2) => {
const weekOfYear1 = weekOfYear(date1);
const yearOfWeek1 = yearOfWeek(date1, weekOfYear1);
const weekStart1 = weekToTimestamp(yearOfWeek1, weekOfYear1, 1);
const weekOfYear2 = weekOfYear(date2);
const yearOfWeek2 = yearOfWeek(date2, weekOfYear2);
const weekStart2 = weekToTimestamp(yearOfWeek2, weekOfYear2, 1);
return (weekStart1 - weekStart2) / 604800000/* milliseconds of a week */;
};
const createScoreTrendChart = () => {
const scoreTrend = [];
const commentTrend = [];
let minScore = Number.MAX_SAFE_INTEGER;
let maxScore = Number.MIN_SAFE_INTEGER;
let firstWeek;
let lastWeek;
const createScoreTrendChartData = () => {
const scoreElementTime = (scoreElement) => { // must be single element
let timestampElement = $(scoreElement).parents('div.ml64').find('div.meta:not(.pb10) > span:nth-child(2)');
if (timestampElement.length > 0) {
return p9TimeTextParser(timestampElement.text().replace(/(^\s+)|(\s+$)|(修改)/g, ''));
}
timestampElement = $(scoreElement).parents('div.ml64').find('div.meta');
if (timestampElement.length > 0) {
const textArray = timestampElement.text().split(/\r?\n/);
let index = -1;
let text;
do {
text = textArray[textArray.length + index].replace(/(^\s+)|(\s+$)|(修改)/g, '');
index -= 1;
} while (text === '');
return p9TimeTextParser(text);
}
return null;
};
scoreElements.each(function () {
const timestamp = scoreElementTime($(this));
if (timestamp !== null) {
const scoreDate = new Date(timestamp);
const weekOfYearValue = weekOfYear(scoreDate);
const yearOfWeekValue = yearOfWeek(scoreDate, weekOfYearValue);
scoreTrend.push([
timestamp, scoreParser($(this)), yearOfWeekValue, weekOfYearValue,
]);
}
});
scoreTrend.sort((e1, e2) => (e1[0] - e2[0]));
let accumulatedScore = 0;
for (let i = 0; i < scoreTrend.length; i += 1) {
accumulatedScore += scoreTrend[i][1];
const updatedAverageScore = accumulatedScore / (i + 1);
scoreTrend[i][1] = updatedAverageScore;
if (updatedAverageScore < minScore) minScore = updatedAverageScore;
if (updatedAverageScore > maxScore) maxScore = updatedAverageScore;
}
const commentCountByWeek = {};
const firstScore = scoreTrend[0];
const lastScore = scoreTrend[scoreTrend.length - 1];
firstWeek = [firstScore[2], firstScore[3]];
lastWeek = [lastScore[2], lastScore[3]];
scoreTrend.forEach((score) => {
const week = `${score[2]}/${score[3]}`;
if (commentCountByWeek[week] === undefined) commentCountByWeek[week] = 1;
else commentCountByWeek[week] += 1;
score.splice(2, 2);
});
for (let year = firstWeek[0]; year <= lastWeek[0]; year += 1) {
const first = year === firstWeek[0] ? firstWeek[1] : 1;
const last = year === lastWeek[0] ? lastWeek[1] : weeksOfYear(year);
for (let week = first; week <= last; week += 1) {
const count = commentCountByWeek[`${year}/${week}`];
commentTrend.push([
weekToTimestamp(year, week, 7.5), count === undefined ? 0 : count,
]);
}
}
};
createScoreTrendChartData();
// 悬浮内容设置
const scoreTrendTooltip = {
split: false,
};
// 日期格式设置
const scoreTrendXaxis = {
type: 'datetime',
dateTimeLabelFormats: {
second: '%Y-%m-%d<br/>%H:%M:%S',
minute: '%Y-%m-%d<br/>%H:%M',
hour: '%Y-%m-%d<br/>%H:%M',
day: '%Y<br/>%m-%d',
week: '%Y<br/>%m-%d',
month: '%Y-%m',
year: '%Y',
},
title: {
display: false,
},
labels: {
style: { color: '#808080' },
},
};
// 绘图数据
const firstWeekDate = new Date(weekToTimestamp(firstWeek[0], firstWeek[1]));
const lastWeekDate = new Date(weekToTimestamp(lastWeek[0], lastWeek[1]));
const totalWeeksPassed = weekDifference(lastWeekDate, firstWeekDate) + 1;
const scoreTrendSeries = [
{
name: '平均分',
yAxis: 0,
data: scoreTrend,
showInLegend: false,
color: '#7CB5EC',
opacity: 1,
tooltip: {
pointFormatter() {
return `<b>${this.y.toFixed(2)}</b>`;
},
},
}, {
name: '周增评分次数',
yAxis: 1,
data: commentTrend,
showInLegend: false,
color: '#E41A1C',
opacity: 0.5,
tooltip: {
headerFormat: '', // tooltip.formatter doesn't work, using this hack to suppress default xAxis label
pointFormatter() {
let weekStr;
if (totalWeeksPassed > 26) {
const weekDate = new Date(this.x);
const weekOfYearNewValue = weekOfYear(weekDate);
const yearOfWeekNewValue = yearOfWeek(weekDate, weekOfYearNewValue);
weekStr = `<span>${yearOfWeekNewValue}年 第${weekOfYearNewValue}周</span><br/>`;
} else {
weekStr = `<span>第${weekDifference(new Date(this.x), firstWeekDate) + 1}周</span><br/>`;
}
return weekStr + (this.y > 0 ? `<b>${this.y}</b>` : '<b>无评论</b>');
},
},
},
];
// 标题设置
const scoreTrendTitle = [
{
text: '均分走势',
style: {
color: '#808080',
},
}, {
text: '热度走势',
style: {
color: '#808080',
},
},
];
// Y轴设置
// ratings: min, max and range
let scoreTrendYAxisScoreMin = Math.floor(minScore);
let scoreTrendYAxisScoreMax = Math.ceil(maxScore);
let scoreTrendYAxisScoreRange = scoreTrendYAxisScoreMax - scoreTrendYAxisScoreMin;
if (scoreTrendYAxisScoreRange === 0) {
// when the data range is 0, the minimum tick range seems to be 4
if (scoreTrendYAxisScoreMin - 2 < 1) {
scoreTrendYAxisScoreMin = 1;
scoreTrendYAxisScoreMax = 5;
} else if (scoreTrendYAxisScoreMax + 2 > 10) {
scoreTrendYAxisScoreMin = 6;
scoreTrendYAxisScoreMax = 10;
} else {
scoreTrendYAxisScoreMin -= 2;
scoreTrendYAxisScoreMax += 2;
}
scoreTrendYAxisScoreRange = 4;
} else if (scoreTrendYAxisScoreRange < 2) {
// when the data range is not 0, the minimum tick range seems to be 2
if (scoreTrendYAxisScoreMin + 2 > 10) {
scoreTrendYAxisScoreMin = scoreTrendYAxisScoreMax - 2;
} else {
scoreTrendYAxisScoreMax = scoreTrendYAxisScoreMin + 2;
}
scoreTrendYAxisScoreRange = 2;
}
// rating counts: min, max and range
let scoreTrendYAxisScoreCountMin = Math.min(...commentTrend.map((i) => i[1]));
let scoreTrendYAxisScoreCountMax = Math.max(...commentTrend.map((i) => i[1]));
let scoreTrendYAxisScoreCountRange = scoreTrendYAxisScoreCountMax
- scoreTrendYAxisScoreCountMin;
if (scoreTrendYAxisScoreCountRange === 0) {
// when the data range is 0, the minimum tick range seems to be 4
if (scoreTrendYAxisScoreCountMin - 2 < 0) {
scoreTrendYAxisScoreCountMin = 0;
scoreTrendYAxisScoreCountMax = 4;
} else {
scoreTrendYAxisScoreCountMin -= 2;
scoreTrendYAxisScoreCountMax += 2;
}
scoreTrendYAxisScoreCountRange = 4;
} else if (scoreTrendYAxisScoreCountRange < 2) {
// when the data range is not 0, the minimum tick range seems to be 2
scoreTrendYAxisScoreCountMax = scoreTrendYAxisScoreCountMin + 2;
scoreTrendYAxisScoreCountRange = 2;
}
let scoreTrendYAxisRatingTick;
let scoreTrendYAxisRatingCountTick;
// adjust min, max, range to ensure that:
// 1. both Y axes have the same number of ticks
// 2. all ticks are integers
if (scoreTrendYAxisScoreRange < scoreTrendYAxisScoreCountRange) {
scoreTrendYAxisRatingTick = 1;
if (scoreTrendYAxisScoreCountRange % scoreTrendYAxisScoreRange > 0) {
scoreTrendYAxisScoreCountRange = scoreTrendYAxisScoreRange
* Math.ceil(scoreTrendYAxisScoreCountRange / scoreTrendYAxisScoreRange);
scoreTrendYAxisScoreCountMax = scoreTrendYAxisScoreCountMin
+ scoreTrendYAxisScoreCountRange;
}
scoreTrendYAxisRatingCountTick = scoreTrendYAxisScoreCountRange
/ scoreTrendYAxisScoreRange;
} else if (scoreTrendYAxisScoreRange > scoreTrendYAxisScoreCountRange) {
scoreTrendYAxisRatingCountTick = 1;
if (scoreTrendYAxisScoreRange % scoreTrendYAxisScoreCountRange > 0) {
scoreTrendYAxisScoreRange = scoreTrendYAxisScoreCountRange
* Math.ceil(scoreTrendYAxisScoreRange / scoreTrendYAxisScoreCountRange);
scoreTrendYAxisScoreMax = scoreTrendYAxisScoreMin + scoreTrendYAxisScoreRange;
if (scoreTrendYAxisScoreMax > 10) {
scoreTrendYAxisScoreMax = 10;
scoreTrendYAxisScoreMin = 10 - scoreTrendYAxisScoreRange;
}
}
scoreTrendYAxisRatingTick = scoreTrendYAxisScoreRange / scoreTrendYAxisScoreCountRange;
} else {
scoreTrendYAxisRatingTick = 1;
scoreTrendYAxisRatingCountTick = 1;
}
const scoreTrendYAxis = [
{
title: {
text: '平均分',
style: {
color: '#7CB5EC',
},
},
min: scoreTrendYAxisScoreMin,
max: scoreTrendYAxisScoreMax,
endOnTick: true,
tickInterval: scoreTrendYAxisRatingTick,
opposite: false,
labels: {
style: { color: '#808080' },
},
}, {
title: {
text: '周增评分次数',
style: {
color: '#F28D8F',
},
},
min: scoreTrendYAxisScoreCountMin,
max: scoreTrendYAxisScoreCountMax,
endOnTick: true,
tickInterval: scoreTrendYAxisRatingCountTick,
opposite: true,
labels: {
style: { color: '#808080' },
},
},
];
// 绘图设置
const scoreTrendChart = {
backgroundColor: 'rgba(0,0,0,0)',
type: 'line',
};
// 图形设置
const scoreTrendPlotOptions = {
line: {
opacity: 1,
},
};
// Credits设置
const scoreTrendChartData = {
chart: scoreTrendChart,
tooltip: scoreTrendTooltip,
xAxis: scoreTrendXaxis,
yAxis: scoreTrendYAxis,
title: scoreTrendTitle,
series: scoreTrendSeries,
plotOptions: scoreTrendPlotOptions,
legend: { enabled: false },
credits: { enabled: false },
};
return scoreTrendChartData;
};
let scoreTotal = 0;
scoreDataBarchart = new Array(10).fill(0);
scoreDataGaussian = [];
scoreElements.each(function () {
const score = scoreParser($(this));
scoreDataGaussian.push(score);
scoreTotal += score;
scoreDataBarchart[score - 1] += 1;
});
const scoreAverage = (scoreTotal / scoreElements.length).toFixed(2);
// adding score average to stats
$('div.min-inner.mt40 div.box.pd10').append(`<em> <span class="alert-success pd5" align="right">均分 ${scoreAverage}</span></em><p/>`);
$('div.min-inner.mt40 div.box.pd10').append('<div id="scoreChartContainer" style="float: left; width: 100%;"></div>');
const psnineStats = $('#scoreChartContainer');
scoreAxis = [];
scoreAxisNoGaussian = [];
let scoreCountMin = Number.MAX_SAFE_INTEGER; let
scoreCountMax = Number.MIN_SAFE_INTEGER;
scoreDataBarchartNoGaussian = scoreDataBarchart.slice(0);
// 评分人数最高区间(分数)
const maxScoreCountIndex = scoreDataBarchart.indexOf(Math.max(...scoreDataBarchart));
// 柱状图颜色
const scoreColors = new Array(10).fill('#3890ff'); // do not assign transparency! otherwise scoreOnclick() will break
scoreColors[maxScoreCountIndex] = '#da314b';
for (let score = 10; score >= 1; score -= 1) {
const index = score - 1;
const scoreCount = scoreDataBarchart[index];
if (scoreCount === 0) {
scoreDataBarchartNoGaussian.splice(index, 1);
} else {
if (scoreCount < scoreCountMin) {
scoreCountMin = scoreCount;
}
if (scoreCount > scoreCountMax) {
scoreCountMax = scoreCount;
}
scoreDataBarchartNoGaussian[index] = { y: scoreCount, color: scoreColors[index] };
scoreAxisNoGaussian.unshift(score);
}
scoreDataBarchart[index] = { y: scoreCount, color: scoreColors[index] };
scoreAxis.unshift(score);
}
psnineStats.append('<div id="scoreBarChart" align="left" style="height: 200px;width: 50%;display: inline-block"/>');
psnineStats.append('<div id="scoreTrendChart" align="right" style="height: 200px;width: 50%;display: inline-block"/>');
const charts = Highcharts.chart('scoreBarChart', createScoreBarChart(scoreElements.length, scoreCountMin, scoreCountMax));
scoreBarChartAddLabelOnclick(charts);
Highcharts.chart('scoreTrendChart', createScoreTrendChart());
}
}
repeatUntilSuccessful(() => {
if (httpCSSFixed()) {
showCriticAverage();
return true;
} return false;
}, 100);
// 右上角头像下拉框中增加插件设定按钮
if (window.localStorage) {
// 如果支持localstorage
const newSettings = JSON.parse(JSON.stringify(settings));
const switchSettings = [
'hoverUnmark',
'replyTraceback',
'nightMode',
'autoNightMode',
'foldTrophySummary',
'newQaStatus',
'hoverHomepage',
'autoPagingInHomepage',
'removeHeaderInBattle',
'autoCheckIn',
'listPostsByNew',
'showAllQAAnswers',
'listQAAnswersByNew',
'showHiddenQASubReply',
'fixTextLinks',
'fixD7VGLinks',
'fixHTTPLinks',
'referGameVariants',
'preferSearchForFindingVariants',
'expandCollapsedSubcomments',
]; // 只有true/false或者enum的设置项
$('.header .dropdown ul').append(`
<li><a href="javascript:void(0);" id="psnine-enhanced-version-opensetting">插件设置</a></li>`);
const visiblePageHeight = $(window.top).height();
$('body').append(`
<style>.setting-panel-box{z-index:9999;background-color:#fff;transition:all .4s ease;position:fixed;left:50%;transform:translateX(-50%);top:-5000px;width:500px;box-shadow:0 0 20px rgba(0,0,0,0.3);padding:10px 0;box-sizing:border-box;border-radius:4px;max-height:${visiblePageHeight < 740 ? visiblePageHeight - 40 : 700}px;overflow-y:scroll;scrollbar-color:#dcdcdc #fff;scrollbar-width:thin}.setting-panel-box::-webkit-scrollbar{width:4px;background-color:#fff}.setting-panel-box::-webkit-scrollbar-button{display:none}.setting-panel-box::-webkit-scrollbar-thumb{background-color:#dcdcdc}.setting-panel-box.show{top:20px}.setting-panel-box h2{margin-bottom:10px;padding-left:20px}.setting-panel-box h4{margin-bottom:10px;padding-left:20px;font-weight:400;color:#1f2f3d;font-size:22px}.setting-panel-box .row{display:flex;align-items:center;justify-content:flex-start;width:100%;margin-bottom:5px;padding-left:20px;box-sizing:border-box}.setting-panel-box .row label{line-height:32px;text-align:left;font-size:14px;color:#606266;width:190px}.setting-panel-box .row .mini{line-height:26px;text-align:left;font-size:14px;color:#606266;margin:0 10px 0 0;width:50px}.setting-panel-box .row .normal{line-height:26px;text-align:left;font-size:14px;color:#606266;margin:0 10px 0 0;width:205px}.setting-panel-box .row textarea{resize:vertical;min-height:30px;border:1px solid #dcdfe6;color:#606266;background-color:#fff;background-image:none;border-radius:4px;-webkit-appearance:none;line-height:26px;box-sizing:border-box;width:227px;padding:0 10px}.setting-panel-box .row input{border:1px solid #dcdfe6;color:#606266;background-color:#fff;background-image:none;border-radius:4px;-webkit-appearance:none;height:26px;line-height:26px;display:inline-block;width:227px;padding:0 10px}.setting-panel-box .row input.slider{height:6px;background-color:#e4e7ed;margin:16px 0;border-radius:3px;position:relative;cursor:pointer;vertical-align:middle;outline:none;padding:0}.setting-panel-box .row input.slider::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border:2px solid #409eff;background-color:#fff;border-radius:50%;transition:.2s;user-select:none}.setting-panel-box .row input.slider::-moz-range-thumb{-webkit-appearance:none;appearance:none;width:16px;height:16px;border:2px solid #409eff;background-color:#fff;border-radius:50%;transition:.2s;user-select:none}.setting-panel-box .row .sliderValue{margin-left:5px}.setting-panel-box .row select{border:1px solid #dcdfe6;color:#606266;background-color:#fff;background-image:none;border-radius:4px;-webkit-appearance:none;height:26px;line-height:26px;display:inline-block;width:227px;padding:0 10px}.setting-panel-box .row span{line-height:32px;text-align:left;font-size:14px;color:#606266;margin-right:10px}.setting-panel-box .btnbox{display:flex;align-items:center;justify-content:center}.setting-panel-box button{-webkit-appearance:button;padding:9px 15px;font-size:12px;border-radius:3px;display:inline-block;line-height:1;white-space:nowrap;cursor:pointer;background:#fff;border:1px solid #dcdfe6;color:#606266;text-align:center;box-sizing:border-box;outline:0;margin:0;transition:.1s;font-weight:500;margin:0 10px}.setting-panel-box button:hover{color:#409eff;border-color:#c6e2ff;background-color:#ecf5ff}.setting-panel-box button.confirm{color:#fff;background-color:#3890ff}.setting-panel-box button.confirm:hover{background-color:#9ec9ff}</style>
<div class=setting-panel-box><h2>PSN中文网功能增强插件设置</h2><div class=row><a href=https://github.com/swsoyee/psnine-enhanced-version><img src=https://img.shields.io/github/stars/swsoyee/psnine-enhanced-version.svg?style=social></img></a></div><div class=row><label>夜间模式</label><select id=nightMode><option value=true>启用<option value=false>关闭</select></div><div class=row><label>自动夜间模式</label><select id=autoNightMode><option value=""SYSTEM"">跟随系统<option value=""TIME"">跟据时间<option value=""OFF"">关闭</select></div><div class=row><label>高亮用户ID</label><textarea name="" id="highlightSpecificID" cols="30" rows="2"></textarea></div><div class=row><label>黑名单ID</label><textarea name="" id="blockList" cols="30" rows="2"></textarea></div><div class=row><label>关键词屏蔽</label><textarea name="" id="blockWordsList" cols="30" rows="2"></textarea></div><div class=row><label>机因中显示被@的内容</label><select id=replyTraceback><option value=true>启用<option value=false>关闭</select></div><div class=row><label>悬浮显示刮刮卡内容</label><select id=hoverUnmark><option value=true>启用<option value=false>关闭</select></div><div class=row><label>个人主页下显示所有游戏</label><select id=autoPagingInHomepage><option value=true>启用<option value=false>关闭</select></div><div class=row><label>自动签到</label><select id=autoCheckIn><option value=true>启用<option value=false>关闭</select></div><div class=row><label>自动向后翻页数</label><input type=number class=normal id=autoPaging></div><div class=row><label>问答区状态优化</label><select id=newQaStatus><option value=true>启用<option value=false>关闭</select></div><div class=row><label>悬浮头像显示个人信息</label><select id=hoverHomepage><option value=true>启用<option value=false>关闭</select></div><div class=row><label>奖杯默认折叠</label><select id=foldTrophySummary><option value=true>启用<option value=false>关闭</select></div><div class=row><label>约战页面去掉发起人头像</label><select id=removeHeaderInBattle><option value=true>启用<option value=false>关闭</select></div><div class=row><label>机因、问答页面按最新排序</label><select id=listPostsByNew><option value=true>启用<option value=false>关闭</select></div><div class=row><label>载入全部问答答案</label><select id=showAllQAAnswers><option value=true>启用<option value=false>关闭</select></div><div class=row><label>答案按最新排列</label><select id=listQAAnswersByNew><option value=true>启用<option value=false>关闭</select></div><div class=row><label>答案显示隐藏回复</label><select id=showHiddenQASubReply><option value=true>启用<option value=false>关闭</select></div><div class=row><label>检测纯文本中的链接</label><select id=fixTextLinks><option value=true>启用<option value=false>关闭</select></div><div class=row><label>修复D7VG链接</label><select id=fixD7VGLinks><option value=true>启用<option value=false>关闭</select></div><div class=row><label>站内使用HTTPS链接</label><select id=fixHTTPLinks><option value=true>启用<option value=false>关闭</select></div><div class=row><label>尝试关联不同版本的游戏</label><select id=referGameVariants><option value=true>启用<option value=false>关闭</select></div><div class=row><label>查询游戏版本优先使用搜索</label><select id=preferSearchForFindingVariants><option value=true>启用<option value=false>关闭</select></div><div class=row><label>展开隐藏的子评论</label><select id=expandCollapsedSubcomments><option value=true>启用<option value=false>关闭</select></div><div class=row><label>无白金游戏图标透明度</label><input id=filterNonePlatinum class=slider type=range min=0 max=1 step=0.1><span id=filterNonePlatinumValue class=sliderValue></span></div><div class=row><label>热门标签回复数阈值</label><input id=hotTagThreshold class=slider type=range min=10 max=100 step=5><span id=hotTagThresholdValue class=sliderValue></span></div><div class=btnbox><button class=confirm>确定</button><button class=cancel>取消</button></div></div>`);
// 点击打开设置面板
$('#psnine-enhanced-version-opensetting').on('click', () => {
$('.setting-panel-box').addClass('show');
['#highlightSpecificID', '#blockList'].forEach((item) => {
tippy(item, {
content: 'ID以英文逗号隔开,不区分大小写',
zIndex: 10000,
});
});
tippy('#blockWordsList', {
content: '屏蔽词以逗号隔开,支持正则表达式',
zIndex: 10000,
});
switchSettings.forEach((name) => {
const newSetting = newSettings[name];
const option = $(`#${name}`);
const optionValue = () => JSON.parse(option.children('option:selected').val());
if (typeof newSetting === 'object') {
$(`#${name} option:nth-child(${newSetting.enum.findIndex((e) => e === newSetting.value) + 1})`)
.attr('selected', 'true');
option.change(() => { newSetting.value = optionValue(); });
} else {
$(`#${name} option:nth-child(${newSetting ? '1' : '2'})`)
.attr('selected', 'true');
option.change(() => { newSettings[name] = optionValue(); });
}
});
// 降低无白金透明度设置
$('#filterNonePlatinum').val(newSettings.filterNonePlatinumAlpha);
$('#filterNonePlatinumValue').html(
`${newSettings.filterNonePlatinumAlpha * 100}%`,
);
$('#filterNonePlatinum').on('input', () => {
const value = $('#filterNonePlatinum').val();
$('#filterNonePlatinumValue').html(`${value * 100}%`);
newSettings.filterNonePlatinumAlpha = value;
});
// 热门标签阈值 hotTagThreshold
$('#hotTagThreshold').val(newSettings.hotTagThreshold);
$('#hotTagThresholdValue').html(newSettings.hotTagThreshold);
$('#hotTagThreshold').on('input', () => {
const value = $('#hotTagThreshold').val();
$('#hotTagThresholdValue').html(value);
newSettings.hotTagThreshold = value;
});
// 自动翻页的页数设定
$('#autoPaging').val(newSettings.autoPaging);
// 高亮用户
const highlightSpecificIDText = newSettings.highlightSpecificID.length
? newSettings.highlightSpecificID.join(',')
: '';
$('#highlightSpecificID').val(highlightSpecificIDText);
// 黑名单
const blockListText = newSettings.blockList.length
? newSettings.blockList.join(',')
: '';
$('#blockList').val(blockListText);
// 关键词屏蔽
const blockWordsList = newSettings.blockWordsList.length
? newSettings.blockWordsList.join(',')
: '';
$('#blockWordsList').val(blockWordsList);
});
// 点击取消
$('.setting-panel-box .btnbox .cancel').on('click', () => {
$('.setting-panel-box').removeClass('show');
});
// 点击确定
$('.setting-panel-box .btnbox .confirm').on('click', () => {
const highlightSpecificIDText = $.trim(
$('#highlightSpecificID').val().replace(',', ','),
).replace(/,$/, '').replace(/^,/, '');
newSettings.highlightSpecificID = highlightSpecificIDText
? highlightSpecificIDText.split(',')
: [];
const blockListText = $.trim(
$('#blockList').val().replace(',', ','),
).replace(/,$/, '').replace(/^,/, '');
newSettings.blockList = blockListText
? blockListText.split(',')
: [];
const blockWordsList = $.trim(
$('#blockWordsList').val().replace(',', ','),
).replace(/,$/, '').replace(/^,/, '');
newSettings.blockWordsList = blockWordsList
? blockWordsList.split(',')
: [];
newSettings.filterNonePlatinumAlpha = Number($('#filterNonePlatinum').val());
newSettings.hotTagThreshold = Number($('#hotTagThreshold').val());
newSettings.autoPaging = Number($('#autoPaging').val());
$('.setting-panel-box').removeClass('show');
localStorage['psnine-night-mode-CSS-settings'] = JSON.stringify(
newSettings,
);
window.location.reload();
});
}
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', onDOMContentReady);
} else {
onDOMContentReady();
}
}());