您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Make RJ code great again!
当前为
// ==UserScript== // @name DLSite RJ code preview // @namespace SettingDust // @description Make RJ code great again! // @include *://*/* // @version 2.1.9 // @license MIT // @grant GM.xmlHttpRequest // @grant GM_xmlhttpRequest // @run-at document-start // ==/UserScript== const RJ_REGEX = new RegExp('R[JE][0-9]{6,8}', 'gi') const VOICELINK_CLASS = 'voicelink' const RJCODE_ATTRIBUTE = 'rjcode' const css = ` .voicepopup { z-index: 50000; max-width: 80%; max-height: 80%; position: fixed; box-shadow: 0 0 0 2.5px rgba(0, 0, 0, 0.12); border-radius: 16px; background-color: white; display: flex; flex-direction: column; overflow: hidden; } .voicepopup img { width: 100%; height: auto; max-width: 360px; } a.voicelink { color: currentColor; font-weight: bold; -webkit-text-stroke: 0.5px !important; line-height: normal; height: unset; vertical-align: text-top; width: fit-content !important; padding: unset !important; display: inline !important; } .voicelink: hover { text-decoration: none; } .voicepopup > .voice-info { padding: 12px 16px; font-size: 0.88rem; line-height: 1.2; max-width: 360px; display: grid; grid-gap: 6px; box-sizing: border-box; color: #212121; } .voicepopup > .voice-info > p { margin: 0; font-weight: bold; } .voicepopup > .voice-info > p > span { font-weight: normal; } .voicepopup .voice-title { margin: 0; font-size: 1.1rem; font-weight: bold; line-height: 1; } .voicepopup .error { height: 210px; line-height: 210px; text-align: center; } .voicepopup.discord-dark { background-color: #36393f; color: #dcddde; font-size: 0.9375rem; }` function getAdditionalPopupClasses() { const hostname = document.location.hostname switch (hostname) { case 'boards.4chan.org': return 'post reply' case 'discordapp.com': return 'discord-dark' default: return null } } function getXmlHttpRequest() { return typeof GM !== 'undefined' && GM !== null ? GM.xmlHttpRequest : GM_xmlhttpRequest } const Parser = { walkNodes: function (elem) { const rjNodeTreeWalker = document.createTreeWalker( elem, NodeFilter.SHOW_TEXT, { acceptNode: function (node) { if (node.parentElement.classList.contains(VOICELINK_CLASS)) return NodeFilter.FILTER_ACCEPT if (node.nodeValue.match(RJ_REGEX)) return NodeFilter.FILTER_ACCEPT }, }, false, ) while (rjNodeTreeWalker.nextNode()) { const node = rjNodeTreeWalker.currentNode if (node.parentElement.classList.contains(VOICELINK_CLASS)) Parser.rebindEvents(node.parentElement) else { Parser.linkify(node) } } }, wrapRJCode: function (rjCode) { var e e = document.createElement('a') e.classList = VOICELINK_CLASS e.href = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode}.html` e.innerHTML = rjCode e.target = '_blank' e.rel = 'noreferrer' e.classList.add(rjCode) e.setAttribute(RJCODE_ATTRIBUTE, rjCode.toUpperCase()) e.addEventListener('mouseover', Popup.over) e.addEventListener('mouseout', Popup.out) e.addEventListener('mousemove', Popup.move) return e }, linkify: function (textNode) { const nodeOriginalText = textNode.nodeValue const matches = [] let match while ((match = RJ_REGEX.exec(nodeOriginalText))) { matches.push({ index: match.index, value: match[0], }) } // Keep text in text node until first RJ code textNode.nodeValue = nodeOriginalText.substring(0, matches[0].index) // Insert rest of text while linkifying RJ codes let prevNode = null for (let i = 0; i < matches.length; ++i) { // Insert linkified RJ code const rjLinkNode = Parser.wrapRJCode(matches[i].value) textNode.parentNode.insertBefore( rjLinkNode, prevNode ? prevNode.nextSibling : textNode.nextSibling, ) // Insert text after if there is any let upper if (i === matches.length - 1) upper = undefined else upper = matches[i + 1].index let substring if ( (substring = nodeOriginalText.substring(matches[i].index + matches[i].value.length, upper)) ) { const subtextNode = document.createTextNode(substring) textNode.parentNode.insertBefore( subtextNode, rjLinkNode.nextElementSibling, ) prevNode = subtextNode } else { prevNode = rjLinkNode } } }, rebindEvents: function (elem) { if (elem.nodeName === 'A') { elem.addEventListener('mouseover', Popup.over) elem.addEventListener('mouseout', Popup.out) elem.addEventListener('mousemove', Popup.move) } else { const voicelinks = elem.querySelectorAll('.' + VOICELINK_CLASS) for (var i = 0, ii = voicelinks.length; i < ii; i++) { const voicelink = voicelinks[i] voicelink.addEventListener('mouseover', Popup.over) voicelink.addEventListener('mouseout', Popup.out) voicelink.addEventListener('mousemove', Popup.move) } } }, } var globalCodes = [] const Popup = { makePopup: function (e, rjCode) { const popup = document.createElement('div') popup.className = 'voicepopup ' + (getAdditionalPopupClasses() || '') popup.id = 'voice-' + rjCode popup.style = 'display: flex' document.body.appendChild(popup) DLsite.request(rjCode, function (workInfo) { if (workInfo === null) popup.innerHTML = "<div class='error'>Work not found.</span>" else { const img = document.createElement('img') img.src = workInfo.img img.onload = () => Popup.move(e) let html = `<div class='voice-info'> <h4 class='voice-title'>${workInfo.title.trim()}</h4> <p>社团名:<span>${workInfo.circle.trim()}</span></p>` if (workInfo.date) html += `<p>贩卖日:<span>${workInfo.date}</span></p>` else if (workInfo.dateAnnounce) html += `<p>发布日期:<span>${workInfo.dateAnnounce}</span></p>` html += `<p>年龄指定:<span>${workInfo.rating.trim()}</span></p>` if (workInfo.cv) html += `<p>声优:<span>${workInfo.cv}</span></p>` if (workInfo.tags) { html += `<p>分类:<span>` workInfo.tags.forEach((tag) => { html += tag + '\u3000' }) html += '</span></p>' } if (workInfo.filesize) html += `<p>文件容量:<span>${workInfo.filesize}</span></p>` html += '</div>' popup.innerHTML = html popup.insertBefore(img, popup.childNodes[0]) } Popup.move(e) }) }, humanFileSize: function (size) { if (!size) return '' var i = Math.floor(Math.log(size) / Math.log(1024)) return ( (size / Math.pow(1024, i)).toFixed(2) * 1 + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i] ) }, over: function (e) { const rjCode = e.target.getAttribute(RJCODE_ATTRIBUTE) const popup = document.querySelector('div#voice-' + rjCode) if (popup) { const style = popup.getAttribute('style').replace('none', 'flex') popup.setAttribute('style', style) } else { Popup.makePopup(e, rjCode) } }, out: function (e) { const rjCode = e.target.getAttribute('rjcode') const popup = document.querySelector('div#voice-' + rjCode) if (popup) { const style = popup.getAttribute('style').replace('flex', 'none') popup.setAttribute('style', style) } }, move: function (e) { const rjCode = e.target.getAttribute('rjcode') const popup = document.querySelector('div#voice-' + rjCode) if (popup) { // 如果右侧没有超出屏幕范围 if (popup.offsetWidth + e.clientX + 24 < window.innerWidth) { popup.style.left = e.clientX + 8 + 'px' } else { // 显示在左侧 popup.style.left = e.clientX - popup.offsetWidth - 8 + 'px' } // 如果下方超出屏幕范围 if (popup.offsetHeight + e.clientY + 16 > window.innerHeight) { // 尽可能靠下 popup.style.top = window.innerHeight - popup.offsetHeight - 16 + 'px' } else { popup.style.top = e.clientY + 'px' } } }, } const DLsite = { parseWorkDOM: function (dom, rj) { // workInfo: { // rj: any; // img: string; // title: any; // circle: any; // date: any; // rating: any; // tags: any[]; // cv: any; // filesize: any; // dateAnnounce: any; // } const workInfo = {} workInfo.rj = rj // let rj_group // if (rj.slice(5) == '000') rj_group = rj // else { // rj_group = (parseInt(rj.slice(2, rj.length - 3)) + 1).toString() + '000' // rj_group = 'RJ' + ('000000' + rj_group).substring(rj_group.length) // } workInfo.img = dom.querySelector('.slider_item.active > picture > img').getAttribute('srcset') // workInfo.img = // 'https://img.dlsite.jp/modpub/images2/work/doujin/' + // rj_group + // '/' + // rj + // '_img_main.jpg' workInfo.title = dom.getElementById('work_name').textContent workInfo.circle = dom.querySelector('span.maker_name').textContent const table_outline = dom.querySelector('table#work_outline') for (var i = 0, ii = table_outline.rows.length; i < ii; i++) { const row = table_outline.rows[i] const row_header = row.cells[0].textContent const row_data = row.cells[1] switch (true) { case row_header.includes('贩卖日'): workInfo.date = row_data.textContent break case row_header.includes('年龄指定'): workInfo.rating = row_data.textContent break case row_header.includes('分类'): const tag_nodes = row_data.querySelectorAll('a') workInfo.tags = [...tag_nodes].map((a) => { return a.textContent }) break case row_header.includes('声优'): workInfo.cv = row_data.textContent break case row_header.includes('文件容量'): workInfo.filesize = row_data.textContent.replace('合计', '').trim() break default: break } } const work_date_ana = dom.querySelector('strong.work_date_ana') if (work_date_ana) { workInfo.dateAnnounce = work_date_ana.innerText // workInfo.img = // 'https://img.dlsite.jp/modpub/images2/ana/doujin/' + // rj_group + // '/' + // rj + // '_ana_img_main.jpg' } console.log(workInfo) return workInfo }, request: function (rjCode, callback) { const url = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode}.html/?locale=zh_CN` getXmlHttpRequest()({ method: 'GET', url, headers: { Accept: 'text/xml', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0)', }, onload: function (resp) { if (resp.readyState === 4 && resp.status === 200) { const dom = new DOMParser().parseFromString( resp.responseText, 'text/html', ) const workInfo = DLsite.parseWorkDOM(dom, rjCode) callback(workInfo) } else if (resp.readyState === 4 && resp.status === 404) DLsite.requestAnnounce(rjCode, callback) }, }) }, requestAnnounce: function (rjCode, callback) { const url = `https://www.dlsite.com/maniax/announce/=/product_id/${rjCode}.html/?locale=ja_JP` getXmlHttpRequest()({ method: 'GET', url, headers: { Accept: 'text/xml', 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0)', }, onload: function (resp) { if (resp.readyState === 4 && resp.status === 200) { const dom = new DOMParser().parseFromString( resp.responseText, 'text/html', ) const workInfo = DLsite.parseWorkDOM(dom, rjCode) callback(workInfo) } else if (resp.readyState === 4 && resp.status === 404) callback(null) }, }) }, } document.addEventListener('DOMContentLoaded', function () { const style = document.createElement('style') style.innerHTML = css document.head.appendChild(style) Parser.walkNodes(document.body) const observer = new MutationObserver(function (m) { for (let i = 0; i < m.length; ++i) { let addedNodes = m[i].addedNodes for (let j = 0; j < addedNodes.length; ++j) { Parser.walkNodes(addedNodes[j]) } } }) document.addEventListener('securitypolicyviolation', function (e) { if (e.blockedURI.includes('img.dlsite.jp')) { const img = document.querySelector(`img[src="${e.blockedURI}"]`) img.remove() } }) observer.observe(document.body, { childList: true, subtree: true }) })