usotsuki_KR

및챌헬퍼_번호체계변경대응ver2

Aug 6th, 2023 (edited)
233
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. // ==UserScript==
  2. // @name     및챌헬퍼_번호체계변경대응ver2
  3. // @version  1.2.1-beta-theif-2
  4. // @include  https://arca.live/b/momoirocode/*
  5. // @include  https://arca.live/b/momoirocode/*
  6. // @include  https://arca.live/b/momoirocode?*
  7. // @author   ggee 개발 // tensorcrisis 수정 // 그걸 또 카와와시즈코 수정
  8. // @grant    GM.xmlHttpRequest
  9. // ==/UserScript==
  10.  
  11. ;(() => {
  12.  
  13.   const lang = document.documentElement.lang
  14.   const DL_BASE_URL = 'https://www.dlsite.com/maniax/work/=/product_id'
  15.   const HVDB_BASE_URL = 'https://hvdb.me/Dashboard/Add/?id='
  16.   const KIKOERU_BASE_URL = 'https://asmr.one/work/'
  17.   const RJ_REGEXP = /(?:rj)?(\d{6,8})/gi
  18.  
  19.   const style = document.createElement('style')
  20.   style.appendChild(document.createTextNode(`
  21. .RJ-modal { display: none; align-items: center; justify-content: center; background: rgba(0,0,0,0.5); position: fixed; width: 100%; height: 100%; top: 0; right: 0; bottom: 0; left: 0; z-index: 2147483647 }
  22. .RJ-modal-content { display: flex; flex-direction: column; width: 320px; border-radius: 10px; background: #fff; box-shadow: 0 2px 5px rgba(0,0,0,0.8); padding-top: 10px; padding-bottom: 10px; text-align: center }
  23. .RJ-modal-title { width: 320px; padding-top: 0.3em; padding-bottom: 0.3em; color: #000 }
  24. .RJ-modal-meta { text-align: left; padding: 10px 20px; line-height: 1.5; margin-top: 25px; }
  25. .RJ-modal-meta a { border-bottom: 1px dotted }
  26. .RJ-modal-meta .main_genre > a + a { margin-left: 10px }
  27. .RJ-modal-meta > dt { float: left; clear: left; text-align: right; min-width: 50px; font-weight: bold; color: #000 }
  28. .RJ-modal-meta > dd { padding-left: 60px; color: #000 }
  29. .RJ-modal-img { width: 100% }
  30. .RJ-modal-button { box-sizing: border-box; width: 100%; display: block; padding-top: 0.6em; padding-bottom: 0.6em; font-size: 1.8em; font-weight: bold; color: #000 }
  31. .RJ-modal-button:hover, .RJ-modal-button:focus { background-color: #ddf; color:#00F }
  32. .RJ-rel { border: 1px dashed #999; border-radius: 5px; padding: 2px; font-weight: bold; }
  33. .RJ-link { background: #999; color: #fff; border-radius: 5px; line-height: 1; display: inline-block; width: 20px; height: 13px; text-align: center; font-weight: bold; cursor: pointer }
  34. .RJ-link:first-child { margin-left: 2px }
  35. .RJ-link:hover { background: blue }
  36. `))
  37.   document.head.appendChild(style)
  38.  
  39.   function request(options = {}) {
  40.     return new Promise((resolve, reject) => {
  41.       GM.xmlHttpRequest(Object.assign({
  42.         method: 'GET',
  43.         onload: function (resp) {
  44.           if (resp.status === 200) { resolve(resp.response) }
  45.           else { reject(resp) }
  46.         },
  47.         onerror: function (resp) {
  48.           reject(resp)
  49.         },
  50.       }, options))
  51.     })
  52.   }
  53.  
  54.   function fetchAPI(rj) {
  55.     const url = `https://www.dlsite.com/maniax/product/info/ajax?product_id=${rj}&cdn_cache_min=1`
  56.     return request({ url, responseType: 'json' })
  57.   }
  58.  
  59.   function makeModal() {
  60.     const container = document.createElement('div')
  61.     container.className = 'RJ-modal'
  62.     container.addEventListener('click', function(event) { if (event.target === this) container.style.display = 'none' })
  63.     document.body.appendChild(container)
  64.     return container
  65.   }
  66.  
  67.   function makeRate(rate) {
  68.     const half = rate % 1 ? '◐' : ''
  69.     const star = Array(Math.floor(rate)).fill('●').join('')
  70.     const empty = Array(Math.floor(5 - rate)).fill('○').join('')
  71.     return [star, half, empty].join('')
  72.   }
  73.  
  74.   const modalContainer = makeModal()
  75.  
  76.   async function showModal(rj) {
  77.     modalContainer.innerHTML = ''
  78.  
  79.     const content = document.createElement('figure')
  80.     content.className = 'RJ-modal-content'
  81.  
  82.     if (!rj.startsWith("RJ")) {
  83.         rj = `RJ${rj}`;
  84.     }
  85.  
  86.     const title = document.createElement('h1')
  87.     title.textContent = rj
  88.     title.className = 'RJ-modal-title'
  89.     content.appendChild(title)
  90.  
  91.     const imgA = document.createElement('a')
  92.     imgA.href = getThumbSrc(rj)
  93.     imgA.target = '_blank'
  94.  
  95.     const img = document.createElement('img')
  96.     img.src = imgA.href
  97.     img.className = 'RJ-modal-img'
  98.     imgA.appendChild(img)
  99.     content.appendChild(imgA)
  100.  
  101.     const a = document.createElement('a')
  102.     a.className = 'RJ-modal-button'
  103.     a.href = `${DL_BASE_URL}/${rj}.html`
  104.     a.target = '_blank'
  105.     a.textContent = 'DLsite'
  106.  
  107.     img.addEventListener('error', (event) => {
  108.       // 이미지 로딩에 실패했을 경우 예고작으로 간주함
  109.       if (event.target.classList.contains('RJ-announce')) { return }
  110.       event.target.classList.add('RJ-announce')
  111.       replaceAnnounceUrl({ a, img })
  112.     })
  113.  
  114.     img.addEventListener('load', (event) => {
  115.       addMetadata({ rj, a, img })
  116.     })
  117.  
  118.     const a2 = a.cloneNode(true)
  119.     a2.href = `${HVDB_BASE_URL}${rj}`
  120.     a2.textContent = 'HVDB'
  121.  
  122.     const a3 = a.cloneNode(true)
  123.     a3.href = `${KIKOERU_BASE_URL}${rj}`
  124.     a3.textContent = 'Kikoeru'
  125.  
  126.     content.appendChild(a)
  127.     content.appendChild(a2)
  128.     content.appendChild(a3)
  129.  
  130.     modalContainer.appendChild(content)
  131.     modalContainer.style.display = 'flex'
  132.   }
  133.  
  134.   let tryCount = 0
  135.  
  136.   async function addMetadata({ rj, a, img }) {
  137.     const numFormat = new Intl.NumberFormat()
  138.     const toBlank = aHtml => aHtml.replace(/a href/g, 'a target="_blank" href')
  139.     const insertBefore = (rf, node) => rf.parentNode.insertBefore(node, rf);
  140.  
  141.     const metaList = document.createElement('dl')
  142.     metaList.className = 'RJ-modal-meta'
  143.  
  144.     const meta = {}
  145.     const APPEND_DICT = lang === 'ja-jp'
  146.     ? {
  147.       '販売日': '발매일',
  148.       '予告開始日': '예고일',
  149.       '声優': '성우',
  150.       '年齢指定': '수위',
  151.       'ジャンル': '태그',
  152.     }
  153.     : {
  154.       '판매일': '발매일',
  155.       '예고 개시일': '예고일',
  156.       '성우': '성우',
  157.       '연령 지정': '수위',
  158.       '작품 형식': '태그',
  159.     }
  160.   const DICT = {
  161.     'TITLE': '제목',
  162.     'CIRCLE': '서클',
  163.     'PRICE': '가격',
  164.     'RATE': '평점',
  165.     'DL': '다운수',
  166.     'WISH': '찜수',
  167.     ...APPEND_DICT,
  168.   }
  169.  
  170.     try {
  171.       const body = await request({ url: a.href })
  172.       const frag = document.createElement('div')
  173.       frag.innerHTML = body
  174.  
  175.       meta.TITLE = frag.querySelector('#work_name > a').textContent
  176.       meta.CIRCLE = toBlank(frag.querySelector('.maker_name').innerHTML)
  177.  
  178.       const table = frag.querySelector('#work_outline')
  179.       const rows = Array.from(table.querySelectorAll('tr'))
  180.       const appendDictKeys = Object.keys(APPEND_DICT);
  181.       const pHtml = rows.forEach(row => {
  182.         const [th, td] = Array.from(row.children)
  183.         if (appendDictKeys.includes(th.textContent)) {
  184.           meta[th.textContent] = toBlank(td.innerHTML)
  185.         }
  186.       })
  187.  
  188.     } catch (e) {
  189.       console.error(e)
  190.       if (e.status === 404) {
  191.         if (tryCount++ < 1) {
  192.           replaceAnnounceUrl({ a })
  193.           addMetadata({ rj, a, img })
  194.         }
  195.         return
  196.       }
  197.     }
  198.  
  199.     try {
  200.       const api = await fetchAPI(rj)
  201.       const data = api[rj] || {}
  202.       const { dl_count, wishlist_count, rate_average_2dp, rate_count, price } = data
  203.       meta.RATE = `${makeRate(rate_average_2dp)} ${rate_average_2dp}(평가수: ${numFormat.format(rate_count)})`
  204.       meta.DL = numFormat.format(dl_count)
  205.       meta.WISH = numFormat.format(wishlist_count)
  206.       meta.PRICE = `${numFormat.format(price)}円`
  207.     } catch (e) { console.error(e) }
  208.  
  209.  
  210.     Object.entries(DICT).forEach(([ key, value ]) => {
  211.       if (!meta[key]) { return }
  212.       const dt = document.createElement('dt')
  213.       const dd = document.createElement('dd')
  214.       dt.textContent = value
  215.       dd.innerHTML = meta[key]
  216.       metaList.appendChild(dt)
  217.       metaList.appendChild(dd)
  218.       // dt가 목차, dd가 내용물
  219.       // 원하는 dict이랑 meta만 없애버리면
  220.  
  221.     })
  222.  
  223.     insertBefore(a, metaList)
  224.     // img 뒤에 추가
  225.   }
  226.  
  227.   function replaceAnnounceUrl({ a, img }) {
  228.     if (img) {
  229.       const toSrcReplaced = img.src.replace('/work/', '/ana/').replace('_img_', '_ana_img_')
  230.       if (img.src !== toSrcReplaced) { img.src = toSrcReplaced }
  231.     }
  232.     if (a) {
  233.       const toHrefReplaced = a.href.replace('/work/', '/announce/')
  234.       if (a.href !== toHrefReplaced) { a.href = toHrefReplaced }
  235.     }
  236.   }
  237.  
  238.   function makeModalButton(rj) {
  239.     const a = document.createElement('span')
  240.     a.setAttribute('data-rj', rj)
  241.  
  242.     rj = rj.length === 6 ? `RJ${rj}` : rj.toUpperCase()
  243.     a.textContent = '?'
  244.     a.className = 'RJ-link'
  245.     a.addEventListener('click', (event) => { event.preventDefault(); event.stopPropagation(); showModal(rj) })
  246.     return a
  247.   }
  248.  
  249.   function getThumbSrc(rj) {
  250.     rj = rj.replace(/rj/gi, '')
  251.     let bucket = Math.ceil(Number(rj) / 1000) * 1000
  252.     bucket = ('000000' + String(bucket)).slice(-rj.length)
  253.     return `https://img.dlsite.jp/modpub/images2/work/doujin/RJ${bucket}/RJ${rj}_img_main.jpg`
  254.   }
  255.  
  256.  
  257.   // 갤에서 RJ코드 해당되는거 글씨 바꾸기
  258.   function filterText(text = '') {
  259.     let matches
  260.     let i = 0
  261.     let rjs = []
  262.  
  263.     while ((matches = RJ_REGEXP.exec(text)) !== null) {
  264.       let rj = matches[0]
  265.       let startIndex = matches.index
  266.       let lastIndex = RJ_REGEXP.lastIndex
  267.  
  268.       if (text[startIndex - 1] && /[\w/px"]/.test(text[startIndex - 1])) {
  269.        // 앞글자가 붙어있음: pass
  270.        continue
  271.      }
  272.  
  273.      if (text[lastIndex] && /[\w/px"]/.test(text[lastIndex])) {
  274.         // 뒷글자가 붙어있음: pass
  275.         continue
  276.       }
  277.  
  278.       rjs.push(rj)
  279.       // text replace
  280.       text = `${text.slice(0, matches.index)}__{${i++}}__${text.slice(lastIndex)}`
  281.     }
  282.  
  283.     for (let j = 0, len = rjs.length; j < len; j++) {
  284.       text = text.replace(
  285.         new RegExp('__\\{' + j + '\\}__', 'g'),
  286.         `<em class="RJ-rel" data-rj="${rjs[j]}">${rjs[j]}</em>`
  287.       )
  288.     }
  289.  
  290.     return text
  291.   }
  292.  
  293.    //
  294.   function addLink(node) {
  295.     if (!node) return
  296.     let matches = node.textContent.match(RJ_REGEXP)
  297.     if (!matches) return
  298.     if (node.classList.contains('RJ')) return
  299.     node.classList.add('RJ')
  300.  
  301.     matches = [...new Set(matches)]
  302.  
  303.     node.innerHTML = filterText(node.innerHTML)
  304.  
  305.     matches.forEach(match => {
  306.       const mb = makeModalButton(match)
  307.       const rj = mb.getAttribute('data-rj')
  308.       const target = node.querySelector(`[data-rj="${rj}"]`)
  309.       if (!target) return
  310.       target.appendChild(mb)
  311.       //버튼을 뒤에 추가
  312.     })
  313.   }
  314.  
  315. const titles = Array.from(document.querySelectorAll('.title ion-ios-photos-outline, .title'))
  316.   const body = document.querySelector('.article-content')
  317.   const titleSubject = document.querySelector('.title_subject')
  318.   const getComments = () => Array.from(document.querySelectorAll('.text'))
  319.   let comments = getComments()
  320.  
  321.   const commentWrap = document.querySelector('.comment_wrap')
  322.   if (commentWrap) {
  323.     const commentObserver = new MutationObserver(mutations => {
  324.       comments = getComments()
  325.       comments.map(addLink)
  326.     })
  327.     commentObserver.observe(commentWrap, { attributes: true, childList: true, characterData: true })
  328.   }
  329.  
  330.   titles.map(addLink)
  331.   addLink(titleSubject)
  332.   addLink(body)
  333.   comments.map(addLink)
  334.   // 타이틀 댓글 제목 등에 링크 추가해주는 부분, 즉 갤용
  335.  
  336. })();
Add Comment
Please, Sign In to add comment