Multiplayer Piano Optimizations [Sounds]

Play sounds when users join, leave, or mention you in Multiplayer Piano

当前为 2025-07-14 提交的版本,查看 最新版本

// ==UserScript==
// @name         Multiplayer Piano Optimizations [Sounds]
// @namespace    https://tampermonkey.net/
// @version      1.1.0
// @description  Play sounds when users join, leave, or mention you in Multiplayer Piano
// @author       zackiboiz, cheezburger0, ccjit
// @match        *://multiplayerpiano.com/*
// @match        *://multiplayerpiano.net/*
// @match        *://multiplayerpiano.org/*
// @match        *://piano.mpp.community/*
// @match        *://mpp.7458.space/*
// @match        *://qmppv2.qwerty0301.repl.co/*
// @match        *://mpp.8448.space/*
// @match        *://mpp.autoplayer.xyz/*
// @match        *://mpp.hyye.xyz/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=multiplayerpiano.net
// @grant        GM_info
// @license      MIT
// ==/UserScript==

(async () => {
    function injectScript(src) {
        return new Promise((resolve, reject) => {
            const s = document.createElement("script");
            s.src = src;
            s.async = false;
            s.onload = resolve;
            s.onerror = e => reject(e);
            document.head.appendChild(s);
        });
    }
    await injectScript("https://code.jquery.com/ui/1.12.1/jquery-ui.js");

    class SoundManager {
        constructor(version) {
            this.version = version;
            this.GAP_MS = 200;
            this.volume = 100;
            this.lastPlayed = {};
            this.loadPacks();
        }

        loadPacks() {
            const defaultPacks = {
                "Default": {
                    MENTION: "https://files.catbox.moe/f5tzag.mp3",
                    JOIN:    "https://files.catbox.moe/t3ztlz.mp3",
                    LEAVE:   "https://files.catbox.moe/kmpz7e.mp3"
                },
                "PRISM": {
                    MENTION: "https://file.garden/aHFDYCLeNBLSceNi/Swoosh.wav",
                    JOIN:    "https://file.garden/aHFDYCLeNBLSceNi/Plug%20In.wav",
                    LEAVE:   "https://file.garden/aHFDYCLeNBLSceNi/Plug%20Out.wav"
                },
                "Win11": {
                    MENTION: "https://file.garden/aHFDYCLeNBLSceNi/Win11%20Notify.wav",
                    JOIN:    "https://file.garden/aHFDYCLeNBLSceNi/Win11%20Plug%20In.wav",
                    LEAVE:   "https://file.garden/aHFDYCLeNBLSceNi/Win11%20Plug%20Out.wav"
                },
                "Discord": {
                    MENTION: "https://file.garden/aHFDYCLeNBLSceNi/Discord%20Ping.mp3",
                    JOIN:    "https://file.garden/aHFDYCLeNBLSceNi/Discord%20Join.mp3",
                    LEAVE:   "https://file.garden/aHFDYCLeNBLSceNi/Discord%20Leave.mp3"
                }
            };

            const stored = localStorage.getItem('savedSoundPacks');
            if (stored) {
                try {
                    this.packs = JSON.parse(stored);
                } catch (e) {
                    console.error("Invalid saved sound packs, resetting to defaults.", e);
                    this.packs = defaultPacks;
                }
            } else {
                this.packs = defaultPacks;
            }

            localStorage.setItem('savedSoundPacks', JSON.stringify(this.packs));

            this.defaultPack = localStorage.getItem('defaultSoundPack') || 'Default';
            if (!this.packs[this.defaultPack]) this.defaultPack = 'Default';
            this.currentPack = this.defaultPack;
            this.SOUNDS = this.packs[this.currentPack];
        }

        saveSoundPack({ NAME, MENTION, JOIN, LEAVE }) {
            if (!NAME || !MENTION || !JOIN || !LEAVE) {
                alert("All fields (NAME, MENTION, JOIN, LEAVE) are required.");
                return;
            }

            for (const [existingName, pack] of Object.entries(this.packs)) {
                if (pack.MENTION === MENTION && pack.JOIN === JOIN && pack.LEAVE === LEAVE) {
                    alert(`Sound pack matches existing pack "${existingName}".`);
                    return;
                }
            }

            let uniqueName = NAME;
            let i = 1;
            while (this.packs[uniqueName]) {
                uniqueName = `${NAME} (${i++})`;
            }
            this.packs[uniqueName] = { MENTION, JOIN, LEAVE };
            localStorage.setItem('savedSoundPacks', JSON.stringify(this.packs));
            alert(`Sound pack "${uniqueName}" imported successfully.`);
            this.updateDropdown();
        }

        setSoundPack(name) {
            if (this.packs[name]) {
                this.SOUNDS = this.packs[name];
                this.currentPack = name;
            }
        }

        setDefaultSoundPack(name) {
            if (this.packs[name]) {
                localStorage.setItem('defaultSoundPack', name);
                this.defaultPack = name;
                this.setSoundPack(name);
            }
        }

        play(src) {
            const now = Date.now();
            if (!this.lastPlayed[src] || now - this.lastPlayed[src] >= this.GAP_MS) {
                this.lastPlayed[src] = now;
                const audio = new Audio(src);
                audio.volume = this.volume / 100;
                audio.play().catch(() => {});
            }
        }

        updateDropdown() {
            const sel = $("#soundpack-select").empty();
            Object.keys(this.packs).forEach(name => {
                const selAttr = name === this.currentPack ? ' selected' : '';
                sel.append(`<option value="${name}"${selAttr}>${name}</option>`);
            });
        }
    }

    const soundManager = new SoundManager(GM_info.script.version);
    let replyMap = {};
    let users = {};

    function handleMessage(msg) {
        const sender = msg.p ?? msg.sender;
        replyMap[msg.id] = sender._id;
        const you = MPP.client.user._id;
        const isMention = msg.a.includes(`@${you}`);
        const isReplyToYou = msg.r && replyMap[msg.r] === you;
        if ((isMention || isReplyToYou) && !document.hasFocus()) {
            soundManager.play(soundManager.SOUNDS.MENTION);
        }
    }

    MPP.client.on("a", handleMessage);
    MPP.client.on("dm", handleMessage);
    MPP.client.on("ch", ch => {
        users = {};
        ch.ppl.forEach(u => users[u._id] = u);
    });
    MPP.client.on("p", p => {
        if (!users[p._id]) soundManager.play(soundManager.SOUNDS.JOIN);
        users[p._id] = p;
    });
    MPP.client.on("bye", u => {
        soundManager.play(soundManager.SOUNDS.LEAVE);
        delete users[u.p];
    });
    MPP.client.on("c", () => {
        MPP.chat.sendPrivate({
            name: `[MPP Sounds] v${soundManager.version}`,
            color: "#ffaa00",
            message: "Sound alerts loaded."
        });
    });

    const $btn = $(`<div id="sounds-btn" class="ugly-button">MPP Sounds</div>`);
    $("#buttons").append($btn);
    const $modal = $(`
        <div id="sound-modal" class="dialog" style="height: 200px; margin-top: -100px;">
            <h3>MPP Sounds</h3><hr>
            <p><label>Select pack: <select id="soundpack-select" class="text"></select></label></p>
            <p><label>Import JSON file: <input type="file" id="soundpack-file" accept=".json" /></label></p>
            <p><button id="set-soundpack" class="submit">SUBMIT</button></p>
        </div>
    `);
    $("#modal #modals").append($modal);

    function hideModals() {
        $("#modal #modals > *").hide();
        $("#modal").hide();
    }
    function showModal() {
        if (MPP.chat) MPP.chat.blur();
        hideModals();
        soundManager.updateDropdown();
        $("#modal").fadeIn(250);
        $modal.show();
    }

    $btn.on("click", showModal);

    $("#soundpack-file").on("change", function() {
        const file = this.files[0];
        if (!file) return;
        const reader = new FileReader();
        reader.onload = e => {
            try {
                const json = JSON.parse(e.target.result);
                soundManager.saveSoundPack(json);
            } catch (err) {
                alert("Invalid JSON file.");
            } finally {
                this.value = "";
            }
        };
        reader.onerror = () => alert("Failed to read file.");
        reader.readAsText(file);
    });

    $("#set-soundpack").on("click", () => {
        const name = $("#soundpack-select").val();
        soundManager.setDefaultSoundPack(name);
        hideModals();
    });
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址