DLSite Links+

Provide links from RJ, RE, VJ, DMM, VG and RG codes as well as providing thumbnails for community distributed files.

// ==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,iVBORw0KGgoAAAANSUhEUgAAACUAAAAgCAYAAACVU7GwAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAibSURBVFhHjZh1qFVbEMbHbXd3FxZ2K2J3YGFii+0fKoqiGAii2FgoYmB3YyeK3dhd1+7O9eY3nnXeub575X2w7lp7nxWzJr6ZfUXh/tZWrlzpMmfO7MCPHz+sr1ixYoxzY2vZsmWzfseOHbY+JhQvXtxlzJjR5gX6R+LEiUP3H+hEiRs3rvTp08eeGYNv375ZnyZNGkmZMqWNY0Lu3LklRYoUEj9+fEmSJIksX75czp8/H/o1OgoUKCCtW7eWqlWrSpAqVSp74REvXrzQSKRz587y/PlzUc2E3ojcuHFDPn78KJkyZZJKlSqZ4DGhRo0aMmLECPn586fUrVtXtm3bJsWKFbP5jx8/tjmfP3+2vQBnvXnzRrp06SLBoEGDwhpAQG4PeIfkV65ckSAI5MCBA7J582bJnz+/JEqUSMqVKyfDhg2TT58+mSb+RI8ePeTly5dStmxZqV+/vuzcuVPmzJkjefPmtT04q1OnTvLhwwe7eIkSJUyopUuXiowcOdLVqlXLbFmmTBnr9RDXv39/9+DBA1ezZk2zOf707NkzpzdzuXLlciq0UzM61axTjdm6DBkyWI8Pzp8/3/Xt29epCd2ePXvsfaFChWxPj4MHDzrVntu3b589d+zY0eYFX758kcuXL+tY5P3795I2bVpp1KiRpEuXTtRBTf0AzaVPn958o2DBgvZ+ypQposKKXkoSJkwoY8eOtbmYYMaMGXLixAm5e/eu7Nq1S1Rg0wTnDR8+XNq0aSOnT58212jQoIFZiHkgePr0qURFRdkDY+zOIQgFvFPrRawHmACMHj3aegKlYcOGMmHCBOnWrZutxaFpXKJp06ZmanwJ040bN85Mvnv3bvOlO3fuyMCBA+1imD3AP3wEcRME7NWrl00A2B5ERmjJkiWt59ZoD+dXk8rt27elWbNm8uTJE/udC+HIEydOlAEDBtg7BDt37pxMnz5dypcvLxUqVLCgUXeR5MmTS/bs2SXAFN7RAebAsTEjYJIHv4FSpUqZAwP1Odm+fXv4t/Xr14c178GBXBgFDB061OZjLrTogWIwMa4UoMZ8+fKFfhJRZ7Yo84cSfW3btrVQZqPEiRNL48aNTTsgS5Ys8vr167BmFyxYEDa5R/Xq1e3Q8ePHG+UgWO/evWXw4MGhGb+RI0cOi1R+dKpG83rf2rdv73ShjYlE1ZqNq1Wr5vQCTgVxqvJoa5Q+wmMiLvK3SKROndpdunQp9OTcr1+/QiPnNACcOr+LqwQ2Gr+4deuWrv8NIubw4cMWHah806ZNUrhwYTMBWkUbuodoWjD/OXPmjN1cKcVMx7xIEBDMxy/hpSZNmpijE8G4ineZo0ePytq1awW1Ow3laDfzDXz9+tVylprLnvVA165dO6ch765evRqeR1+6dOlo6327d++ezfn+/bv1e/futR4oDbgXL16YxnLmzGmcF+ihlp+SJk2q6/8FN+7ataskSJAgfJupU6fKzJkzhSzQr18/S0/JkiWTxYsX282Jvsig8cCP9HxLK+wxb948o4kqVaoYN7Vo0cIsggxHjhyxNU4nuTp16jh15PDtkB4od7mTJ0+6R48euYsXL7pVq1aZplTVdrv9+/fbfG6pjhxeH9lgffxWk62rV6+e+ZyGv9PIdx06dHCHDh2ys5R03bt37xzk42Bw/ODYsWPh0NYUYPnt7du3dsuzZ8+Kql30cMuJhDMRCKUwhhCHDBkiefLkkfv375v/sA5oGpHjx48bfcBb8BHchBXwZzWr5Uailsi20kU1YGbwAgESMCZkc9RatGhRI1XVkvGJ+pOsWLHCBIKFyQSYb8uWLbbeCwS4ECzPXgQHpt66datVDpzx8OFDUa0bAZPqAqRVR7Tb0zy4HdxDv3DhQlm0aJGow0vz5s2Nq+CxV69eWfSNGjXKLsV8DXmZPHmy7YEQ4NSpU9K9e3dRNzBGz5o1q11OKcbeoTWEQeseZnddhFmNJ3jWDS0S8LMiRYq4li1bOtWW05C1agGoac33NN1YdBKpSi3mf0Sn35vqgKjVMsfWKjk7FS68z6xZs4wB8CcNBke02EJKXA9IknceqmK3ceNGd+3aNXvGwTmA0oUwp1fT2yEIyJhefdX20bRk61QbTmsnW6/at3fMA7xjHxoqs4VECNCU4ebOnWsMPXv2bKeZ3q1evdqpmcJRgiAczEa+VyI0TaERntXZbS57kx2IVi6CYABNsTdz/T7syz6WZlhI4wd1SluEGlG7MrA9A62PrPda8hvSYsOGDRuc1ktm0j+hmcF6r2kaCIgGgPPqQgtXIkInGKkSCBAsjkh0AAgSQsSRfeN3kjcpSs1sNRIBBElyBolbz7P1gOitXbu2jSFnqCdMvFp8hTWlG1oDSK3cYcQJvFPGppWbN2+GRtEB+UamFY8LFy6ERr/P8loChLjV1AhF5BFNWhE6vbW7fv26TfK9x59mU56xPjZwIJVB5IWoz2MCcwIID54A5CHViJXDcAZ8Anz16aFrzbyehzDz34BZqDJ8RQr4CooNAbaFcQEk5ytODvaVofJH+OPCwwsESBP/B5GVJqV0TGDfAAeEjQHUTwpBC6pGq3focXCYmhoL4Jg4ugeaUrOHnmIHl/OgrvIgkKIBoiTr69Caftmabfm+87zCmLCFQ7SccWPGjLE5kcAXqK9iAr9pNNoYIia4qG5jA1TgtEYygTy79+zZ035EKARCMBrcBXzpywerVhb2zgPGjg2TJk1y+lVs/MQHMIBwEdpj2bJl7P27ptZvOct3PMO2+sVhkxAkMrf5tKAJ2eYSeevWrbM09GeUerCXFnd2QUCGoO4HXNbnPLIBWSMO5TA21U9mq8X5OJw2bZqVE/qJLVrimm+p+YxgiU6ilWdKmiVLltg/M4gmvueosfiyJuKoGnhfuXJlq7OoEJR+ZM2aNdKqVSsLJhXGenNw9dWoqCj5B3Bom7hmzZDuAAAAAElFTkSuQmCC
// @grant       none
// @run-at      document-idle
// ==/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 = ('<div class="hgg2dBox">\n' +
                    ' <h2>Quicklink Script Tutorial</h2>\n' +
                    ' <hr>\n' +
                    ' <p>This script is designed to make browsing and sharing hentai games more comfy in /hgg*/ threads.</p>\n' +
                    ' <p>Syntax: The codes are parsed in the following ways.</p>\n' +
                    ' <table><tbody>\n' +
                    '     <tr>\n' +
                    '         <td>DLSite Releases:</td>\n' +
                    '         <td><a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/work/=/product_id/RJ146992" class="lewds">RJ146992</a> and <a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/work/=/product_id/RJ146992" class="lewds">https://www.dlsite.com/maniax/work/=/product_id/RJ146992</a></td>\n' +
                    '     </tr>\n' +
                    '     <tr>\n' +
                    '     <td></td><td><a rel="noreferrer" target="_blank" href="https://www.dlsite.com/pro/work/=/product_id/VJ010879" class="lewds">VJ010879</a> and <a rel="noreferrer" target="_blank" href="https://www.dlsite.com/pro/work/=/product_id/VJ010879" class="lewds">https://www.dlsite.com/pro/work/=/product_id/VJ010879</a> work for Professional works as well.</td>\n' +
                    '     </tr>\n' +
                    '     <tr>\n' +
                    '         <td>DLSite Announces:</td>\n' +
                    '         <td><a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/announce/=/product_id/RJ197797" class="lewds">RJA197797</a> and <a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/announce/=/product_id/RJ197797" class="lewds">RA197797</a> and <a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/announce/=/product_id/RJ197797" class="lewds">https://www.dlsite.com/maniax/announce/=/product_id/RJ197797</a></td>\n' +
                    '     </tr>\n' +
                    '     <tr>\n' +
                    '         <td>DLSite Circles:</td>\n' +
                    '         <td><a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11840" class="lewds">RG11840</a> and <a rel="noreferrer" target="_blank" href="https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11840" class="lewds">https://www.dlsite.com/maniax/circle/profile/=/maker_id/RG11840</a></td>\n' +
                    '     </tr>\n' +
                    '     <tr>\n' +
                    '         <td>DLSite Blogs:</td>\n' +
                    '         <td><a rel="noreferrer" target="_blank" href="http://b.dlsite.net/RG23067/">http://b.dlsite.net/RG23067/</a> Full URLs only, you may link to specific posts as well.</td>\n' +
                    '     </tr>\n' +
                    '     <tr>\n' +
                    '         <td>DMM Releases:</td>\n' +
                    '         <td><a rel="noreferrer" target="_blank" href="https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_107232/" class="lewds">DMM107232</a> and <a rel="noreferrer" target="_blank" href="https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_107232/" class="lewds">d_107232</a> and <a rel="noreferrer" target="_blank" href="https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_107232/" class="lewds">https://www.dmm.co.jp/dc/doujin/-/detail/=/cid=d_107232/</a></td>\n' +
                    '     </tr>\n' +
                    ' </tbody></table>\n' +
                    ' <hr>\n' +
                    ' <p>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.</p>\n' +
                    '</div>')
                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 => {
            // <wbr>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)