Greasy Fork is available in English.

NotificationSoundSettings

Интерфейс изменения звука уведомлений.

// ==UserScript==
// @name         NotificationSoundSettings
// @namespace    MeloniuM/LZT
// @version      1.4.1
// @description  Интерфейс изменения звука уведомлений.
// @author
// @match        *://lolz.live/*
// @match        *://zelenka.guru/*
// @grant        none
// ==/UserScript==

(function () {
    'use strict';

    $("<style/>").text(`
    input#sound-file::file-selector-button {
        border-radius: 6px;
        border-style: none;
    }
    .NotificationSoundSettings {
        position: absolute;
        cursor: pointer;
        right: 85px;
        top: 14px;
        font-size: 18px;
    }
    .NotificationSound--icon{
        color: #949494;
    }
    .NotificationSound--icon:hover{
        color: rgb(58, 169, 119);
    }
    #NSSuploaded-filename {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        background: #2b2b2b;
        border: 1px solid #444;
        border-radius: 4px;
        padding: 6px 12px;
        margin: 6px 0;
        font-size: 13px;
        color: #ccc;
        cursor: pointer;
        user-select: none;
        white-space: nowrap;
        text-overflow: ellipsis;
    }
    `).appendTo("head");

    const STORAGE_KEY = 'customNotificationSound';
    const MAX_FILE_SIZE = 500 * 1024;
    let audio = null;

    const OriginalAudio = window.Audio;
    let NotifAudio;
    let fix_notif_sound = true;

	window.Audio = function (src) {
		// Проверяем, нужно ли заменить источник
		if (src === Im.soundNotificationFile && Im.customSoundNotificationFile) {
			console.log('[Audio] Заменён src:', src, '→', Im.customSoundNotificationFile);
			src = Im.customSoundNotificationFile;
		}

		return new OriginalAudio(src);
	};

	window.Audio.prototype = OriginalAudio.prototype;

    $(window).on("load", function() {
        //фикс звука
        Im.soundNotificationFile = 'https://lolz.live/public/pm2.wav';
        Im.customSoundNotificationFile = Im.soundNotificationFile
        applyStoredSound();
        NotifAudio = new Audio(Im.soundNotificationFile);
        let Notification = $('html').data('Im.Notification');
        Notification.displayNotificationOld = Notification.displayNotification;
        Notification.displayNotification = (e) => {
            Notification.displayNotificationOld(e);
            if (!e.isSiteFocused && e.shouldPlayAudio && Im.soundNotificationsEnabled && fix_notif_sound) {
                try {
                    NotifAudio?.play();
                } catch (e) {};
            }
        };
    })

    $(document).bind('PopupMenuShow', function(e){
        let $menu = e.$menu
        if (!$menu.is('#AlertsMenu')) {
            return;
        }
        if ($menu.find('.NotificationSoundSettings').length) return;
        let $settings = createSoundSettingsUI();

        $menu.prepend($settings).xfActivate();
        prefillInputs();

        $('#NSSsound-mode').on('change', function () {
            updateModeUI(this.value);
        });

        $('#NSSsound-url').on('input', toggleApplyButton);
        $('#NSSsound-file').on('change', toggleApplyButton);
        $('#NSSsound-mode').on('change', toggleApplyButton);

        $('#fix_notif_sound').on('click', (e) => {
            e.stopPropagation();

            let val = localStorage.getItem(STORAGE_KEY);

            try {
                let parsed = {fix: fix_notif_sound};
                if (val) {
                    parsed = JSON.parse(val);
                }

                fix_notif_sound = $('#fix_notif_sound').prop('checked');
                parsed.fix = fix_notif_sound;
                localStorage.setItem(STORAGE_KEY, JSON.stringify(parsed));
            } catch (e) {
                console.warn('Ошибка применения сохранённого звука');
            }
        });


        $('#NSSapply-sound').on('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            const urlInput = $('#NSSsound-url').val().trim();
            const fileInput = $('#NSSsound-file').get(0).files[0];

            if (urlInput) {
                if (!isValidURL(urlInput)) {
                    alert('Введите корректную ссылку на звук');
                    return;
                }
                const data = {
                    type: 'url',
                    value: urlInput,
                    fix: $('#fix_notif_sound').val()
                };
                localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
                Im.customSoundNotificationFile = urlInput;
                alert('Ссылка на звук сохранена');
            } else if (fileInput) {
                if (fileInput.size > MAX_FILE_SIZE) {
                    alert(`Файл слишком большой. Лимит: ${MAX_FILE_SIZE / 1024} КБ`);
                    return;
                }
                const reader = new FileReader();
                reader.onload = e => {
                    const data = {
                        type: 'file',
                        name: fileInput.name,
                        value: e.target.result,
                        fix: $('#fix_notif_sound').val()
                    };
                    localStorage.setItem(STORAGE_KEY, JSON.stringify(data));
                    Im.customSoundNotificationFile = data.value;
                    alert('Файл сохранён и применён');
                    NotifAudio = new Audio(Im.soundNotificationFile);
                };
                reader.readAsDataURL(fileInput);
            } else {
                alert('Введите ссылку или выберите файл');
            }
        });

        $('#NSSuploaded-filename').on('click', () => {
            e.stopPropagation();
            e.preventDefault();
            $('#NSSsound-file').click();
        });

        $('#NSSsound-file, #NSSsound-mode').on('click', (e) => {
            e.stopPropagation();
        });

        $('#NSSsound-file').on('change', function () {
            const file = this.files[0];
            updateFilenameDisplay(file?.name);
            $('#NSStest-sound').slideDown(200);
        });

        $('#NSSreset-sound').on('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            localStorage.removeItem(STORAGE_KEY);
            Im.customSoundNotificationFile = Im.soundNotificationFile;
            NotifAudio = new Audio(Im.soundNotificationFile);
            updateFilenameDisplay();
            $('#NSSsound-url').val('');
            $('#NSSsound-file').val('');
            $('#NSSapply-sound').slideUp(200);
        });

        $('#NSStest-sound').on('click', (e) => {
            e.stopPropagation();
            e.preventDefault();
            const urlInput = $('#NSSsound-url').val().trim();
            const fileInput = $('#NSSsound-file').get(0).files[0];

            if (urlInput) {
                playSound(urlInput);
            } else if (fileInput) {
                if (fileInput.size > MAX_FILE_SIZE) {
                    alert(`Файл слишком большой. Лимит: ${MAX_FILE_SIZE / 1024} КБ`);
                    return;
                }
                const reader = new FileReader();
                reader.onload = e => playSound(e.target.result);
                reader.readAsDataURL(fileInput);
            } else {
                playSound(Im.customSoundNotificationFile);
            }
        });

    });

    function isValidURL(url) {
        try {
            new URL(url);
            return true;
        } catch (_) {
            return false;
        }
    }


    function createSoundSettingsUI() {
        return $(`
            <div class="NotificationSoundSettings navTab Popup PopupInPopup PopupClosed">
                <a rel="Menu" class="navLink NoPopupGadget">
                    <i class="fa fa-file-audio NotificationSound--icon Popup"></i>
                </a>
                <div class="Menu HeaderMenu Popup" style="left: 276.453px;top: 53px !important;position: fixed;padding: 10px;">
                    <div style="margin-bottom: 10px; font-size: 15px; font-weight: bold;">
                        Настройка звука уведомлений
                    </div>
                    <div style="margin-bottom: 8px;">
                        <label for="NSSsound-mode"><strong>Источник звука:</strong></label>
                        <select id="NSSsound-mode" style="margin-left: 8px; background: #2b2b2b; color: #fff; border: 1px solid #444; border-radius: 4px; padding: 4px;">
                            <option value="url">Ссылка</option>
                            <option value="file">Файл</option>
                        </select>
                        <label for="fix_notif_sound"><input type="checkbox" checked name="fix_notif_sound" id="fix_notif_sound" value="1">фикс звука</label>
                    </div>

                   <label style="display:block; margin-bottom: 6px;">
                        <span>Ссылка:</span>
                        <input type="text" id="NSSsound-url" placeholder="https://..." style="width:100%; box-sizing: border-box; padding:5px; background:#2b2b2b; color:#fff; border:1px solid #444; border-radius:4px;">
                    </label>
                    <label for="sound-file" style="display: none; margin-top: 6px;">
                        <input type="file" id="NSSsound-file" accept="audio/*" style="display: none;">
                        <div id="NSSuploaded-filename" class="button">
                            <span style="color: #ccc;">Выбран файл:</span>
                            <div style="color: #888;">Нажмите для выбора файла</div>
                        </div>
                    </label>
                    <div style="display: flex; flex-direction: column; gap: 6px;">
                        <button id="NSSapply-sound" style="display: none;background:#4caf50; color:white; padding:6px; border:none; border-radius:4px; cursor:pointer;">Применить</button>
                        <button id="NSStest-sound" style="background:#2196f3; color:white; padding:6px; border:none; border-radius:4px; cursor:pointer;">Тест</button>
                        <button id="NSSreset-sound" style="background:#f44336; color:white; padding:6px; border:none; border-radius:4px; cursor:pointer;">Сбросить</button>
                    </div>
                </div>
            </div>
        `);
    }


    function applyStoredSound() {
        const val = localStorage.getItem(STORAGE_KEY);
        if (!val) return;

        try {
            const parsed = JSON.parse(val);
            if (parsed && parsed.value) {
                Im.customSoundNotificationFile = parsed.value;
            }
            if (parsed?.fix) {
                fix_notif_sound = !!parsed?.fix;
            }
        } catch (e) {
            console.warn('Ошибка применения сохранённого звука');
        }
    }

    function toggleApplyButton() {
        const mode = $('#NSSsound-mode').val();
        const url = $('#NSSsound-url').val().trim();
        const file = $('#NSSsound-file').get(0).files[0];

        const shouldShow = (mode === 'url' && url) || (mode === 'file' && file);
        if (shouldShow) {
            $('#NSSapply-sound').slideDown(200);
        } else {
            $('#NSSapply-sound').slideUp(200);
        }
    }

    function playSound(src) {
        try {
            audio?.pause();
            audio = new Audio(src);
            audio.play().catch(err => {
                console.error('Ошибка воспроизведения:', err);
                alert('Не удалось воспроизвести звук.');
            });
        } catch {
            alert('Ошибка воспроизведения звука');
        }
    }

    function updateFilenameDisplay(name) {
        const MAX_LENGTH = 24;
        let displayName = name || 'Нажмите для выбора файла';

        if (name && name.length > MAX_LENGTH) {
            const start = name.slice(0, 6);
            const end = name.slice(-7);
            displayName = `${start}...${end}`;
        }

        $('#NSSuploaded-filename').html(`
            <span style="color: #ccc;">Выбран файл:</span>
            <div style="color: #ccc;">${displayName}</div>
        `);
    }


    function updateModeUI(mode) {
        if (mode === 'url') {
            $('#NSSsound-url').closest('label').show();
            $('#NSSuploaded-filename').closest('label').hide();
        } else if (mode === 'file') {
            $('#NSSsound-url').closest('label').hide();
            $('#NSSuploaded-filename').closest('label').show();
        }
    }

    function prefillInputs() {
        const val = localStorage.getItem(STORAGE_KEY);
        if (!val) return;

        try {
            const parsed = JSON.parse(val);

            if (parsed.type === 'url') {
                $('#NSSsound-url').val(parsed.value);
            } else if (parsed.type === 'file') {
                updateFilenameDisplay(parsed.name || '[без имени]');
            }
            $('#NSSsound-mode').val(parsed.type);
            if (parsed?.fix) {
                fix_notif_sound = !!parsed?.fix;
            }
            $('#fix_notif_sound').prop('checked', fix_notif_sound);
            updateModeUI(parsed.type);
            if (parsed.value) {
                Im.customSoundNotificationFile = parsed.value;
                $('#NSSapply-sound').show();
            }
        } catch (e) {
            console.warn('Ошибка чтения сохранённого звука');
        }
    }

})();