Mark Watched YouTube Videos 1.3.51 (Modified)
3 removals
Words removed | 2 |
Total words | 1577 |
Words removed (%) | 0.13 |
460 lines
20 additions
Words added | 24 |
Total words | 1599 |
Words added (%) | 1.50 |
470 lines
// ==UserScript==
// ==UserScript==
// @name Mark Watched YouTube Videos
// @name Mark Watched YouTube Videos
// @namespace MarkWatchedYouTubeVideos
// @namespace MarkWatchedYouTubeVideos
// @version 1.3.51
// @version 1.3.51
// @license AGPL v3
// @license AGPL v3
// @author jcunews
// @author jcunews
// @description Add an indicator for watched videos on YouTube. Use GM menus to display history statistics, backup history, and restore/merge history.
// @description Add an indicator for watched videos on YouTube. Use GM menus to display history statistics, backup history, and restore/merge history.
// @website https://greasyfork.org/en/users/85671-jcunews
// @website https://greasyfork.org/en/users/85671-jcunews
// @match *://www.youtube.com/*
// @match *://www.youtube.com/*
// @grant GM_getValue
// @grant GM_getValue
// @grant GM_registerMenuCommand
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_setValue
// @grant unsafeWindow
// @grant unsafeWindow
// @run-at document-start
// @run-at document-start
// ==/UserScript==
// ==/UserScript==
/*
/*
- Use ALT+LeftClick or ALT+RightClick on a video list item to manually toggle the watched marker. The mouse button is defined in the script and can be changed.
- Use ALT+LeftClick or ALT+RightClick on a video list item to manually toggle the watched marker. The mouse button is defined in the script and can be changed.
- For restoring/merging history, source file can also be a YouTube's history data JSON (downloadable from https://support.google.com/accounts/answer/3024190?hl=en). Or a list of YouTube video URLs (using current time as timestamps).
- For restoring/merging history, source file can also be a YouTube's history data JSON (downloadable from https://support.google.com/accounts/answer/3024190?hl=en). Or a list of YouTube video URLs (using current time as timestamps).
*/
*/
(() => {
(() => {
//=== config start ===
//=== config start ===
var maxWatchedVideoAge = 5 * 365; //number of days. set to zero to disable (not recommended)
var maxWatchedVideoAge = 0; // 5 * 365; //number of days. set to zero to disable (not recommended)
var contentLoadMarkDelay = 600; //number of milliseconds to wait before marking video items on content load phase (increase if slow network/browser)
var contentLoadMarkDelay = 600; //number of milliseconds to wait before marking video items on content load phase (increase if slow network/browser)
var markerMouseButtons = [0, 1]; //one or more mouse buttons to use for manual marker toggle. 0=left, 1=right, 2=middle. e.g.:
var markerMouseButtons = [0, 1]; //one or more mouse buttons to use for manual marker toggle. 0=left, 1=right, 2=middle. e.g.:
//if `[0]`, only left button is used, which is ALT+LeftClick.
//if `[0]`, only left button is used, which is ALT+LeftClick.
//if `[1]`, only right button is used, which is ALT+RightClick.
//if `[1]`, only right button is used, which is ALT+RightClick.
//if `[0,1]`, any left or right button can be used, which is: ALT+LeftClick or ALT+RightClick.
//if `[0,1]`, any left or right button can be used, which is: ALT+LeftClick or ALT+RightClick.
//=== config end ===
//=== config end ===
var
var
watchedVideos, ageMultiplier = 24 * 60 * 60 * 1000, xu = /\/watch(?:\?|.*?&)v=([^&]+)|\/shorts\/([^\/\?]+)/,
watchedVideos, ageMultiplier = 24 * 60 * 60 * 1000, xu = /\/watch(?:\?|.*?&)v=([^&]+)|\/shorts\/([^\/\?]+)/,
querySelector = Element.prototype.querySelector, querySelectorAll = Element.prototype.querySelectorAll;
querySelector = Element.prototype.querySelector, querySelectorAll = Element.prototype.querySelectorAll;
function getVideoId(url) {
function getVideoId(url) {
var vid = url.match(xu);
var vid = url.match(xu);
if (vid) vid = vid[1] || vid[2];
if (vid) vid = vid[1] || vid[2];
return vid;
return vid;
}
}
function watched(vid) {
function watched(vid) {
return !!watchedVideos.entries[vid];
return !!watchedVideos.entries[vid];
}
}
function processVideoItems(selector) {
function processVideoItems(selector) {
var items = document.querySelectorAll(selector), i, link;
var items = document.querySelectorAll(selector), i, link;
for (i = items.length-1; i >= 0; i--) {
for (i = items.length-1; i >= 0; i--) {
if (link = querySelector.call(items[i], "A")) {
if (link = querySelector.call(items[i], "A")) {
if (watched(getVideoId(link.href))) {
if (watched(getVideoId(link.href))) {
items[i].classList.add("watched");
items[i].classList.add("watched");
} else items[i].classList.remove("watched");
} else items[i].classList.remove("watched");
}
}
}
}
}
}
function processAllVideoItems() {
function processAllVideoItems() {
//home page
//home page
processVideoItems(`.yt-uix-shelfslider-list>.yt-shelf-grid-item`);
processVideoItems(`.yt-uix-shelfslider-list>.yt-shelf-grid-item`);
processVideoItems(`
processVideoItems(`
#contents.ytd-rich-grid-renderer>ytd-rich-item-renderer,
#contents.ytd-rich-grid-renderer>ytd-rich-item-renderer,
#contents.ytd-rich-shelf-renderer ytd-rich-item-renderer.ytd-rich-shelf-renderer,
#contents.ytd-rich-shelf-renderer ytd-rich-item-renderer.ytd-rich-shelf-renderer,
#contents.ytd-rich-grid-renderer>ytd-rich-grid-row ytd-rich-grid-media`);
#contents.ytd-rich-grid-renderer>ytd-rich-grid-row ytd-rich-grid-media`);
//subscriptions page
//subscriptions page
processVideoItems(`.multirow-shelf>.shelf-content>.yt-shelf-grid-item`);
processVideoItems(`.multirow-shelf>.shelf-content>.yt-shelf-grid-item`);
//history:watch page
//history:watch page
processVideoItems(`ytd-section-list-renderer[page-subtype="history"] .ytd-item-section-renderer>ytd-video-renderer`);
processVideoItems(`ytd-section-list-renderer[page-subtype="history"] .ytd-item-section-renderer>ytd-video-renderer`);
//channel/user home page
//channel/user home page
processVideoItems(`
processVideoItems(`
#contents>.ytd-item-section-renderer>.ytd-newspaper-renderer,
#contents>.ytd-item-section-renderer>.ytd-newspaper-renderer,
#items>.yt-horizontal-list-renderer`); //old
#items>.yt-horizontal-list-renderer`); //old
processVideoItems(`
processVideoItems(`
#contents>.ytd-channel-featured-content-renderer,
#contents>.ytd-channel-featured-content-renderer,
#contents>.ytd-shelf-renderer>#grid-container>.ytd-expanded-shelf-contents-renderer`); //new
#contents>.ytd-shelf-renderer>#grid-container>.ytd-expanded-shelf-contents-renderer`); //new
//channel/user video page
//channel/user video page
processVideoItems(`
processVideoItems(`
.yt-uix-slider-list>.featured-content-item,
.yt-uix-slider-list>.featured-content-item,
.channels-browse-content-grid>.channels-content-item,
.channels-browse-content-grid>.channels-content-item,
#items>.ytd-grid-renderer`);
#items>.ytd-grid-renderer`);
//channel/user playlist page
//channel/user playlist page
processVideoItems(`
processVideoItems(`
.expanded-shelf>.expanded-shelf-content-list>.expanded-shelf-content-item-wrapper,
.expanded-shelf>.expanded-shelf-content-list>.expanded-shelf-content-item-wrapper,
.ytd-playlist-video-renderer`);
.ytd-playlist-video-renderer`);
//channel/user playlist item page
//channel/user playlist item page
processVideoItems(`
processVideoItems(`
.pl-video-list .pl-video-table .pl-video,
.pl-video-list .pl-video-table .pl-video,
ytd-playlist-panel-video-renderer`);
ytd-playlist-panel-video-renderer`);
//channel/user search page
//channel/user search page
if (/^\/(?:c|channel|user)\/.*?\/search/.test(location.pathname)) {
if (/^\/(?:c|channel|user)\/.*?\/search/.test(location.pathname)) {
processVideoItems(`.ytd-browse #contents>.ytd-item-section-renderer`); //new
processVideoItems(`.ytd-browse #contents>.ytd-item-section-renderer`); //new
}
}
//search page
//search page
processVideoItems(`
processVideoItems(`
#results>.section-list .item-section>li,
#results>.section-list .item-section>li,
#browse-items-primary>.browse-list-item-container`); //old
#browse-items-primary>.browse-list-item-container`); //old
processVideoItems(`
processVideoItems(`
.ytd-search #contents>ytd-video-renderer,
.ytd-search #contents>ytd-video-renderer,
.ytd-search #contents>ytd-playlist-renderer,
.ytd-search #contents>ytd-playlist-renderer,
.ytd-search #items>ytd-video-renderer`); //new
.ytd-search #items>ytd-video-renderer`); //new
//video page
//video page
processVideoItems(`
processVideoItems(`
.watch-sidebar-body>.video-list>.video-list-item,
.watch-sidebar-body>.video-list>.video-list-item,
.playlist-videos-container>.playlist-videos-list>li`); //old
.playlist-videos-container>.playlist-videos-list>li`); //old
processVideoItems(`
processVideoItems(`
.ytd-compact-video-renderer,
.ytd-compact-video-renderer,
.ytd-compact-radio-renderer`); //new
.ytd-compact-radio-renderer`); //new
}
}
function addHistory(vid, time, noSave, i) {
function addHistory(vid, time, noSave, i) {
if (!watchedVideos.entries[vid]) {
if (!watchedVideos.entries[vid]) {
watchedVideos.index.push(vid);
watchedVideos.index.push(vid);
} else {
} else {
i = watchedVideos.index.indexOf(vid);
i = watchedVideos.index.indexOf(vid);
if (i >= 0) watchedVideos.index.push(watchedVideos.index.splice(i, 1)[0])
if (i >= 0) watchedVideos.index.push(watchedVideos.index.splice(i, 1)[0])
}
}
watchedVideos.entries[vid] = time;
watchedVideos.entries[vid] = time;
if (!noSave) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
if (!noSave) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
}
}
function delHistory(index, noSave) {
function delHistory(index, noSave) {
delete watchedVideos.entries[watchedVideos.index[index]];
delete watchedVideos.entries[watchedVideos.index[index]];
watchedVideos.index.splice(index, 1);
watchedVideos.index.splice(index, 1);
if (!noSave) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
if (!noSave) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
}
}
var dc, ut;
var dc, ut;
function parseData(s, a, i, j, z) {
function parseData(s, a, i, j, z) {
try {
try {
dc = false;
dc = false;
s = JSON.parse(s);
s = JSON.parse(s);
//convert to new format if old format.
//convert to new format if old format.
//old: [{id:<strVID>, timestamp:<numDate>}, ...]
//old: [{id:<strVID>, timestamp:<numDate>}, ...]
//new: {entries:{<stdVID>:<numDate>, ...}, index:[<strVID>, ...]}
//new: {entries:{<stdVID>:<numDate>, ...}, index:[<strVID>, ...]}
if (Array.isArray(s) && (!s.length || (("object" === typeof s[0]) && s[0].id && s[0].timestamp))) {
if (Array.isArray(s) && (!s.length || (("object" === typeof s[0]) && s[0].id && s[0].timestamp))) {
a = s;
a = s;
s = {entries: {}, index: []};
s = {entries: {}, index: []};
a.forEach(o => {
a.forEach(o => {
s.entries[o.id] = o.timestamp;
s.entries[o.id] = o.timestamp;
s.index.push(o.id);
s.index.push(o.id);
});
});
} else if (("object" !== typeof s) || ("object" !== typeof s.entries) || !Array.isArray(s.index)) return null;
} else if (("object" !== typeof s) || ("object" !== typeof s.entries) || !Array.isArray(s.index)) return null;
//reconstruct index if broken
//reconstruct index if broken
if (s.index.length !== (a = Object.keys(s.entries)).length) {
if (s.index.length !== (a = Object.keys(s.entries)).length) {
s.index = a.map(k => [k, s.entries[k]]).sort((x, y) => x[1] - y[1]).map(v => v[0]);
s.index = a.map(k => [k, s.entries[k]]).sort((x, y) => x[1] - y[1]).map(v => v[0]);
dc = true;
dc = true;
}
}
return s;
return s;
} catch(z) {
} catch(z) {
return null;
return null;
}
}
}
}
function parseYouTubeData(s, a) {
function parseYouTubeData(s, a) {
try {
try {
s = JSON.parse(s);
s = JSON.parse(s);
//convert to native format if YouTube format.
//convert to native format if YouTube format.
//old: [{titleUrl:<strUrl>, time:<strIsoDate>}, ...] (excludes irrelevant properties)
//old: [{titleUrl:<strUrl>, time:<strIsoDate>}, ...] (excludes irrelevant properties)
//new: {entries:{<stdVID>:<numDate>, ...}, index:[<strVID>, ...]}
//new: {entries:{<stdVID>:<numDate>, ...}, index:[<strVID>, ...]}
if (Array.isArray(s) && (!s.length || (("object" === typeof s[0]) && s[0].titleUrl && s[0].time))) {
if (Array.isArray(s) && (!s.length || (("object" === typeof s[0]) && s[0].titleUrl && s[0].time))) {
a = s;
a = s;
s = {entries: {}, index: []};
s = {entries: {}, index: []};
a.forEach((o, m, t) => {
a.forEach((o, m, t) => {
if (o.titleUrl && (m = o.titleUrl.match(xu))) {
if (o.titleUrl && (m = o.titleUrl.match(xu))) {
if (isNaN(t = (new Date(o.time)).getTime())) t = (new Date()).getTime();
if (isNaN(t = (new Date(o.time)).getTime())) t = (new Date()).getTime();
s.entries[m[1] || m[2]] = t;
s.entries[m[1] || m[2]] = t;
s.index.push(m[1] || m[2]);
s.index.push(m[1] || m[2]);
}
}
});
});
s.index.reverse();
s.index.reverse();
return s;
return s;
} else return null;
} else return null;
} catch(a) {
} catch(a) {
return null;
return null;
}
}
}
}
function mergeData(o, a) {
function mergeData(o, a) {
o.index.forEach(i => {
o.index.forEach(i => {
if (watchedVideos.entries[i]) {
if (watchedVideos.entries[i]) {
if (watchedVideos.entries[i] < o.entries[i]) watchedVideos.entries[i] = o.entries[i];
if (watchedVideos.entries[i] < o.entries[i]) watchedVideos.entries[i] = o.entries[i];
} else watchedVideos.entries[i] = o.entries[i];
} else watchedVideos.entries[i] = o.entries[i];
});
});
a = Object.keys(watchedVideos.entries);
a = Object.keys(watchedVideos.entries);
watchedVideos.index = a.map(k => [k, watchedVideos.entries[k]]).sort((x, y) => x[1] - y[1]).map(v => v[0]);
watchedVideos.index = a.map(k => [k, watchedVideos.entries[k]]).sort((x, y) => x[1] - y[1]).map(v => v[0]);
}
}
function getHistory(a, b) {
function getHistory(a, b) {
a = GM_getValue("watchedVideos");
a = GM_getValue("watchedVideos");
if (a === undefined) {
if (a === undefined) {
a = '{"entries": {}, "index": []}';
a = '{"entries": {}, "index": []}';
} else if ("object" === typeof a) a = JSON.stringify(a);
} else if ("object" === typeof a) a = JSON.stringify(a);
if (b = parseData(a)) {
if (b = parseData(a)) {
watchedVideos = b;
watchedVideos = b;
if (dc) b = JSON.stringify(b);
if (dc) b = JSON.stringify(b);
} else b = JSON.stringify(watchedVideos = {entries: {}, index: []});
} else b = JSON.stringify(watchedVideos = {entries: {}, index: []});
GM_setValue("watchedVideos", b);
GM_setValue("watchedVideos", b);
}
}
function doProcessPage() {
function doProcessPage() {
//get list of watched videos
//get list of watched videos
getHistory();
getHistory();
//remove old watched video history
//remove old watched video history
var now = (new Date()).valueOf(), changed, vid;
var now = (new Date()).valueOf(), changed, vid;
if (maxWatchedVideoAge > 0) {
if (maxWatchedVideoAge > 0) {
while (watchedVideos.index.length) {
while (watchedVideos.index.length) {
if (((now - watchedVideos.entries[watchedVideos.index[0]]) / ageMultiplier) > maxWatchedVideoAge) {
if (((now - watchedVideos.entries[watchedVideos.index[0]]) / ageMultiplier) > maxWatchedVideoAge) {
delHistory(0, false);
delHistory(0, false);
changed = true;
changed = true;
} else break;
} else break;
}
}
if (changed) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
if (changed) GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
}
}
//check and remember current video
//check and remember current video
if ((vid = getVideoId(location.href)) && !watched(vid)) addHistory(vid, now);
if ((vid = getVideoId(location.href)) && !watched(vid)) addHistory(vid, now);
//mark watched videos
//mark watched videos
processAllVideoItems();
processAllVideoItems();
}
}
function processPage() {
function processPage() {
setTimeout(doProcessPage, Math.floor(contentLoadMarkDelay / 2));
setTimeout(doProcessPage, Math.floor(contentLoadMarkDelay / 2));
}
}
function delayedProcessPage() {
function delayedProcessPage() {
setTimeout(doProcessPage, contentLoadMarkDelay);
setTimeout(doProcessPage, contentLoadMarkDelay);
}
}
function toggleMarker(ele, i) {
function toggleMarker(ele, i) {
if (ele) {
if (ele) {
if (ele.href) {
if (ele.href) {
i = getVideoId(ele.href);
i = getVideoId(ele.href);
} else {
} else {
while (ele) {
while (ele) {
while (ele && (!ele.__data || !ele.__data.data || !ele.__data.data.videoId)) ele = ele.__dataHost || ele.parentNode;
while (ele && (!ele.__data || !ele.__data.data || !ele.__data.data.videoId)) ele = ele.__dataHost || ele.parentNode;
if (ele) {
if (ele) {
i = ele.__data.data.videoId;
i = ele.__data.data.videoId;
break
break
}
}
}
}
}
}
if (i) {
if (i) {
if ((ele = watchedVideos.index.indexOf(i)) >= 0) {
if ((ele = watchedVideos.index.indexOf(i)) >= 0) {
delHistory(ele);
delHistory(ele);
} else addHistory(i, (new Date()).valueOf());
} else addHistory(i, (new Date()).valueOf());
processAllVideoItems();
processAllVideoItems();
}
}
}
}
}
}
var rxListUrl = /\/\w+_ajax\?|\/results\?search_query|\/v1\/(browse|next|search)\?/;
var rxListUrl = /\/\w+_ajax\?|\/results\?search_query|\/v1\/(browse|next|search)\?/;
var xhropen = XMLHttpRequest.prototype.open, xhrsend = XMLHttpRequest.prototype.send;
var xhropen = XMLHttpRequest.prototype.open, xhrsend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.open = function(method, url) {
XMLHttpRequest.prototype.open = function(method, url) {
this.url_mwyv = url;
this.url_mwyv = url;
return xhropen.apply(this, arguments);
return xhropen.apply(this, arguments);
};
};
XMLHttpRequest.prototype.send = function(method, url) {
XMLHttpRequest.prototype.send = function(method, url) {
if (rxListUrl.test(this.url_mwyv) && !this.listened_mwyv) {
if (rxListUrl.test(this.url_mwyv) && !this.listened_mwyv) {
this.listened_mwyv = 1;
this.listened_mwyv = 1;
this.addEventListener("load", delayedProcessPage);
this.addEventListener("load", delayedProcessPage);
}
}
return xhrsend.apply(this, arguments);
return xhrsend.apply(this, arguments);
};
};
var fetch_ = unsafeWindow.fetch;
var fetch_ = unsafeWindow.fetch;
unsafeWindow.fetch = function(opt) {
unsafeWindow.fetch = function(opt) {
let url = opt.url || opt;
let url = opt.url || opt;
if (rxListUrl.test(opt.url || opt)) {
if (rxListUrl.test(opt.url || opt)) {
return fetch_.apply(this, arguments).finally(delayedProcessPage);
return fetch_.apply(this, arguments).finally(delayedProcessPage);
} else return fetch_.apply(this, arguments);
} else return fetch_.apply(this, arguments);
};
};
addEventListener("DOMContentLoaded", sty => {
addEventListener("DOMContentLoaded", sty => {
sty = document.createElement("STYLE");
sty = document.createElement("STYLE");
sty.innerHTML = `
sty.innerHTML = `
.watched:not(ytd-thumbnail):not(.details):not(.metadata), .watched .yt-ui-ellipsis
.watched:not(ytd-thumbnail):not(.details):not(.metadata), .watched .yt-ui-ellipsis {
{ outline: .2em solid #aca; background-color: #cec !important }
/* outline: .2em solid #aca; */
/* background-color: #cec !important */
opacity: 0.25;
-webkit-filter: grayscale(1);
filter: grayscale(1);
}
html[dark] .watched, html[dark] .watched .yt-ui-ellipsis,
html[dark] .watched, html[dark] .watched .yt-ui-ellipsis,
.playlist-videos-container>.playlist-videos-list>li.watched,
.playlist-videos-container>.playlist-videos-list>li.watched,
.playlist-videos-container>.playlist-videos-list>li.watched>a,
.playlist-videos-container>.playlist-videos-list>li.watched>a,
.playlist-videos-container>.playlist-videos-list>li.watched .yt-ui-ellipsis
.playlist-videos-container>.playlist-videos-list>li.watched .yt-ui-ellipsis {
{ outline: .2em solid #030; background-color: #030 !important }`;
/* outline: .2em solid #030; */
/* background-color: #030 !important */
opacity: 0.25;
-webkit-filter: grayscale(1);
filter: grayscale(1);
}`;
document.head.appendChild(sty);
document.head.appendChild(sty);
var nde = Node.prototype.dispatchEvent;
var nde = Node.prototype.dispatchEvent;
Node.prototype.dispatchEvent = function(ev) {
Node.prototype.dispatchEvent = function(ev) {
if (ev.type === "yt-service-request-completed") {
if (ev.type === "yt-service-request-completed") {
clearTimeout(ut);
clearTimeout(ut);
ut = setTimeout(doProcessPage, contentLoadMarkDelay / 2)
ut = setTimeout(doProcessPage, contentLoadMarkDelay / 2)
}
}
return nde.apply(this, arguments)
return nde.apply(this, arguments)
};
};
});
});
var lastFocusState = document.hasFocus();
var lastFocusState = document.hasFocus();
addEventListener("blur", () => {
addEventListener("blur", () => {
lastFocusState = false;
lastFocusState = false;
});
});
addEventListener("focus", () => {
addEventListener("focus", () => {
if (!lastFocusState) processPage();
if (!lastFocusState) processPage();
lastFocusState = true;
lastFocusState = true;
});
});
addEventListener("click", (ev) => {
addEventListener("click", (ev) => {
if ((markerMouseButtons.indexOf(ev.button) >= 0) && ev.altKey) {
if ((markerMouseButtons.indexOf(ev.button) >= 0) && ev.altKey) {
ev.stopImmediatePropagation();
ev.stopImmediatePropagation();
ev.stopPropagation();
ev.stopPropagation();
ev.preventDefault();
ev.preventDefault();
toggleMarker(ev.target);
toggleMarker(ev.target);
}
}
}, true);
}, true);
if (markerMouseButtons.indexOf(1) >= 0) {
if (markerMouseButtons.indexOf(1) >= 0) {
addEventListener("contextmenu", (ev) => {
addEventListener("contextmenu", (ev) => {
if (ev.altKey) toggleMarker(ev.target);
if (ev.altKey) toggleMarker(ev.target);
});
});
}
}
if (window["body-container"]) { //old
if (window["body-container"]) { //old
addEventListener("spfdone", processPage);
addEventListener("spfdone", processPage);
processPage();
processPage();
} else { //new
} else { //new
var t = 0;
var t = 0;
function pl() {
function pl() {
clearTimeout(t);
clearTimeout(t);
t = setTimeout(processPage, 300);
t = setTimeout(processPage, 300);
}
}
(function init(vm) {
(function init(vm) {
if (vm = document.getElementById("visibility-monitor")) {
if (vm = document.getElementById("visibility-monitor")) {
vm.addEventListener("viewport-load", pl);
vm.addEventListener("viewport-load", pl);
} else setTimeout(init, 100);
} else setTimeout(init, 100);
})();
})();
(function init2(mh) {
(function init2(mh) {
if (mh = document.getElementById("masthead")) {
if (mh = document.getElementById("masthead")) {
mh.addEventListener("yt-rendererstamper-finished", pl);
mh.addEventListener("yt-rendererstamper-finished", pl);
} else setTimeout(init2, 100);
} else setTimeout(init2, 100);
})();
})();
addEventListener("load", delayedProcessPage);
addEventListener("load", delayedProcessPage);
addEventListener("spfprocess", delayedProcessPage);
addEventListener("spfprocess", delayedProcessPage);
}
}
GM_registerMenuCommand("Display History Statistics", () => {
GM_registerMenuCommand("Display History Statistics", () => {
function sum(r, v) {
function sum(r, v) {
return r + v;
return r + v;
}
}
function avg(arr) {
function avg(arr) {
return arr && arr.length ? Math.round(arr.reduce(sum, 0) / arr.length) : "(n/a)";
return arr && arr.length ? Math.round(arr.reduce(sum, 0) / arr.length) : "(n/a)";
}
}
var pd, pm, py, ld = [], lm = [], ly = [];
var pd, pm, py, ld = [], lm = [], ly = [];
getHistory();
getHistory();
Object.keys(watchedVideos.entries).forEach((k, t) => {
Object.keys(watchedVideos.entries).forEach((k, t) => {
t = new Date(watchedVideos.entries[k]);
t = new Date(watchedVideos.entries[k]);
if (!pd || (pd !== t.getDate())) {
if (!pd || (pd !== t.getDate())) {
ld.push(1);
ld.push(1);
pd = t.getDate();
pd = t.getDate();
} else ld[ld.length - 1]++;
} else ld[ld.length - 1]++;
if (!pm || (pm !== (t.getMonth() + 1))) {
if (!pm || (pm !== (t.getMonth() + 1))) {
lm.push(1);
lm.push(1);
pm = t.getMonth() + 1;
pm = t.getMonth() + 1;
} else lm[lm.length - 1]++;
} else lm[lm.length - 1]++;
if (!py || (py !== t.getFullYear())) {
if (!py || (py !== t.getFullYear())) {
ly.push(1);
ly.push(1);
py = t.getFullYear();
py = t.getFullYear();
} else ly[ly.length - 1]++;
} else ly[ly.length - 1]++;
});
});
if (watchedVideos.index.length) {
if (watchedVideos.index.length) {
pd = (new Date(watchedVideos.entries[watchedVideos.index[0]])).toLocaleString();
pd = (new Date(watchedVideos.entries[watchedVideos.index[0]])).toLocaleString();
pm = (new Date(watchedVideos.entries[watchedVideos.index[watchedVideos.index.length - 1]])).toLocaleString();
pm = (new Date(watchedVideos.entries[watchedVideos.index[watchedVideos.index.length - 1]])).toLocaleString();
} else {
} else {
pd = "(n/a)";
pd = "(n/a)";
pm = "(n/a)";
pm = "(n/a)";
}
}
alert(`\
alert(`\
Number of entries: ${watchedVideos.index.length}
Number of entries: ${watchedVideos.index.length}
Oldest entry: ${pd}
Oldest entry: ${pd}
Newest entry: ${pm}
Newest entry: ${pm}
Average viewed videos per day: ${avg(ld)}
Average viewed videos per day: ${avg(ld)}
Average viewed videos per month: ${avg(lm)}
Average viewed videos per month: ${avg(lm)}
Average viewed videos per year: ${avg(ly)}\
Average viewed videos per year: ${avg(ly)}\
`);
`);
});
});
GM_registerMenuCommand("Backup History Data", (a, b) => {
GM_registerMenuCommand("Backup History Data", (a, b) => {
document.body.appendChild(a = document.createElement("A")).href = URL.createObjectURL(new Blob([JSON.stringify(watchedVideos)], {type: "application/json"}));
document.body.appendChild(a = document.createElement("A")).href = URL.createObjectURL(new Blob([JSON.stringify(watchedVideos)], {type: "application/json"}));
a.download = `MarkWatchedYouTubeVideos_${(new Date()).toISOString()}.json`;
a.download = `MarkWatchedYouTubeVideos_${(new Date()).toISOString()}.json`;
a.click();
a.click();
a.remove();
a.remove();
URL.revokeObjectURL(a.href);
URL.revokeObjectURL(a.href);
});
});
GM_registerMenuCommand("Restore History Data", (a, b) => {
GM_registerMenuCommand("Restore History Data", (a, b) => {
function askRestore(o) {
function askRestore(o) {
if (confirm(`Selected history data file contains ${o.index.length} entries.\n\nRestore from this data?`)) {
if (confirm(`Selected history data file contains ${o.index.length} entries.\n\nRestore from this data?`)) {
if (mwyvrhm_ujs.checked) {
if (mwyvrhm_ujs.checked) {
mergeData(o);
mergeData(o);
} else watchedVideos = o;
} else watchedVideos = o;
GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
GM_setValue("watchedVideos", JSON.stringify(watchedVideos));
a.remove();
a.remove();
doProcessPage();
doProcessPage();
}
}
}
}
if (window.mwyvrh_ujs) return;
if (window.mwyvrh_ujs) return;
(a = document.createElement("DIV")).id = "mwyvrh_ujs";
(a = document.createElement("DIV")).id = "mwyvrh_ujs";
a.innerHTML = `<style>
a.innerHTML = `<style>
#mwyvrh_ujs{
#mwyvrh_ujs{
display:flex;position:fixed;z-index:99999;left:0;top:0;right:0;bottom:0;margin:0;border:none;padding:0;background:rgb(0,0,0,0.5);
display:flex;position:fixed;z-index:99999;left:0;top:0;right:0;bottom:0;margin:0;border:none;padding:0;background:rgb(0,0,0,0.5);
color:#000;font-family:sans-serif;font-size:12pt;line-height:12pt;font-weight:normal;cursor:pointer;
color:#000;font-family:sans-serif;font-size:12pt;line-height:12pt;font-weight:normal;cursor:pointer;
}
}
#mwyvrhb_ujs{
#mwyvrhb_ujs{
margin:auto;border:.3rem solid #007;border-radius:.3rem;padding:.5rem .5em;background-color:#fff;cursor:auto;
margin:auto;border:.3rem solid #007;border-radius:.3rem;padding:.5rem .5em;background-color:#fff;cursor:auto;
}
}
#mwyvrht_ujs{margin-bottom:1rem;font-size:14pt;line-height:14pt;font-weight:bold}
#mwyvrht_ujs{margin-bottom:1rem;font-size:14pt;line-height:14pt;font-weight:bold}
#mwyvrhmc_ujs{margin:.5em 0 1em 0;text-align:center}
#mwyvrhmc_ujs{margin:.5em 0 1em 0;text-align:center}
#mwyvrhi_ujs{display:block;margin:1rem auto .5rem auto;overflow:hidden}
#mwyvrhi_ujs{display:block;margin:1rem auto .5rem auto;overflow:hidden}
</style>
</style>
<div id="mwyvrhb_ujs">
<div id="mwyvrhb_ujs">
<div id="mwyvrht_ujs">Mark Watched YouTube Videos</div>
<div id="mwyvrht_ujs">Mark Watched YouTube Videos</div>
Please select a file to restore history data from.
Please select a file to restore history data from.
<div id="mwyvrhmc_ujs"><label><input id="mwyvrhm_ujs" type="checkbox" checked /> Merge history data instead of replace.</label></div>
<div id="mwyvrhmc_ujs"><label><input id="mwyvrhm_ujs" type="checkbox" checked /> Merge history data instead of replace.</label></div>
<input id="mwyvrhi_ujs" type="file" multiple />
<input id="mwyvrhi_ujs" type="file" multiple />
</div>`;
</div>`;
a.onclick = e => {
a.onclick = e => {
(e.target === a) && a.remove();
(e.target === a) && a.remove();
};
};
(b = querySelector.call(a, "#mwyvrhi_ujs")).onchange = r => {
(b = querySelector.call(a, "#mwyvrhi_ujs")).onchange = r => {
r = new FileReader();
r = new FileReader();
r.onload = (o, t) => {
r.onload = (o, t) => {
if (o = parseData(r = r.result)) { //parse as native format
if (o = parseData(r = r.result)) { //parse as native format
if (o.index.length) {
if (o.index.length) {
askRestore(o);
askRestore(o);
} else alert("File doesn't contain any history entry.");
} else alert("File doesn't contain any history entry.");
} else if (o = parseYouTubeData(r)) { //parse as YouTube format
} else if (o = parseYouTubeData(r)) { //parse as YouTube format
if (o.index.length) {
if (o.index.length) {
askRestore(o);
askRestore(o);
} else alert("File doesn't contain any history entry.");
} else alert("File doesn't contain any history entry.");
} else { //parse as URL list
} else { //parse as URL list
o = {entries: {}, index: []};
o = {entries: {}, index: []};
t = (new Date()).getTime();
t = (new Date()).getTime();
r = r.replace(/\r/g, "").split("\n");
r = r.replace(/\r/g, "").split("\n");
while (r.length && !r[0].trim()) r.shift();
while (r.length && !r[0].trim()) r.shift();
if (r.length && xu.test(r[0])) {
if (r.length && xu.test(r[0])) {
r.forEach(s => {
r.forEach(s => {
if (s = s.match(xu)) {
if (s = s.match(xu)) {
o.entries[s[1] || s[2]] = t;
o.entries[s[1] || s[2]] = t;
o.index.push(s[1] || s[2]);
o.index.push(s[1] || s[2]);
}
}
});
});
if (o.index.length) {
if (o.index.length) {
askRestore(o);
askRestore(o);
} else alert("File doesn't contain any history entry.");
} else alert("File doesn't contain any history entry.");
} else alert("Invalid history data file.");
} else alert("Invalid history data file.");
}
}
};
};
r.readAsText(b.files[0]);
r.readAsText(b.files[0]);
};
};
document.documentElement.appendChild(a);
document.documentElement.appendChild(a);
b.click();
b.click();
});
});
})();
})();