Netflix Mark Watched

Mark Netflix shows as watched.

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==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
// ==/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 = $('<div class="watched_eye">&#128065;</div>');

    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(`
    <style>
    .watched_eye {
      font-size: 57px;
      padding: 10px;
      position: absolute;
      bottom: -40px;
      left: 0;
      background: gray;
      border-radius: 6px;
      height: 30px;
      line-height: 30px;
    }
    .watched_eye:hover {
      opacity: 0.3;
      cursor: pointer;
    }
    .title-card-container.g_watched {
      opacity: 0.3;
    }
    </style>
  `);
});