Misc utils library

Library for misc util functions for userscripts

Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greasyfork.org/scripts/581026/1842191/Misc%20utils%20library.js

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         Misc utils library
// @namespace    MiscUtilsLib
// @description  Library for misc util functions for userscripts
// @author       kekekeKaj
// @version      1.1.1
// @grant        none
// ==/UserScript==  

// IMPORTANT: update version number when publishing!!!
function getMiscUtilsLibInfo() {
    return 'Misc Utils Lib v1.1';
}

// ----LOCAL STORAGE CACHE-------

function getLocalStorageKeys() {
    return {
        stackMsgTemplates: generateLocalStorageKey('stackMsgTemplates'),
        reviewMsgTemplates: generateLocalStorageKey('reviewMsgTemplates'),
        recMsgTemplates: generateLocalStorageKey('recMsgTemplates'),

        lastClearTimestamp: generateLocalStorageKey('lastClearTimestamp'),
        reportedItemsInfo: generateLocalStorageKey('reportedItemsInfo'),
        modSettings: generateLocalStorageKey('modSettings'),

        reportInfoPrefix: generateLocalStorageKey('reportInfo.'),
        stackInfoPrefix: generateLocalStorageKey('stackInfo.'),
        reviewInfoPrefix: generateLocalStorageKey('reviewInfo.')
    }
}

function loadCachedReportedItemInfo(reportType, itemId) {
    if (itemId == null) {
        return null;
    }

    switch(reportType) {
        case 'stack':
            return loadFromJSONInLocalStorage(getLocalStorageKeys().stackInfoPrefix + itemId);
        case 'review':
            return loadFromJSONInLocalStorage(getLocalStorageKeys().reviewInfoPrefix + itemId);

        default:
            throw 'Cannot load cached reported item: invalid report type';
    }
}

function buildReportedItemStorageKey(reportType, itemId) {
    if (itemId == null) {
        return null;
    }

    switch(reportType) {
        case 'stack':
            return getLocalStorageKeys().stackInfoPrefix + itemId;
        case 'review':
            return getLocalStorageKeys().reviewInfoPrefix + itemId;

        default:
            throw 'Cannot build reported item storage key: invalid report type';
    }
}

function getSessionStorageKeys() {
    return {
        stackEditResult: generateLocalStorageKey('stackEditResult'),
        targetItemId: generateLocalStorageKey('targetItemId')
    }
}

function getReviewAdminStorageKeyBase() {
    return 'reviewAdmin';
}

function generateLocalStorageKey(keyword) {
    return `${getReviewAdminStorageKeyBase()}.${keyword}`;
}

function saveAsJSONInLocalStorage(key, item, logItem = false) {
    if (key == null || item == null) {
        console.warn('Cannot save item: key or item is null!');
        return;
    }
    setLocalStorageItem(key, JSON.stringify(item), logItem);
}

function loadFromJSONInLocalStorage(key, logItem = false) {
    if (key == null) {
        return null;
    }

    const objJSON = localStorage.getItem(key);
    const parsedObj = objJSON == null ? null : JSON.parse(objJSON);

    if (logItem) {
        console.info(`Loaded from key ${key}: `, parsedObj);
    }

    return parsedObj;
}

function loadModSettings(defaultModName) {
    const modSettings = getDefaultModSettings();
    if (defaultModName != null) {
        modSettings.modName = moderator;
    }
    
    overrideWithModSettings(
        modSettings, loadFromJSONInLocalStorage(getLocalStorageKeys().modSettings, true));

    return modSettings;
}

function setLocalStorageItem(key, item, logItem = false) {
    try {
        localStorage.setItem(key, item);

        if (logItem) {
            console.info(`Setting local storage value for ${key}: ${item}`);
        }
    } catch(err) {
        if (err instanceof DOMException) {
            console.warn('Error when trying to store item: ', err); 
            console.warn('LocalStorage may be full. Will attempt to clear storage');
            if (shouldClearReportCache()) {
                console.log("Auto clearing old report data.");
                clearMALModCache();
            }
        } else {
            console.error('Unknown error when storing item: ', err)
        }
    }
}

function clearMALModCache() {
    Object.keys(localStorage).forEach(function (e) {
        if (e.startsWith(getReviewAdminStorageKeyBase()) || e.includes("submissions&type")) {
            localStorage.removeItem(e);
        }
    });

    const lastClearTimestampKey = getLocalStorageKeys().lastClearTimestamp;
    localStorage.setItem(lastClearTimestampKey, String(Date.now()));
}

/**
 * Function to check whether to clear the queue cache.
 * @returns true if we don't have a last clear timestamp or if the timestamp is from more than a day ago.
 */
function shouldClearReportCache() {
    const lastClearTimestampKey = getLocalStorageKeys().lastClearTimestamp;
    const epochTimeStr = localStorage.getItem(lastClearTimestampKey);

    if (epochTimeStr == null) {
        return true;
    } 

    const lastClearTime = new Date(parseInt(epochTimeStr));
    const oneDayInMs = 60000 * 60 * 24;
    return lastClearTime == null || Date.now() - lastClearTime > oneDayInMs;
}

// --- FETCH ---
function fetchPostReq(url, data, successCallback, errorCallback) {
    fetch(
        url,
        {
            method: "POST",
            body:  data
        })
    .then(response => { return response.text() })
    .then(successCallback)
    .catch(errorCallback);
}

function fetchGetReq(url, successCallback, errorCallback) {
    fetch(url)
        .then(unwrapResponseTxt)
        .then(successCallback)
        .catch(errorCallback);
}

function unwrapResponseTxt(response) {
    if (response.ok) {
        return response.text();
    }

    throw new Error('Request failed for URL: ' + response.url);
}

// --- OTHER FUNCTIONS ---

/**
 * Loads msg templates and returns them in a map
 * @param {string} localStorageKey The local storage key from which to load the messages.
 * @returns {Map} of msg summary -> msgs
 */
function loadMsgTemplates(localStorageKey) {
    const rawTemplateStr = localStorage.getItem(localStorageKey);
    if (rawTemplateStr == null) {
        console.error('No templates found in local storage!');
        return null;
    }

    const splitTemplates = rawTemplateStr.split("*****").filter(segment => segment.trim().length > 0);

    // if the parsing was correct, we should end up with even number of segments representing pairs 
    // consisting of a stock message and its summary.
    if (splitTemplates.length % 2 !== 0) {
        console.error("Error parsing message templates!");
        return null;
    }

    const msgTemplateMap = new Map();
    for (let i = 0; i < splitTemplates.length; i += 2) {
        msgTemplateMap.set(splitTemplates[i].trim(), splitTemplates[i + 1].trim());
    }

    return msgTemplateMap;
}

function getDefaultModSettings() {
    return {
        modName: "",
        modTitle: "User Content Moderator",
        bulkActionDelayInMs: 200
    }
}

/**
 * Overrides an object's values with the ones from the mod's settings config.
 * 
 * @param {any} objToOverride 
 * @param {any} modSettings 
 */
function overrideWithModSettings(objToOverride, modSettings) {
    if (modSettings == null) {
        return;
    }

    if (hasNonWhitespaceValue(modSettings.modName)) {
        objToOverride.modName = modSettings.modName;
    }

    if (hasNonWhitespaceValue(modSettings.modTitle)) {
        objToOverride.modTitle = modSettings.modTitle;
    }

    if (modSettings.bulkActionDelayInMs != null) {
        objToOverride.bulkActionDelayInMs = modSettings.bulkActionDelayInMs;
    }
}

function getProfileUrl(username) {
    return `/profile/${username}`;
}