Twitter Block With Love

Block all users who love a certain tweet

目前为 2020-06-26 提交的版本。查看 最新版本

// ==UserScript==
// @name        Twitter Block With Love
// @namespace   https://www.eolstudy.com
// @version     1.1
// @description Block all users who love a certain tweet
// @author      Eol
// @run-at      document-end
// @match       https://twitter.com/*
// @match       https://mobile.twitter.com/*
// @require     https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js
// @require     https://cdn.jsdelivr.net/npm/qs/dist/qs.min.js
// @require     https://code.jquery.com/jquery-3.4.1.min.js
// @require     https://greasyfork.org/scripts/2199-waitforkeyelements/code/waitForKeyElements.js?version=6349
// ==/UserScript==

/* global axios $ Qs waitForKeyElements*/

(_ => {
  var lang = document.documentElement.lang
  // alert("lang = " + document.documentElement.lang)
  const translations = {
      // add your own language here by copy one and modifying it
    "en": {
      lang_name: "English",
      likedby: "Timeline: Liked by", // aria-label
      title: "Liked by",
      btn: "Block All",
      success: "All Users Blocked!"
    },
    "en-GB": {
      lang_name: "British English",
      likedby: "Timeline: Liked by",
      title: "Liked by",
      btn: "Block All",
      success: "All Users Blocked!"
    },
    "zh": {
      lang_name: "简体中文",
      likedby: '时间线:喜欢者',
      title: "喜欢者",
      btn: "拉清单",
      success: "已屏蔽所有为这条推文打心的账户!"
    },
    "zh-Hant": {
      lang_name: "正體中文",
      likedby: '時間軸:已被喜歡',
      title: "已被喜歡",
      btn: "全部封鎖",
      success: "已封鎖所有喜歡了這條推文的賬戶!"
    },
    "ja": {
      lang_name: "日本語",
      likedby: "タイムライン: いいねしたユーザー",
      title: "いいねしたユーザー",
      btn: "一括でブロック",
      success: "全てブロックした!"
      }
  }
  var i18n = (translations[lang])
  if (!i18n) {
    var langnames = []
    for(let l in translations) langnames.push(translations[l].lang_name)
    langnames = langnames.join(", ")
    alert("Twitter Block With Love userscript does not support your language. Please add your language to the translations string in the editor, or switch the language of Twitter Web App to the following languages: " + langnames + ".")
    }

  function get_cookie (cname) {
    var name = cname + "="
    var ca = document.cookie.split(';')
    for(var i=0; i<ca.length; i++)
    {
      var c = ca[i].trim()
      if (c.indexOf(name)==0) {
        return c.substring(name.length,c.length)
      }
    }
    return ""
  }

  const ajax = axios.create({
    baseURL: 'https://api.twitter.com',
    withCredentials: true,
    headers: {
      'Authorization': 'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
      'X-Twitter-Auth-Type': 'OAuth2Session',
      'X-Twitter-Active-User': 'yes',
      'X-Csrf-Token': get_cookie('ct0')
    }
  })

  function get_tweet_id () {
    // https://twitter.com/any/thing/status/1234567/anything => 1234567/anything => 1234567
    return location.href.split('status/')[1].split('/')[0]
  }

  async function fetch_likers (tweetId) {
    const users = await ajax.get(`/2/timeline/liked_by.json?tweet_id=${tweetId}`).then(
      res => res.data.globalObjects.users
    )

    let likers = []
    for (let user in users) {
      likers.push(users[user].id_str)
    }
    return likers
  }

  function block_user (id) {
    ajax.post('/1.1/blocks/create.json', Qs.stringify({
      user_id: id
    }), {
      headers: {
        "Content-Type":'application/x-www-form-urlencoded'
      }
    })
  }

  async function block_all_likers () {
    const tweet_id = get_tweet_id()
    const likers = await fetch_likers(tweet_id)
    likers.forEach(id => block_user(id))
  }

  function clear_view () {
    const container = $('div[aria-label="'+ i18n.likedby + '"]')
    container.children().fadeOut(400, _ => {
      const notice = $(`
        <div style="
          color: rgb(224, 36, 94);
          text-align: center;
          margin-top: 3em;
          font-size: x-large;
        ">
          <span>${i18n.success}</span>
        </div>
      `)
      container.append(notice)
    })
  }

  function mount_block_button (dom) {
    const btn_mousedown = 'bwl-btn-mousedown'
    const btn_hover = 'bwl-btn-hover'

    $('head').append(`
      <style>
        .bwl-btn-base {
          min-height: 30px;
          padding-left: 1em;
          padding-right: 1em;
          border: 1px solid rgb(29, 161, 242) !important;
          border-radius: 9999px;
        }
        .${btn_mousedown} {
          background-color: rgba(29, 161, 242, 0.2);
          cursor: pointer;
        }
        .${btn_hover} {
          background-color: rgba(29, 161, 242, 0.1);
          cursor: pointer;
        }
        .bwl-btn-inner-wrapper {
          font-weight: bold;
          -webkit-box-align: center;
          align-items: center;
          -webkit-box-flex: 1;
          flex-grow: 1;
          color: rgba(29,161,242,1.00);
          display: flex;
        }
        .bwl-text-font {
          font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Ubuntu, "Helvetica Neue", sans-serif;
        }
      </style>
    `)

    const button = $(`
      <div
        aria-haspopup="true"
        role="button"
        data-focusable="true"
        class="bwl-btn-base"
      >
        <div class="bwl-btn-inner-wrapper">
          <span>
            <span class="bwl-text-font">${i18n.btn}</span>
          </span>
        </div>
      </div>
    `)
    .addClass(dom.prop('classList')[0])
    .hover(function () {
      $(this).addClass(btn_hover)
    }, function () {
      $(this).removeClass(btn_hover)
      $(this).removeClass(btn_mousedown)
    })
    .on('selectstart', function () {
      return false
    })
    .mousedown(function () {
      $(this).removeClass(btn_hover)
      $(this).addClass(btn_mousedown)
    })
    .mouseup(function () {
      $(this).removeClass(btn_mousedown)
      if ($(this).is(':hover')) {
        $(this).addClass(btn_hover)
      }
    })
    .click(block_all_likers)
    .click(clear_view)

    dom.append(button)
  }

  function main () {
    waitForKeyElements('h2:has(> span:contains(' + i18n.title + '))', dom => {
      mount_block_button(dom.parent().parent().parent())
    })
  }

  main()
})()