Electron项目:逆向网易云音乐API实现非vip音乐搜索和下载

Electron项目:逆向网易云音乐API实现音乐搜索和下载

这个项目主要是基于Electron并通过逆向网易云音乐的搜索和音乐播放api,分析音乐请求的data参数,复现了加密算法,实现请求。
完整项目已开源到 github,需要自行下载ReverseWangYi-Music

项目效果展示

项目展示

项目结构

该项目主要包含以下核心文件:

  1. main.js - Electron应用的主进程文件。
  2. renderer.js - Electron应用的渲染进程文件。
  3. Encrypt.js - 包含加密功能的辅助类。
  4. NeteaseCloudMusic.js - 用于与网易云音乐API交互和下载音乐的类。
  5. SearchMusic.js - 用于搜索音乐的类。

接下来,我将逐一介绍这些文件。

核心代码分析

Encrypt.js

Encrypt.js文件定义了一个名为Encrypt的类,该类包含了用于加密数据的方法。以下是类中两个核心方法的代码和分析:

const crypto = require('crypto');

class Encrypt {
  constructor(text) {
    this.data = {
      encSecKey: '01ec48cb405730aa77f993a988cc1f5bc1938511d75f49eddc581f2fe2aaf18988853200564b2d4b1312cf6e0bb344425addce5a4c81b38b89a5973900946bd100b0f1865d22d2a8e5dd8be208eb5d6eb2f71309a165daeffe95355e1e44edd65bdf28088fe4f5e835a7d9f7569fc2530f9d17c00b51cfafbe421eb462247ea3',
    };
    this.text = text;
    this.key = '0CoJUm6Qyw8W8jud';
  }

  getFormData() {
    const i = "4JknCzx6uEXUwxpU";
    const firstEncrypt = this.aesEncrypt(this.text, this.key);
    this.data.params = this.aesEncrypt(firstEncrypt, i);
    return this.data;
  }

  aesEncrypt(text, key) {
    const iv = Buffer.from('0102030405060708');
    let padding = 16 - text.length % 16;
    text += String.fromCharCode(padding).repeat(padding);
    const cipher = crypto.createCipheriv('aes-128-cbc', Buffer.from(key, 'utf-8'), iv);
    return cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
  }
}

module.exports = Encrypt;

  • getFormData方法:这个方法是获取加密后的表单数据的核心方法。它首先调用aesEncrypt方法进行一次加密,然后再次调用aesEncrypt方法进行第二次加密,以确保数据的安全性。
  • aesEncrypt方法:这个方法实现了AES加密算法,它接收文本和密钥作为参数,并返回加密后的文本。

main.js

main.js文件是Electron应用的主进程文件,它负责创建应用窗口和处理IPC(Inter-Process Communication)消息。以下是文件中几个核心事件处理函数的代码和分析:

const { app, BrowserWindow, ipcMain } = require('electron');
const SearchMusic = require('./SearchMusic');
const NeteaseCloudMusic = require('./NeteaseCloudMusic');
const fs = require('fs');
const path = require('path');

function createWindow() {
    const win = new BrowserWindow({
        width: 800,
        height: 600,
        webPreferences: {
            nodeIntegration: true,
            contextIsolation: false
        },
    });

    win.loadFile('index.html');
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
    if (process.platform !== 'darwin') {
        app.quit();
    }
});

app.on('activate', () => {
    if (BrowserWindow.getAllWindows().length === 0) {
        createWindow();
    }
});

ipcMain.on('search-song', async (event, songName) => {
    const searchMusic = new SearchMusic(JSON.stringify({ s: songName, limit: 30, type: 1, csrf_token: '' }));
    const songs = await searchMusic.search();
    event.sender.send('search-result', songs);
});

ipcMain.on('download-song', async (event, song) => {
    const neteaseCloudMusic = new NeteaseCloudMusic(song);
    try {
        await neteaseCloudMusic.music();
        event.sender.send('download-status', '下载完成');
    } catch (error) {
        console.error(error);
        event.sender.send('download-status', '下载失败: ' + error.message);
    }
});

ipcMain.on('update-cookie', (event, newCookie) => {
    try {
        // 获取配置文件的路径
        let configPath = path.resolve(__dirname, './headerConfig.js');
        
        // 清除之前的模块缓存
        delete require.cache[require.resolve('./headerConfig.js')];

        // 重新加载配置文件
        let config = require('./headerConfig.js');

        // 更新cookie值
        config.headers['cookie'] = newCookie;

        // 将更新后的配置对象转换为字符串
        let configData = "module.exports = " + JSON.stringify(config, null, 4) + ";";

        // 写入更新后的配置对象到文件
        fs.writeFileSync(configPath, configData);

        // 回应渲染进程
        event.reply('update-cookie-status', '更新成功');
    } catch (error) {
        console.error(error);
        event.reply('update-cookie-status', '更新失败: ' + error.message);
    }
});


  • createWindow函数:这个函数负责创建应用的主窗口。它设置了窗口的初始大小和加载的HTML文件。
  • IPC事件处理函数:这些函数负责处理来自渲染进程的IPC消息。例如,search-song事件处理函数负责处理歌曲搜索请求,而download-song事件处理函数则负责处理歌曲下载请求。

renderer.js

renderer.js文件是Electron应用的渲染进程文件,它负责与HTML页面交互和处理来自主进程的IPC消息。以下是文件中几个核心事件监听器和IPC消息处理函数的代码和分析:

const { ipcRenderer } = require('electron');

function showAlert(title, text, icon) {
    Swal.fire({ title, text, icon });
}

function toggleCookieInputDisplay() {
    const cookieInput = document.getElementById('cookieInput');
    cookieInput.style.display = cookieInput.style.display === 'none' ? 'block' : 'none';
}

document.getElementById('searchButton').addEventListener('click', () => {
    const songName = document.getElementById('songName').value;
    ipcRenderer.send('search-song', songName);
});

ipcRenderer.on('search-result', (event, songs) => {
    const tableBody = document.getElementById('resultTable').getElementsByTagName('tbody')[0];
    tableBody.innerHTML = '';
    songs.forEach(song => {
        const row = tableBody.insertRow();
        row.insertCell(0).textContent = song.song_id;
        row.insertCell(1).textContent = song.song_name;
        row.insertCell(2).textContent = song.singer;
        const downloadCell = row.insertCell(3);
        const downloadButton = document.createElement('button');
        downloadButton.textContent = '下载';
        downloadButton.classList.add('btn', 'btn-success');
        downloadButton.addEventListener('click', () => {
            ipcRenderer.send('download-song', song);
        });
        downloadCell.appendChild(downloadButton);
    });
});

document.getElementById('updateCookieButton').addEventListener('click', () => {
    const cookieInput = document.getElementById('cookieInput');

    if (cookieInput.style.display === 'none') {
        cookieInput.style.display = 'block';
    } else {
        Swal.fire({
            title: '你确定吗?',
            text: "你即将更新Cookie。",
            icon: 'warning',
            showCancelButton: true,
            confirmButtonColor: '#3085d6',
            cancelButtonColor: '#d33',
            confirmButtonText: '是的,更新它!',
            cancelButtonText: '取消'
        }).then((result) => {
            if (result.isConfirmed) {
                const newCookie = cookieInput.value;
                ipcRenderer.send('update-cookie', newCookie);
                toggleCookieInputDisplay();
            } else {
                toggleCookieInputDisplay();
            }
        });
    }
});

ipcRenderer.on('update-cookie-status', (event, status) => {
    const icon = status.includes('失败') ? 'error' : 'success';
    showAlert('更新Cookie状态', status, icon);
});

ipcRenderer.on('download-status', (event, status) => {
    const icon = status.includes('失败') ? 'error' : 'success';
    showAlert('下载状态', status, icon);
});

  • 事件监听器:这些监听器负责处理用户界面的交互。例如,搜索按钮的点击事件监听器负责获取用户输入的歌曲名并发送IPC消息到主进程进行搜索。
  • IPC消息处理函数:这些函数负责响应主进程发送的IPC消息。例如,search-result消息处理函数负责接收主进程发送的搜索结果并更新UI。

NeteaseCloudMusic.js & SearchMusic.js

这两个文件分别定义了NeteaseCloudMusicSearchMusic类,这两个类包含了与网易云音乐API交互的核心方法。在这两个类中,我使用Encrypt类来加密请求数据,并使用axios库来发送HTTP请求到API服务器。

NeteaseCloudMusic.js

const axios = require('axios');
const fs = require('fs');
const Encrypt = require('./Encrypt');
const config = require('./headerConfig');

class NeteaseCloudMusic {
  constructor(song) {
    this.url = 'https://music.163.com/weapi/song/enhance/player/url/v1?csrf_token=';
    this.headers = config.headers;
    this.text = `{"ids":"[${song.song_id}]","level":"standard","encodeType":"aac","csrf_token":""}`;
    this.name = song.song_name;
    this.singer = song.singer;
  }

  async music() {
    const data = new Encrypt(this.text).getFormData();
    try {
      const res = await axios.post(this.url, data, { headers: this.headers });
      const songUrl = res.data.data[0].url;
      console.log(songUrl);

      if (!songUrl) {
        throw new Error('会员歌曲,没有链接');
      }

      const content = await this.download(songUrl);
      this.save(content);
    } catch (error) {
      console.error(error);
      throw error; // 抛出错误让外层的 catch 块捕获
    }
  }

  async download(url) {
    try {
      const res = await axios({
        url,
        method: 'GET',
        responseType: 'arraybuffer', // 说明我们期望得到一个二进制的buffer
      });
      return res.data;
    } catch (error) {
      console.error('Error downloading the song: ', error);
      throw error;
    }
  }

  save(content) {
    if (content) {
      const path = __dirname;
      if (!fs.existsSync(`${path}/musicDownLoad`)) {
        fs.mkdirSync(`${path}/musicDownLoad`);
      }
      const musicPath = `${path}/musicDownLoad/${this.name} ${this.singer}.m4a`;
      if (!fs.existsSync(musicPath)) {
        fs.writeFileSync(musicPath, content);
      }
    }
  }

}

module.exports = NeteaseCloudMusic;
  • 这部分代码主要是实现音乐的下载并保存到指定的 musicDownLoad 目录。

SearchMusic.js

const axios = require('axios');
const Encrypt = require('./Encrypt'); // Assuming Encrypt class is in 'Encrypt.js' file
const config = require('./headerConfig');

class SearchMusic {
    constructor(text) {
        this.url = 'https://music.163.com/weapi/cloudsearch/get/web?csrf_token=';
        this.headers = config.headers;
        this.text = text;
    }

    async search() {
        const data = new Encrypt(this.text).getFormData();
        try {
            const res = await axios.post(this.url, data, { headers: this.headers });
            const songList = res.data.result.songs.map(song => ({
                song_id: song.id,
                song_name: song.name,
                singer: song.ar[0].name,
            }));
            return songList;
        } catch (error) {
            console.error(error);
        }
    }
}

module.exports = SearchMusic;

  • 10
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Evan_422

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值