Misc utils library

Library for misc util functions for userscripts

Este script no debería instalarse directamente. Es una biblioteca que utilizan otros scripts mediante la meta-directiva de inclusión // @require https://update.greasyfork.org/scripts/581026/1842191/Misc%20utils%20library.js

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

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

Tendrás que instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Tendrás que instalar una extensión como Tampermonkey antes de poder instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Tendrás que instalar una extensión como Stylus antes de poder instalar este script.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

Para poder instalar esto tendrás que instalar primero una extensión de estilos de usuario.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==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}`;
}