TrixMusic

TriX Executor's Music Player.

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!)

// ==UserScript==
// @name         TrixMusic
// @namespace    http://tampermonkey.net/
// @version      1.1.7
// @description  TriX Executor's Music Player.
// @author       Painsel
// @match        *://*/*
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @require      https://code.jquery.com/ui/1.13.2/jquery-ui.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/howler/2.2.3/howler.min.js
// @resource     JQUERY_UI_CSS https://code.jquery.com/ui/1.13.2/themes/base/jquery-ui.css
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_info
// ==/UserScript==

/*
  Copyright (c) 2025 Painsel
  All Rights Reserved.

  This script is proprietary software. You may not use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software.

  UNAUTHORIZED COPYING, DISTRIBUTION, OR MODIFICATION OF THIS SCRIPT,
  EITHER IN WHOLE OR IN PART, IS STRICTLY PROHIBITED.
*/


(function() {
    'use strict';

    // --- SETTINGS GATEKEEPER ---
    const showOnAllSites = GM_getValue('trixShowOnAllSites', true);

    if (!showOnAllSites) {
        const currentUrl = window.location.href;
        const allowedSites = [
            'https://territorial.io/',
            'https://fxclient.github.io/FXclient/'
        ];
        const isAllowed = allowedSites.some(site => currentUrl.startsWith(site));

        if (!isAllowed) {
            console.log("TrixMusic: Execution stopped. This site is not in the allowed list.");
            return;
        }
    }

    // --- 1. INJECT STYLESHEETS & FONTS ---

    const jqueryUiCss = GM_getResourceText("JQUERY_UI_CSS");
    GM_addStyle(jqueryUiCss);

    const faLink = document.createElement('link');
    faLink.setAttribute('rel', 'stylesheet');
    faLink.setAttribute('href', 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');
    document.head.appendChild(faLink);

    const fontLink = document.createElement('link');
    fontLink.setAttribute('rel', 'stylesheet');
    fontLink.setAttribute('href', 'https://fonts.googleapis.com/css?family=Roboto:300,400,500,700');
    document.head.appendChild(fontLink);

    // --- 2. DEFINE HTML AND CSS ---

    const playerHTML = `
        <div class="cont trix-music-container" style="display: none;">
          <div class="player-main">
              <div class="cover">
                <div class="bg"></div>
                <div class="middle">
                  <div class="image">
                    <img src="" id="cover"/>
                  </div>
                  <h1>...</h1>
                  <h2>...</h2>
                </div>
              </div>
              <div class="bar">
                <div class="m_slider"></div>
                <div class="time-display"><span class="current-time">0:00</span> / <span class="total-time">0:00</span></div>
                <div class="buttons">
                  <a class="btn random"><i class="fa fa-random"></i></a>
                  <a class="btn prev"><i class="fa fa-step-backward"></i></a>
                  <a class="btn play"><i class="fa fa-play"></i><i class="fa fa-pause"></i></a>
                  <a class="btn next"><i class="fa fa-step-forward"></i></a>
                  <a class="btn loop active" title="Loop Playlist (Autoplay Next)"><i class="fa fa-refresh"></i></a>
                  <a class="btn volume"><i class="fa fa-volume-up"></i><i class="fa fa-volume-off"></i></a>
                </div>
              </div>
          </div>
          <div class="list-area">
              <ul class="list"></ul>
              <div class="suggestion-box">
                  <p>Have any suggestions? Send a message to Painsel!</p>
                  <a href="https://form.jotform.com/253124626389563" class="suggestion-btn" target="_blank" rel="noopener noreferrer">Add a Song</a>
                  <div class="settings-area">
                      <label>
                          <input type="checkbox" id="trix-show-on-all-sites">
                          Show on All Websites
                      </label>
                  </div>
                  <div id="trixmusic-version" class="version-footer"></div>
                  <div id="trixmusic-update-footer" class="update-footer" style="display: none;">
                      <span>New Version available. Update now?</span>
                      <a href="https://update.greasyfork.org/scripts/555311/TrixMusic.user.js" class="update-footer-btn" target="_blank">Update</a>
                  </div>
              </div>
          </div>
        </div>
        <button id="trixmusic-toggle-button">Toggle TrixMusic</button>
    `;

    const playerCSS = `
        #trixmusic-toggle-button {
            position: fixed; bottom: 15px; right: 15px; z-index: 99998;
            padding: 10px 15px; background-color: #FE186B; color: white;
            border: none; border-radius: 5px; cursor: move; font-family: 'Roboto', sans-serif;
            box-shadow: 0 2px 10px rgba(0,0,0,0.2);
        }
        .trix-music-container * { position: relative; cursor: default; box-sizing: border-box; }
        .trix-music-container {
            position: fixed; background: #fff; width: 950px; height: 500px;
            box-shadow: 0px 0px 50px #aaa; font-family: 'Roboto', sans-serif;
            z-index: 99999; cursor: grab; display: flex; overflow: hidden;
        }
        .trix-music-container:active { cursor: grabbing; }

        /* Main Player Area (Left Side) */
        .player-main { width: 550px; height: 100%; display: flex; flex-direction: column; }
        .player-main .cover { flex-grow: 1; background: #000; overflow: hidden; transition: 0.5s; z-index: 10; }
        .player-main .cover .bg {
            width: 100%; height: 100%; opacity: 0.5;
            filter: blur(10px); transform: scale(1.2); z-index: 10;
            background-size: cover; background-position: center;
        }
        .player-main .cover .middle {
            position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%);
            text-align: center; color: #fff; transition: 0.5s; z-index: 30; width: 100%;
        }
        .player-main .cover .middle .image { width: 200px; height: 200px; overflow: hidden; border-radius: 50%; margin: 0 auto; }
        .player-main .cover .middle .image img { display: block; width: 100%; }
        .player-main .cover .middle h1 { font-size: 20px; padding: 10px 0; }
        .player-main .cover .middle h2 { font-size: 12px; opacity: 0.5; }

        /* Bar */
        .player-main .bar {
            width: 100%; height: 100px; position: relative; bottom: 0; left: 0;
            text-align: center; transition: 0.5s; z-index: 20; color: #fff; background: #000;
        }
        .player-main .bar .m_slider { background: #555; width: 100%; height: 5px; cursor: pointer;}
        .player-main .bar .m_slider .ui-slider-range { height: 100%; background: #FE186B; }
        .player-main .bar .m_slider .ui-slider-handle {
            width: 15px; height: 15px; top: -5px; margin-left: -8px;
            border-radius: 50%; display: block; background: #FFF; border: none;
        }
        .player-main .bar .buttons { width: 100%; top: 20px; }
        .player-main .bar .btn {
            display: inline-block; width: 30px; height: 30px; text-align: center;
            overflow: hidden; vertical-align: middle; margin: 0px 5px;
            transition: 0.5s; cursor: pointer; color: #fff;
        }
        .player-main .bar .btn:hover { color: #FE186B; }
        .player-main .bar .btn.toggle i { top: -30px; }
        .player-main .bar .btn i { display: block; line-height: 30px; top: 0px; transition: 0.5s; pointer-events: none; }
        .player-main .bar .btn.play { width: 50px; height: 50px; font-size: 30px; margin: 0 8px; }
        .player-main .bar .btn.play i { line-height: 50px; }
        .player-main .bar .btn.play.toggle i { top: -50px; }
        .player-main .bar .btn.random { position: absolute; left: 20px; top: 10px; }
        .player-main .bar .btn.volume { position: absolute; right: 20px; top: 10px; }
        .player-main .bar .btn.loop { position: absolute; right: 60px; top: 10px; opacity: 0.3; }
        .player-main .bar .btn.loop.active { opacity: 1; }
        .player-main .bar .btn.loop i.fa-repeat { font-size: 1.1em; }
        .player-main .bar .time-display {
            position: absolute; top: 10px; left: 50%; transform: translateX(-50%);
            font-size: 12px; color: white; opacity: 0.7;
        }

        /* List Area & Suggestion Box (Right Side) */
        .list-area {
            position: relative; width: 400px; height: 100%; z-index: 20;
            padding: 10px; box-sizing: border-box; display: flex; flex-direction: column; background: #f7f7f7;
        }
        .list { list-style: none; flex-grow: 1; overflow-y: auto; margin: 0; padding: 0; }
        .list li { background: rgba(255, 255, 255, 0.9); width: 100%; margin-top: 10px; transition: background 0.5s; cursor: pointer; }
        .list li * { pointer-events: none; }
        .list li .text { height: 50px; display: inline-block; width: calc(100% - 55px); padding: 5px 10px; overflow: hidden; }
        .list li .text p { font-size: 16px; margin: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; color: #333; }
        .list li .text p + p { font-size: 12px; opacity: 0.6; }
        .list li.playing { color: #FE186B; background: #fff; box-shadow: 0 0 5px rgba(254, 24, 107, 0.5); }
        .list li.playing .pl { opacity: 1; }
        .list li:hover { background: #eee; }
        .list li .pl { transition: 0.5s; position: absolute; right: 10px; top: 0; line-height: 50px; opacity: 0; }
        .list li img { height: 50px; vertical-align: top; display: inline-block; }
        .suggestion-box {
            padding: 15px 10px 5px 10px; text-align: center; flex-shrink: 0;
            border-top: 1px solid #eee; margin-top: 10px;
        }
        .suggestion-box p { font-size: 12px; color: #666; margin: 0 0 10px 0; }
        .suggestion-btn {
            display: inline-block; padding: 8px 15px; background-color: #FE186B;
            color: #fff !important; text-decoration: none; border-radius: 5px;
            font-size: 13px; font-weight: 500; transition: background-color 0.2s ease; cursor: pointer;
        }
        .suggestion-btn:hover { background-color: #d11458; }
        .settings-area { margin-top: 15px; font-size: 12px; color: #555; }
        .settings-area input { vertical-align: middle; margin-right: 5px; }
        .version-footer { font-size: 11px; color: #aaa; margin-top: 8px; }
        .update-footer {
            font-size: 11px; color: #c0392b; margin-top: 8px; padding: 5px;
            background-color: #f9e3e3; border-radius: 4px; display: flex;
            justify-content: space-between; align-items: center;
        }
        .update-footer-btn {
            text-decoration: none; background-color: #27ae60; color: #fff !important;
            padding: 3px 8px; border-radius: 4px; font-size: 11px;
            margin-left: 10px; cursor: pointer;
        }
        .update-footer-btn:hover { background-color: #2ecc71; }
    `;

    // --- UPDATE CHECKER LOGIC ---
    const updateModalCSS = `
        .trix-update-overlay {
            position: fixed; top: 0; left: 0; width: 100%; height: 100%;
            background: rgba(0,0,0,0.7); z-index: 100000; display: flex;
            align-items: center; justify-content: center;
        }
        .trix-update-modal {
            background: #fff; padding: 25px; border-radius: 8px; text-align: center;
            max-width: 400px; font-family: 'Roboto', sans-serif;
        }
        .trix-update-modal h2 { margin-top: 0; color: #c0392b; }
        .trix-update-modal p { margin-bottom: 20px; color: #333; }
        .trix-update-modal .modal-buttons button {
            padding: 10px 20px; border: none; border-radius: 5px;
            cursor: pointer; font-size: 14px; margin: 0 10px;
        }
        .trix-update-modal .update-btn { background-color: #27ae60; color: white; }
        .trix-update-modal .later-btn { background-color: #7f8c8d; color: white; }
    `;

    const updateModalHTML = (updateURL) => `
        <div class="trix-update-overlay">
            <div class="trix-update-modal">
                <h2>OUTDATED VERSION</h2>
                <p>You are using an outdated version of TrixMusic. Please update for the best experience!</p>
                <div class="modal-buttons">
                    <button class="update-btn" onclick="window.open('${updateURL}', '_blank');">Update now!</button>
                    <button class="later-btn">Remind me later!</button>
                </div>
            </div>
        </div>
    `;

    function showUpdateNotification(updateURL) {
        GM_addStyle(updateModalCSS);
        const modalElement = document.createElement('div');
        modalElement.innerHTML = updateModalHTML(updateURL);
        document.body.appendChild(modalElement);

        modalElement.querySelector('.later-btn').addEventListener('click', () => modalElement.remove());
        modalElement.querySelector('.update-btn').addEventListener('click', () => modalElement.remove());
        $('#trixmusic-update-footer').show();
    }

    function checkForUpdates() {
        const currentVersion = GM_info.script.version;
        const updateURL = 'https://update.greasyfork.org/scripts/555311/TrixMusic.user.js';

        GM_xmlhttpRequest({
            method: 'GET',
            url: updateURL,
            onload: function(response) {
                const match = response.responseText.match(/@version\s+([\d.]+)/);
                if (match) {
                    const latestVersion = match[1];
                    if (latestVersion > currentVersion) {
                        showUpdateNotification(updateURL);
                    }
                }
            }
        });
    }

    // --- 3. INJECT HTML & SCRIPT LOGIC ---

    document.body.insertAdjacentHTML('beforeend', playerHTML);
    GM_addStyle(playerCSS);

    $(function() {
        // --- Player State and Data ---
        const player = {
            tracklist: [
                {
                    title: 'Passo Bem Solto',
                    artist: 'ATLXS',
                    cover: 'https://upload.wikimedia.org/wikipedia/en/thumb/3/34/Passo_Bem_Solto_-_Atlxs.jpg/250px-Passo_Bem_Solto_-_Atlxs.jpg',
                    file: 'https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/ATLXS_-_PASSO_BEM_SOLTO_-_Slowed_@BaseNaija%20(2).mp3'
                },
                {
                    title: 'Honeypie',
                    artist: 'JAWNY',
                    cover: 'https://i1.sndcdn.com/artworks-8JDT4NmXVBHXwi02-5YRUCA-t500x500.jpg',
                    file: 'https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/JAWNY_-_Honeypie_muzonov.net_(mp3.pm).mp3'
                },
                {
                    title: 'It Has To Be This Way',
                    artist: 'Jamie Christopherson & Logan Mader',
                    cover: 'https://i1.sndcdn.com/artworks-000072742433-j335oj-t500x500.jpg',
                    file: 'https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/Metal%20Gear%20Rising-%20Revengeance%20OST%20-%20It%20Has%20To%20Be%20This%20Way%20_Senator%20Battle_%20-%20Jamie%20Christopherson%20-%20SoundLoadMate.com.mp3'
                },
                {
                    title: 'D.D.D.D',
                    artist: '(K)NoW_NAME',
                    cover: 'https://i.scdn.co/image/ab67616d0000b2731b0339f042d798da979c898f',
                    file: 'https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/D.D.D.D..mp3'
                }
            ],
            currentIndex: 0,
            currentSound: null,
            isPlaying: false,
            loopMode: 1, // 0 = off, 1 = loop playlist (autoplay next), 2 = loop single
            isRandom: false,
            isMuted: false
        };

        // --- Core Functions ---
        const loadTrack = (index, shouldPlay) => {
            if (player.currentSound) player.currentSound.unload();
            const track = player.tracklist[index];
            player.currentIndex = index;

            $('.cover .image img').attr('src', track.cover);
            $('.cover .bg').css('background-image', 'url(' + track.cover + ')');
            $('.cover h1').text(track.title);
            $('.cover h2').text(track.artist);
            $('.list li').removeClass('playing');
            $(`.list li#track-${index}`).addClass('playing');

            player.currentSound = new Howl({
                src: [track.file],
                html5: true,
                volume: player.isMuted ? 0 : 1,
                loop: player.loopMode === 2,
                onplay: () => {
                    player.isPlaying = true;
                    $('.play').addClass('toggle');
                    requestAnimationFrame(step);
                },
                onpause: () => {
                    player.isPlaying = false;
                    $('.play').removeClass('toggle');
                },
                onend: () => {
                    if (player.loopMode === 1) {
                        nextTrack();
                    } else if (player.loopMode === 0) {
                        if (!player.isRandom && player.currentIndex < player.tracklist.length - 1) {
                            nextTrack();
                        } else if (player.isRandom) {
                            nextTrack();
                        } else {
                            player.isPlaying = false;
                            $('.play').removeClass('toggle');
                        }
                    }
                },
                onload: () => {
                    updateTimeDisplay();
                    if (shouldPlay) playTrack();
                }
            });
        };

        const playTrack = () => { if (player.currentSound && !player.currentSound.playing()) player.currentSound.play(); };
        const pauseTrack = () => { if (player.currentSound && player.currentSound.playing()) player.currentSound.pause(); };

        const nextTrack = () => {
            let nextIndex;
            if (player.isRandom) {
                do { nextIndex = Math.floor(Math.random() * player.tracklist.length); } while (nextIndex === player.currentIndex && player.tracklist.length > 1);
            } else { nextIndex = (player.currentIndex + 1) % player.tracklist.length; }
            loadTrack(nextIndex, true);
        };

        const prevTrack = () => {
            const prevIndex = (player.currentIndex - 1 + player.tracklist.length) % player.tracklist.length;
            loadTrack(prevIndex, true);
        };

        const seek = (value) => {
            if (player.currentSound && player.currentSound.state() === 'loaded') {
                player.currentSound.seek(player.currentSound.duration() * (value / 100));
            }
        };

        // --- UI Update Functions ---
        const formatTime = (secs) => {
            const minutes = Math.floor(secs / 60) || 0;
            const seconds = Math.floor(secs - minutes * 60) || 0;
            return minutes + ':' + (seconds < 10 ? '0' : '') + seconds;
        };

        const updateTimeDisplay = () => {
            if (!player.currentSound) return;
            const duration = player.currentSound.duration() || 0;
            const seekPos = player.currentSound.seek() || 0;
            $('.current-time').text(formatTime(seekPos));
            $('.total-time').text(formatTime(duration));
        };

        const step = () => {
            if (player.currentSound && player.currentSound.playing()) {
                const seekPos = player.currentSound.seek() || 0;
                const duration = player.currentSound.duration() || 0;
                if (duration) $('.m_slider').slider('value', (seekPos / duration) * 100);
                updateTimeDisplay();
                requestAnimationFrame(step);
            }
        };

        // --- Initialization ---
        const container = $('.trix-music-container');
        container.css({
            top: ($(window).height() - container.outerHeight()) / 2 + 'px',
            left: ($(window).width() - container.outerWidth()) / 2 + 'px'
        });

        $('#trixmusic-version').text('v' + GM_info.script.version);

        $.each(player.tracklist, (i, track) => {
            $('.list').append(
                `<li id="track-${i}">
                    <img src="${track.cover}"/>
                    <div class="text"><p>${track.title}</p><p>${track.artist}</p></div>
                    <i class="fa fa-music pl"></i>
                </li>`
            );
        });

        $(".m_slider").slider({
            range: "min", value: 0, min: 0, max: 100,
            slide: (event, ui) => {
                const newTime = player.currentSound.duration() * (ui.value / 100);
                $('.current-time').text(formatTime(newTime));
            },
            stop: (event, ui) => {
                seek(ui.value);
            }
        });
        loadTrack(0, false);

        // --- Event Handlers ---
        $('.play').on('click', () => (player.currentSound.playing()) ? pauseTrack() : playTrack());
        $('.next').on('click', nextTrack);
        $('.prev').on('click', prevTrack);

        $('.volume').on('click', function() {
            $(this).toggleClass('toggle');
            player.isMuted = $(this).hasClass('toggle');
            Howler.volume(player.isMuted ? 0 : 1);
        });

        $('.loop').on('click', function() {
            player.loopMode = (player.loopMode + 1) % 3;
            const icon = $(this).find('i');
            icon.removeClass('fa-refresh fa-repeat');

            if (player.loopMode === 1) { // Loop Playlist
                $(this).addClass('active').attr('title', 'Loop Playlist (Autoplay Next)');
                icon.addClass('fa-refresh');
            } else if (player.loopMode === 2) { // Loop Single
                $(this).addClass('active').attr('title', 'Loop Single Song');
                icon.addClass('fa-repeat');
            } else { // Loop Off
                $(this).removeClass('active').attr('title', 'Loop Off');
                icon.addClass('fa-refresh');
            }
            if (player.currentSound) player.currentSound.loop(player.loopMode === 2);
        });

        $('.random').on('click', function() { $(this).toggleClass('active'); player.isRandom = $(this).hasClass('active'); });

        $('.list').on('click', 'li', function() {
            if ($(this).data('isSorting')) return;
            loadTrack(parseInt($(this).attr('id').replace('track-', '')), true);
        });

        // --- Settings Handler ---
        const settingsCheckbox = $('#trix-show-on-all-sites');
        settingsCheckbox.prop('checked', GM_getValue('trixShowOnAllSites', true));
        settingsCheckbox.on('change', function() {
            GM_setValue('trixShowOnAllSites', $(this).prop('checked'));
            alert('TrixMusic settings saved. Please reload the page for changes to take full effect.');
        });

        // --- Draggable Logic ---
        container.draggable({ handle: ".player-main", containment: "window" });
        $('.list').sortable({
            revert: true,
            items: "> li",
            start: function(event, ui) { ui.item.data('isSorting', true); },
            stop: function(event, ui) { setTimeout(() => { ui.item.data('isSorting', false); }, 100); }
        }).disableSelection();

        $('#trixmusic-toggle-button').draggable({
            containment: "window",
            start: function() { $(this).data('isDragging', true); },
            stop: function() { setTimeout(() => { $(this).data('isDragging', false); }, 100); }
        }).on('click', function() {
            if (!$(this).data('isDragging')) { container.toggle(); }
        });

        // --- INITIATE UPDATE CHECK ---
        checkForUpdates();
    });
})();