MPP Userscript Core

A library to simplify userscript creation for Multiplayer Piano.

Versione datata 10/06/2026. Vedi la nuova versione l'ultima versione.

Questo script non dovrebbe essere installato direttamente. È una libreria per altri script da includere con la chiave // @require https://update.greatest.deepsurf.us/scripts/582107/1848141/MPP%20Userscript%20Core.js

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

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

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

/* command registry */
const cmds = {};
function registerCommand(name, func, {...options} = {}) {
    if (!name)
        throw new Error('Command name must be provided.');
    if (typeof name !== 'string')
        throw new TypeError('Command name must be a string.');
    
    if (func == null)
        throw new Error('Command function must be provided.');
    if (typeof func !== 'function');
        throw new TypeError('Command function must be a function.');

    cmds[name] = {
        ...options,
        func
    }
    return cmds[name];
}

/* utilities */
function send(msg) {
    MPP.chat.send(msg);
}
function receive(msg, user = MPP.client.getOwnParticipant(), date = Date.now()) {
    MPP.chat.receive({
        m: 'a',
        a: msg,
        p: user,
        t: date
    });
}
function storeItem(key, data) {
    if (!key)
        throw new Error('Item key must be provided.');
    if (typeof key !== 'string')
        throw new TypeError('Item key must be a string.');

    return localStorage[key] = JSON.stringify(data);
}
function readItem(key, fallback = null) {
    if (!key)
        throw new Error('Item key must be provided.');
    if (typeof key !== 'string')
        throw new TypeError('Item key must be a string.');

    return localStorage[key] != null ? JSON.parse(localStorage[key]) : fallback;
}
function findUsers(query) {
    const normalize = text => text.toLowerCase().replace(/[^a-z0-9 -_]+/, '');
    return Object.values(MPP.client.ppl).filter(
        v => normalize(v.name).includes(normalize(query)) ||
             normalize(v._id).includes(normalize(query))
    );
}
function findUser(query) {
    return findUsers(query)?.[0];
}

/* ranks */
const ranks = {
    user: 0
};
const userRanks = readItem('userRanks', {});
function clearUserRank(targetUserID) {
    for (const [name, rank] of ranks) {
        userRanks[rank] =
            userRanks[rank].filter(userID => userID !== targetUserID);
        if (userRanks[rank].length === 0)
            delete userRanks[rank];
    }
    storeItem('userRanks', userRanks);
}
function getUserRank(userID) {
    let foundRank = -Infinity;
    for (const [name, rank] of ranks) {
        if (
            userRanks.includes(userID)
            &&
            rank > foundRank
        ) foundRank = rank;
    }
    if (!Number.isFinite(foundRank)) foundRank = ranks.user;
    return foundRank;
}
function setUserRank(userID, rank) {
    // short-circuiting
    if (rank == null)
        throw new Error('Rank ID or name is required.');
    if (typeof rank !== 'number' && typeof rank !== 'string')
        throw new TypeError('Rank must be a string or a number.');

    // coercion
    if (typeof rank === 'string') ranks[rank];
    if (!Object.values(ranks).includes(rank))
        throw new ReferenceError(`Unknown rank ${JSON.stringify(rank)}`);

    clearUserRank(userID);
    userRanks[rank] ??= [];
    userRanks[rank].push(userID);
    storeItem('userRanks', userRanks);
}

/* command handler */
let prefix = '/';
let private = false;
MPP.client.on('a', async event => {
    // event metadata
    const args = event.a.split(' ');
    const user = event.p;
    const userRank = getUserRank(user._id);
    const rawcmd = args[0];
    const fullcmd = rawcmd.toLowerCase();
    const cmd = fullcmd.substring(prefix.length);
    args.shift();

    // handler short-circuit cases
    if (!fullcmd.startsWith(prefix)) return;
    if (private && user._id !== MPP.client.getOwnParticipant()._id) return;
    
    // find command
    let targetCommand;
    if (cmds[cmd] != null) targetCommand = cmds[cmd];
    else for (const [name, info] of cmds) {
        if (info.aliases.includes(cmd)) targetCommand = info;
    }

    // executor short-circuit cases
    if (targetCommand == null) {
        send(`The command \`${prefix + cmd}\` does not exist.`);
        return;
    }
    if ((targetCommand?.rank ?? ranks.user) > userRank) {
        send('You do not have permission to use this command.');
        return;
    }

    // execute command
    try {
        await targetCommand.func({
            user, args, cmd, fullcmd, rawcmd
        });
    } catch (error) {
        if (Error.isError(error)) {
            const errorLocation = [.../(\d+):(\d+)/.exec(new Error().stack)].slice(1,3);
            const line = errorLocation[0];
            const col = errorLocation[1];
            send(`❌ Uncaught ${error.name} at line ${line} column ${col}: ${error.msg}`);
        } else
            send(`❌ Uncaught RawThrow: ${JSON.stringify(error)}`);
    }
});