YouTube - Comment Snub

Block annoying user comments

Verze ze dne 16. 12. 2018. Zobrazit nejnovější verzi.

// ==UserScript==
// @name            YouTube - Comment Snub
// @description     Block annoying user comments
// @version         1.0.1
// @author          wormboy
// @license         MIT
// @namespace       patchmonkey
// @include         https://www.youtube.com/*
// @run-at          document-idle
// @grant           GM.setValue
// @grant           GM.getValue
// @noframes
// ==/UserScript==

let blacklist = null

async function filterComments(nodes, tag) {
  const users = await blacklist
  for (const node of nodes) {
    if (node instanceof HTMLElement && node.matches(tag)) {
      const href = node.querySelector('#author-thumbnail a').getAttribute('href')
      node.hidden = users.includes(href)
      addButton(node, href)
    }
  }
}

function quarantineUser(event) {
  const { user } = event.currentTarget.dataset
  const jobs = []
  blacklist = GM.getValue('blacklist', []).then(list => {
    return Array.from(new Set([...list, user]))
  })
  jobs.push(blacklist)
  jobs.push(blacklist.then(list => GM.setValue('blacklist', list)))
  const threads = document.querySelectorAll('ytd-comment-thread-renderer')
  jobs.push(filterComments(threads, 'ytd-comment-thread-renderer'))
  for (const thread of threads) {
    const replies = thread.querySelectorAll('ytd-comment-renderer')
    jobs.push(filterComments(replies, 'ytd-comment-renderer'))
  }
  Promise.all(jobs).catch(e => console.error(e))
}

function reusable(strs) {
  return function(...vals) {
    return strs.map((s, i) => `${s}${vals[i] || ''}`).join('')
  }
}

const buttonTemplate = reusable`
  <yt-icon-button>
    <yt-icon></yt-icon>
  </yt-icon-button>
  <paper-tooltip>${'tooltip'}</paper-tooltip>
`

const buttonCss = `
  --yt-button-icon-size: var(--ytd-comment-thumb-dimension);
  margin: 0 8px 0 -8px;
`

const iconButtonCss = `
  padding: var(--yt-button-icon-padding, 8px);
  width: var(--yt-button-icon-size, var(--yt-icon-width, 40px));
  height: var(--yt-button-icon-size, var(--yt-icon-height, 40px));
`

const iconCss = `
  color: var(--iron-icon-fill-color, currentcolor);
`

const svgIcon = reusable`
  <svg viewBox="0 0 ${'width'} ${'height'}" preserveAspectRatio="xMidYMid meet" style="pointer-events: none; display: block; width: 100%; height: 100%;">
    <g class="style-scope yt-icon">${'vector'}</g>
  </svg>
`

const iconVectors = {
  shutup: `
    <circle fill="#FFCC4D" cx="18" cy="18" r="18"/>
    <path fill="#664500" d="M14.034 14.499c0 1.934-1.119 3.5-2.5 3.5s-2.5-1.566-2.5-3.5c0-1.933 1.119-3.5 2.5-3.5s2.5 1.567 2.5 3.5m13 0c0 1.934-1.119 3.5-2.5 3.5s-2.5-1.566-2.5-3.5c0-1.933 1.119-3.5 2.5-3.5s2.5 1.567 2.5 3.5m-2.033 12.505H10c-1 0-1-1-1-1v-1s0-1.003 1-1.003l15.001.003v3z"/>
    <path fill="#99AAB5" d="M35.255 26.084l-7.713-2.121c-.72-.197-1.049.287-1.171.547l-1.64-.784L24 25.255v-2.254h-2v2h-2v-2h-2.001v2H16v-2h-2v2h-2v-2h-2v3h2v2h2v-2h1.999v2h2v-2H20v2h2v-2h1.643l-.58 1.212 1.648.788-.099.207s-.248.737.373 1.275c.621.537 5.285 4.735 5.285 4.735s.899.866 1.769.079c.738-.67 3.649-6.02 3.914-6.983.266-.964-.698-1.23-.698-1.23zm-3.772 6.071l-2.644-2.248 1.614-3.069 3.338 1.132-2.308 4.185z"/>
  `
}

function addButton(node, href) {
  if (node.querySelector('#toxic')) return
  const el = document.createElement('ytd-toggle-button-renderer')
  el.id = 'toxic'
  el.style.cssText = buttonCss
  el.setAttribute('button-renderer', '')
  el.setAttribute('is-icon-button', '')
  el.setAttribute('has-no-text', '')
  el.innerHTML = buttonTemplate('Snub')
  el.dataset.user = href
  el.addEventListener('click', quarantineUser)
  el.querySelector('yt-icon-button').style.cssText = iconButtonCss
  const icon = el.querySelector('yt-icon')
  icon.style.cssText = iconCss
  icon.innerHTML = svgIcon(36, 36, iconVectors.shutup)
  const toolbar = node.querySelector('#toolbar')
  toolbar.insertBefore(el, toolbar.firstElementChild)
}

const threadObserver = new MutationObserver(function(records) {
  const debug = e => console.error(e)
  for (const { target, addedNodes } of records) {
    if (target.id == 'contents') {
      filterComments(addedNodes, 'ytd-comment-thread-renderer').catch(debug)
    } else if (target.id == 'loaded-replies') {
      filterComments(addedNodes, 'ytd-comment-renderer').catch(debug)
    }
  }
})

const commentsObserver = new MutationObserver(function(records) {
  const comments = document.querySelector('ytd-app ytd-comments')
  if (comments) {
    commentsObserver.disconnect()
    threadObserver.observe(comments, { childList: true, subtree: true })
  }
})

window.addEventListener('yt-navigate-finish', observe)
function observe() {
  blacklist = GM.getValue('blacklist', [])
  threadObserver.disconnect()
  commentsObserver.observe(document.body, { childList: true, subtree: true })
}

observe()