// ==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 // @downloadURL none // ==/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(); })();