PTP Ratings Exporter for IMDb

Automatically add and rate movies to your IMDb watchlist

// ==UserScript==
// @name         PTP Ratings Exporter for IMDb
// @namespace    PTP Ratings Exporter for IMDb
// @version      1.1.0
// @description  Automatically add and rate movies to your IMDb watchlist
// @author       Delgan
// @match        https://passthepopcorn.me/forums.php?*&threadid=37787&*
// @match        https://passthepopcorn.me/forums.php?*&threadid=37787
// @match        https://passthepopcorn.me/forums.php?threadid=37787&*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

function main() {
    let placeholder = `Fill in this text box with an JSON array looking like this:
    [
        {"title": "Sonatine", "year": 1993, "imdb": {"id": "tt0108188", "url": "https://www.imdb.com/title/tt0108188/", "my_rating": 9}},
        {"title": "Se7en", "year": 1995, "imdb": {"id": "tt0114369", "url": "https://www.imdb.com/title/tt0114369/", "my_rating": 6}}
    ]`

    let textarea = `<textarea id='ptp-ratings-exporter-textarea' style='width: 100%; height: 7rem; padding: 10px;' placeholder='${placeholder}'></textarea>`;
    let button = `<button id='ptp-ratings-exporter-button' style='display:block; margin: auto; margin-top: 10px;'>Rate my movies</button>`;
    let checkbox = `<label style='cursor:pointer;display:block;text-align:center;margin-top:5px;'><input id='ptp-ratings-exporter-checkbox-watchlist' type="checkbox" /> Add movies to watchlist</label>`;
    let progression = `<div id='ptp-ratings-exporter-progression' data-count="0" data-progress="0" style='text-align:center; margin-top: 5px;'></div>`;

    document.getElementById("content").insertAdjacentHTML("afterbegin", textarea + checkbox + button + progression);
    document.getElementById("ptp-ratings-exporter-button").addEventListener("click", rateMovies);
    renderProgression();
}

function getAuthToken(imdbId, onSuccess) {
    GM_xmlhttpRequest({
        url: `https://www.imdb.com/title/${imdbId}/`,
        method: "PUT",
        timeout: 30000,
        onload: function (response) {
            let auth;
            try {
                let html = document.createElement("html");
                html.innerHTML = response.responseText;
                auth = html.querySelector("#star-rating-widget").getAttribute("data-auth");
            } catch (err) {
                alert(`An error occurred while fetching IMDb auth token: '${err.message}'`);
                return;
            }
            onSuccess(auth);
        },
        onerror: function (_) {
            alert(`The HTTP request for auth token  failed`);
        },
        ontimeout: function (_) {
            alert(`The HTTP request for auth token timed out`);
        },
    });
}

function addMovieToWatchlist(imdbId) {
    GM_xmlhttpRequest({
        url: `https://www.imdb.com/watchlist/${imdbId}`,
        method: "PUT",
        timeout: 30000,
        onload: function (_) {
            console.log(`Successfully added movie '${imdbId}' to watchlist`);
        },
        onerror: function (_) {
            alert(`The HTTP request for movie '${imdbId}' failed`);
        },
        ontimeout: function (_) {
            alert(`The HTTP request for movie '${imdbId}' timed out`);
        },
    });
}

function rateMovie(imdbId, rating, auth) {
    let data = {
        "tconst": imdbId,
        "rating": Math.min(Math.max(rating, 1), 10).toFixed(),
        "auth": auth,
        "tracking_tag": "title-maindetails",
        "pageId": imdbId,
        "pageType": "title",
        "subpageType": "main",
    };

    let payload = "";
    for (let field in data) {
        payload += `${field}=${encodeURIComponent(data[field])}&`;
    }

    let headers = {
        'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64; rv:77.0) Gecko/20100101 Firefox/77.0',
        'Accept': 'application/json, text/javascript, */*; q=0.01',
        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
        'X-Requested-With': 'XMLHttpRequest',
    };

    GM_xmlhttpRequest({
        url: `https://www.imdb.com/ratings/_ajax/title`,
        method: "POST",
        headers: headers,
        timeout: 30000,
        data: payload,
        onload: function (_) {
            console.log(`Successfully rated movie '${imdbId}'`);
        },
        onerror: function (_) {
            alert(`The HTTP request for movie '${imdbId}' failed`);
        },
        ontimeout: function (_) {
            alert(`The HTTP request for movie '${imdbId}' timed out`);
        },
    });
}

function disableForm() {
    document.getElementById("ptp-ratings-exporter-textarea").setAttribute("disabled", "disabled");
    document.getElementById("ptp-ratings-exporter-button").setAttribute("disabled", "disabled");
}

function enableForm() {
    document.getElementById("ptp-ratings-exporter-textarea").removeAttribute("disabled");
    document.getElementById("ptp-ratings-exporter-button").removeAttribute("disabled");
}


function resetProgression(progress, count) {
    let progression = document.getElementById("ptp-ratings-exporter-progression");
    progression.setAttribute("data-progress", progress);
    progression.setAttribute("data-count", count);
    renderProgression();
}

function updateProgression() {
    let progression = document.getElementById("ptp-ratings-exporter-progression");
    let progress = parseInt(progression.getAttribute("data-progress"));
    progression.setAttribute("data-progress", progress + 1);
    renderProgression();

    if (progress + 1 === parseInt(progression.getAttribute("data-count"))) {
        enableForm();
    }
}

function renderProgression() {
    let progression = document.getElementById("ptp-ratings-exporter-progression");
    let progress = progression.getAttribute("data-progress")
    let count = progression.getAttribute("data-count");
    if (count === "0") {
        progression.innerHTML = "Progression: ---";
    } else {
        progression.innerHTML = `Progression: ${progress} / ${count}`;
    }
}

function rateMovies() {
    disableForm();

    resetProgression(0, 0);

    let text = document.getElementById("ptp-ratings-exporter-textarea").value;

    if (text === "") {
        alert("No data to parse");
        enableForm();
        return;
    }

    let movies;

    try {
        movies = JSON.parse(text);
    } catch (err) {
        alert(`Unable to parse the JSON array: "${err.message}"`);
        enableForm();
        return;
    }

    if (!Array.isArray(movies)) {
        alert("Invalid parsed JSON, it should be an array");
        enableForm();
        return;
    }

    if (movies.length === 0) {
        enableForm();
        return;
    }

    resetProgression(0, movies.length);

    let addMoviesToWatchList = document.getElementById("ptp-ratings-exporter-checkbox-watchlist");

    let delay = 1000;

    for (let movie of movies) {
        if (!movie.hasOwnProperty("imdb")) {
            alert("Missing 'imdb' property in one data entry");
            updateProgression();
            continue;
        }
        if (!movie.imdb.hasOwnProperty("id")) {
            alert("Missing 'id' property in one data entry");
            updateProgression();
            continue;
        }
        if (!movie.imdb.hasOwnProperty("my_rating")) {
            alert("Missing 'my_rating' property in one data entry");
            updateProgression();
            continue;
        }

        let imdbId = movie.imdb.id;
        let myRating = parseFloat(movie.imdb.my_rating);

        if (!imdbId.match(/tt[0-9]+/)) {
            alert(`The imdb id format is incorrect: '${imdbId}'`);
            updateProgression();
            continue;
        }
        if (Number.isNaN(myRating) || myRating < 0 || myRating > 10) {
            alert(`The rating value is incorrect: '${myRating}'`);
            updateProgression();
            continue;
        }

        setTimeout(function () {addMoviesToWatchList
            updateProgression();
            getAuthToken(imdbId, function (auth) {
                if (addMoviesToWatchList) {
                    addMovieToWatchlist(imdbId);
                }
                rateMovie(imdbId, myRating, auth);
            });
        }, delay);

        delay += 1000;
    }
}

main();