Greasy Fork is available in English.
Add an indicator for watched videos on YouTube
当前为 
// ==UserScript==
// @name        Mark Watched YouTube Videos
// @namespace   MarkWatchedYouTubeVideos
// @description Add an indicator for watched videos on YouTube
// @version     1.0.1
// @author      jcunews
// @include     https://www.youtube.com/*
// @grant       GM_getValue
// @grant       GM_setValue
// ==/UserScript==
(function() {
  
  //=== config start ===
  var maxWatchedVideoAge = 30; //number of days. set to zero to disable (not recommended)
  //=== config end ===
  var watchedVideos, ageMultiplier = 24 * 60 * 60 * 1000;
  function getVideoId(url) {
    var vid = url.match(/\/watch(?:\?|.*?&)v=([^&]+)/);
    if (vid) vid = vid[1] || vid[2];
    return vid;
  }
  function watched(vid) {
    return watchedVideos.some(function(v) {
      return v.id === vid;
    });
  }
  function processVideoItems(selector) {
    var items = document.querySelectorAll(selector), i, link;
    for (i = items.length-1; i >= 0; i--) {
      link = items[i].querySelector("A");
      if (link && watched(getVideoId(link.href))) {
        items[i].classList.add("watched");
      }
    }
  }
  function processPage() {
    //get list of watched videos
    watchedVideos = GM_getValue("watchedVideos");
    if (!watchedVideos) {
      watchedVideos = "[]";
      GM_setValue("watchedVideos", watchedVideos);
    }
    try {
      watchedVideos = JSON.parse(watchedVideos);
      if (watchedVideos.length && (("object" !== typeof watchedVideos[0]) || !watchedVideos[0].id)) {
        watchedVideos = "[]";
        GM_setValue("watchedVideos", watchedVideos);
      }
    } catch(z) {
      watchedVideos = "[]";
      GM_setValue("watchedVideos", watchedVideos);
    }
    //remove old watched video history
    var i = 0, now = (new Date()).valueOf();
    if (maxWatchedVideoAge > 0) {
      while (i < watchedVideos.length) {
        if (((now - watchedVideos.timestamp) / ageMultiplier) > maxWatchedVideoAge) {
          watchedVideos.splice(0, 1);
        } else break;
      }
    }
    //check and remember current video
    var vid = getVideoId(location.href);
    if (vid && !watched(vid)) {
      watchedVideos.push({id: vid, timestamp: now});
      GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
    }
    //=== mark watched videos ===
    //subscriptions page
    processVideoItems(".multirow-shelf>.shelf-content>.yt-shelf-grid-item");
    //channel/user home page feeds
    processVideoItems(".feed-item-main>.feed-item-main-content>.yt-lockup-video");
    //channel/user home page videos
    processVideoItems(".yt-uix-shelfslider-body>.yt-uix-shelfslider-list>.channels-content-item");
    //channel/user home page playlists
    processVideoItems(".expanded-shelf>.expanded-shelf-content-list>.expanded-shelf-content-item-wrapper");
    //channel/user videos page
    processVideoItems(".channels-browse-content-grid>.channels-content-item");
    //video page
    processVideoItems(".watch-sidebar-body>.video-list>.video-list-item");
  }
  var style = document.createElement("STYLE");
  style.innerHTML = '\
/* subscription page, channel/user home page feeds */\
.watched .yt-lockup-content, .watched .yt-lockup-content *,\
/* channel/user home page videos, channel/user videos page */\
.watched .channels-content-item,\
/* video page */\
.watched .content-wrapper,\
.watched>a\
    { background-color: #cec }\
';
  document.head.appendChild(style);
  var lastFocusState = document.hasFocus();
  addEventListener("blur", function() {
    lastFocusState = false;
  });
  addEventListener("focus", function() {
    if (!lastFocusState) processPage();
    lastFocusState = true;
  });
  addEventListener("spfdone", processPage);
  processPage();
})();