Nico Excluder

ユーザ拒否リストに引っかかった動画を非表示にする

目前為 2020-06-19 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Nico Excluder
// @namespace    https://i544c.github.io
// @version      0.1.6
// @description  ユーザ拒否リストに引っかかった動画を非表示にする
// @author       i544c
// @match        https://www.nicovideo.jp/ranking/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(async () => {
  'use strict';

  GM_setValue();

  const _debug = (...msg) => {
    console.log(`[Nico Excluder] ${msg.join(' ')}`);
  };

  const _fetch = url => new Promise((resolve, _reject) => {
    GM_xmlhttpRequest({
      url,
      method: 'GET',
      headers: {
        'User-Agent': 'nico_excluder/0.1.6',
      },
      onload: res => resolve(res.responseText),
    });
  });

  class NicoApi {
    static endpointGetThumbInfo(contentId) {
      return `https://ext.nicovideo.jp/api/getthumbinfo/${contentId}`;
    }

    static async getUserId(contentId) {
      const url = this.endpointGetThumbInfo(contentId);
      const body = await _fetch(url);
      const domparser = new DOMParser();
      const response = domparser.parseFromString(body, 'text/xml');
      const userId = response.getElementsByTagName('user_id')[0].textContent;
      return userId;
    }
  }

  class List {
    constructor() {
      this.array = GM_getValue('denyUserList', []);
      this.url = GM_getValue('denyUserListUrl', null);
    }

    canUpdate() {
      if (!this.url) return false;

      const now = new Date();
      const lastUpdatedAt = new Date(GM_getValue('updatedAt', 0));
      lastUpdatedAt.setHours(lastUpdatedAt.getHours() + 1);
      return now.getTime() > lastUpdatedAt.getTime();
    }

    async update() {
      if (!this.canUpdate()) return;

      const body = await _fetch(this.url);
      const array = JSON.parse(body);
      const now = new Date();
      GM_setValue('denyUserList', array);
      GM_setValue('updatedAt', now.getTime());
      _debug('Updated');
      this.array = array;
    }
  }

  class Job {
    constructor(denyUserList) {
      this.denyUserList = denyUserList;
      this.timer = null;
      this.interval = 1000;
      this.queue = [];
    }

    enqueue(contentId) {
      this.queue.push(contentId);
    }

    dequeue() {
      return this.queue.shift();
    }

    start() {
      this.timer = window.setInterval(() => this.run(), this.interval);
    }

    stop() {
      window.clearInterval(this.timer);
    }

    async run() {
      if (this.queue.length === 0) return;

      const contentId = this.dequeue();
      const userId = await NicoApi.getUserId(contentId);
      _debug(contentId, userId);
      if (!this.denyUserList.includes(userId)) return;

      _debug('Goodbye!', NicoApi.endpointGetThumbInfo(contentId));
      document.querySelector(`div.MediaObject[data-video-id=${contentId}`).remove();
    }
  }

  const list = new List;
  await list.update();

  const job = new Job(list.array);
  job.start();

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === 'attributes') {
        const contentId = mutation.target.parentElement.getAttribute('data-deflist-item-id');
        if (!contentId || job.queue.includes(contentId)) return;

        job.enqueue(contentId);
      }
    });
  });

  const thumbs = document.querySelectorAll('div.Thumbnail-image');
  thumbs.forEach(thumb => {
    observer.observe(thumb, { attributes: true });
  });
})();