Misc utils library

Library for misc util functions for userscripts

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/581026/1842191/Misc%20utils%20library.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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