teratail NG

  • // ==UserScript==
    // @title        teratail NG
    // @name         teratail NG
    // @namespace    http://let.hatelabo.jp/Lhankor_Mhy/let/jIKH0YzigMAA
    // @version      0.1
    // @description  teratailのNG回答者リストをローカルに保存して、質問を見えなくする
    // @author       Lhankor_Mhy
    // @match        https://teratail.com/
    // @match        https://teratail.com/feed/*
    // @match        https://teratail.com/tags/*
    // @match        https://teratail.com/questions/*
    // @icon         https://www.google.com/s2/favicons?domain=teratail.com
    // @license      public domain
    // @grant        none
    // ==/UserScript==
    
    (function () {
      'use strict';
    
      // CONST
      const localStorageKey = '63cc7db5-31e8-7c4f-6dbb-2210c1c056de';
      //const headers = new Headers({ 'Authorization': 'Bearer [APIToken]' });
      const headers = {};
    
      // pushState hack
      const pushState = history.pushState.bind(history);
      history.pushState = (...arg) => {
        document.querySelectorAll('.boxContentWrap')?.forEach?.(NGEraser);
        pushState(...arg);
      }
    
      // NGID リストを取得
      const NGIDs = new Set(JSON.parse(localStorage.getItem(localStorageKey)));
    
    
      // 質問ユーザを localStorage から取得して返す、なければ API を叩いて localStorage にセットして返す
      const getQuestionUser = async questionId => {
    
        let questionUserName = localStorage.getItem(`${localStorageKey}${questionId}`);
    
        if (!questionUserName) {
          questionUserName = (
            await (
              await fetch(`https://teratail.com/api/v1/questions/${questionId}`, {
                headers
              })
            ).json()
          ).question?.user?.display_name;
          questionUserName ??= '退会済みユーザー'; // 退会済みユーザーはAPIが値を返さない
          localStorage.setItem(`${localStorageKey}${questionId}`, questionUserName);
        }
    
        return questionUserName;
    
      }
    
      // 質問一覧のメインループ
      const NGEraser = (element) => {
        element.querySelectorAll('.C-questionFeedItem').forEach(async e => {
    
          const txtUpdateGenre = e.querySelector('.txtUpdateGenre')?.textContent;
          const questionId = e.querySelector('[data-question-id]').dataset.questionId;
          const txtUserName = e.querySelector('.txtUserName .C-textLink')?.textContent?.trim?.();
    
          // ユーザ名がない場合はgetQuestionUser、ある場合で、「分前にコメント」などがあればgetQuestionUser、または「分前に質問」「分前に質問を編集」などでなければgetQuestionUser
          const userName = txtUserName && (!txtUpdateGenre || txtUpdateGenre.includes('質問')) ?
            txtUserName :
            await getQuestionUser(questionId);
    
          // localStorage に退会済みユーザー名が残っていたら表示する
          if (e.querySelector('.txtUserName')?.textContent?.trim?.() === '退会済みユーザー' && userName !== '退会済みユーザー') e.querySelector('.txtUserName').textContent += `質問ユーザー:${userName}`;
    
          changeNGView(userName, e);
        });
      }
    
      // 🤞えんがちょボタン
      const addToggleNG = (element) => {
        const toggleNG = document.createElement('span');
        const userName = element.querySelector('.c-userName__link').textContent.trim();
        toggleNG.dataset.userName = userName;
        toggleNG.textContent = '🤞';
        toggleNG.style.cursor = 'pointer';
        toggleNG.style.border = "1px solid gray";
        toggleNG.style.borderRadius = "3px";
        toggleNG.style.background = "lightgray";
        toggleNG.addEventListener('click', toggleNGListener(userName));
    
        element.insertAdjacentElement('beforeend', toggleNG)
    
        changeNGView(toggleNG.dataset.userName, toggleNG.parentElement);
      }
    
      // NGID表示トグル処理
      const changeNGView = (userName, element) => {
        if (NGIDs.has(userName)) {
          element.style.opacity = "0.1";
        } else {
          element.style.opacity = "";
        }
      }
    
      // 🤞えんがちょボタンのリスナ
      const toggleNGListener = userName => event => {
        if (NGIDs.has(userName)) {
          NGIDs.delete(userName);
        } else {
          NGIDs.add(userName);
        }
        changeNGView(userName, event.target.parentElement);
        localStorage.setItem(localStorageKey, JSON.stringify([...NGIDs]));
      }
    
      // init
      document.querySelectorAll('.boxContentWrap')?.forEach?.(NGEraser);
      document.querySelectorAll('.p-questioner')?.forEach?.(addToggleNG);
    
    
      //TODO APIの例外処理 ユーザーページにえんがちょボタンいれる? ユーザー名変更に対応するは無理かな?
    
    })();
  • Permalink
    このページへの個別リンクです。
    RAW
    書かれたコードへの直接のリンクです。
    Packed
    文字列が圧縮された書かれたコードへのリンクです。
    Userscript
    Greasemonkey 等で利用する場合の .user.js へのリンクです。
    Loader
    @require やソースコードが長い場合に多段ロードする Loader コミのコードへのリンクです。
    Metadata
    コード中にコメントで @xxx と書かれたメタデータの JSON です。