Greasy Fork

Greasy Fork is available in English.

Popmundo Better Repertoire UI

Permite organizar o repertorio por grupos nomeados. Com funcionalidad de backup daquela banda e restauração de backup.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name        Popmundo Better Repertoire UI
// @namespace   Violentmonkey Scripts
// @match       https://*.popmundo.com/World/Popmundo.aspx/Artist/Repertoire/*
// @exclude     /^https:\/\/.*\.popmundo\.com\/World\/Popmundo\.aspx\/Artist\/Repertoire\/[^/]+\/.+$/
// @author      Drinkwater
// @grant       GM_setValue
// @grant       GM_getValue
// @grant       GM_deleteValue
// @license     M.I.T
// @version     2.1
// @description Permite organizar o repertorio por grupos nomeados. Com funcionalidad de backup daquela banda e restauração de backup.

// ==/UserScript==

const currentUrl = window.location.href;
const bandId = currentUrl.split('/').pop();
const urlDomain = window.location.hostname;
let grupos = GM_getValue('grupos', []);
const style = document.createElement('style');
style.innerHTML = `
  .accordion-header {
        background-image: url(Images/bgr-item-header.png);
        background-color: #56686f;
        background-repeat: repeat-x;
        color: #fff;
        font-weight: 500;
        padding: 16px;
        cursor: pointer;
        border-radius: 8px;
        font-size: 12px;
        transition: background-color 0.3s ease, transform 0.2s ease;
    }

    /* Efeito hover */
    .accordion-header:hover {
        background-color: #3c4b51;
        transform: translateY(-2px);
        box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
    }
.accordion-content {
    max-height: 0;
    opacity: 0;
    overflow: hidden;
    padding: 0 10px;
    border: 1px solid #ccc;
    border-top: none;
    background-color: white;
    transition: max-height 0.4s ease, opacity 0.4s ease;
}

.accordion-content.open {
    max-height: 1000px; /* Defina uma altura máxima grande o suficiente */
    opacity: 1;
}

    .accordion-row {
        /* Define qualquer estilo adicional para as linhas accordion, se necessário */
    }
        .modal-dialog {
        width: 300px;
        height: auto;
    }
    .accordion-row td{
    padding: 5px 0px !important ;

}
.accordion-content tr{
    display: flex;
    justify-content: space-between;
}
.accordion-content tr td{
  max-width:90px;
}



`;

document.head.appendChild(style);

function createDeleteGroupModal() {
    const overlay = jQuery('<div>', { class: 'modal-overlay' }).css({
        position: 'fixed',
        top: 0,
        left: 0,
        width: '100%',
        height: '100%',
        backgroundColor: 'rgba(0, 0, 0, 0.5)',
        zIndex: 999
    }).appendTo('body');

    const modal = jQuery('<div>', { class: 'modal-dialog' }).css({
        position: 'fixed',
        top: '50%',
        left: '50%',
        transform: 'translate(-50%, -50%)',
        backgroundColor: '#fff',
        padding: '20px',
        borderRadius: '12px',
        boxShadow: '0 6px 16px rgba(0, 0, 0, 0.15)',
        zIndex: 1000,
        width: '320px',
        textAlign: 'center',
        fontFamily: 'Arial, sans-serif'
    }).appendTo('body');

    // Título do modal
    const modalTitle = jQuery('<h3>').text('Deletar Grupo').css({
        marginBottom: '15px',
        fontSize: '18px',
        color: '#333',
        fontWeight: 'bold'
    }).appendTo(modal);

    // Select para escolher o grupo
    const selectGroup = jQuery('<select>').css({
        marginBottom: '15px',
        padding: '5px',
        width: '100%',
        fontSize: '14px',
        borderRadius: '6px',
        border: '1px solid #ccc'
    }).appendTo(modal);

    // Exibir grupos que começam com o bandId, exceto o grupo "default", e mostrar sem o prefixo
    grupos.forEach(grupo => {
        if (grupo.startsWith(`${bandId}_`)) {
            selectGroup.append(jQuery('<option>', { value: grupo, text: grupo.replace(`${bandId}_`, '') }));
        }
    });

    // Botões de confirmação e cancelamento
    const confirmButton = jQuery('<button>').text('Confirmar').css({
        marginRight: '10px',
        padding: '8px 12px',
        backgroundColor: '#28a745',
        color: '#fff',
        border: 'none',
        borderRadius: '6px',
        cursor: 'pointer',
        fontSize: '14px',
    }).appendTo(modal);

    const cancelButton = jQuery('<button>').text('Cancelar').css({
        padding: '8px 12px',
        backgroundColor: '#dc3545',
        color: '#fff',
        border: 'none',
        borderRadius: '6px',
        cursor: 'pointer',
        fontSize: '14px'
    }).appendTo(modal);

    // Função para fechar o modal
    function closeModal() {
        modal.remove();
        overlay.remove();
    }

    // Evento de clique no botão de cancelamento
    cancelButton.on('click', function () {
        closeModal();
    });

    // Evento de clique no botão de confirmação
    confirmButton.on('click', function () {
        const selectedGroup = selectGroup.val();
        if (selectedGroup) {

            const tbody = jQuery('#tablesongs tbody');
            tbody.find('tr').each(function (index, row) {
                // Obter o grupo salvo para a música
                const currentGroup = GM_getValue(`${bandId}_song_${index}_group`);
                // Verifica se o grupo atual da música é o que está sendo deletado
                if (currentGroup === selectedGroup) {
                    GM_deleteValue(`${bandId}_song_${index}_group`);
                }
            });

            // Remove o grupo do GM_store
            grupos = grupos.filter(grupo => grupo !== selectedGroup);
            GM_setValue('grupos', grupos);

            alert(`Grupo "${selectedGroup.replace(`${bandId}_`, '')}" foi deletado`);
            closeModal();
            location.reload(); // Recarrega a página para atualizar os selects
        }
    });
}
jQuery(document).ready(function () {
    // Create a div for color pickers
    const colorPickerDiv = jQuery('<div>', { class: 'box', css: { marginBottom: '10px', padding: '10px', border: '1px solid #ccc' } });
    const heading = jQuery('<h2>').text('Configuração das Cores').appendTo(colorPickerDiv);

    // Create color pickers
    const propriaColorPickerLabel = jQuery('<label>', { css: { marginRight: '20px' } }).text('Musica própria: ').appendTo(colorPickerDiv);
    const propriaColorPicker = jQuery('<input>', { type: 'color', value: GM_getValue('propriaColor', '#000000') }).appendTo(propriaColorPickerLabel);

    const naoPropriaColorPickerLabel = jQuery('<label>').text('Comprada/Cover: ').appendTo(colorPickerDiv);
    const naoPropriaColorPicker = jQuery('<input>', { type: 'color', value: GM_getValue('naoPropriaColor', '#008000') }).appendTo(naoPropriaColorPickerLabel);
    const hr = jQuery('<hr>').appendTo(colorPickerDiv);

    // Create the submit button
    const submitButton = jQuery('<input>', { type: 'submit', value: 'Salvar Cores', class: 'cnf', css: { marginRight: '10px' } }).appendTo(colorPickerDiv);
    const createGroup = jQuery('<input>', { type: 'button', value: 'Criar novo grupo', class: 'cnf', css: { marginRight: '10px' } }).appendTo(colorPickerDiv);
    const deleteGroupButton = jQuery('<input>', { type: 'button', value: 'Deletar Grupos', class: 'cnf', css: { marginRight: '10px' } }).appendTo(colorPickerDiv);

    const createBackupButton = jQuery('<input>', { type: 'button', value: 'Criar Backup', class: 'cnf', css: { marginRight: '10px' } }).appendTo(colorPickerDiv);
    const restoreBackupButton = jQuery('<input>', { type: 'button', value: 'Restaurar Backup', class: 'cnf', css: { marginRight: '10px' } }).appendTo(colorPickerDiv);


    // Save the selected colors in GM storage when changed
    propriaColorPicker.on('change', function () {
        GM_setValue('propriaColor', propriaColorPicker.val());
    });

    naoPropriaColorPicker.on('change', function () {
        GM_setValue('naoPropriaColor', naoPropriaColorPicker.val());
    });

    // Handle the submit button click
    submitButton.on('click', function () {
        alert('Cores atualizadas com sucesso!');
        location.reload(); // Refresh the page
    });
    deleteGroupButton.on('click', function () {
        createDeleteGroupModal();
    });

    createGroup.on('click', function () {
        let groupName = prompt("Nome do grupo que deseja criar:");
        if (groupName) {
            const uniqueGroupName = `${bandId}_${groupName}`; // Prefixa o nome do grupo com o bandId
            grupos.push(uniqueGroupName); // Adiciona o novo grupo com o bandId
            GM_setValue('grupos', grupos); // Salva a lista de grupos atualizada
            alert(`Grupo "${uniqueGroupName}" criado com sucesso!`);
            location.reload(); // Recarrega a página para atualizar os selects
        }
    });

    // Insert the color picker div above the table
    jQuery('#tablesongs').before(colorPickerDiv);

    // Cria um iframe único que será reutilizado para cada link e o oculta
    const iframe = jQuery('<iframe>', {
        id: 'songIframe',
        width: '0px',
        height: '0px',
        css: { display: 'none' } // Deixa o iframe oculto inicialmente
    }).appendTo('#ppm-wrapper');

    // Seleciona todos os links da tabela
    const links = jQuery('#tablesongs tbody tr td a');
    let currentIndex = 0;

    // Função para processar um link
    function processLink() {
        if (currentIndex >= links.length) {
            console.log('Processamento concluído para todos os links.');
            iframe.remove(); // Remove o iframe após concluir o processamento de todos os links
            return;
        }

        const currentLink = jQuery(links[currentIndex]);

        // Verifica se o link contém uma imagem <img>, se contiver, pula para o próximo
        if (currentLink.find('img').length > 0) {
            currentIndex++;
            processLink();
            return;
        }

        const songName = currentLink.text(); // Get song name to display later
        const currentLinkHref = currentLink.attr('href');
        const songId = currentLinkHref.split('/').pop(); // Extract song ID from the URL

        const songData = GM_getValue(`${bandId}_${songId}`, null); // Use song ID as the key

        if (songData) {
            // If song data is already stored, display it directly
            displaySongData(songData, currentLink);
            currentIndex++;
            processLink();
        } else {
            const linkFormatted = "https://" + urlDomain + currentLinkHref;
            iframe.attr('src', linkFormatted);

            iframe.off('load').on('load', function () {
                const iframeContent = iframe.contents();
                let div1stBox = iframeContent.find('div.box').first();
                const bandHref = div1stBox.find('a[href^="/World/Popmundo.aspx/Artist/"]').attr('href');
                let bandaPropria = false;
                if (bandHref) {
                    const bandIdFromHref = bandHref.split('/').pop();
                    if (bandIdFromHref === bandId) {
                        bandaPropria = true;
                    }
                }

                let div2ndBox = iframeContent.find('div.box').eq(1);
                const melodiaTitle = div2ndBox.find('p a').eq(0).attr('title');
                const letraTitle = div2ndBox.find('p a').eq(1).attr('title');

                if (melodiaTitle && letraTitle) {
                    const notaMelodia = melodiaTitle.split('/')[0];
                    const notaLetra = letraTitle.split('/')[0];

                    const songData = {
                        songName,
                        notaMelodia,
                        notaLetra,
                        bandaPropria
                    };

                    // Save song data using the song ID as the key
                    GM_setValue(`${bandId}_${songId}`, songData);

                    displaySongData(songData, currentLink);
                } else {
                    console.error('Não foi possível encontrar os títulos para as notas da melodia e da letra.');
                }

                currentIndex++;
                processLink();
            });
        }
    }

    // Função para exibir os dados da música
    // Função para exibir os dados da música
    function displaySongData(songData, linkElement) {
        // Verifica se o ID do link é o da coluna de músicas, onde o ID contém 'lnkArtistSong'
        if (linkElement.attr('id') && linkElement.attr('id').includes('lnkArtistSong')) {
            let newText = `${songData.songName} (${songData.notaMelodia}/${songData.notaLetra})`;
            linkElement.text(newText);

            const propriaColor = GM_getValue('propriaColor', '#000000'); // Default green
            const naoPropriaColor = GM_getValue('naoPropriaColor', '#008000'); // Default yellow

            if (songData.bandaPropria) {
                linkElement.css({ 'color': propriaColor });
            } else {
                linkElement.css({ 'color': naoPropriaColor });
            }
        }
    }


    // Inicia o processamento do primeiro link
    processLink();



    // Função para criar o backup
    function exportBackup() {
        const backupData = {
            bandId: bandId,
            // Filtra os grupos que começam com o bandId atual
            grupos: grupos.filter(grupo => grupo.startsWith(`${bandId}_`)),
            musicas: {}
        };

        // Itera sobre os grupos filtrados para associar as músicas corretas
        backupData.grupos.forEach(grupo => {
            backupData.musicas[grupo] = [];
            const tbody = document.getElementById('tablesongs').querySelector('tbody');
            const rows = tbody.querySelectorAll('tr');
            rows.forEach(row => {
                const songLink = row.querySelector('a');
                if (!songLink) return;

                const songUrl = songLink.getAttribute('href');
                const songId = songUrl.substring(songUrl.lastIndexOf('/') + 1);

                const savedGroup = GM_getValue(`${bandId}_song_${songId}_group`, '');
                if (savedGroup === grupo) {
                    backupData.musicas[grupo].push({
                        songId: songId,
                        songName: songLink.textContent.trim()
                    });
                }
            });
        });

        const backupBlob = new Blob([JSON.stringify(backupData, null, 2)], { type: 'application/json' });
        const backupUrl = URL.createObjectURL(backupBlob);
        const downloadLink = document.createElement('a');
        downloadLink.href = backupUrl;
        downloadLink.download = `backup_band_${bandId}.json`;
        document.body.appendChild(downloadLink);
        downloadLink.click();
        document.body.removeChild(downloadLink);

        console.log("Backup criado com sucesso!");
    }

// Criar o elemento de entrada de arquivo persistente
const fileInput = jQuery('<input>', { type: 'file', accept: 'application/json', id: 'importBackupInput', style: 'display: none;' });
fileInput.appendTo(document.body);

// Associar o evento de mudança ao input de arquivo
fileInput.on('change', function (event) {
    const file = event.target.files[0];
    if (!file) return;

    const reader = new FileReader();
    reader.onload = (e) => {
        try {
            const backupData = JSON.parse(e.target.result);

            if (backupData.bandId !== bandId) {
                alert("O backup não corresponde ao Band ID atual!");
                return;
            }

            // Restaurar os grupos e músicas no GM Store
            grupos = backupData.grupos;
            GM_setValue('grupos', grupos);

            Object.entries(backupData.musicas).forEach(([grupo, musicas]) => {
                musicas.forEach(musica => {
                    GM_setValue(`${bandId}_song_${musica.songId}_group`, grupo);
                });
            });

            alert("Backup restaurado com sucesso!");
            location.reload(); // Recarregar a página para refletir as mudanças
        } catch (error) {
            console.error("Erro ao restaurar o backup:", error);
            alert("Erro ao restaurar o backup. Verifique o arquivo selecionado.");
        }
    };
    reader.readAsText(file);
});

    // Adicionando eventos aos botões
    createBackupButton.on('click', exportBackup);

    restoreBackupButton.on('click', () => {
        // Disparar o clique no input de arquivo
        document.getElementById('importBackupInput').click();
    });


    // Função para adicionar selectboxes em cada linha e salvar a seleção
    function addSelectBoxesToRows(tbody, accordionMap) {
        const rows = tbody.querySelectorAll('tr');
        rows.forEach((row) => {
            // Verifica se a linha não é um accordion (verificamos se a linha contém um link de música)
            const songLink = row.querySelector('a');
            if (!songLink || songLink.textContent.includes('Grupo')) {
                return; // Pula esta linha, pois é um accordion
            }
            if (row.classList.contains('accordion-row')) {
                return; // Se for um accordion, pula essa linha
            }

            // Extração do ID da música a partir do link
            const songUrl = songLink.getAttribute('href');
            const songId = songUrl.substring(songUrl.lastIndexOf('/') + 1);

            const songName = songLink.textContent.trim();
            const newCell = document.createElement('td');
            const select = createSelectBox(songId, accordionMap, row, songName);
            newCell.appendChild(select);
            row.appendChild(newCell);

            const savedGroup = GM_getValue(`${bandId}_song_${songId}_group`, ''); // Usando string vazia como padrão
            if (savedGroup && accordionMap[savedGroup]) {
                // Mover apenas músicas com grupos definidos (exceto default)
                accordionMap[savedGroup].appendChild(row);

                // Aplicar largura de 15% apenas nas colunas com `progressBar`
                row.querySelectorAll('td').forEach(td => {
                    if (td.querySelector('.progressBar')) {
                        td.style.width = '100%';
                    }
                });
            }
        });
    }

    // Função para adicionar o cabeçalho "Grupo" no thead
    function addTableHeader(thead) {
        const headerRow = thead.querySelector('tr');
        const newHeader = document.createElement('th');
        newHeader.textContent = 'Grupo';
        headerRow.appendChild(newHeader);
    }

    // Função para criar os accordions apenas para os grupos que começam com o bandId
    // Função para criar os accordions apenas para os grupos que começam com o bandId
    function createAccordions(tbody) {
        let accordionMap = {};

        // Filtra os grupos que começam com o bandId respectivo
        const filteredGroups = grupos.filter(grupo => grupo.startsWith(`${bandId}_`));

        // Referência para a primeira linha existente no tbody
        const firstRow = tbody.querySelector('tr');

        filteredGroups.forEach(grupo => {
            const accordionRow = document.createElement('tr');
            accordionRow.classList.add('accordion-row'); // Adiciona uma classe para identificar que é um accordion

            const accordionCell = document.createElement('td');
            accordionCell.setAttribute('colspan', tbody.closest('table').querySelectorAll('th').length);

            const accordionHeader = createAccordionHeader(grupo);
            const accordionContent = createAccordionContent();

            accordionHeader.addEventListener('click', () => {
                toggleAccordionContent(accordionContent);
            });

            accordionCell.appendChild(accordionHeader);
            accordionCell.appendChild(accordionContent);
            accordionRow.appendChild(accordionCell);

            // Insere o accordion no topo, antes da primeira linha
            tbody.insertBefore(accordionRow, firstRow);

            accordionMap[grupo] = accordionContent;
        });

        return accordionMap;
    }


    // Função para criar o cabeçalho clicável do accordion
    function createAccordionHeader(grupo) {
        const accordionHeader = document.createElement('div');
        accordionHeader.classList.add('accordion-header'); // Usa uma classe CSS para o cabeçalho do accordion

        // Exibe o grupo sem o prefixo bandId_ para visualização
        accordionHeader.textContent = `Grupo: ${grupo.replace(`${bandId}_`, '')}`;

        return accordionHeader;
    }

    // Função para criar o conteúdo do accordion
    function createAccordionContent() {
        const accordionContent = document.createElement('div');
        accordionContent.classList.add('accordion-content'); // Usa uma classe CSS para o conteúdo do accordion
        return accordionContent;
    }

    // Função para alternar a exibição do conteúdo do accordion
    function toggleAccordionContent(content) {
        if (content.classList.contains('open')) {
            content.classList.remove('open');
        } else {
            content.classList.add('open');
        }
    }

    function createSelectBox(songId, accordionMap, row, songName) {
        const select = document.createElement('select')
        select.style.maxWidth = '85px'; // Define o tamanho máximo


        // Adiciona a opção "Nenhum" para permitir que a música não pertença a nenhum grupo
        const noneOption = document.createElement('option');
        noneOption.value = ''; // Valor vazio para indicar "Nenhum"
        noneOption.textContent = 'Nenhum';
        select.appendChild(noneOption);

        // Filtra os grupos que começam com o bandId respectivo
        const filteredGroups = grupos.filter(grupo => grupo.startsWith(`${bandId}_`));

        // Cria as opções de select, exibindo o nome do grupo sem o prefixo bandId_
        filteredGroups.forEach(grupo => {
            const option = document.createElement('option');
            option.value = grupo; // Mantém o valor completo para salvar
            option.textContent = grupo.replace(`${bandId}_`, ''); // Remove o prefixo apenas para exibição
            select.appendChild(option);
        });

        // Define o grupo salvo ou a opção "Nenhum" como selecionada
        const savedGroup = GM_getValue(`${bandId}_song_${songId}_group`, ''); // Padrão é vazio ("Nenhum")
        select.value = savedGroup;

        select.addEventListener('change', () => {
            const selectedGroup = select.value;

            if (selectedGroup) {
                // Salva o grupo selecionado
                GM_setValue(`${bandId}_song_${songId}_group`, selectedGroup);
                console.log(`Salvando grupo: ${songName}, Grupo: ${selectedGroup}`);

                // Move a música para o grupo selecionado
                if (accordionMap[selectedGroup]) {
                    accordionMap[selectedGroup].appendChild(row);
                }
            } else {
                // Remove do grupo e retorna à tabela original
                GM_setValue(`${bandId}_song_${songId}_group`, '');
                console.log(`Removendo grupo da música: ${songName}`);

                // Move a linha de volta para o tbody original da tabela
                const originalTableBody = document.getElementById('tablesongs').querySelector('tbody');
                originalTableBody.appendChild(row);
            }
        });

        return select;
    }


    // Função principal que inicializa a tabela e seus componentes
    function initializeTable() {
        const table = document.getElementById('tablesongs');
        if (table) {
            const tbody = table.querySelector('tbody');
            const thead = table.querySelector('thead');
            if (thead) {
                addTableHeader(thead);
            }
            const accordionMap = createAccordions(tbody);
            addSelectBoxesToRows(tbody, accordionMap);
        }
    }

    // Função para remover as linhas que não foram atribuídas a nenhum accordion


    // Função principal que chama todas as outras
    function main() {
        initializeTable();
    }

    // Chamada da função principal
    main();
});