// ==UserScript==
// @name Netflix Mark Watched
// @namespace watched_netflix
// @version 2.1
// @description Mark Netflix shows as watched.
// @include https://www.netflix.com/*
// @require https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_listValues
// @grant GM_deleteValue
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/477428/Netflix%20Mark%20Watched.user.js
// @updateURL https://update.greasyfork.icu/scripts/477428/Netflix%20Mark%20Watched.meta.js
// ==/UserScript==
// Function to migrate data from localStorage (old) to GM storage (new)
function migrateWatchedData() {
var migrated = false;
// Iterate through all localStorage keys
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.startsWith("watched_")) {
var value = localStorage.getItem(key);
// Store the value using GM_setValue
GM_setValue(key, value);
console.log("Migrated:", key, value);
migrated = true;
}
}
if (migrated) {
// Delete migrated entries from localStorage
for (var i = 0; i < localStorage.length; i++) {
var key = localStorage.key(i);
if (key.startsWith("watched_")) {
localStorage.removeItem(key);
console.log("Deleted from localStorage:", key);
}
}
alert("Migration of watched data to new GM storage completed.");
} else {
console.log("No watched shows data found in localStorage to migrate.");
}
}
// Function to add the watched button
function addWatchedButton(element) {
var id = JSON.parse(decodeURI(element.data('ui-tracking-context'))).video_id;
var watched = GM_getValue("watched_" + id, 'false');
if (watched === 'true') {
element.closest('.title-card-container').addClass('g_watched');
}
var watchedEye = $('
👁
');
watchedEye.click(function () {
var cardContainer = $(this).closest('.title-card-container');
var isWatched = cardContainer.hasClass('g_watched');
if (isWatched) {
cardContainer.removeClass('g_watched');
markAsUnwatched(id);
} else {
cardContainer.addClass('g_watched');
markAsWatched(id);
}
});
element.closest('.title-card-container').append(watchedEye);
}
// Function to mark a show as watched
function markAsWatched(videoId) {
GM_setValue("watched_" + videoId, "true");
}
// Function to mark a show as unwatched
function markAsUnwatched(videoId) {
GM_setValue("watched_" + videoId, "false");
}
// Backup data related to watched shows to a JSON file
function backupWatchedShows() {
var watchedData = {};
GM_listValues().forEach(function (key) {
if (key.startsWith("watched_")) {
watchedData[key] = GM_getValue(key);
}
});
var data = JSON.stringify(watchedData);
var blob = new Blob([data], { type: "application/json" });
var url = URL.createObjectURL(blob);
var a = document.createElement("a");
a.href = url;
a.download = "watchedShowsBackup.json";
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
// Restore data related to watched shows from a JSON file
function restoreWatchedShows() {
var input = document.createElement("input");
input.type = "file";
input.accept = ".json";
input.click();
input.addEventListener("change", function () {
var file = input.files[0];
var reader = new FileReader();
reader.onload = function () {
try {
var data = JSON.parse(reader.result);
var existingKeys = GM_listValues().filter(key => key.startsWith("watched_"));
var action = prompt("Choose an action: 'overwrite' or 'merge'. Type 'overwrite' to replace existing data or 'merge' to combine with existing data.", "overwrite");
if (action === "overwrite") {
for (var key in data) {
GM_setValue(key, data[key]);
}
alert("Watched data has been overwritten successfully.");
} else if (action === "merge") {
for (var key in data) {
if (!existingKeys.includes(key)) {
GM_setValue(key, data[key]);
}
}
alert("Watched data has been merged successfully.");
} else {
alert("Invalid action specified. No changes were made.");
}
} catch (e) {
alert("Error restoring data: " + e);
}
};
if (file) {
reader.readAsText(file);
}
});
}
$(document).ready(function () {
// Run migration on script start
migrateWatchedData();
// Initial execution for the elements present on the page load
$('[data-ui-tracking-context]').each(function () {
addWatchedButton($(this));
});
// Add a MutationObserver to detect when new elements are added
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if (mutation.addedNodes) {
$(mutation.addedNodes).find('[data-ui-tracking-context]').each(function () {
addWatchedButton($(this));
});
}
});
});
// Observe changes in the DOM, including dynamically loaded content
observer.observe(document.body, { childList: true, subtree: true });
GM_registerMenuCommand("Backup Watched", backupWatchedShows);
GM_registerMenuCommand("Restore Watched", restoreWatchedShows);
$('head').append(`
`);
});