最近碰到一个问题就是我个需要让我给他往u盘里下载歌曲,歌曲格式是mp3的,第一时间我想到的就是用qq音乐下载因为我有会员直接下载就可以,但是最后我去看下载好的音乐的时候缺发现是mgg格式。经过一番搜索发现mgg格式是qq音乐专用的,所以未了解决这个问题

1.软件准备

  1. QQ音乐(版本不高于19.51)自用19.43https://cloud.xiaoshuai.online/s/jRfM gthu

  2. 解锁https://tool.liumingye.cn/unlock-music/

  3. 格式工厂(转换ogg到mp3)http://formatfactory.org/CN/download.html

  4. MusicTag(补全音乐歌词封面等信息) https://cloud.xiaoshuai.online/s/k5h6mkop

2.查看QQ音乐版本

首先保证QQ音乐版本不高于19.51,若高于,需要重新安装不高于19.51的版本,下面提供两种该版本下载方法:(!!最新版本的QQ音乐虽然也能下载.mgg文件,但转换工具转成的.ogg文件将会无法播放,并且.ogg文件通过格式工厂再行转换会失败,本人亲测19.43版本下完全可用

目前 Windows 客户端 19.51 或更低版本下载的歌曲文件无需密钥,其余平台的官方正式版本均需要提取密钥。对于高版本的只有获取了这个密钥才能去解析这个mgg格式。所以需要降级.

降级安装包可以从这个网站获取

https://www.zhuanhuanyun.cn/

3.从QQ音乐下载歌曲

4.安装脚本猫(如果只需要一首歌不用安装)

因为音乐解锁网站不能批量下载,所以我写了个针对音乐解锁网站的批量下载工具

// ==UserScript==
// @name         音乐解锁助手
// @namespace    ScriptCat
// @version      0.1.0
// @description  解锁音乐工具增强脚本
// @author       You
// @match        https://tool.liumingye.cn/unlock-music/
// @grant        GM_notification
// @license      MIT
// @homepage     https://scriptcat.org/
// ==/UserScript==

(function() {
    'use strict';
    
    // 初始化日志函数
    const log = (...args) => {
        console.log('[音乐解锁助手]', ...args);
    };

    // 创建下载按钮界面
    function createBatchUI() {
        const container = document.createElement('div');
        container.style.cssText = `
            position: fixed;
            top: 10px;
            left: 10px;
            background: var(--chakra-colors-white);
            padding: 15px;
            border-radius: var(--chakra-radii-lg);
            box-shadow: var(--chakra-shadows-lg);
            z-index: 10000;
            width: 320px;
            user-select: none;
            font-family: var(--chakra-fonts-body);
        `;

        // 添加拖动功能
        let isDragging = false;
        let currentX = 10;  // 初始X位置
        let currentY = 10;  // 初始Y位置
        let initialX;
        let initialY;

        function dragStart(e) {
            if (e.type === "mousedown") {
                initialX = e.clientX - currentX;
                initialY = e.clientY - currentY;
            } else {
                initialX = e.touches[0].clientX - currentX;
                initialY = e.touches[0].clientY - currentY;
            }
            
            if (e.target === titleBar) {
                isDragging = true;
            }
        }

        function dragEnd() {
            isDragging = false;
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                
                if (e.type === "mousemove") {
                    currentX = e.clientX - initialX;
                    currentY = e.clientY - initialY;
                } else {
                    currentX = e.touches[0].clientX - initialX;
                    currentY = e.touches[0].clientY - initialY;
                }

                // 确保不会拖出屏幕
                const rect = container.getBoundingClientRect();
                const maxX = window.innerWidth - rect.width;
                const maxY = window.innerHeight - rect.height;
                
                currentX = Math.min(Math.max(0, currentX), maxX);
                currentY = Math.min(Math.max(0, currentY), maxY);

                container.style.transform = `translate(${currentX}px, ${currentY}px)`;
            }
        }

        // 标题栏
        const titleBar = document.createElement('div');
        titleBar.textContent = '批量下载助手';
        titleBar.style.cssText = `
            margin: -15px -15px 15px -15px;
            padding: 12px 15px;
            background: var(--chakra-colors-blue-500);
            color: white;
            border-radius: var(--chakra-radii-lg) var(--chakra-radii-lg) 0 0;
            font-weight: var(--chakra-fontWeights-bold);
            font-size: 16px;
            cursor: move;
            text-align: center;
        `;

        // 添加拖动事件监听
        titleBar.addEventListener('mousedown', dragStart);
        titleBar.addEventListener('touchstart', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('touchmove', drag);
        document.addEventListener('mouseup', dragEnd);
        document.addEventListener('touchend', dragEnd);

        // 序号设置卡片
        const numberCard = document.createElement('div');
        numberCard.style.cssText = `
            background: var(--chakra-colors-white);
            border: 1px solid var(--chakra-colors-gray-200);
            border-radius: var(--chakra-radii-md);
            padding: 15px;
            margin-bottom: 12px;
        `;

        const numberTitle = document.createElement('div');
        numberTitle.textContent = '序号设置';
        numberTitle.style.cssText = `
            font-weight: 600;
            margin-bottom: 12px;
            color: var(--chakra-colors-gray-700);
            display: flex;
            align-items: center;
        `;

        // 添加复选框到标题旁边
        const checkboxWrapper = document.createElement('div');
        checkboxWrapper.style.cssText = `
            display: flex;
            align-items: center;
            margin-left: auto;
        `;

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.id = 'addPrefix';
        checkbox.style.marginRight = '4px';

        const checkboxLabel = document.createElement('label');
        checkboxLabel.htmlFor = 'addPrefix';
        checkboxLabel.textContent = '启用';
        checkboxLabel.style.fontSize = '14px';

        checkboxWrapper.appendChild(checkbox);
        checkboxWrapper.appendChild(checkboxLabel);
        numberTitle.appendChild(checkboxWrapper);

        // 输入区域使用网格布局
        const inputGrid = document.createElement('div');
        inputGrid.style.cssText = `
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 12px;
        `;

        // 创建所有输入框
        const digitInput = document.createElement('input');
        digitInput.type = 'number';
        digitInput.value = '2';
        digitInput.min = '1';
        digitInput.max = '10';
        digitInput.title = '序号位数';
        digitInput.style.cssText = `
            padding: 4px 8px;
            border: 1px solid var(--chakra-colors-gray-200);
            border-radius: var(--chakra-radii-md);
            text-align: center;
            width: 100%;
        `;

        const startNumInput = document.createElement('input');
        startNumInput.type = 'number';
        startNumInput.value = '1';
        startNumInput.min = '0';
        startNumInput.title = '起始序号';
        startNumInput.style.cssText = digitInput.style.cssText;

        const separatorInput = document.createElement('input');
        separatorInput.type = 'text';
        separatorInput.value = '_';
        separatorInput.title = '分隔符';
        separatorInput.style.cssText = digitInput.style.cssText;

        const prefixInput = document.createElement('input');
        prefixInput.type = 'text';
        prefixInput.placeholder = '自定义前缀(可选)';
        prefixInput.style.cssText = digitInput.style.cssText;

        // 预览卡片
        const previewCard = document.createElement('div');
        previewCard.style.cssText = `
            background: var(--chakra-colors-gray-50);
            border-radius: var(--chakra-radii-md);
            padding: 12px;
            margin-bottom: 12px;
            font-size: 13px;
            color: var(--chakra-colors-gray-600);
            word-break: break-all;
        `;
        previewCard.textContent = 'example.mp3';

        // 添加位置选择
        const positionSelect = document.createElement('select');
        positionSelect.style.cssText = `
            padding: 4px 8px;
            border: 1px solid var(--chakra-colors-gray-200);
            border-radius: var(--chakra-radii-md);
            background: white;
            width: 100%;
        `;

        // 添加位置选项
        const positions = [
            { value: 'before', text: '在名称前' },
            { value: 'after', text: '在名称后' },
            { value: 'custom', text: '指定位置' }
        ];

        positions.forEach(pos => {
            const option = document.createElement('option');
            option.value = pos.value;
            option.textContent = pos.text;
            positionSelect.appendChild(option);
        });

        // 添加位置输入框
        const positionInput = document.createElement('input');
        positionInput.type = 'number';
        positionInput.min = '0';
        positionInput.value = '0';
        positionInput.title = '指定插入位置';
        positionInput.style.cssText = `
            padding: 4px 8px;
            border: 1px solid var(--chakra-colors-gray-200);
            border-radius: var(--chakra-radii-md);
            text-align: center;
            width: 60px;
            display: none;  // 默认隐藏
        `;

        // 监听位置选择变化
        positionSelect.addEventListener('change', () => {
            positionInput.style.display = positionSelect.value === 'custom' ? 'block' : 'none';
            updatePreview();
        });

        // 更新预览函数
        function updatePreview() {
            if (!checkbox.checked) {
                previewCard.textContent = 'example.mp3';
                return;
            }

            const prefix = prefixInput.value.trim();
            const separator = separatorInput.value || '_';
            const paddedIndex = String(parseInt(startNumInput.value) || 1).padStart(parseInt(digitInput.value) || 2, '0');
            const position = positionSelect.value;
            const customPosition = parseInt(positionInput.value) || 0;
            
            let newName;
            const fileName = 'example';
            const ext = '.mp3';

            switch (position) {
                case 'before':
                    newName = prefix 
                        ? `${prefix}${separator}${paddedIndex}${separator}${fileName}${ext}`
                        : `${paddedIndex}${separator}${fileName}${ext}`;
                    break;
                case 'after':
                    newName = prefix 
                        ? `${paddedIndex}${separator}${fileName}${separator}${prefix}${ext}`
                        : `${paddedIndex}${separator}${fileName}${ext}`;
                    break;
                case 'custom':
                    if (prefix) {
                        const beforePos = fileName.slice(0, customPosition);
                        const afterPos = fileName.slice(customPosition);
                        newName = `${paddedIndex}${separator}${beforePos}${separator}${prefix}${separator}${afterPos}${ext}`;
                    } else {
                        newName = `${paddedIndex}${separator}${fileName}${ext}`;
                    }
                    break;
            }
            
            previewCard.textContent = newName;
        }

        // 为所有输入添加事件监听
        [checkbox, digitInput, startNumInput, separatorInput, prefixInput].forEach(input => {
            input.addEventListener('input', updatePreview);
        });

        // 创建输入组
        function createInputGroup(label, input) {
            const group = document.createElement('div');
            group.style.cssText = `
                display: flex;
                flex-direction: column;
                gap: 4px;
            `;
            
            const labelEl = document.createElement('div');
            labelEl.textContent = label;
            labelEl.style.cssText = `
                font-size: 12px;
                color: var(--chakra-colors-gray-600);
            `;
            
            group.appendChild(labelEl);
            group.appendChild(input);
            return group;
        }

        // 前缀设置卡片
        const prefixCard = document.createElement('div');
        prefixCard.style.cssText = numberCard.style.cssText;

        const prefixTitle = document.createElement('div');
        prefixTitle.textContent = '自定义文字设置';
        prefixTitle.style.cssText = numberTitle.style.cssText;

        const prefixContent = document.createElement('div');
        prefixContent.style.cssText = `
            display: flex;
            flex-direction: column;
            gap: 12px;
        `;

        // 添加位置选项
        const positionWrapper = document.createElement('div');
        positionWrapper.style.cssText = `
            display: flex;
            gap: 8px;
            align-items: flex-end;
        `;
        
        positionWrapper.appendChild(createInputGroup('添加位置', positionSelect));
        positionWrapper.appendChild(positionInput);
        
        prefixContent.appendChild(positionWrapper);

        // 分隔符和自定义文字的容器
        const customTextWrapper = document.createElement('div');
        customTextWrapper.style.cssText = `
            display: grid;
            grid-template-columns: 60px 1fr;
            gap: 12px;
            align-items: start;
        `;

        // 修改分隔符输入框
        separatorInput.maxLength = 1;  // 限制只能输入一个字符
        separatorInput.style.textAlign = 'center';

        // 修改自定义文字输入框
        prefixInput.placeholder = '自定义文字';

        // 组装UI
        numberCard.appendChild(numberTitle);
        numberCard.appendChild(inputGrid);
        inputGrid.appendChild(createInputGroup('位数', digitInput));
        inputGrid.appendChild(createInputGroup('起始值', startNumInput));

        customTextWrapper.appendChild(createInputGroup('分隔符', separatorInput));
        customTextWrapper.appendChild(createInputGroup('自定义文字', prefixInput));
        prefixContent.appendChild(customTextWrapper);
        
        prefixCard.appendChild(prefixTitle);
        prefixCard.appendChild(prefixContent);

        // 下载按钮
        const downloadButton = document.createElement('button');
        downloadButton.textContent = '开始批量下载';
        downloadButton.style.cssText = `
            width: 100%;
            padding: 10px;
            background: var(--chakra-colors-blue-500);
            color: white;
            border: none;
            border-radius: var(--chakra-radii-md);
            font-weight: 600;
            cursor: pointer;
            transition: background 0.2s;
            margin-top: 15px;
            &:hover {
                background: var(--chakra-colors-blue-600);
            }
        `;

        // 状态显示
        const status = document.createElement('div');
        status.style.cssText = `
            margin-top: 10px;
            font-size: 13px;
            color: var(--chakra-colors-gray-600);
            text-align: center;
        `;

        // 组装UI
        container.appendChild(titleBar);
        container.appendChild(numberCard);
        container.appendChild(prefixCard);
        container.appendChild(previewCard);
        container.appendChild(downloadButton);
        container.appendChild(status);

        // 添加到body并设置初始位置
        document.body.appendChild(container);
        container.style.transform = `translate(${currentX}px, ${currentY}px)`;

        // 添加位置输入框的事件监听
        positionInput.addEventListener('input', updatePreview);

        // 添加计数显示到状态栏
        function updateMusicCount() {
            // 查找所有音频元素
            const musicItems = document.querySelectorAll('audio');
            // 查找所有已下载的项(通过下载按钮的状态来判断)
            const downloadedItems = document.querySelectorAll('a.chakra-link[style*="opacity: 0.5"]');
            status.textContent = `共 ${musicItems.length} 个文件,已下载 ${downloadedItems.length} 个`;
        }

        // 修改下载按钮点击事件
        downloadButton.addEventListener('click', async () => {
            const downloadLinks = Array.from(document.querySelectorAll('a.chakra-link')).filter(link => 
                link.hasAttribute('download')
            );
            
            if (downloadLinks.length === 0) {
                status.textContent = '没有找到可下载的文件!';
                return;
            }

            status.textContent = `开始下载 ${downloadLinks.length} 个文件...`;
            downloadButton.disabled = true;

            try {
                let count = 0;
                const addPrefix = checkbox.checked;
                const prefix = prefixInput.value.trim();
                const digits = parseInt(digitInput.value) || 2;
                const separator = separatorInput.value || '_';
                const startNum = parseInt(startNumInput.value) || 1;
                const position = positionSelect.value;

                for (const link of downloadLinks) {
                    // 获取音乐项的容器元素
                    const musicItem = link.closest('.chakra-collapse');
                    
                    // 处理文件名
                    if (addPrefix) {
                        const originalName = link.getAttribute('download');
                        const paddedIndex = String(startNum + count).padStart(digits, '0');
                        
                        // 根据选择的位置生成新文件名
                        let newName;
                        const nameWithoutExt = originalName.replace(/\.[^/.]+$/, '');
                        const ext = originalName.match(/\.[^/.]+$/)?.[0] || '';

                        switch (position) {
                            case 'before':
                                newName = prefix 
                                    ? `${prefix}${separator}${paddedIndex}${separator}${originalName}`
                                    : `${paddedIndex}${separator}${originalName}`;
                                break;
                            case 'after':
                                newName = prefix 
                                    ? `${paddedIndex}${separator}${nameWithoutExt}${separator}${prefix}${ext}`
                                    : `${paddedIndex}${separator}${originalName}`;
                                break;
                            case 'custom':
                                // 可以添加更复杂的自定义位置逻辑
                                newName = prefix 
                                    ? `${paddedIndex}${separator}${nameWithoutExt}${separator}${prefix}${ext}`
                                    : `${paddedIndex}${separator}${originalName}`;
                                break;
                        }
                        
                        link.setAttribute('download', newName);
                    }

                    // 确保链接在视图中
                    link.scrollIntoView({ behavior: 'smooth', block: 'center' });
                    
                    // 触发下载
                    link.click();
                    
                    // 标记已下载的音乐项
                    if (musicItem) {
                        musicItem.style.cssText += `
                            border: 2px solid #ff4444 !important;
                            border-radius: 8px;
                            margin: 5px 0;
                            padding: 5px;
                            transform: scale(0.95);
                            transform-origin: center;
                            transition: transform 0.3s ease;
                            opacity: 0.8;
                        `;
                        // 同时标记下载按钮
                        link.style.opacity = '0.5';
                    }

                    count++;
                    updateMusicCount();
                    await new Promise(resolve => setTimeout(resolve, 800));
                }

                status.textContent = `下载完成!共 ${downloadLinks.length} 个文件`;
                GM_notification({
                    text: `已下载 ${count} 个文件`,
                    title: '批量下载完成',
                    timeout: 3000
                });
            } catch (err) {
                status.textContent = '下载过程出错:' + err.message;
                console.error(err);
            } finally {
                downloadButton.disabled = false;
                updateMusicCount();  // 最后更新一次计数
            }
        });

        // 初始化时显示计数
        updateMusicCount();

        // 添加 MutationObserver 监听音乐列表变化
        const observer = new MutationObserver(() => {
            updateMusicCount();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 返回container
        return container;
    }

    // 脚本初始化
    function init() {
        log('脚本已启动');
        
        function checkAndCreateUI() {
            // 检查特定元素是否存在,表明页面已完全加载
            const mainContent = document.querySelector('.chakra-ui-light');
            if (mainContent) {
                // 确保UI只创建一次
                if (!document.querySelector('#batch-download-helper')) {
                    const container = createBatchUI();
                    container.id = 'batch-download-helper';
                    log('批量下载界面已添加');
                }
            } else {
                setTimeout(checkAndCreateUI, 500);  // 减少等待时间
            }
        }

        // 开始检查
        checkAndCreateUI();

        // 监听路由变化(因为是SPA)
        const observer = new MutationObserver(() => {
            checkAndCreateUI();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // 确保在页面准备好后执行
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();

5.将mgg文件解锁为ogg文件

然后手动下载或者批量下载

6.使用格式工厂将ogg文件转为mp3文件

7.使用MusicTag补全音乐信息

  1. 文件-添加目录

  2. 刷新下,要是没有出现歌曲可以试一下

  3. 全选所有文件

  4. 批量-自动匹配标签

  5. 选择需要补全的信息单击确定完成