// ==UserScript== // @name PSN中文网功能增强 // @namespace https://swsoyee.github.io // @version 0.9.54 // @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 // @run-at document-start // @downloadURL none // ==/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, // 答案按时间顺序排列 listQAAnswersByOld: false, // 答案显示隐藏回复 showHiddenQASubReply: false, // 检测纯文本中的链接 fixTextLinks: true, // 修复D7VG链接 fixD7VGLinks: true, // 站内使用HTTPS链接 fixHTTPLinks: false, }; 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:#fff!important}.twoge{color:#fff!important}.inav{background:#3d3d3d!important}.inav li.current{background:#4b4b4b!important}.ml100 p{color:#fff!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}`)); 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(); } } onDocumentStart(); function onDOMContentReady() { // run when DOM is loaded 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,') 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,') 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,') no-repeat center; }`); GM_addStyle(` .fa-coins { width: 15px; height: 15px; float: left; background: url('data:image/svg+xml;utf8,') no-repeat center; }`); /* * 页面右下角追加点击跳转到页面底部按钮 */ const toPageBottom = () => { $('.bottombar').append("B"); $('#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 showMarkMessage = (isOn) => { if (isOn) { window.addEventListener('load', () => { $('.mark').hover( function () { $(this).css({ color: 'rgb(255,255,255)' }); }, function () { $(this).css({ color: $(this).css('background-color') }); }, ); }); } }; showMarkMessage(settings.hoverUnmark); /* * 自动签到功能 * @param isOn 是否开启功能 */ const automaticSignIn = (isOn) => { // 如果签到按钮存在页面上 if (isOn && $('[class$=yuan]').length > 0) { $('[class$=yuan]').click(); } }; 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("
"); 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 = $('
').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 = `等级同步`; const upgame = `游戏同步`; updateButtonForm.append(upbase, upgame); } } // 帖子优化 /* * 功能:对发帖楼主增加“楼主”标志 * @param userId 用户(楼主)ID */ const addOPBadge = (userId) => { $('.psnnode').each((i, n) => { // 匹配楼主ID,变更CSS if ($(n).text() === userId) { $(n).after('楼主'); } }); }; /* * 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(`
 ${element.earned}`); } }); }); } }, 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}`, count: reqCount - missCount, }; }; return truncateElem(elem, length).text; }; /* * 功能:回复内容回溯,仅支持机因、主题 * @param isOn 是否开启功能 */ const showReplyContent = (isOn) => { if (isOn) { 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 allSource = $('.post .ml64 > .content'); // 每一层楼的回复者用户名 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 replyContentObject = allSource.eq(outputID).clone(); const replyContentPlainText = replyContentObject.text(); replyContentObject.find('.mark').text((index, text) => `${text}`); const replyContentText = replyContentObject.text(); let replyContentTruncatedText = $(truncateHtml($('

').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( `
${linkContent[1]} ${replyContentTruncatedText}
`, ); // 如果内容超过45个字符,则增加悬浮显示全文内容功能 if (replyContentPlainText.length > 45) { tippy(`.responserContent_${floor}_${outputID}`, { content: replyContentText, animateFill: false, maxWidth: '500px', }); } } } } } } } }; /* * 功能:增加帖子楼层信息 */ 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(`  #${baseFloorIndex}`); baseFloorIndex += 1; subFloorIndex = -1; } else { $(el).children('a:last') .after( `  #${baseFloorIndex}${subFloorIndex}`, ); 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(` 🔥热门 `); } }); }; addHotTag(); /* * 功能:层内逆序显示 * @param isOn 是否开启该功能 */ const reverseSubReply = (isOn) => { if (!isOn || !/(\/trophy\/\d+)|(\/psngame\/\d+\/comment)|(\/psnid\/.+?\/comment)/.test(window.location.href)) return; $('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('