KoebutaSlayer

Adds a button that eliminates evil replys to tweet details.

目前為 2017-03-03 提交的版本,檢視 最新版本

// ==UserScript==
// @name           KoebutaSlayer
// @namespace      https://github.com/albno273/KoebutaSlayer
// @description    Adds a button that eliminates evil replys to tweet details.
// @description:ja ツイート詳細に声豚のリプライを抹殺するボタンを追加します。
// @include        https://twitter.com/*
// @version        2.0.2
// @require        https://openuserjs.org/src/libs/sizzle/GM_config.js
// @grant          GM_getValue
// @grant          GM_setValue
// @grant          GM_log
// @license        MIT License
// ==/UserScript==

// 仕様スクリプト: GM config
// https://openuserjs.org/src/libs/sizzle/GM_config.js
// 参考: Extract images for Twitter
// https://gf.qytechs.cn/ja/scripts/15271-extract-images-for-twitter

// 除外リスト(初期設定)
const defaultWhitelistArray = [
  'suzaki_aya',      'ibuking_1114', 'maaya_taso',      '0812asumikana',
  'numakura_manami', '38kMarie',     'Yaskiyo_manager', 'kurapimk',
  'kanekosanndesu',  'AyakaOhashi',  'Lynn_0601_',      'takamori_723',
  'OnoSaki1126',     'nojomiy',      'akekodao',        'yuumin_uchiyama',
  'marika_0222',     'Miho_Aaaa',    'osorasan703',     'fuchigami_mai',
  'han_meg_han',     'Erietty_55',   'RiccaTachibana',  'tanezakiatsumi',
  'yuuka_aisaka',    '0309akari',    'mikakokomatsu',   'shiori_izawa',
  'TomoyoKurosawa',  'eerie_eery',   'mikami_shiori',   'reimatsuzaki',
  'shimoda_asami',   'Uesakasumire', 'coloruri',        'yuichupunch',
  'ErikoMatsui',     'ousakichiyo',  'nanjolno',        'Emiryun',
  'HiRoMi_ig',       'makomorino',   'yukari_tamura'
];

// 初期設定
GM_config.init(
  {
    'id':    'KoebutaSlayerConfig',
    'title': 'KoebutaSlayer 設定',
    'fields':
    {
      'slayBehavior':
      {
        'label':   '抹殺時の挙動',
        'type':    'radio',
        'options': ['非表示にする', 'ニンジャスレイヤー風'],
        'default': '非表示にする'
      },
      'whitelist':
      {
        'label':   'ホワイトリスト(ID を改行で区切って入力してください)',
        'type':    'textarea',
        'default': `${defaultWhitelistArray.join('\n')}`
      }
    },
    'events':
    {
      'save': () => {
        // 単語構成文字と改行以外の文字があったら注意
        if(/[^0-9_a-zA-Z\n]/g.test(GM_config.fields['whitelist'].toValue()))
          alert('Caution!\nホワイトリストに半角英数字とアンダーバー、' +
                '改行以外の文字が入っていませんか?');
        alert('設定を保存しました。');
      },
      'reset': () => {
        alert('設定を初期化しました。');
      }
    }
  }
);

(() => {

  const processedList       = new WeakMap();

  let slayCountBeforeExec = 0; // ツイート抹殺数(起動前)
  let slayCountAfterExec  = 0; // ツイート抹殺数(起動後)

  // 抹殺ボタンを作る
  const createSlayer = (tweetList) => {
    const slayer = document.createElement('div');
    slayer.setAttribute('class', 'ProfileTweet-action js-toggleState ProfileTweet-action--Slay');
    slayer.innerHTML =
    '<button class="ProfileTweet-actionButton js-actionButton js-actionSlay" type="button">' +
      '<div class="IconContainer js-tooltip" title="抹殺">' +
        '<span class="Icon Icon--close"></span>' +
        '<span class="u-hiddenVisually">抹殺</span>' +
      '</div>' +
      '<div class="IconTextContainer">' +
        '<span class="ProfileTweet-actionCount ">' +
          '<span class="ProfileTweet-actionCountForPresentation Slay-counter" aria-hidden="true"></span>' +
        '</span>' +
      '</div>' +
    '</button>';
    slayer.addEventListener('mouseenter', () => {
      const icon    = slayer.getElementsByClassName('Icon')[0];
      const counter = slayer.getElementsByClassName('Slay-counter')[0];
      icon.style.color    = 'darkred';
      counter.style.color = 'darkred';
    });
    slayer.addEventListener('mouseleave', () => {
      const icon    = slayer.getElementsByClassName('Icon')[0];
      const counter = slayer.getElementsByClassName('Slay-counter')[0];
      if(counter.textContent == '') {
        icon.style.color    = '';
        counter.style.color = '';
      }
    });
    slayer.addEventListener('click', () => {
      slayTweet(tweetList);
      // 抹殺数が増えた時だけ
      if(slayCountAfterExec != 0)
        recordCount();
    });
    return slayer;
  };

  // 抹殺ボタンを追加
  const addSlayer = () => {
    const tweetList = document.getElementsByClassName('ProfileTweet-actionList');
    for (let i = 0; i < tweetList.length; i++) {
      const tweet = tweetList[i];
      if(processedList.has(tweet))
        continue;
      else {
        // 画面遷移前のボタンが残った時に削除
        const oldSlayer = tweet.getElementsByClassName('ProfileTweet-action--Slay')[0];
        if(oldSlayer)
          oldSlayer.remove();
        // ツイート詳細欄にのみボタンを登録
        // TODO: parentNode 連打やめたい
        if(tweet.parentNode.parentNode.parentNode.classList.contains('permalink-tweet-container')) {
          processedList.set(tweet, 1);
          tweet.appendChild(createSlayer(tweetList));
        }
      }
    }
  };

  // 設定ボタンを作る
  const createConfig = () => {
    const config = document.createElement('li');
    config.setAttribute('class', 'moments js-moments-tab');
    config.innerHTML =
    '<a role="button" href="#" class="js-nav js-tooltip js-dynamic-tooltip" ' +
    'data-component-context="moments_nav" data-nav="moments" data-placement="bottom">' +
      '<span class="Icon Icon--close Icon--large"></span>' +
      '<span class="text">抹殺設定</span>'+
    '</a>';
    config.addEventListener('click', () => { GM_config.open(); });
    return config;
  };

  // 設定ボタンを追加
  const addConfig = () => {
    const navList = document.getElementsByClassName('js-global-actions');
    for (let i = 0; i < navList.length; i++) {
      const nav = navList[i];
      if(processedList.has(nav))
        continue;
      else {
        // 画面遷移前のボタンが残った時に削除
        const oldConfig = nav.getElementsByClassName('ProfileTweet-action--Slay--config')[0];
        if(oldConfig)
          oldConfig.remove();
        // 登録
        processedList.set(nav, 1);
        nav.appendChild(createConfig());
      }
    }
  };

  // ツイートを抹殺
  const slayTweet = (tweetList) => {
    const behavior = GM_config.get('slayBehavior');
    const wl       = GM_config.get('whitelist').split('\n');
    for (let i = 0; i < tweetList.length; i++) {
      // 画面遷移前のツイートも含まれるのでリプライのみ抽出
      // TODO: parentsNode 連打やめたい
      const tweets = tweetList[i].parentNode.parentNode.parentNode.parentNode
        .getElementsByClassName('permalink-descendant-tweet');
      if(tweets.length == 1) {
        const tweet    = tweets[0];
        const from     = tweet.getAttribute('data-screen-name');
        const to       = tweet.getAttribute('data-mentions');
        // チェインはされてるけど @id が明記されていない場合
        if(wl.indexOf(from) == -1 && to == null)
          slay(behavior, tweet);
        else {
          const toArr = to.split(' ');
          toArr.forEach((value, index, array) => {
            if (wl.indexOf(from) == -1 && wl.indexOf(value) >= 0)
              slay(behavior, tweet);
          });
        }
      }
    }
  }
  const slay = (behavior, tweet) => {
    if(behavior == '非表示にする')
       tweet.style.display = 'none';
    else if(behavior == 'ニンジャスレイヤー風') {
      tweet.getElementsByClassName('tweet-text')[0].textContent = 'アバーッ!';
      tweet.getElementsByClassName('fullname')[0].textContent   = 'Koebuta Slayer';
      tweet.getElementsByClassName('username')[0].innerHTML     = '<s>@</s><b>koebutaslayer</b>';
      tweet.getElementsByClassName('js-action-profile-avatar')[0]
        .setAttribute('src', 'https://pbs.twimg.com/profile_images/716042850903830528/PLNG3AVA.jpg');
      if(tweet.getElementsByClassName('AdaptiveMediaOuterContainer')[0])
        tweet.getElementsByClassName('AdaptiveMediaOuterContainer')[0].remove();
    }
    slayCountAfterExec++;
  }

  // 抹殺したツイート数をお知らせ
  const recordCount = () => {
    const counter = document.getElementsByClassName('Slay-counter')[0];
    counter.textContent = slayCountAfterExec;
    if(slayCountAfterExec - slayCountBeforeExec != 0)
      slayCountBeforeExec = slayCountAfterExec;
    slayCountAfterExec = 0;
  }

  // DOMの更新が入るたびにボタンを追加
  (() => {
    let DOMObserverTimer = false;
    const DOMObserverConfig = {
      attributes: true,
      childList: true,
      subtree: true
    };
    const DOMObserver = new MutationObserver(function () {
      if (DOMObserverTimer !== 'false') {
        clearTimeout(DOMObserverTimer);
      }
      DOMObserverTimer = setTimeout(function () {
        DOMObserver.disconnect();
        addSlayer();
        DOMObserver.observe(document.body, DOMObserverConfig);
      }, 100);
    });
    DOMObserver.observe(document.body, DOMObserverConfig);
  })();

  // 初回起動
  addSlayer();
  addConfig();

}) ();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址