Greasy Fork

Greasy Fork is available in English.

Phigros谱面信息提取

提取Phigros谱面信息并导出为JSON

// ==UserScript==
// @name         Phigros谱面信息提取
// @namespace    http://tampermonkey.net/
// @version      3.1
// @description  提取Phigros谱面信息并导出为JSON
// @author       FrandreJoestar
// @match        https://mzh.moegirl.org.cn/Phigros/%E8%B0%B1%E9%9D%A2%E4%BF%A1%E6%81%AF
// @icon         https://img.moegirl.org.cn/common/a/ab/Phigros_Icon_3.0.0.png
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    // 创建按钮
    const btn = document.createElement('button');
    btn.innerHTML = '导出谱面信息';
    btn.style.position = 'fixed';
    btn.style.top = '20px';
    btn.style.right = '20px';
    btn.style.zIndex = 9999;
    btn.style.padding = '12px 20px';
    btn.style.backgroundColor = '#673AB7';
    btn.style.color = 'white';
    btn.style.border = 'none';
    btn.style.borderRadius = '8px';
    btn.style.cursor = 'pointer';
    btn.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
    btn.style.fontSize = '16px';
    document.body.appendChild(btn);

    // 提取歌曲基础信息
    const infoExtractor = (table, label) => {
        const rows = table.querySelectorAll('tr');
        for (const row of rows) {
            const cells = Array.from(row.children);
            for (let i = 0; i < cells.length; i++) {
                const cell = cells[i];
                if (cell.textContent.trim() === label) {
                    let valueIndex = i + 1;
                    while (cells[valueIndex]?.hasAttribute('colspan')) {
                        const colspan = parseInt(cells[valueIndex].getAttribute('colspan'), 10) || 1;
                        valueIndex += colspan;
                    }
                    return cells[valueIndex]?.textContent.trim() || null;
                }
            }
        }
        return null;
    };

    // 提取歌曲基础信息
    const mtExtractor = (table, label) => {
        const rows = table.querySelectorAll('tr');
        for (const row of rows) {
            const cells = Array.from(row.children);
            for (let i = 0; i < cells.length; i++) {
                const cell = cells[i];
                if (cell.textContent.trim() === label) {
                    let valueIndex = i + 1;
                    while (cells[valueIndex]?.hasAttribute('colspan')) {
                        const colspan = parseInt(cells[valueIndex].getAttribute('colspan'), 10) || 1;
                        valueIndex += colspan;
                    }
                    return cells[valueIndex-2]?.textContent.trim() || null;
                }
            }
        }
        return null;
    };

    // 多标签提取器(bpm 和 时长)
    const multiLabelExtractor = (table, labels) => {
        for (const label of labels) {
            const value = mtExtractor(table, label);
            if (value) return value;
        }
        return null;
    };

    // 多标签提取器(信息)
    const multiLabelExtractor1 = (table, labels) => {
        for (const label of labels) {
            const value = infoExtractor(table, label);
            if (value) return value;
        }
        return null;
    };

    btn.addEventListener('click', () => {
        const result = Array.from(document.querySelectorAll('table.wikitable')).map(table => {
            const song = {
                name: table.querySelector('th').textContent.trim().replace(/\n/g, ''),
                duration: 0,
                bpm: parseInt(multiLabelExtractor(table, ['BPM'])?.replace(/\D/g, '') || 0),
                composer: multiLabelExtractor1(table, ['曲师', '作曲家']) || '未知',
                illustrator: multiLabelExtractor1(table, ['画师', '插图']) || '未知',
                difficulties: {}
            };

            // 处理时长信息
            const durationText = multiLabelExtractor(table, ['时长', '长度']);
            if (durationText) {
                const cleanTime = durationText.replace(/[^0-9:]/g, '');
                song.duration = cleanTime.split(':').reduce((acc, time) => (acc * 60) + parseInt(time || 0, 10), 0);
            }

            // 提取难度信息
            const diffHeader = Array.from(table.querySelectorAll('th')).find(th => th.textContent.includes('难度'));
            if (diffHeader) {
                let currentRow = diffHeader.closest('tr').nextElementSibling;
                while (currentRow && currentRow.querySelector('td')) {
                    const cells = currentRow.querySelectorAll('td');
                    if (cells.length >= 5) {
                        const diffName = cells[0].querySelector('b')?.textContent.trim();
                        if (diffName) {
                            song.difficulties[diffName] = {
                                level: parseInt(cells[1].textContent, 10) || 0,
                                constant: parseFloat(cells[2].textContent) || 0.0,
                                notes: parseInt(cells[3].textContent, 10) || 0,
                                mapper: cells[4].textContent.trim().replace(/\s+/g, ' ')
                            };
                        }
                    }
                    currentRow = currentRow.nextElementSibling;
                }
            }

            return song;
        });

        // 生成下载文件(保持不变)
        const timestamp = new Date().toISOString().slice(0, 19).replace(/[-T:]/g, '');
        const blob = new Blob([JSON.stringify(result, null, 2)], { type: 'application/json' });
        const url = URL.createObjectURL(blob);
        const link = document.createElement('a');
        link.href = url;
        link.download = `phigros_data_${timestamp}.json`;
        document.body.appendChild(link);
        link.click();
        document.body.removeChild(link);
        URL.revokeObjectURL(url);
    });
})();