gaeulbyul / 트위터 유령트윗 열어보기

// ==UserScript==
// @name        트위터 유령트윗 열어보기
// @namespace   gaeulbyul.userscripts
// @match       https://twitter.com/*
// @match       https://mobile.twitter.com/*
// @grant       none
// @version     0.0.2
// @author      가을별
// @license     MIT
// @description 유령계정이 작성한 것으로 보이는 트윗을 열어봅니다.
// ==/UserScript==

function dig(obj) {
  try {
    return obj()
  } catch (err) {
    if (err instanceof TypeError) {
      return null
    } else {
      throw err
    }
  }
}


function getReactEventHandlers(target) {
  const key = Object.keys(target)
    .filter(k => k.startsWith('__reactEventHandlers'))
    .pop()
  return key ? target[key] : null
}

function* elemsFromMutations(mutations) {
  for (const {addedNodes} of mutations) {
    for (const node of addedNodes) {
      if (node instanceof HTMLElement) {
        yield node
      }
    }
  }
}

new MutationObserver(mutations => {
  const tweetElems = []
  for (const elem of elemsFromMutations(mutations)) {
    Array
      .from(elem.querySelectorAll('article[role=article]'))
      .forEach(elem => tweetElems.push(elem))
  }
  for (const tweetElem of tweetElems) {
    const tombstoneNotice = tweetElem.querySelector('a[href="https://help.twitter.com/rules-and-policies/notices-on-twitter"]')
    if (!tombstoneNotice) {
      continue
    }
    const reh = getReactEventHandlers(tweetElem.parentElement)
    const key = reh.children._owner.key
    const epitaph = dig(() => reh.children._owner.stateNode.props.data.data.content.epitaph)
    // missing이 아닌 다른게 들어가면 다른 사유로 트윗이 안 보이는 경우. (예를 들어 프로텍트)
    if (!/missing/i.test(epitaph)) {
      continue
    }
    const tweetIdMatch = /^tweet-(\d+)$/.exec(key)
    if (tweetIdMatch) {
      const tweetId = tweetIdMatch[1]
      const showTweetElem = document.createElement('a')
      // 공개된 트윗의 경우, 유저네임 부분에 임의로 넣어도 알아서 리다이렉트가 된다.
      // 따라서, 유저네임 부분을 i로 한다.
      showTweetElem.href = `https://${location.hostname}/i/status/${tweetId}`
      showTweetElem.textContent = '<이 트윗 열어보기>'
      showTweetElem.title = `이 트윗 링크를 열어봅니다. 유령계의 트윗은 보이나 프로텍트,차단당함,트윗/계정삭제,계정정지에 해당하는 트윗은 보이지 않습니다.`
      showTweetElem.style.color = 'violet'
      showTweetElem.style.margin = '0 5px'
      tombstoneNotice.parentElement.appendChild(showTweetElem)
    }
  }
}).observe(document.body, {
  childList: true,
  subtree: true,
})