// ==UserScript== // @name DLSite Links+ // @namespace Loli-A-Best // @include *://boards.4chan.org/vg/thread/* // @include *://boards.4chan.org/h/thread/* // @include *://boards.4chan.org/*/thread/* // @include *://boards.4channel.org/vg/thread/* // @include *://boards.4channel.org/h/thread/* // @include *://boards.4channel.org/*/thread/* // @include *://arch.b4k.co/*/thread/* // @include *://ipfs.io/ipfs/* // @include *://ipfs.infura.io/ipfs/* // @include *://yuki.la/vg/* // @version 1.12z // @description Provide links from RJ, RE, VJ, DMM, VG and RG codes as well as providing thumbnails for community distributed files. // @icon data:image/png;base64,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 // @grant none // @run-at document-idle // @downloadURL none // ==/UserScript== (() => { 'use strict' const d = document const Chan = { DMMCode: /(?:(?:dmm|www|https?)[^>\s]+)?(?:cid=)?(?:d_|DMM)(\d{6})/gi, RJCode: /((?:(?:dlsite|www|http|maniax)[^>\s]+)?[rv][jea]a?((\d{3})\d{3})(?:\.html)?)/gi, RGBlog: /(http:\/\/\S*b\.dlsite\.net\/(?:rg\d{5}\/)?archives\/\d{3,8}\.html)/gi, RGCirc: /(?:(?:http|www)?\S*com\S*|^|\s)[rv]g(\d{5})(?:\.html)?/gi, // 4chan-X specific variables fourchanxLinkifyRegex: /((https?|mailto|git|magnet|ftp|irc):([a-z\d%\/?])|([-a-z\d]+[.])+(aero|asia|biz|cat|com|coop|dance|info|int|jobs|mobi|moe|museum|name|net|org|post|pro|tel|travel|xxx|xyz|edu|gov|mil|[a-z]{2})([:\/]|(?![^\s"]))|[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}\.[\d]{1,3}|[-\w\d.@]+@[a-z\d.-]+\.[a-z\d])/gi, thread: d.querySelector('.thread'), games: [], linkify: false, fourchanx: false, oneechan: false, prev: d.createElement('img'), container: d.createElement('div'), content: d.createElement('div'), toggle: d.createElement('a'), CSS: { hgg2dCSS: ('' + '#preview { display: block; position: fixed; top: 0; padding: 0; margin: 0; z-index: 8;}\n' + '.previewBar { position: fixed; right: 3em; width: 6.5em; bottom: 12em; z-index: 6; padding: 0; margin: 0; max-height: 35%; overflow-y: auto; overflow-x: hidden; }\n' + '.previewBar > div { padding: 0; margin: 0; width: 100%; }\n' + '.previewBar > div > a { display: block; }\n' + '.previewBarToggle { float: right; }\n' + '.previewBarToggle::before { content: "["; color: #000 !important; }\n' + '.previewBarToggle::after { content: "]"; color: #000 !important; }\n' + '.hgg2dOverlay { background: rgba(0,0,0,0.8); display: none; height: 100%; left: 0; position: fixed; top: 0; width: 100%; z-index: 7; }\n' + '.hgg2dBox { position: fixed; top: 20%; left: 20%; width:50%; padding: 2em; border: 1em solid #34345C; overflow: hidden; }\n' + '.hgg2dOverlay:target { outline:none; display: block; }\n' + '.hgg2dBox table { display: block; }\n' + '.hgg2dTut { float: right; margin-right: 5px; }\n' + '.hgg2dTut::before { content: "["; color: #000 !important; }\n' + '.hgg2dTut::after { content: "]"; color: #000 !important; }'), init: () => { const style = d.createElement('style') style.appendChild(d.createTextNode(Chan.CSS.hgg2dCSS)) d.head.appendChild(style) } }, Firstrun: { init: () => { const lightbox = d.createElement('div') const div = d.createElement('div') const close = d.createElement('a') const showTutorial = d.createElement('a') close.textContent = 'Click me to close' close.href = '#' close.style.fontWeight = 'bold' div.innerHTML = ('
\n' + '

Quicklink Script Tutorial

\n' + '
\n' + '

This script is designed to make browsing and sharing hentai games more comfy in /hgg*/ threads.

\n' + '

Syntax: The codes are parsed in the following ways.

\n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + ' \n' + '
DLSite Releases:RJ146992 and https://www.dlsite.com/maniax/work/=/product_id/RJ146992
VJ010879 and https://www.dlsite.com/pro/work/=/product_id/VJ010879 work for Professional works as well.
DLSite Announces:RJA197797 and RA197797 and https://www.dlsite.com/maniax/announce/=/product_id/RJ197797
DLSite Circles:RG11840 and https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11840
DLSite Blogs:http://b.dlsite.net/RG23067/ Full URLs only, you may link to specific posts as well.
DMM Releases:DMM107232 and d_107232 and https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_107232/
\n' + '
\n' + '

Note that if you link using an RJ code rather than an RJA code the script will attempt to take you to a Releases page.\n' + ' This is by design so that you have more control over where links are headed.

\n' + '
') lightbox.id = 'hgg2dTutorial' lightbox.classList.add('hgg2dOverlay') lightbox.appendChild(div) div.firstElementChild.appendChild(close) div.firstElementChild.style.borderColor = window.getComputedStyle(d.body).backgroundColor; div.firstElementChild.style.backgroundColor = window.getComputedStyle(d.body).backgroundColor; d.body.appendChild(lightbox) if (!localStorage.getItem('hgg2dFirstrun')) { d.location.href = d.location.href.split('#')[0] + '#hgg2dTutorial' localStorage.setItem('hgg2dFirstrun', true) } showTutorial.classList.add('hgg2dTut') showTutorial.textContent = 'Quicklinks Tutorial' showTutorial.href = '#hgg2dTutorial' d.querySelector('.navLinks.desktop').appendChild(showTutorial) } }, handlePrevError: e => { return (err) => { if (!e.target.origHref) e.target.origHref = e.target.href // Change link if necessary if (e.numErrors == 1) e.target.href = e.target.href.match(/announce/) != null ? e.target.href : e.target.href.replace(/work(.*)R(.\d+)/ig, 'announce$1R$2') else e.target.href = e.target.origHref Chan.prev.style.visibility = 'hidden' Chan.prev.onerror = null // Try redoing the hover with the new link Chan.hover(e) } }, // Check every post for if the linkify setting is toggled on shortcircuiting // when a definitive answer is found, else repeating checkForLinkify: () => { Chan.linkify = !!Chan.thread.querySelector('.linkify') if (Chan.linkify) return const posts = Array.from(Chan.thread.querySelectorAll('.postMessage')) for (let i = 0; i < posts.length; i++) { if (Chan.fourchanxLinkifyRegex.test(posts[i].textContent) && !!posts[i].querySelector('.linkify')) { Chan.linkify = false return } } if (!Chan.linkify) setTimeout(Chan.checkForLinkify, 3000) }, // Reached threshold of saving lines by adding in a generic method createAnch: (text) => { const anch = d.createElement('a') anch.rel = 'noreferrer' anch.target = '_blank' anch.textContent = text return anch }, // createX functions are called with the element, followed by each of its regex capture groups createBlog: (el, match) => { const anch = Chan.createAnch(match) anch.href = match return anch }, createCirc: (el, match, code) => { const anch = Chan.createAnch(match) if (match.includes('RG')) { if (match.includes('ecchi-eng')) { anch.href = `https://www.dlsite.com/ecchi-eng/circle/profile/=/maker_id/RG${code}` } else { anch.href = `https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG${code}` } } else { anch.href = `https://www.dlsite.com/pro/circle/profile/=/maker_id/VG${code}` } return anch }, createDMM: (el, match, code) => { const anch = Chan.createAnch(match) anch.href = `https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_${code}` anch.classList.add('lewds') if (Chan.games.indexOf('DMM' + code) === -1) { Chan.games.push('DMM' + code) const text = d.createTextNode('DMM' + code) const node = anch.cloneNode() node.appendChild(text) Chan.content.appendChild(node) } return anch }, createRJ: (el, match, text, code) => { const anch = Chan.createAnch(text) const pattern = 'https://www.dlsite.com/{0}/{1}/=/product_id/{2}{3}' let circleType = [] let workType = '' if (text.includes('announce') || /[rv]j?a/i.test(text)) workType = 'announce' else workType = 'work' if (text.includes('VJ')) circleType.push('pro', 'VJ') else if (text.includes('RE')) circleType.push('ecchi-eng', 'RE') else circleType.push('maniax', 'RJ') anch.href = pattern.format(circleType[0], workType, circleType[1], code) anch.classList.add('lewds') if (workType.includes('announce')) circleType[1] = circleType[1].replace('J', 'A') if (Chan.games.indexOf(circleType[1] + code) === -1) { Chan.games.push(circleType[1] + code) const text = d.createTextNode(circleType[1] + code) const node = anch.cloneNode() node.appendChild(text) Chan.content.appendChild(node) } return anch }, hover: (e) => { const t = e.target.classList.contains('lewds') ? e.target : undefined if (!e.numErrors) e.numErrors = 0; if (t === undefined || e.numErrors > 2) { Chan.prev.style.visibility = 'hidden' Chan.prev.onerror = null return } const pattern = 'https://img.dlsite.jp/modpub/images2/{0}/{1}/{2}{3}000/{2}{4}{5}_img_main.jpg' const rect = e.target.getBoundingClientRect() // announce or work let pageType = [] // doujin or pro let circleType = [] if (e.target.href.includes('dlsite')) { const code = e.target.href.split('/product_id/')[1].substr(2, 6) if (e.target.href.includes('announce')) pageType.push('ana', '_ana') else pageType.push('work', '') if (e.target.href.includes('VJ')) circleType.push('professional', 'VJ') else if (e.target.href.includes('RE')) circleType.push('doujin', 'RE') else circleType.push('doujin', 'RJ') e.numErrors++; Chan.prev.onerror = Chan.handlePrevError(e) if (e.numErrors == 3) circleType[1] = 'RJ' let roundCode = parseInt(code.substr(0, 3)) if (code % 1000 != 0) roundCode++ Chan.prev.src = pattern.format(pageType[0], circleType[0], circleType[1], Chan.padLeft(roundCode, 3), code, pageType[1]); } else if (e.target.href.includes('dmm.co')) { const code = e.target.href.split('cid=')[1].substr(0, 8) Chan.prev.src = `https://pics.dmm.co.jp/digital/game/${code}/${code}pr.jpg` } Chan.prev.style.visibility = '' Chan.prev.style.top = ((window.innerHeight - rect.top < 420) ? window.innerHeight - 435 : rect.top - 15) + 'px' Chan.prev.style.left = ((window.innerWidth - rect.left < 560) ? rect.left - 565 : rect.right + 5) + 'px' }, // Alexander Dickson's replace text function with minor changes matchText: (node, regex, callback, excludeElements) => { excludeElements = excludeElements || ['a'] var child = node.firstChild || -1 while (child) { switch (child.nodeType) { case 1: if (excludeElements.includes(child.tagName.toLowerCase())) break Chan.matchText(child, regex, callback, excludeElements) break case 3: let bk = 0 child.data.replace(regex, function (all) { let args = [...arguments], offset = args[args.length - 2], newTextNode = child.splitText(offset + bk), tag bk -= child.data.length + all.length newTextNode.data = newTextNode.data.substr(all.length) tag = callback.apply(window, [child].concat(args)) child.parentNode.insertBefore(tag, newTextNode) child = newTextNode }) regex.lastIndex = 0 break } child = child.nextSibling } return node }, // Hide and unload src to prevent it looking like two codes are the same game until the new image loads. out: (e) => { const t = e.target.classList.contains('lewds') ? e.target : undefined if (t === undefined) return Chan.prev.style.visibility = 'hidden' Chan.prev.src = '' }, // Cached padLeft because input will always be 0-2 characters in our use case padLeft: (str, len) => { const cache = [ '', '0', '00' ] // ensure str is string str = String(str) len = len - str.length return cache[len] + str }, setPreviewBar: () => { if (localStorage.getItem('hgg2d previewbar') === 'true') { Chan.container.style.visibility = '' Chan.toggle.textContent = 'Previewbar Off' } else { Chan.container.style.visibility = 'hidden' Chan.toggle.textContent = 'Previewbar On' } if (Chan.fourchanx && Chan.oneechan) { Chan.container.style.bottom = '4em' } else if (Chan.fourchanx) { Chan.container.style.bottom = '9em' } else if (Chan.oneechan) { Chan.container.style.bottom = '5em' } }, togglePreviewBar: e => { localStorage.setItem('hgg2d previewbar', !(localStorage.getItem('hgg2d previewbar') === 'true')) Chan.setPreviewBar() }, work: el => { // s get in the way with little benefit, easier to work with if simply removed Array.from(el.querySelectorAll('wbr')).forEach(t => { const parent = t.parentNode parent.removeChild(t) parent.normalize() }) Chan.matchText(el, Chan.DMMCode, Chan.createDMM) Chan.matchText(el, Chan.RJCode, Chan.createRJ) Chan.matchText(el, Chan.RGBlog, Chan.createBlog) Chan.matchText(el, Chan.RGCirc, Chan.createCirc) if (Chan.linkify) Array.from(el.querySelectorAll('.linkify:not(lewds)')).forEach(link => link.classList.add('lewds')) }, init: () => { if (!String.prototype.format) { String.prototype.format = function () { const args = arguments return this.replace(/{(\d+)}/g, (match, number) => { return typeof args[number] != 'undefined' ? args[number] : match }) } } new MutationObserver(function (mutations) { const posts = [] // If someone wants to show me some meme magic on how to map/reduce this // I would be more than happy to accept the Pull request // Looks aids because it has to play nice with 4chan-X which separates // every post insertion into separate mutation events, and also creates // two mutation events every time you come back to or leave the tab for (let i = 0; i < mutations.length; i++) { if (mutations[i].addedNodes.length > 0) { for (let x = 0; x < mutations[i].addedNodes.length; x++) { if (mutations[i].addedNodes[x].tagName === 'DIV') { posts.push(mutations[i].addedNodes[x].lastElementChild.lastElementChild) } } } } posts.forEach(post => Chan.work(post)) }).observe(Chan.thread, { childList: true, attributes: true }) // HTML Area Chan.prev.setAttribute('id', 'preview') Chan.prev.setAttribute('style', 'visibility: hidden;') Chan.prev.onerror = () => { Chan.prev.style.visibility = 'hidden' } d.body.appendChild(Chan.prev) Chan.container.classList.add('previewBar') d.body.appendChild(Chan.container) Chan.container.appendChild(Chan.content) Chan.toggle.setAttribute('href', 'javascript:;') Chan.toggle.classList.add('previewBarToggle') Chan.toggle.appendChild(d.createTextNode('toggle')) if(document.location.hostname !== 'arch.b4k.co') d.querySelector('.navLinksBot').appendChild(Chan.toggle) // Zero_G modified line // Events Area d.body.addEventListener('mouseover', Chan.hover, false) d.body.addEventListener('mouseout', Chan.out, false) Chan.toggle.addEventListener('click', Chan.togglePreviewBar, false) // With all settings removed, and additional extensions installed, 4chan-X // will add the 'fourchan-x' class to the documentElement. setTimeout(() => { Chan.fourchanx = d.documentElement.classList.contains('fourchan-x') Chan.oneechan = d.documentElement.classList.contains('oneechan') if (Chan.fourchanx) { Chan.checkForLinkify() } Chan.setPreviewBar() }, 500) if(document.location.hostname === 'arch.b4k.co') Array.from(d.querySelectorAll('.text')).forEach(el => Chan.work(el)) // Zero_G added line else Array.from(d.querySelectorAll('.postMessage')).forEach(el => Chan.work(el)) // Zero_G modified line Chan.CSS.init() Chan.Firstrun.init() }, } const Ipfs = { init: () => { Ipfs.CSS() Ipfs.HTML() const anchors = Array.from(d.querySelectorAll('a')).filter(el => /R[JE]\d{6}/gi.test(el.textContent)) anchors.forEach(anchor => Ipfs.generateRoot(anchor)) }, generateRoot: (anchor) => { // div to house the images // img to test whether or not the image is there const div = d.createElement('div') const img = d.createElement('img') div.classList.add('x-scrollable') img.addEventListener('error', Ipfs.retry) img.addEventListener('load', Ipfs.continue) img.target = div img.retry = true anchor.parentNode.appendChild(div) anchor = anchor.textContent anchor = /(R[JE])(\d{3})\d{3}/gi.exec(anchor) img.src = `https://img.dlsite.jp/modpub/images2/work/doujin/${anchor[1]}${Chan.padLeft(Number(anchor[2]) + 1, 3)}000/${anchor[0]}_img_main.jpg` }, generateNext: (current, img) => { if (current === 0) return img.src.replace('main', 'smp1') return img.src.replace(/smp\d+\.jpg/gi, `smp${Number(/smp(\d+)\.jpg/gi.exec(img.src)[1]) + 1}.jpg`) }, continue: (loadEvent) => { // loadEvent's members are currentTarget (img) and srcElement (img as well) const img = loadEvent.currentTarget const container = img.target Ipfs.createNew(img.src, container) img.retry = true if (localStorage.getItem('singlePreview') === 'true') return if (img.src.includes('main.jpg')) { img.src = Ipfs.generateNext(0, img) return } img.src = Ipfs.generateNext(/smp(\d+)/gi.exec(img.src)[1], img) }, retry: (errorEvent) => { const img = errorEvent.currentTarget if (img.src.includes('main.jpg') && img.retry) { img.retry = false const replacer = img.src.includes('RJ') ? 'RE' : 'RJ' img.src = img.src.replace(/R[JE]/gi, replacer) } }, createNew: (src, container) => { const image = d.createElement('img') image.classList.add('x-inline') image.src = src if (src.includes('main.jpg')) { const a = d.createElement('a') const href = `https://www.dlsite.com/${src.includes('RE') ? 'ecchi-eng' : 'maniax'}/work/=/product_id/${/(R[JE]\d{6})_/gi.exec(src)[1]}` a.href = href image.setAttribute('title', 'Click here to got to DLSite') a.appendChild(image) container.appendChild(a) return } container.appendChild(image) }, CSS: () => { const css = d.createElement('style') const tds = Array.from(d.querySelector('tr').querySelectorAll('td')) css.textContent = '.x-inline {' + 'display: inline-block;' + 'max-height: 220px;' + '}' + 'table {' + 'table-layout: fixed;' + '}' + '.x-scrollable {' + 'overflow-x: auto;' + 'white-space: nowrap;' + '}' + '.x-container:empty {' + 'display: none;' + '}' + '.x-toggle {' + 'margin: 10px;' + '}' d.head.appendChild(css) // Sets the widths of the first data columns such that the table doesn't get fucked // from being set to fixed-layout (needed for the scrolling preview bar) tds[0].style = 'width: 32px;' tds[2].style = 'width: 117.15px;' }, HTML: () => { const button = d.createElement('button') button.textContent = `Toggle Multiple Previews: ${localStorage.getItem('singlePreview') === 'true' ? 'On' : 'Off'}` button.classList.add('x-toggle') button.addEventListener('click', () => { localStorage.setItem('singlePreview', !(localStorage.getItem('singlePreview') === 'true')) const singlePreview = localStorage.getItem('singlePreview') === 'true' button.textContent = `Toggle Multiple Previews: ${singlePreview ? 'On' : 'Off'}` button.disabled = true setTimeout(() => { document.querySelector('button').disabled = false }, 2000) // Reload page when the user toggles the multi-preview on. if (singlePreview === 'false') d.location.reload() // If we reached this point then the user has disabled seeing the extra // images that are already loaded. Future extra images will not continue // to load. const style = d.createElement('style') style.textContent = '.x-inline:not(:first-child) { display: none; }' d.head.appendChild(style) }) d.querySelector('#header').appendChild(button) } } switch (document.location.hostname) { case 'boards.4channel.org': case 'boards.4chan.org': case 'yuki.la': // Zero_G added line case 'arch.b4k.co': // Zero_G added line Chan.init() break case 'ipfs.io': case 'ipfs.infura.io': Ipfs.init() break } }).call(this)