Nico Excluder

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

当前为 2020-06-23 提交的版本,查看 最新版本

// ==UserScript==
// @name         Nico Excluder
// @namespace    https://i544c.github.io
// @version      1.0.2
// @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 { version } = GM_info.script;

  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/${version}`,
      },
      onload: res => resolve(res.responseText),
    });
  });

  function* _counter() {
    let i = 0;
    while(true) yield ++i;
  }

  class ApiCache {
    constructor() {
      this.badContents = GM_getValue('cacheBadContents', []);
      this.badContentsMax = 100;
    }

    addBadContents(contentId, userId) {
      if (this.findBadContent(contentId)) return;

      this.badContents.push({ contentId, userId });
      this.badContents.splice(0, this.badContents.length - this.badContentsMax);
      GM_setValue('cacheBadContents', this.badContents);
    }

    findBadContent(contentId) {
      return this.badContents.find(item => item.contentId === contentId);
    }
  }

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

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

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

    canUpdate() {
      const now = new Date();
      const lastUpdatedAt = new Date(GM_getValue('updatedAt', 0));
      lastUpdatedAt.setHours(lastUpdatedAt.getHours() + 1);
      return this.url && 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();
      this.array = array;
      GM_setValue('denyUserList', array);
      GM_setValue('updatedAt', now.getTime());
      _debug('Updated');
    }
  }

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

    check(contentId) {
      const cachedContent = this.apiCache.findBadContent(contentId);
      cachedContent
        ? this.run(contentId, cachedContent.userId)
        : this.enqueue(contentId)
    }

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

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

    start() {
      if (this.timer) {
        console.warn('Already running');
        return;
      }

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

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

    async run(contentId = this.dequeue(), userId = null) {
      if (!contentId) return;

      if (!userId) { // Called from queue
        userId = await NicoApi.getUserId(contentId);
      }
      _debug(contentId, userId);
      if (!this.denyUsers.includes(userId)) return;

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

  const denyUserList = new DenyUserList;
  await denyUserList.update();

  const apiCache = new ApiCache;
  const job = new Job(denyUserList.array, apiCache);
  job.start();

  const observer = new MutationObserver(mutations => {
    mutations.forEach(mutation => {
      if (mutation.type === 'attributes' && mutation.attributeName === 'data-loaded') {
        const contentId = mutation.target.parentElement.getAttribute('data-deflist-item-id');
        job.check(contentId);
      }
    });
  });

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

QingJ © 2025

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