diff --git a/.env.prod.example b/.env.prod.example index c4d52d7..db98ec6 100644 --- a/.env.prod.example +++ b/.env.prod.example @@ -8,6 +8,9 @@ ENABLE_PROXY = false ## 代理配置 PROXY_URL = "https://your-proxy-url.com/?proxy=" +### 随机IP设置 +## 启用随机中国IP(默认关闭);启用后,所有请求默认使用随机中国IP,除非请求参数randomCNIP显式关闭 +ENABLE_RANDOM_CN_IP = false ### UnblockNeteaseMusic 设置项 ## 启用全局解灰, 无论是否调用参数都会使用解灰(不推荐开启) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 71cb0ec..30fa743 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -14,17 +14,17 @@ body: id: terms attributes: label: 确认事项 - description: 在提交Bug报告前,请确认以下事项 + description: | + 在提交Bug报告前,请确认以下事项: + + - 我已经搜索了现有的issues,确认这不是重复问题 + - 我使用的是最新版本的API, 而且是官方发布的版本,而不是fork或修改版 + - 不处理别人搭建的线上服务的问题,此项目提供任何线上服务不保证质量 + - 如果不是提建议,提 issues 如果不照着模版来将不会优先处理或放着不管 + - 维护项目都是业余时间,精力有限,我只能挑容易解决的issues处理,为了节约双方时间,请尽可能提供足够的有用的信息,给的信息不够我只能根据精力和时间看情况处理,如果模板信息看都不看就删掉,我不会进行任何回复,并且一个月后close掉issue + options: - - label: 我已经搜索了现有的issues,确认这不是重复问题 - required: true - - label: 我使用的是最新版本的API, 而且是官方发布的版本,而不是fork或修改版 - required: true - - label: 不处理别人搭建的线上服务的问题,此项目提供任何线上服务不保证质量 - required: true - - label: 如果不是提建议,提 issues 如果不照着模版来将不会优先处理或放着不管 - required: true - - label: 维护项目都是业余时间,精力有限,我只能挑容易解决的issues处理,为了节约双方时间,请尽可能提供足够的有用的信息,给的信息不够我只能根据精力和时间看情况处理,如果模板信息看都不看就删掉,我不会进行任何回复,并且一个月后close掉issue + - label: 我已确认以上事项 required: true - type: input @@ -36,29 +36,14 @@ body: validations: required: true - - type: dropdown - id: os - attributes: - label: 操作系统或平台 - description: 您在哪个操作系统上遇到了这个问题? - options: - - Windows 10 - - Windows 11 - - Ubuntu 20.04 - - Ubuntu 22.04 - - macOS - - 其他 Linux 发行版 - - 使用部署平台或其他 (请在描述中说明) - validations: - required: true - - type: dropdown id: deployment attributes: - label: 部署平台 - description: + label: 部署方式 + description: 此项目支持本地部署和云平台部署,您使用的是哪种方式? options: - - 我使用的自己的服务器部署 + - 本机直接运行 (node / pm2) + - 本机 Docker 部署 - Vercel - Heroku - Railway diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 12bac4e..299c034 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,5 +1,8 @@ blank_issues_enabled: true contact_links: + - name: ↑请尽量使用议题模板创建议题↑ + url: https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced/issues/new/choose + about: 选择适合的议题模板可以帮助我们更快地定位和解决问题 - name: 提问的艺术 url: https://github.com/ryanhanwu/How-To-Ask-Questions-The-Smart-Way/blob/main/README-zh_CN.md about: 默认所有 Issues 发起者均已了解此处的内容 \ No newline at end of file diff --git a/.github/workflows/Build_Image.yml b/.github/workflows/Build_Image.yml deleted file mode 100644 index 25cb958..0000000 --- a/.github/workflows/Build_Image.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Publish Docker image - -on: - release: - types: [published] - workflow_dispatch: - -permissions: - contents: read - packages: write - -jobs: - build-and-push: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@v6 - - # 读取 package.json 的版本号 - - name: Read package version - id: pkg - run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Log in to GitHub Container Registry - uses: docker/login-action@v3 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - name: Build and push Docker image - uses: docker/build-push-action@v7 - with: - context: . - push: true - tags: | - moefurina/ncm-api:latest - moefurina/ncm-api:${{ env.VERSION }} - ghcr.io/neteasecloudmusicapienhanced/ncm-api:latest - ghcr.io/neteasecloudmusicapienhanced/ncm-api:${{ env.VERSION }} - platforms: linux/amd64,linux/arm64/v8 diff --git a/.github/workflows/build-and-pr.yml b/.github/workflows/build-and-pr.yml deleted file mode 100644 index 9e61945..0000000 --- a/.github/workflows/build-and-pr.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: Build and Create PR - -on: - workflow_dispatch: # 手动触发 - push: - branches: [main] - -jobs: - build: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest, macos-latest] - include: - - os: ubuntu-latest - platform: linux - target: node18-linux-x64 - output: precompiled/app - - os: windows-latest - platform: win - target: node18-win-x64 - output: precompiled/app.exe - - os: macos-latest - platform: macos - target: node18-macos-x64 - output: precompiled/app - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Setup Node.js - uses: actions/setup-node@v6 - with: - node-version: '18' - - - name: Install dependencies - run: | - npm install -g pnpm - pnpm install - - - name: Build for ${{ matrix.platform }} - run: | - npm run pkg${{ matrix.platform }} - env: - PKG_TARGET: ${{ matrix.target }} - - - name: Upload artifacts - uses: actions/upload-artifact@v7 - with: - name: app-${{ matrix.platform }} - path: ${{ matrix.output }} - if-no-files-found: error - - create-pr: - needs: build - runs-on: ubuntu-latest - if: github.event_name == 'workflow_dispatch' # 只在手动触发时创建PR - - steps: - - name: Checkout code - uses: actions/checkout@v6 - - - name: Download all artifacts - uses: actions/download-artifact@v8 - with: - path: precompiled - - - name: Display structure of downloaded files - run: ls -R - - - name: Setup Node.js - uses: actions/setup-node@v6 - - - name: Create new branch and commit - run: | - git config --global user.name 'github-actions[bot]' - git config --global user.email 'github-actions[bot]@users.noreply.github.com' - - BRANCH_NAME="auto-build-$(date +%Y%m%d-%H%M%S)" - echo "BRANCH_NAME=$BRANCH_NAME" >> $GITHUB_ENV - git checkout -b $BRANCH_NAME - - # 复制并整理下载的文件 - mkdir -p precompiled - cp app-win/* precompiled/ || true - cp app-linux/* precompiled/ || true - cp app-macos/* precompiled/ || true - - # 提交更改 - git add precompiled/ - git commit -m "Auto-build: Add compiled binaries for win, linux, macos" || exit 0 - - # 推送到远程仓库 - git push origin $BRANCH_NAME - - - name: Create Pull Request - uses: actions/github-script@v8 - with: - script: | - const { data: pullRequest } = await github.rest.pulls.create({ - owner: context.repo.owner, - repo: context.repo.repo, - title: 'Auto-build: Add compiled binaries for win, linux, macos', - head: '${{ env.BRANCH_NAME }}', - base: 'main', - body: 'This PR contains newly built binaries for Windows, Linux, and macOS platforms.' - }); - console.log(`Created PR #${pullRequest.number}: ${pullRequest.html_url}`); diff --git a/.github/workflows/build-dev.yml b/.github/workflows/build-dev.yml new file mode 100644 index 0000000..f57f971 --- /dev/null +++ b/.github/workflows/build-dev.yml @@ -0,0 +1,53 @@ +name: Build Artifacts + +on: + workflow_dispatch: # 手动触发 + push: + branches: [main] + +jobs: + build: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + include: + - os: ubuntu-latest + platform: linux + target: node18-linux-x64 + output: precompiled/app + - os: windows-latest + platform: win + target: node18-win-x64 + output: precompiled/app.exe + - os: macos-latest + platform: macos + target: node18-macos-x64 + output: precompiled/app + + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: '18' + + - name: Install dependencies + run: | + npm install -g pnpm + pnpm install + + - name: Build for ${{ matrix.platform }} + run: | + npm run pkg${{ matrix.platform }} + env: + PKG_TARGET: ${{ matrix.target }} + + - name: Upload artifacts + uses: actions/upload-artifact@v7 + with: + name: app-${{ matrix.platform }} + path: ${{ matrix.output }} + if-no-files-found: error \ No newline at end of file diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml deleted file mode 100644 index 7376d35..0000000 --- a/.github/workflows/npm.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Release - -on: - release: - types: [published] - -permissions: - contents: read - id-token: write - -jobs: - publish: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v6 - - - uses: actions/setup-node@v6 - with: - node-version: 24 - cache: npm - - - run: npm ci - - - run: npm publish --access public \ No newline at end of file diff --git a/.github/workflows/release-on-version-change.yml b/.github/workflows/release-on-version-change.yml index 9df98f1..13018ee 100644 --- a/.github/workflows/release-on-version-change.yml +++ b/.github/workflows/release-on-version-change.yml @@ -103,7 +103,7 @@ jobs: - name: Setup Node.js uses: actions/setup-node@v6 with: - node-version: 18 + node-version: 24 cache: pnpm @@ -136,7 +136,7 @@ jobs: esac - name: Upload build artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: ${{ matrix.platform }}-binary path: release-artifacts/* @@ -165,17 +165,16 @@ jobs: fetch-tags: true - name: Download build artifacts - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v8 with: path: release-artifacts + merge-multiple: true - - name: Flatten artifacts + - name: List flattened artifacts shell: bash run: | mkdir -p final-artifacts - - find release-artifacts -type f -exec cp {} final-artifacts/ \; - + cp release-artifacts/* final-artifacts/ ls -lah final-artifacts - name: Generate release notes @@ -230,4 +229,87 @@ jobs: body_path: release-notes.md files: final-artifacts/* draft: false - prerelease: false \ No newline at end of file + prerelease: false + + publish-docker: + name: Publish Docker Image + needs: detect + + if: | + needs.detect.outputs.should_release == 'true' && + needs.detect.outputs.tag_exists != 'true' + + runs-on: ubuntu-latest + + permissions: + contents: read + packages: write + + steps: + - name: Checkout repository + uses: actions/checkout@v6 + + - name: Read package version + id: pkg + run: echo "VERSION=$(jq -r .version package.json)" >> $GITHUB_ENV + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push Docker image + uses: docker/build-push-action@v7 + with: + context: . + push: true + tags: | + moefurina/ncm-api:latest + moefurina/ncm-api:${{ env.VERSION }} + ghcr.io/neteasecloudmusicapienhanced/ncm-api:latest + ghcr.io/neteasecloudmusicapienhanced/ncm-api:${{ env.VERSION }} + platforms: linux/amd64,linux/arm64/v8 + + publish-npm: + name: Publish to npm + needs: detect + + if: | + needs.detect.outputs.should_release == 'true' && + needs.detect.outputs.tag_exists != 'true' + + runs-on: ubuntu-latest + + permissions: + contents: read + id-token: write + + steps: + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v6 + with: + version: 9 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - run: pnpm publish --access public --provenance \ No newline at end of file diff --git a/CHANGELOG.MD b/CHANGELOG.MD deleted file mode 100644 index 28a4cfe..0000000 --- a/CHANGELOG.MD +++ /dev/null @@ -1,1418 +0,0 @@ -# 更新日志 -### 4.30.2 | 2026.02.22 -- feat(cloud): 添加云盘上传模式选择功能并优化上传配置 (#116) -- refactor: Refactor random Chinese IP generator (#110) -- fix(server): Correctly inject client IP into request parameters (#108) -- refactor(ui): More elegant display of URL and debug buttons (#109) -- feat: 添加评论统计数据 (#122) - -### 4.30.1 | 2026.02.11 -- feat: Add user playlist endpoints & domain overrides (#105) - -### 4.30.0 | 2026.02.06 -- feat: 新增音乐人黑胶会员任务接口 `/musician/vip/tasks` (#95) -- feat: 自动构建: 添加Windows、Linux、macOS预编译二进制文件 (#88) -- fix: 修复模块未定义问题 -- chore: 更新依赖项 (music-metadata: ^11.11.1 -> ^11.11.2, ansi-escapes: ^7.2.0 -> ^7.3.0, commander: ^14.0.2 -> ^14.0.3) -- chore: 更新GitHub Actions (checkout: v4 -> v6, setup-node: v4 -> v6, upload-artifact: v4 -> v6, download-artifact: v4 -> v7, github-script: v7 -> v8) -- refactor: 注释掉IP地址日志输出以提升隐私保护 -- refactor: 重构前端测试页面, 主要改进: - - 统一使用简洁现代的设计风格 - - 去除渐变背景和复杂动画 - - 使用纯色背景(#f5f5f5)和白色卡片 - - 优化表单布局和交互体验 - - 增强错误处理和加载状态 - - 移除第三方框架依赖(Tailwind、Bootstrap、MDUI) - - 升级Vue 2到Vue 3 - - 将硬编码的配置项移至前端表单 - - 所有页面现在都保持一致的设计语言,简洁清爽,功能完整。 - -### 4.25.0 | 2024.11.16 -- feat: 增加副歌时间、相关歌单推荐接口,原有相关歌单接口已废弃;fix: 将部分易盾白名单接口替换为eapi [#30](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/30) -- fix: 播客上传接口 [#32](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/32) - -### 4.24.0 | 2024.10.20 -- 新增用户徽章,用户状态,听歌足迹,今日收听,歌单导入等接口 [#29](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/29) - -### 4.23.3 | 2024.10.15 -- 更正iPhone请求的cookie [#28](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/28) - -### 4.23.2 | 2024.10.12 -- cookie参数补充 [#27](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/27) - -### 4.23.1 | 2024.10.06 -- 模拟pc客户端的ua和cookie字段 [#26](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/26) - -### 4.23.0 | 2024.10.03 -- 修复登录冻结问题 [#25](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/25) -- 增加歌曲动态封面接口 [#24](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/24) -- 增加用户是否互相关注接口 [#24](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/24) -- 增加歌曲是否喜爱接口 [#24](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/24) -- 增加 杜比全景声 音质的说明。补充客户端下载链接新版接口的说明 [#23](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/23) - - -### 4.22.0 | 2024.07.16 -- 增加部分接口(获取客户端歌曲下载链接 - 新版、当前账号关注的用户/歌手);eapi返回值加密可被控制,默认不加密;代码优化 [#15](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/15) -- 增加`会员下载歌曲记录` `会员本月下载歌曲记录` `已购买单曲` 等接口 [#16](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/16) -- 增加API调试界面 [#16](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/16) - - -### 4.21.2 | 2024.07.09 -- 问题修复 [#14](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/14) - -### 4.21.1 | 2024.06.27 -- 问题修复 [#13](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/13) - -### 4.21.0 | 2024.06.26 -- 新增云盘导入歌曲接口 [#11](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/11) -- 代码优化 [#12](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/12) - -### 4.20.0 | 2024.06.12 -- 一起听从机模式完善 [#8](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/8) -- 云盘上传后缀名判断完善 - -### 4.19.9 | 2024.05.31 -- 云盘上传中文乱码问题修复 [#9](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/issues/9) - -### 4.19.8 | 2024.05.19 -- 云盘上传问题修复 [#7](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/7) - -### 4.19.7 | 2024.05.14 -- 文档更新 -- 类型声明问题修复 [#6](https://gitlab.com/Binaryify/neteasecloudmusicapi/-/merge_requests/6) - -### 4.19.6 | 2024.05.11 -- 例子更新 - -### 4.19.5 | 2024.05.08 -- 中间件问题修复 - -### 4.19.4 | 2024.05.07 -- 参数接收问题修复 - -### 4.19.3 | 2024.05.07 -- 参数调整 - -### 4.19.1 | 2024.05.07 - -- 返回数据优化 - -### 4.19.0 | 2024.05.07 - -- 云贝签到接口替换 -- 新增 eapi 解析接口和使用 demo - -### 4.18.3 | 2024.05.07 - -- 云盘上传问题修复 - -### 4.18.2 | 2024.04.30 - -- 配置和 typo 修复 - -### 4.18.0 | 2024.04.30 - -- 补充游客 deviceid,防止都指向相同账户,避免网络拥堵提示问题 -- ip 设置问题修复 -- 新增`电台排行榜获取`, `获取声音歌词`接口 - -### 4.17.1 | 2024.04.29 - -- song/url 参数问题修复 - -### 4.17.0 | 2024.04.29 - -- ua 更新 -- cookie 处理优化 -- 收藏与取消收藏歌单接口更新 - -### 4.16.6 | 2024.04.25 - -- ua 问题修复 -- 使用 crypto-js 重构 crypto 部分 - -### 4.16.4 | 2024.04.23 - -- 听歌识曲接口和 demo 页面问题修复和更新 - -### 4.16.3 | 2024.04.19 - -- cookie version 更新 - -### 4.16.2 | 2024.04.18 - -- 分享接口问题修复 -- cookie 补全 - -### 4.16.0 | 2024.04.18 - -- ua 更新,修复接口提示网络拥挤问题 -- 支持手动传入 ua 参数,修改 user-agents - -### 4.15.8 | 2024.03.29 - -- 播客声音排序接口更新,补充字段 -- 新增 `删除播客`接口 - -### 4.15.7 | 2024.03.21 - -- 播客分段上传 - -### 4.15.6 | 2024.03.12 - -- 文档和示例更新 - -### 4.15.5 | 2024.02.28 - -- 文档更新 - -### 4.15.3 | 2024.01.29 - -- 文件重命名,防止部署到 Vercel 404 - -### 4.15.2 | 2024.01.29 - -- 上传接口问题修复 - -### 4.15.1 | 2024.01.26 - -- 版本更新提示不展示问题修复 - -### 4.15.0 | 2024.01.26 - -- 新增 `私人 FM 模式选择` 接口 - -### 4.14.2 | 2024.01.23 - -- 文档自启动 - -### 4.14.1 | 2024.01.13 - -- UA 固定,防止触发风控 #1867 - -### 4.14.0 | 2023.12.20 - -- appver 更新 -- fix: /artist/detail 登录状态下调用提示网络拥挤的问题 #1853 -- 歌曲红心数量,歌曲音质详情,本地歌曲文件匹配网易云歌曲信息 #1852 -- crypto.js 重构 #1839 -- 播客列表详情, 播客声音排序 #1843 -- song_url number 类型修复 #1837 -- 更新获取歌曲详情接口的 TypeScript 定义与文档,增加 Hi-Res 类型 #1836 - -### 4.13.8 | 2023.10.27 - -- Docker 构建平台支持调整(只支持 linux/arm64 和 linux/amd64) - -### 4.13.7 | 2023.10.26 - -- 修复 Docker 构建镜像安装依赖速度慢的问题 - -### 4.13.6 | 2023.10.26 - -- 修复匿名登录下,部分接口提示网络太拥挤问题 #1829 - -### 4.13.5 | 2023.10.22 - -- Dockfile 更新,移除 linux/s390x 平台,防止构建失败 - -### 4.13.4 | 2023.10.22 - -- 修复`更新用户信息`接口报错问题 #1824 - -- 部分接口移除`avatarImgId_str`字段 -- 新增日推歌曲不感兴趣接口 #1816 - -### 4.13.3 | 2023.10.10 - -- 添加播客声音搜索接口 #1814 - -### 4.13.2 | 2023.09.25 - -- 修改 wiki 相关接口 - -### 4.13.1 | 2023.09.23 - -- `/verify/getQr` 补充二维码 dataurl 返回数据 - -### 4.13.0 | 2023.09.23 - -- 新增 `专辑简要百科信息` `歌曲简要百科信息` `歌手简要百科信息` `mv简要百科信息` `搜索歌手` `用户贡献内容` `用户贡献条目、积分、云贝数量` #1805 -- 新增 `年度听歌报告` 接口 #1809 - -### 4.12.2 | 2023.09.12 - -- 新增 `播客声音列表`接口 -- 修复 anonymous_token 路径异常 #1795 - -### 4.12.1 | 2023.09.10 - -- 补充 `get/userids`(根据 nickname 获取 userid) 接口 - -### 4.12.0 | 2023.09.10 - -- 听歌识曲接口完善, 补充 demo 页面 - -- NMTID 动态添加 #1792 - -- weapi ua 固定 - -### 4.11.3 | 2023.09.09 - -- 返回内容的`code`统一处理 - -- 单元测试问题修复 - -- song/url 返回排序处理 #1792 - -### 4.11.2 | 2023.09.09 - -- 修复`vercel`无文件创建权限问题 - -### 4.11.1 | 2023.09.08 - -- `anonymous_token` 配置抽离 - -- `anonymous_token` 生成稳定性问题修复 - -### 4.11.0 | 2023.09.07 - -- 新增 `播客搜索`,`播客上传声音`接口 #1789 - -### 4.10.2 | 2023.09.04 - -- 修复 docker 缺失文件问题 #1791 - -### 4.10.1 | 2023.08.21 - -- 补充匿名登录 username 算法, anonymous_token 动态生成 - -### 4.10.0 | 2023.08.21 - -- 禁用 NMTID, 恢复手机登录和邮箱登录 #1788 -- 状态码判断完善,补充 verify 相关接口 #1783 -- 增加对带用户名密码的代理支持 #1787 - -### 4.9.2 | 2023.08.15 - -- 补充 `/vip/info/v2` 接口 - -### 4.9.1 | 2023.08.15 - -- `/vip/info` 接口增加`uid`参数 - -### 4.9.0 | 2023.07.20 - -- 新增沉浸环绕声音质,修改部分文案以同步客户端更改 #1760 - -- 增加对鲸云臻音、鲸云母带音质的支持 #1731 - -- 新增星评馆简要评论获取接口 #1770 - -- 更新 song_detail 返回值类型 #1772 - -- NodeJS 环境要求提高至 v14 - -### 4.8.11 | 2023.05.29 - -- 支持 headers 不携带 cookie 信息 - -### 4.8.10 | 2023.04.07 - -- 补充私信和通知接口 - -### 4.8.9 | 2023.01.18 - -- 补充一起听相关接口 #1677 - -### 4.8.8 | 2023.01.18 - -- 补充腾讯云 serverless 部署说明 - -- 添加逐字歌词接口 #1669 - -- CloudSearch 接口使用 eapi 代替 weapi #1670 - -- axios 相关代码调整 - -### 4.8.7 | 2023.01.04 - -- 手机登录问题修复 [#1658] - -### 4.8.6 | 2023.01.02 - -- 手机登录问题修复 [#1658] - -### 4.8.5 | 2022.12.28 - -- 手机登录问题修复 [#1661] - -### 4.8.4 | 2022.12.19 - -- 邮箱登录问题修复 - -### 4.8.3 | 2022.12.19 - -- 修复了手机号登录接口 [#1653] - -- 增加若干曲风相关接口 [#1623] - -### 4.8.2 | 2022.09.13 - -- 修复 song/url 接口提示网络拥堵的问题 - -- 单元测试修复 - -### 4.8.1 | 2022.09.12 - -- 解决网络拥堵提示问题 - -### 4.7.0 | 2022.09.02 - -- 新增 API: 新版音乐链接获取 [#1583] - -- 新增 API: 歌曲百科简要信息 [#1596] - -- 新增 API: 乐谱相关 API [#1596] - -- ResourceType 补充 [#1497] - -### 4.6.7 | 2022.07.17 - -- 音乐是否可用接口更新 #1544 - -- 获取精品歌单接口更新描述 #1544 - -### 4.6.6 | 2022.06.20 - -- npx 方式运行完善和增加文档说明 - -### 4.6.5 | 2022.06.19 - -- 修复 npx 使用路径错误 - -### 4.6.4 | 2022.06.15 - -- 修复歌单收藏/取消收藏歌曲接口报错问题 #1551 - -### 4.6.3 | 2022.06.15 - -- 修复 npm 包文件缺失的问题 - -### 4.6.2 | 2022.05.30 - -- 修复测试不通过的问题 - -### 4.6.1 | 2022.05.29 - -- 修复请求接口提示需要验证的问题,增加游客登录接口,服务启动更新游客 cookie - -### 4.6.0 | 2022.05.29 - -- 修复请求接口提示需要验证的问题 [#1541](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1541) - -### 4.5.14 | 2022.05.06 - -- 修复获取歌单所有歌曲接口分页问题 [#1524](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1524) - -- 增加支持罗马音歌词返回 [#1523](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1523) - -### 4.5.12 | 2022.04.15 - -- 新增`黑胶时光机`接口 [#1511](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1511) - -### 4.5.11 | 2022.04.06 - -- 修复云盘接口 mimetype 获取错误 [#1503](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1503) - -### 4.5.10 | 2022.03.28 - -- 修复了若干问题 - -- 新增`歌单更新播放量`接口 - -### 4.5.9 | 2022.03.20 - -- 修复云盘上传接口部分文件名格式上传失败的问题 - -- 新增 `/inner/version` 接口,用于获取当前版本号 - -### 4.5.8 | 2022.03.05 - -- 新增歌手粉丝数量接口[#1485](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1485) - -- 新增音乐人任务(新)接口 - -- 更新 `appver` - -### 4.5.6 | 2022.02.12 - -- 歌单封面上传接口缺失参数时返回状态码修正 - -### 4.5.6 | 2022.02.09 - -- 新增重复昵称检测接口 [#1469](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1469) - -### 4.5.5 | 2022.02.09 - -- 搜索接口支持搜索声音 - -### 4.5.4 | 2022.02.09 - -- 修复云盘上传无法获取到文件的问题 - -### 4.5.3 | 2022.02.04 - -- 增加签到进度接口 [#1462](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1462) - -### 4.5.2 | 2022.01.28 - -- 入口文件优化 [#1457](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1457) - -### 4.5.0 | 2022.01.27 - -- app.js 重构[#1453](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1453) - -- 修复 pkg 打包问题 - -### 4.3.0 | 2022.01.22 - -- fix: 发送/删除评论、点赞失败 [#1446](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1446) - -- 增加二进制文件 node 标识 [#1440](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1440) - -- 获取歌单所有歌曲增加 offset 偏移量设置[#1435](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1435) - -### 4.2.0 | 2021.11.23 - -- 合并相同接口(发送文本动态,使用`/share/resource`接口代替);增加歌手视频接口;增加创建共享歌单的用法 [#1402](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1402) - -- 新增最近播放-歌曲,最近播放-视频,最近播放-声音,最近播放-歌单,最近播放-专辑,最近播放-播客等接口 - -### 4.1.1 | 2021.11.20 - -- 添加乐签信息接口 [#1365](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1365) - -### 4.1.0 | 2021.11.20 - -- 修复新版评论返回参数错误问题 [#1393](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1393) [#1377](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1377) - -- 新增获取歌单所有歌曲的 API [#1397](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1397) - -- 新增发送文本动态接口, 获取客户端歌曲下载链接 url 接口 [#1391](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1391) - -### 4.0.23 | 2021.9.15 - -- 修复文件上传设置问题 [#1355](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1355) - -- 修复 interface 不完整问题 [#1356](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1356) - -### 4.0.22 | 2021.9.08 - -- 修复 URI malformed 错误 [#1347](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1347) - -### 4.0.21 | 2021.9.04 - -- 修复云盘上传失败问题 #1332 - -- 修复调用不存在的接口导致 API 崩溃的问题 #1345 - -- 修复代理设置问题 #1343 - -- 新增歌曲相关视频接口(歌曲相关视频 公开隐私歌单) [#1337](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1337) - -### 4.0.20 | 2021.8.20 - -- 手机登录增加验证码登录方式 [#1328](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1328) - -### 4.0.19 | 2021.8.10 - -- 修复若干问题 - -### 4.0.17 | 2021.7.16 - -- 修复若干问题 - -### 4.0.16 | 2021.6.2 - -- 新增歌手粉丝,数字专辑详情,数字专辑销量,音乐人数据概况,音乐人播放趋势,音乐人任务,账号云豆数,领取云豆等接口 [#1252](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1252) - -### 4.0.15 | 2021.5.29 - -- 新增已购单曲,获取 mlog 播放地址,将 mlog id 转为视频 id,vip 成长值,vip 成长值获取记录,vip 任务,领取 vip 成长值等接口 [#1248](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1248) - -### 4.0.14 | 2021.5.28 - -- 增加云贝推歌接口,云贝推歌历史记录接口 [#1246](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1246) - -### 4.0.13 | 2021.5.24 - -- 修复解析问题,改善 Docker 支持 [#1241](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/1241) - -### 4.0.12 | 2021.5.1 - -- 首页-发现接口增加 cursor 参数,refresh 默认设为 false [#1217](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1217) - -- 更新`song/detail` 接口 - -### 4.0.11 | 2021.4.26 - -- 新增云盘歌曲信息匹配纠正接口 [#1212](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1212) - -### 4.0.10 | 2021.4.09 - -- 新增用户历史评论接口 [#1197](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1197) - -### 4.0.9 | 2021.3.13 - -- 新增一起听状态接口 [#1170](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1170) - -### 4.0.8 | 2021.2.27 - -- 加入 vercel 配置文件,支持 vercel 部署 - -### 4.0.7 | 2021.2.27 - -- 更新红心接口,修复红心接口 460 错误问题 [#1151](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1151) - -- 更新发送验证码接口 - -- 注册接口添加 countrycode 参数 [#1152](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1152) - -- 新增绑定手机接口 [#1152](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1152) - -- 更新 song/detail 接口 [#1143](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1143) - -- 用户粉丝接口修改分页参数 [#1161](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1161) - -### 4.0.6 | 2021.2.20 - -- 修复 eapi 接口无法正确解密 response 的问题 [#1138](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1138) - -### 4.0.5 | 2021.2.19 - -- 修复红心接口默认不红心的问题 [#1126](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1126) - -### 4.0.4 | 2021.2.18 - -- 移除云村热评接口(官方下架) [#1111](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1111) -- 更新 app version - -### 4.0.3 | 2021.1.28 - -- 修复云盘接口中文音乐信息乱码的问题 [#1108](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1108) - -### 4.0.2 | 2021.1.18 - -- 修复未绑定手机号对歌单添加或删除歌曲无响应的问题 [#1099](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1099) - -### 4.0.1 | 2021.1.09 - -- 新增歌单详情动态接口 [#1088](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1088) - -### 4.0.0 | 2021.1.03 - -- 新增云盘上传接口,新增二维码登录相关接口和相关 demo(http://localhost:3000/qrlogin.html, http://localhost:3000/cloud.html),更新 d.ts - -- 升级部分接口加密方法("linuxapi" 都替换到了"api") - -- 更新 `login/status` 接口(返回字段和之前不一样) - -### 3.47.5 | 2020.12.20 - -- 更新 appver [#1060](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1060) - -### 3.47.4 | 2020.12.03 - -- 修复收藏的专栏接口无法调用的问题 [#1042](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1042) - -### 3.47.3 | 2020.11.22 - -- 新增歌手详情接口 [#1035](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1035) - -### 3.47.2 | 2020.11.15 - -- 新增关注歌手新歌/新 MV 接口 [#1028](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1028) - -### 3.47.1 | 2020.11.14 - -- 修复使用 post 请求取消喜欢音乐会失败的问题 [#1024](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1024) - -- 新增抱一抱评论和评论抱一抱列表接口 [#1016](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1016) - -- 新增收藏的专栏接口[#1026](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1026) - -### 3.46.1 | 2020.11.7 - -- 修复私信音乐接口出现风险提示的问题 - -### 3.46.0 | 2020.11.7 - -- 添加私信音乐接口 [#1016](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1016) - -- 添加最近联系人接口 - -- 修复用户动态数量不准确问题 [#1010](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1010) - -- 修复 cloudsearch 接口分页问题 [#1015](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1015) - -### 3.45.3 | 2020.11.1 - -- `相似歌手`,`首页-发现-圆形图标入口列表`接口增加匿名 token[#877](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/877) [#988](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/988) - -- 修复`音乐 url`接口 POST 方式手动传入 cookie 报错问题 [#1005](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/1005) - -### 3.45.2 | 2020.10.26 - -- 云贝完成任务接口增加`depositCode`参数 - -### 3.45.1 | 2020.10.25 - -- 修复代理配置失效的问题 [#992](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/992) - -- 修复新碟上架不返回周数据的问题,修复推荐新音乐接口返回数量问题,并添加 limit 参数支持 [#981](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/981) - -- 添加`云贝`相关接口 [#985](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/985) - -- 添加`用户账号信息`接口 - -- 替换接口文件所有 http url 为 https - -### 3.44.0 | 2020.10.17 - -- 更新`电台详情`,`电台节目详情`接口 [#977](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/977) - -#### Breaking change - -- `电台详情`接口更新后数据结构有变化 - -### 3.43.0 | 2020.10.16 - -- 新增`电台订阅者列表`接口 [#971](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/971) - -### 3.42.4 | 2020.10.07 - -- 修复新评论接口分页参数问题 - -### 3.42.3 | 2020.10.05 - -- 修复新评论接口分页参数问题 - -### 3.42.2 | 2020.10.05 - -- 更新歌单详情接口 - -### 3.42.1 | 2020.10.04 - -- 新增`用户绑定信息`,`用户绑定手机`,`新版评论`,`点赞过的视频`,`收藏视频到视频歌单`,`删除视频歌单里的视频`,`最近播放的视频`,`音乐日历`等接口 - -- 创建歌单接口增加`type`参数,可创建视频歌单 - -### 3.41.2 | 2020.09.20 - -- 更新`获取音乐 url`接口,未登录状态返回试听片段 [#897](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/897) - -### 3.41.1 | 2020.09.19 - -- 新增`电台个性推荐接口` [#824](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/824) - -### 3.41.0 | 2020.09.19 - -- 新增`精品歌单标签列表`接口 [#921](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/921) - -- 新增`用户等级信息`接口 [#929](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/929) - -- 增加新接口的 d.ts 文件,修复登录接口的 d.ts 的 countrycode 为非可选属性的错误 - -### 3.40.1 | 2020.09.13 - -- 更新 TypeScript 声明 [#928](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/928) - -### 3.40.0 | 2020.09.12 - -- 新增 TypeScript 声明文件 [#908](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/908) -- 更改随机 UA 相关逻辑[#922](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/922) - -### 3.39.0 | 2020.08.23 - -- 新增`cloudsearch`接口[#893](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/893) -- `mv 地址`接口修改分辨率参数 [#883](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/883) -- 修复新碟上架接口分页问题 [#892](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/892) - -### 3.38.0 | 2020.08.09 - -- 新增`楼层评论`,`歌手全部歌曲`接口 [#864](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/864) [#867](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/867) -- 支持收藏 VIP 或付费歌曲到歌单 [#860](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/860) -- 支持手动传入`realIP` [#863](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/863) - -### 3.37.2 | 2020.08.04 - -- 修复依赖问题 - -### 3.37.0 | 2020.08.03 - -- 新增`更新头像`,`歌单封面上传`接口和相关例子 [#403](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/403) [#857](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/857) -- 加入`axios`依赖 - -### 3.36.0 | 2020.07.26 - -- 新增`全部新碟`,`数字专辑-新碟上架`,`数字专辑&数字单曲-榜单`,`数字专辑-语种风格馆`,`数字专辑详情`接口 [#852](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/852) -- 更新`新碟上架`接口,修改传入参数,返回数据结构有变化 - -### 3.35.0 | 2020.07.18 - -- 新增`首页-发现`,`首页-发现-圆形图标入口列表`接口 [#851](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/851) - -### 3.34.2 | 2020.07.13 - -- 修复`获取用户播放记录`接口参数错误问题 [#849](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/849) - -- 增加`国家编码列表`接口 [#841](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/841) - -### 3.34.1 | 2020.07.06 - -- 登录接口增加 `md5_password` 参数 [#839](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/839) - -### 3.34.0 | 2020.06.25 - -- 排行榜接口废弃 idx 参数,只支持 id 参数,修复返回数据异常问题 [#830](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/830) -- 新增`获取历史日推可用日期列表`,`获取历史日推详细数据` 接口 - -### 3.33.2 | 2020.06.23 - -- 更新每日推荐接口 [#826](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/826) - -### 3.33.1 | 2020.06.15 - -- 修复直接调用时传入 cookie 不生效的问题 [#822](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/822) - -### 3.33.0 | 2020.06.10 - -- 歌手榜支持地区参数 [#818](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/818) -- 新增视频分类列表,推荐视频,获取全部视频列表接口 [#816](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/816) -- 内置 apicache,修复不能在 NodeJS v13 版本使用的问题 [#817](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/817) - -### 3.32.3 | 2020.06.07 - -- 修复 Nodejs 下 cookie 使用格式问题 [#812](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/812) - -### 3.32.2 | 2020.06.05 - -- 新增独家放送列表接口 [#808](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/808) - -### 3.32.1 | 2020.06.03 - -- 新增歌曲排序接口 - -### 3.32.0 | 2020.06.03 - -- 更新排行榜接口,支持传入榜单 id -- 新增榜单顺序调整接口 [#806](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/806) -- 完善错误提示信息 - -### 3.31.1 | 2020.05.19 - -- 修复`cookie`没返回的问题 [#778](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/778) - -### 3.31.0 | 2020.05.18 - -- 支持 `Node.js` 调用,参考`module_example` 文件夹下的 `test.js` - -### 3.30.0 | 2020.05.17 - -- 登录接口返回内容增加`cookie`字段,支持手动传入 cookie - -### 3.29.1 | 2020.05.13 - -- 调整通知接口分页参数 [#761](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/761) - -### 3.29.0 | 2020.05.11 - -- 支持批量删除歌单 [#760](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/760) - -### 3.28.0 | 2020.05.05 - -- 新增获取 mv 点赞转发评论数数据接口 -- 新增获取视频点赞转发评论数数据接口 - -### 3.27.0 | 2020.04.20 - -- 新增购买专辑接口 by [TimonPeng](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/740) - -### 3.26.0 | 2020.04.08 - -#### Breaking change - -- 更新歌手分类列表接口参数,因`cat`参数失效,调整为`type`和`area`参数 - -### 3.25.4 | 2020.03.18 - -- 更新歌词,歌手分类列表接口 - -- 更新文档 - -### 3.25.3 | 2019.11.08 - -- 升级依赖,去除多余依赖 - -- 优化 `test.html` - -### 3.25.2 | 2019.11.07 - -- 修复邮箱登录状态码错误,增加相关提示 [#633](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/633) - -### 3.25.0 | 2019.11.06 - -- 新增 `云村热评` 接口[#626](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/626) - -- 新增 `歌手热门50首歌曲` 接口 - -- 新增`电台24小时节目榜`,`电台24小时主播榜`, `电台最热主播榜`,`电台主播新人榜`,`电台付费精品榜` 接口 [#606](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/606) - -- 调整 `歌手分类列表 ` 接口参数 [#624](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/624) - -### 3.24.2 | 2019.10.28 - -- 修改默认绑定 HOST [#620](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/620) - -### 3.24.1 | 2019.10.25 - -- 修改默认绑定 HOST [#615](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/615) - -### 3.24.0 | 2019.10.22 - -- 新增`类别热门电台` 接口 [#607](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/607) - -### 3.23.0 | 2019.10.16 - -- 修复电台 banner 接口无数据问题[#601](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/601) - -- 更新排行榜 [#602](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/602) - -- 新增`电台排行榜`,`新晋电台榜`,`热门电台榜`接口 [#604](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/604) - -### 3.22.4 | 2019.09.26 - -- 修复私信历史记录分页参数问题,更新文档 [#599](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/599) - -### 3.22.3 | 2019.09.24 - -- 手机号码检测: 添加国家码作为参数,方便检测国外手机号码 [#598](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/598) - -### 3.22.2 | 2019.09.18 - -- 排行榜参数更新,更新文档[#592](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/592) - -### 3.22.1 | 2019.09.12 - -- 支持回复评论[#589](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/589) - -### 3.22.0 | 2019.08.25 - -- 支持 CORS 预检 [#564](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/564) [#578](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/578) - -### 3.21.1 | 2019.08.21 - -- 修复推荐歌单和网易出品 mv 参数错误,更新文档 [#571](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/571) [#572](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/572) - -### 3.21.0 | 2019.08.20 - -- 新增`歌单删除接口`[#570](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/570) - -### 3.20.0 | 2019.08.06 - -- 新增`更新歌单描述`,`更新歌单名`,`更新歌单标签`,`默认搜索关键词` 接口,更新文档[#547](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/547) - -### 3.19.0 | 2019.07.24 - -- 新增`检测手机号码是否已注册`和`初始化昵称`接口[#540](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/540) - -### 3.18.6 | 2019.07.15 - -- 修复注册异常的问题 [#532](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/532) - -### 3.18.5 | 2019.07.14 - -- 修复部分歌曲无法获得播放链接的问题 [#531](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/531) - -### 3.18.3 | 2019.07.04 - -- 修复全部 mv`/mv/all` 接口分页参数错误的问题 [#524](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/524) - -### 3.18.2 | 2019.07.03 - -- 修复听歌打卡接口 `/scrobble` 失效问题 - -### 3.18.1 | 2019.06.30 - -- 评论接口增加 `before` 参数以获取超过 5000 条评论数据 [#521](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/521) - -- 修复 `/msg/comments` 传入参数和文档描述不一致问题 - -### 3.18.0 | 2019.06.29 - -- 新增 `更换绑定手机接口` - -#### Breaking change - -1. 调整注册接口由 `/captch/register` 修改为 `/register/cellphone` - -2. 调整发送短信接口由 `/captch/sent` 修改为 `/captcha/sent` - -3. 调整短信验证接口由 `/captch/verify` 修改为 `/captcha/verify` - -### 3.17.0 | 2019.06.29 - -- 新增 `专辑动态信息` `热搜列表(详细)` 接口,更新文档 - -### 3.16.0 | 2019.06.27 - -- 新增 `收藏/取消收藏专辑` 接口 - -- 调整歌曲评论接口使用客户端版本接口 - -### 3.15.0 | 2019.06.16 - -- 新增`获取视频标签列表`,`网易出品`,`全部mv`接口, `最新 mv` 和 `mv 排行` 接口加入地区参数,更新文档 - -### 3.14.0 | 2019.06.10 - -- 获取用户粉丝列表接口修改请求参数,更新文档 - -### 3.13.1 | 2019.06.09 - -- 修复获取用户粉丝列表接口只能获取 1000 个的问题 - -### 3.13.0 | 2019.05.19 - -- 新增 eapi 算法 (via:[#491](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/491)) - -- 新增 batch 批量请求接口 (via:[#491](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/491)) - -- 用户动态增加分页参数 - -### 3.12.0 | 2019.05.10 - -- 增加`私信内容、我的数字专辑`接口 - -### 3.11.0 | 2019.05.09 - -- 增加`通知-私信、通知-评论、通知-@我、通知-通知、设置、云盘数据详情`接口 - -### 3.10.2 | 2019.05.09 - -- 增加`分享歌曲、歌单、mv、电台、电台节目到动态`接口 - -### 3.10.1 | 2019.05.08 - -- 增加转发动态接口 - -- 增加删除动态接口 - -### 3.9.0 | 2019.05.03 - -- 新增 云盘歌曲删除, 热门话题, 电台 - 推荐类型, 电台 - 非热门类型, 电台 - 今日优选, 心动模式/智能播放等接口 - -- 更新文档:banner 接口 增加 `type` 参数; 获取动态列表接口增加 `pagesize` 和 `lasttime` 参数; 电台 - 付费精选接口修改默认`limit`为 30 - -### 3.8.1 | 2019.04.24 - -- 修复歌词接口出错问题 - -### 3.8.0 | 2019.04.14 - -- 增加注册,发送验证码,校验验证码接口 via:[https://github.com/Binaryify/NeteaseCloudMusicApi/pull/460](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/460) @[KongValley](https://github.com/KongValley) - -### 3.7.1 | 2019.04.09 - -- 修复登录 460 问题 - -### 3.7.0 | 2019.03.20 - -- 修复喜欢音乐接口参数判断问题 - -- 增加歌单收藏者列表接口 - -### 3.6.0 | 2019.03.15 - -- 调整动态评论获取接口 url,使之和其他评论获取接口更统一 - -### 3.5.0 | 2019.03.14 - -- 增加获取动态评论接口 - -- 支持给动态点赞 - -- 支持给动态评论点赞 - -- 支持给动态发送/删除评论 - -### 3.4.0 | 2019.01.29 - -- 增加已收藏专辑列表接口 - -### 3.3.0 | 2019.01.27 - -- 增加视频标签下的视频获取接口 - -- 增加 pac 代理支持 - -### 3.2.0 | 2019.01.19 - -- 增加获取首页新碟上架数据以及更新听歌排行 - -- 更新搜索建议接口 - -### 3.1.0 | 2019.01.06 - -- 修复评论接口返回 460 Cheating 的问题 - -- 新增`已收藏MV`接口,更新文档 - -### 3.0.9 | 2018.12.15 - -- 修复关注异常的问题 #399 - -### 3.0.8 | 2018.12.12 - -- 更新文档 #386 #394 - -- 优化电台节目接口 - -### 3.0.7 | 2018.11.21 - -- 修复歌单详情列表,排行榜,所有榜单失效的问题,更新文档 #380 #381 - -### 3.0.4 | 2018.11.15 - -- 修复 `/song/url` 接口无法返回多个音乐数据的问题 - -### 3.0.3 | 2018.11.09 - -- 修复取消喜欢歌曲失败问题 [#360](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/360) - -- 补充已喜欢音乐列表接口说明文档 [#370](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/370) - -- 默认关闭 debug 模式 [#365](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/365) - -- 更新 Dockerfile 文件 [#367](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/367) - -### 3.0.1 | 2018.10.21 - -- 合并 PR([#351](https://github.com/Binaryify/NeteaseCloudMusicApi/pull/351)) - -- 文档增加 `/top/song` 接口 - -- `/banner` 换成 linux api,返回结构有所变动 - -- `/check/music` 已知 bug 修复 - -### 3.0.0 | 2018.10.14 - -#### 整体 - -- 完善文档,增加之前没写进文档的接口说明 - -- 重写 createRequest 返回 Promise 对象 - -- 模块化路由 - -- 模块化, 剥离 res,req, 方便导出调用 - -- 增加 cookie-parser - -##### 参数修改 - -- `/song/detail` 增加多 id 支持 - -- `/toplist/detail` 移除参数 - -- `/resource/like` 增加参数 `type` - -- `/top/playlist/highquality` 增加分页参数 `before` - -##### 统一参数 - -- `/artist/sub` 与 `artist/unsub` 合并, 用`query.t` - -- `/follow` 中 `query.type` 换成 `query.t` - -- `/comment` 中 `query.action` 换成 `query.t` - -##### URL 重命名 - -- `/video` 改为 `video/url` - -- `/mv` 改为 `mv/detail` - -- `/music/url` 改为 `/song/url` - -##### 转发逻辑修改 - -- `/toplist/artist` 换成 weapi - -- `/mv/url` 去除了 pipe - -##### BUG 修复 - -- `/playlist/create`, `/playlist/update` 被判欺骗,增加 cookie - -##### 路由增删 - -- 删除 `/recommend/dislike` - -- 增加 `/video/sub` (收藏视频), `/mv/sub` (收藏 MV) - -- 增加 `/video/detail` (视频详情) - -- 增加 `/related/allvideo` (相关视频) - -### 2.20.5 | 2018.09.29 - -修复非法参数 403 #335, 修复代理错误 #334 - -### 2.20.4 | 2018.09.27 - -修复点赞失效的问题 - -### 2.20.3 | 2018.09.26 - -- 增加退出登录接口 -- 修正 /check/music 的检查逻辑 -- 优化 Cookies 设置 -- 重构单元测试 - - [by @nondanee](https://github.com/nondanee) - -- 增加 301 需要登录提示信息 - -- 更新文档 - -### 2.20.2 | 2018.09.22 - -增加热门评论和视频评论接口,更新文档 - -### 2.20.1 | 2018.09.17 - -优化版本检查功能 - -### 2.20.0 | 2018.09.06 - -新增版本检查功能 - -### 2.19.0 | 2018.08.29 - -新增获取视频数据接口,新增发送/删除评论接口,修复登录状态接口问题,完善文档 #301,感谢 @izhenyuls - -### 2.17.0 | 2018.08.28 - -新增登录状态查询接口 #302 ,完善文档,完善路由注册 #297 - -### 2.16.0 | 2018.08.09 - -- Fixed #288,#289,#290 - -解决歌曲 URL 请求被判 Cheating,修复私信接收异常 #291 - -### 2.15.0 | 2018.07.30 - -新增相关歌单推荐和付费精选接口,增加歌手列表接口按首字母索引查找参数 - -### 2.14.0 | 2018.07.03 - -修复无法使用邮箱问题 - -### 2.13.0 | 2018.06.05 - -增加自动注册路由的功能,简化路由注册逻辑 - -### 2.12.0 | 2018.05.27 - -更新文档,优化歌单详情接口 - -### 2.11.1 | 2018.05.24 - -更新文档,优化`/dj/program`接口 - -### 2.11.0 | 2018.05.21 - -增加收藏歌手列表&订阅电台列表 - -### 2.10.0 | 2018.05.17 - -歌单操作调整为批量操作 - -### 2.9.9 | 2018.05.16 - -Bug 修复 - -### 2.9.8 | 2018.05.10 - -新增歌手分类列表,收藏/取消收藏歌手接口,新增更新用户信息,更新歌单接口 - -### 2.9.6 | 2018.05.08 - -新增发送私信相关接口,新增新建歌单,收藏/取消收藏歌单接口 - -### 2.9.4 | 2018.05.04 - -新增热搜接口,更新 banner 接口 - -### 2.9.2 | 2018.02.28 - -修复登录失败会崩溃的问题 - -### 2.9.1 | 2018.01.26 - -docker 构建文件的一些增强以及增加访问日志和调试输出 - -### 2.8.9 | 2018.01.24 - -修复歌单详情数据不完整的问题,更新依赖 - -### 2.8.8 | 2018.01.22 - -修复排行榜数据不完整的问题 , 优化部分代码 , 更新文档部分描述 - -### 2.8.6 | 2018.01.16 - -修复歌单详情接口数据不完整的问题 - -### 2.8.5 | 2018.01.16 - -修复评论点赞失败的问题 - -### 2.8.4 | 2018.01.15 - -优化 cookie 设置 - -### 2.8.3 | 2018.01.12 - -优化部分功能和文档 - -### 2.8.2 | 2018.01.05 - -增加 Dockerfile,支持以 Docker 容器模式运行 - -### 2.8.1 | 2018.01.04 - -添加了 proxy 功能 - -### 2.8.0 | 2018.01.04 - -用 'request' 重写了请求函数 - -### 2.7.9 | 2017.12.11 - -更新排行榜接口 , 新增云音乐 ACG 音乐榜 , 云音乐嘻哈榜 - -### 2.7.7 | 2017.11.27 - -更新 / 修复排行榜接口 , 更新 / 修复推荐歌单接口 - -### 2.7.7 | 2017.11.27 - -更新 / 修复排行榜接口 , 更新 / 修复推荐歌单接口 - -### 2.7.2 | 2017.9.7 - -修复搜索接口 offset 参数失效问题 - -### 2.7.0 | 2017.8.21 - -优化刷新登录代码 - -### 2.6.5 | 2017.7.16 - -优化 CORS 设置 - -### 2.6.4 | 2017.7.16 - -添加缓存机制和随机 UA 机制 感谢[@u3u](https://github.com/u3u) -[issue:77](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/77) 优化请求 -代码 感谢 [@huhuime](https://github.com/huhuime) -[issue:83](https://github.com/Binaryify/NeteaseCloudMusicApi/issues/83) - -### 2.6.2 | 2017.7.16 - -修复垃圾桶接口 - -### 2.6.1 | 2017.7.16 - -修复红心接口 - -### 2.6.0 | 2017.6.25 - -修复签到接口 - -### 2.5.9 | 2017.6.14 - -增加启动说明页 - -### 2.5.8 | 2017.6.1 - -修复若干细节问题 - -### 2.5.7 | 2017.5.22 - -修复若干问题 - -### 2.5.6 | 2017.5.14 - -增加动态消息接口 - -### 2.5.5 | 2017.5.10 - -修复 mv 排行榜接口崩溃问题 - -### 2.5.4 | 2017.5.5 - -新增点赞接口 , 更新文档 - -### 2.5.3 | 2017.5.2 - -修复歌手单曲数据空白问题和文档获取歌手单曲 url 描述问题 , 更新文档 - -### 2.5.0 | 2017.4.29 - -增加 mv/ 专辑 / 歌单评论接口 , 增加云盘相关接口 , 增加获取用户动态 / 信息接口 , -增加关注 / 粉丝列表接口 , 增加收藏歌单接口 , 增加相似 mv/ 歌曲 / 用户接口 , 增加 -banner 接口 , 增加刷新登录接口 , 增加电台相关接口 , 补充评论接口 , 更新文档 - -### 2.4.6 | 2017.4.21 - -增加播放 mv 接口 , 更新文档 - -### 2.4.5 | 2017.4.20 - -增加歌手专辑 , 歌手单曲等接口 , 修复 /album 接口描述错误 , 更新文档 - -### 2.4.0 | 2017.4.20 - -增加歌单(网友精选碟 ), 新碟上架 , 热门歌手等接口 , 更新文档 - -### 2.3.4 | 2017.4.20 - -增加歌曲详情接口 , 更新文档 - -### 2.3.0 | 2017.4.15 - -增加排行榜接口 , 更新文档 - -### 2.2.0 |2017.4.14 - -增加私人 FM, 喜欢歌曲 , 垃圾桶 , 每日签到等接口 , 更新文档 - -### 2.1.3 | 2017.4.6 - -改善文档 - -### 2.1.0 | 2017.4.6 - -增加获取评论接口以及对应单元测试 , 增加更新日志 - -### 2.0.0 | 2017.4.1 - -版本升级到 2.0. 增加使用文档 , 完成项目重构 , 增加更完善的单元测试 , 升级 api 到 -v2+, 支持登录并获取用户信息和创建的歌单 , 可通过获取音乐 url 接口获取用户歌单里 -的的音乐 , 获取每日推荐歌单和每日推荐音乐 diff --git a/Dockerfile b/Dockerfile index e8ffa26..115bfa1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,4 +13,4 @@ RUN yarn --network-timeout=100000 EXPOSE 3000 -CMD [ "/sbin/tini", "--", "node", "app.js" ] +CMD [ "/sbin/tini", "--", "node", "app.js" ] \ No newline at end of file diff --git a/README.MD b/README.MD index 4c98afc..d95c7d3 100644 --- a/README.MD +++ b/README.MD @@ -30,7 +30,7 @@ ## 项目简介 -网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署(如果原版诈尸, 我会及时同步 or 归档)。 +网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署 > [!IMPORTANT] > @@ -131,6 +131,7 @@ $ sudo docker run -d -p 3000:3000 ncm-api | **CORS_ALLOW_ORIGIN** | `*` | 允许跨域请求的域名。可填写单个源,或使用逗号分隔多个源(例如 `https://a.com,https://b.com`)。 | | **ENABLE_PROXY** | `false` | 是否启用反向代理功能。 | | **PROXY_URL** | `https://your-proxy-url.com/?proxy=` | 代理服务地址。仅当 `ENABLE_PROXY=true` 时生效。 | +| **ENABLE_RANDOM_CN_IP** | `false` | 是否默认启用随机中国IP。启用后,所有请求默认使用随机中国IP,除非请求参数 `randomCNIP` 显式关闭。 | | **ENABLE_GENERAL_UNBLOCK** | `true` | 是否启用全局解灰(推荐开启)。开启后所有歌曲都尝试自动解锁。 | | **ENABLE_FLAC** | `true` | 是否启用无损音质(FLAC)。 | | **SELECT_MAX_BR** | `false` | 启用无损音质时,是否选择最高码率音质。 | @@ -214,7 +215,7 @@ pnpm test 原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新) -感谢大佬们为逆向eapi, weapi等加密算法所做的贡献 +感谢大佬们为逆向eapi, weapi, xeapi等加密算法所做的贡献 项目参考: diff --git a/generateConfig.js b/generateConfig.js index d60e224..7640065 100644 --- a/generateConfig.js +++ b/generateConfig.js @@ -2,6 +2,7 @@ const fs = require('fs') const path = require('path') const { register_anonimous } = require('./main') const { cookieToJson, generateRandomChineseIP } = require('./util/index') +const { getXeapiPublicKey } = require('./util/xeapiKey') const tmpPath = require('os').tmpdir() async function generateConfig() { @@ -20,5 +21,21 @@ async function generateConfig() { } catch (error) { console.log(error) } + try { + let currentPublicKey = {} + try { + currentPublicKey = JSON.parse( + fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'), + ) + } catch (_) {} + const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId) + fs.writeFileSync( + path.resolve(tmpPath, 'xeapi_public_key'), + JSON.stringify(publicKey), + 'utf-8', + ) + } catch (error) { + console.log(error) + } } module.exports = generateConfig diff --git a/interface.d.ts b/interface.d.ts index fa1a7eb..071338b 100644 --- a/interface.d.ts +++ b/interface.d.ts @@ -1841,3 +1841,724 @@ export function voice_lyric( id: number | string } & RequestBaseConfig, ): Promise + +export function aidj_content_rcmd( + params: { + latitude?: string | number + longitude?: string | number + } & RequestBaseConfig, +): Promise + +export function album_privilege( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function artist_detail_dynamic( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function artist_follow_count( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function broadcast_category_region_get( + params: RequestBaseConfig, +): Promise + +export function broadcast_channel_collect_list( + params: MultiPageConfig & RequestBaseConfig, +): Promise + +export function broadcast_channel_currentinfo( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function broadcast_channel_list( + params: { + categoryId?: string | number + regionId?: string | number + lastId?: string | number + score?: string | number + } & MultiPageConfig & + RequestBaseConfig, +): Promise + +export function broadcast_sub( + params: { + t: SubAction + id: string | number + } & RequestBaseConfig, +): Promise + +export function chart_detail( + params: { + chartCode: string | number + targetId: string | number + targetType: string | number + } & RequestBaseConfig, +): Promise + +export function chart_song_detail( + params: { + chartCode: string | number + targetId: string | number + targetType: string | number + } & RequestBaseConfig, +): Promise + +export function cloud_import( + params: { + md5: string + id?: string | number + bitrate?: string | number + fileSize?: string | number + artist?: string + album?: string + song?: string + } & RequestBaseConfig, +): Promise + +export function cloud_lyric_get( + params: { + uid: string | number + sid: string | number + } & RequestBaseConfig, +): Promise + +export function cloud_upload_complete( + params: { + songId: string | number + resourceId: string | number + md5: string + filename: string + song?: string + artist?: string + album?: string + bitrate?: string | number + } & RequestBaseConfig, +): Promise + +export function cloud_upload_token( + params: { + md5: string + fileSize: string | number + filename: string + bitrate?: string | number + } & RequestBaseConfig, +): Promise + +export function comment_info_list( + params: { + ids?: string + type?: CommentType + } & RequestBaseConfig, +): Promise + +export function comment_report( + params: { + id: string | number + cid: string | number + reason: string | number + } & RequestBaseConfig, +): Promise + +export function creator_authinfo_get( + params: RequestBaseConfig, +): Promise + +export function dj_difm_all_style_channel( + params: { + sources?: string + } & RequestBaseConfig, +): Promise + +export function dj_difm_channel_subscribe( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function dj_difm_channel_unsubscribe( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function dj_difm_playing_tracks_list( + params: { + channelId: string | number + limit?: string | number + source?: string | number + } & RequestBaseConfig, +): Promise + +export function dj_difm_subscribe_channels_get( + params: { + sources?: string + } & RequestBaseConfig, +): Promise + +export function fanscenter_basicinfo_age_get( + params: RequestBaseConfig, +): Promise + +export function fanscenter_basicinfo_gender_get( + params: RequestBaseConfig, +): Promise + +export function fanscenter_basicinfo_province_get( + params: RequestBaseConfig, +): Promise + +export function fanscenter_overview_get( + params: RequestBaseConfig, +): Promise + +export function fanscenter_trend_list( + params: { + startTime?: string | number + endTime?: string | number + type?: string | number + } & RequestBaseConfig, +): Promise + +export function lbs_city_code( + params: { + bizCode?: string + } & RequestBaseConfig, +): Promise + +export function listen_data_realtime_report( + params: { + type?: string + } & RequestBaseConfig, +): Promise + +export function listen_data_report( + params: { + type?: string + endTime?: string | number + } & RequestBaseConfig, +): Promise + +export function listen_data_today_song( + params: RequestBaseConfig, +): Promise + +export function listen_data_total(params: RequestBaseConfig): Promise + +export function listen_data_year_report( + params: RequestBaseConfig, +): Promise + +export function listentogether_accept( + params: { + roomId: string | number + inviterId: string | number + } & RequestBaseConfig, +): Promise + +export function listentogether_end( + params: { + roomId: string | number + } & RequestBaseConfig, +): Promise + +export function listentogether_heatbeat( + params: { + roomId: string | number + songId: string | number + playStatus: string | number + progress: string | number + } & RequestBaseConfig, +): Promise + +export function listentogether_play_command( + params: { + roomId: string | number + commandType: string | number + progress?: string | number + playStatus: string | number + formerSongId: string | number + targetSongId: string | number + clientSeq: string | number + } & RequestBaseConfig, +): Promise + +export function listentogether_room_check( + params: { + roomId: string | number + } & RequestBaseConfig, +): Promise + +export function listentogether_room_create( + params: RequestBaseConfig, +): Promise + +export function listentogether_sync_list_command( + params: { + roomId: string | number + commandType: string | number + userId: string | number + version: string | number + randomList: string + displayList: string + } & RequestBaseConfig, +): Promise + +export function listentogether_sync_playlist_get( + params: { + roomId: string | number + } & RequestBaseConfig, +): Promise + +export function mlog_music_rcmd( + params: { + mvid?: string | number + songid?: string | number + limit?: string | number + } & RequestBaseConfig, +): Promise + +export function music_first_listen_info( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function personal_fm_mode( + params: { + mode: string + submode?: string + limit?: string | number + } & RequestBaseConfig, +): Promise + +export function playlist_category_list( + params: { + cat?: string + limit?: string | number + } & RequestBaseConfig, +): Promise + +export function playlist_detail_rcmd_get( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function playlist_import_name_task_create( + params: { + local?: string + importStarPlaylist?: boolean + ['name']?: string + ['description']?: string + ['id']?: string | number + ['url']?: string + } & RequestBaseConfig, +): Promise + +export function playlist_import_task_status( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function playlist_privacy( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function playmode_song_vector( + params: { + ids: string + } & RequestBaseConfig, +): Promise + +export function radio_sport_get( + params: { + bpm?: string | number + } & RequestBaseConfig, +): Promise + +export function recent_listen_list(params: RequestBaseConfig): Promise + +export function recommend_songs_dislike( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function register_anonimous(params: RequestBaseConfig): Promise + +export function sati_resource_list( + params: { + tag: string + } & RequestBaseConfig, +): Promise + +export function sati_resource_list_more( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function sati_resource_sub( + params: { + id: string | number + cancel?: string | boolean + } & RequestBaseConfig, +): Promise + +export function sati_resource_sub_list( + params: RequestBaseConfig, +): Promise + +export function sati_tag_list(params: RequestBaseConfig): Promise + +export function sati_timescene_resources_get( + params: RequestBaseConfig, +): Promise + +export function search_match( + params: { + title?: string + album?: string + artist?: string + duration?: string | number + md5?: string + } & RequestBaseConfig, +): Promise + +export function search_suggest_pc( + params: { + keyword: string + } & RequestBaseConfig, +): Promise + +export function send_album( + params: { + id: string | number + msg?: string + user_ids: string + } & RequestBaseConfig, +): Promise + +export function send_song( + params: { + id: string | number + msg?: string + user_ids: string + } & RequestBaseConfig, +): Promise + +export function song_chorus( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_creators( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_downlist( + params: MultiPageConfig & RequestBaseConfig, +): Promise + +export function song_download_url_v1( + params: { + id: string | number + level: SoundQualityType + } & RequestBaseConfig, +): Promise + +export function song_dynamic_cover( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_like( + params: { + id: string | number + like?: string | boolean + uid?: string | number + } & RequestBaseConfig, +): Promise + +export function song_like_check( + params: { + ids: string + } & RequestBaseConfig, +): Promise + +export function song_lyrics_mark( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_lyrics_mark_add( + params: { + id: string | number + markId?: string + data?: string + } & RequestBaseConfig, +): Promise + +export function song_lyrics_mark_del( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_lyrics_mark_user_page( + params: MultiPageConfig & RequestBaseConfig, +): Promise + +export function song_monthdownlist( + params: MultiPageConfig & RequestBaseConfig, +): Promise + +export function song_music_detail( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_red_count( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function song_singledownlist( + params: MultiPageConfig & RequestBaseConfig, +): Promise + +export function song_url_match( + params: { + id: string | number + source?: string + } & RequestBaseConfig, +): Promise + +export function song_url_ncmget(params: RequestBaseConfig): Promise + +export function song_url_v1_302( + params: { + id: string | number + level: SoundQualityType + } & RequestBaseConfig, +): Promise + +export function starpick_comments_summary( + params: RequestBaseConfig, +): Promise + +export function summary_annual( + params: { + year: string + } & RequestBaseConfig, +): Promise + +export function threshold_detail_get( + params: RequestBaseConfig, +): Promise + +export function toplist_detail_v2(params: RequestBaseConfig): Promise + +export function ugc_album_get( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function ugc_artist_get( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function ugc_artist_search( + params: { + keyword: string + limit?: string | number + } & RequestBaseConfig, +): Promise + +export function ugc_detail( + params: { + auditStatus?: string + type?: string | number + sortBy?: string + order?: string + } & MultiPageConfig & + RequestBaseConfig, +): Promise + +export function ugc_mv_get( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function ugc_song_get( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function ugc_user_devote(params: RequestBaseConfig): Promise + +export function user_detail_new( + params: { + uid: string | number + } & RequestBaseConfig, +): Promise + +export function user_follow_mixed( + params: { + size?: string | number + cursor?: string | number + scene?: string | number + } & RequestBaseConfig, +): Promise + +export function user_medal( + params: { + uid: string | number + } & RequestBaseConfig, +): Promise + +export function user_mutualfollow_get( + params: { + uid: string | number + } & RequestBaseConfig, +): Promise + +export function user_playlist_collect( + params: { + uid: string | number + } & MultiPageConfig & + RequestBaseConfig, +): Promise + +export function user_playlist_create( + params: { + uid: string | number + } & MultiPageConfig & + RequestBaseConfig, +): Promise + +export function user_social_status( + params: { + uid: string | number + } & RequestBaseConfig, +): Promise + +export function user_social_status_edit( + params: { + type: string | number + iconUrl?: string + content?: string + actionUrl?: string + } & RequestBaseConfig, +): Promise + +export function user_social_status_rcmd( + params: RequestBaseConfig, +): Promise + +export function user_social_status_support( + params: RequestBaseConfig, +): Promise + +export function verify_getQr( + params: { + vid: string | number + type: string | number + token: string + evid: string + sign: string + } & RequestBaseConfig, +): Promise + +export function verify_qrcodestatus( + params: { + qr: string + } & RequestBaseConfig, +): Promise + +export function vip_sign(params: RequestBaseConfig): Promise + +export function vip_sign_info(params: RequestBaseConfig): Promise + +export function vip_tasks_v1( + params: { + id?: string | number + } & RequestBaseConfig, +): Promise + +export function voice_detail( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function voice_upload( + params: { + songFile: { + name: string + data: string | Buffer + } + } & RequestBaseConfig, +): Promise + +export function voicelist_detail( + params: { + id: string | number + } & RequestBaseConfig, +): Promise + +export function voicelist_list( + params: { + voiceListId: string | number + } & MultiPageConfig & + RequestBaseConfig, +): Promise + +export function voicelist_my_created( + params: { + limit?: string | number + } & RequestBaseConfig, +): Promise + +export function voicelist_search( + params: { + keyword?: string + limit?: string | number + offset?: string | number + } & RequestBaseConfig, +): Promise + +export function voicelist_trans( + params: { + radioId?: string | number + programId?: string | number + position?: string | number + } & MultiPageConfig & + RequestBaseConfig, +): Promise diff --git a/module/decrypt.js b/module/decrypt.js new file mode 100644 index 0000000..673b8bc --- /dev/null +++ b/module/decrypt.js @@ -0,0 +1,97 @@ +const { + eapiResDecrypt, + eapiReqDecrypt, + aesDecrypt, + xeapiResDecrypt, +} = require('../util/crypto') +const CryptoJS = require('crypto-js') + +const linuxapiKey = 'rFgB&h#%2?^eDg:Q' + +module.exports = async (query, request) => { + const crypto = query.crypto || 'eapi' + const data = query.data || query.hexString || '' + const isReq = query.isReq !== 'false' + + if (!data) { + return { + status: 400, + body: { code: 400, message: 'data is required' }, + } + } + + try { + let result + switch (crypto) { + case 'eapi': { + const pureHex = data.replace(/\s/g, '') + result = isReq ? eapiReqDecrypt(pureHex) : eapiResDecrypt(pureHex) + break + } + + case 'weapi': { + if (isReq) { + return { + status: 400, + body: { + code: 400, + message: + 'weapi 请求解密需要 RSA 私钥,暂不支持;仅支持 weapi 返回数据解密(e_r=true 时与 eapi 相同)', + }, + } + } + const pureHex = data.replace(/\s/g, '') + result = eapiResDecrypt(pureHex) + break + } + + case 'linuxapi': { + if (isReq) { + const pureHex = data.replace(/\s/g, '') + const decrypted = aesDecrypt(pureHex, linuxapiKey, '', 'hex') + result = JSON.parse(decrypted.toString(CryptoJS.enc.Utf8)) + } else { + result = typeof data === 'string' ? JSON.parse(data) : data + } + break + } + + case 'xeapi': { + if (isReq) { + return { + status: 400, + body: { + code: 400, + message: + 'xeapi 请求解密涉及 X25519 ECDH 密钥交换,流程复杂,暂不支持;仅支持 xeapi 返回数据解密', + }, + } + } + const buf = Buffer.from(data, 'base64') + result = xeapiResDecrypt(buf) + break + } + + case 'api': { + result = typeof data === 'string' ? JSON.parse(data) : data + break + } + + default: + return { + status: 400, + body: { code: 400, message: `未知加密方式: ${crypto}` }, + } + } + + return { + status: 200, + body: { code: 200, data: result }, + } + } catch (error) { + return { + status: 400, + body: { code: 400, message: `解密失败: ${error.message}` }, + } + } +} diff --git a/module/pl_count.js b/module/pl_count.js index 79de58c..bb21ea1 100644 --- a/module/pl_count.js +++ b/module/pl_count.js @@ -1,4 +1,5 @@ // 私信和通知接口 + const createOption = require('../util/option.js') module.exports = (query, request) => { const data = {} diff --git a/module/register_xeapikey.js b/module/register_xeapikey.js new file mode 100644 index 0000000..9372f35 --- /dev/null +++ b/module/register_xeapikey.js @@ -0,0 +1,74 @@ +const { default: axios } = require('axios') +const encrypt = require('../util/crypto') +const { APP_CONF } = require('../util/config.json') + +const generateNonce = () => { + let nonce = '' + for (let i = 0; i < 16; i++) { + nonce += Math.floor(Math.random() * 10).toString() + } + return nonce +} + +module.exports = async (query, request) => { + const nonce = generateNonce() + const timestamp = String(Date.now()) + const deviceId = query.deviceId || global.deviceId || '' + const currentKeyVersion = query.currentKeyVersion || '' + + const data = { + appVersion: '9.1.65', + currentKeyVersion, + deviceId, + nonce, + os: 'android', + requestType: 'active', + signature: encrypt.xeapiSign(timestamp, nonce), + t1: '', + t2: '', + timestamp, + uid: '', + } + + const res = await axios({ + method: 'POST', + url: APP_CONF.apiDomain + '/api/gorilla/anti/crawler/security/key/get', + headers: { + 'User-Agent': + 'NeteaseMusic/9.1.65.240927161425(9001065);Dalvik/2.1.0 (Linux; U; Android 14; 23013RK75C Build/UKQ1.230804.001)', + Cookie: deviceId ? `deviceId=${encodeURIComponent(deviceId)}` : '', + }, + data: new URLSearchParams(data).toString(), + proxy: false, + }) + + if ( + !res.data || + res.data.code !== 200 || + !res.data.data || + !res.data.data.encryptedData + ) { + throw new Error('xeapi public key request failed') + } + if ( + !res.data.data.signature || + encrypt.xeapiSign(res.data.data.timestamp, nonce) !== + res.data.data.signature + ) { + throw new Error('xeapi public key response signature mismatch') + } + + const publicKey = encrypt.xeapiDecryptPublicKey(res.data.data.encryptedData) + if (!publicKey.sk) { + throw new Error('xeapi public key response missing sk') + } + + return { + status: 200, + body: { + ...publicKey, + deviceId, + }, + cookie: [], + } +} diff --git a/module/song_cloud_download.js b/module/song_cloud_download.js new file mode 100644 index 0000000..f246513 --- /dev/null +++ b/module/song_cloud_download.js @@ -0,0 +1,9 @@ +// 从云盘获取歌曲下载链接 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + songId: query.id, + } + return request(`/api/cloud/dowonload`, data, createOption(query, 'eapi')) +} diff --git a/module/song_url_v1.js b/module/song_url_v1.js index 03b3f49..3a9c326 100644 --- a/module/song_url_v1.js +++ b/module/song_url_v1.js @@ -53,5 +53,9 @@ module.exports = async (query, request) => { if (data.level == 'sky') { data.immerseType = 'c51' } - return request(`/api/song/enhance/player/url/v1`, data, createOption(query)) + return request( + `/api/song/enhance/player/url/v1`, + data, + createOption(query, 'xeapi'), + ) } diff --git a/module/vip_sign.js b/module/vip_sign.js index cb396ed..8539ba7 100644 --- a/module/vip_sign.js +++ b/module/vip_sign.js @@ -3,5 +3,9 @@ const createOption = require('../util/option.js') module.exports = (query, request) => { const data = {} - return request(`/api/vip-center-bff/task/sign`, data, createOption(query)) + return request( + `/api/vip-center-bff/task/sign`, + data, + createOption(query, 'weapi'), + ) } diff --git a/module/vip_sign_detail.js b/module/vip_sign_detail.js new file mode 100644 index 0000000..7cf6b97 --- /dev/null +++ b/module/vip_sign_detail.js @@ -0,0 +1,14 @@ +// 黑胶乐签打卡详情 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + signDayTime: query.timestamp, + type: '1', + } + return request( + `/api/vipnewcenter/app/level/user/checkin/history/detail`, + data, + createOption(query, 'eapi'), + ) +} diff --git a/module/vip_sign_history.js b/module/vip_sign_history.js new file mode 100644 index 0000000..9cd3d47 --- /dev/null +++ b/module/vip_sign_history.js @@ -0,0 +1,13 @@ +// 黑胶乐签打卡历史 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + type: '0', + } + return request( + `/api/vipnewcenter/app/minidesk/music/sign/pc`, + data, + createOption(query, 'xeapi'), + ) +} diff --git a/module/vip_sign_info.js b/module/vip_sign_info.js index 8d77f13..96c5091 100644 --- a/module/vip_sign_info.js +++ b/module/vip_sign_info.js @@ -1,4 +1,4 @@ -// 黑胶乐签签到信息 +// 黑胶乐签未来签到信息 const createOption = require('../util/option.js') module.exports = (query, request) => { diff --git a/module/vip_tasks_v1.js b/module/vip_tasks_v1.js new file mode 100644 index 0000000..1794bc8 --- /dev/null +++ b/module/vip_tasks_v1.js @@ -0,0 +1,14 @@ +// 会员任务 - 新版 + +const createOption = require('../util/option.js') +module.exports = (query, request) => { + const data = { + taskType: 'app_vip_task_center', + userId: query.id, + } + return request( + `/api/middle/vip/mission/user/progress/list`, + data, + createOption(query, 'xeapi'), + ) +} diff --git a/package.json b/package.json index 6072c85..1e19314 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@neteasecloudmusicapienhanced/api", - "version": "4.33.0", + "version": "4.34.3", "description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护", "scripts": { "dev": "nodemon app.js", @@ -41,6 +41,14 @@ ], "main": "main.js", "types": "./interface.d.ts", + "repository": { + "type": "git", + "url": "git+https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git" + }, + "bugs": { + "url": "https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced/issues" + }, + "homepage": "https://neteasecloudmusicapienhanced.js.org/", "engines": { "node": ">=12" }, @@ -65,7 +73,7 @@ "data" ], "dependencies": { - "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.1", + "@neteasecloudmusicapienhanced/unblockmusic-utils": "^0.3.2", "axios": "^1.16.1", "crypto-js": "^4.2.0", "dotenv": "^17.4.2", @@ -88,12 +96,12 @@ "@types/express-fileupload": "^1.5.1", "@types/mocha": "^10.0.10", "@types/node": "25.5.0", - "@typescript-eslint/eslint-plugin": "^8.59.4", - "@typescript-eslint/parser": "^8.59.4", + "@typescript-eslint/eslint-plugin": "^8.60.0", + "@typescript-eslint/parser": "^8.60.0", "eslint": "^9.39.4", "eslint-config-prettier": "^10.1.8", "eslint-plugin-html": "^8.1.4", - "eslint-plugin-prettier": "^5.5.5", + "eslint-plugin-prettier": "^5.5.6", "globals": "^17.6.0", "husky": "^9.1.7", "intelli-espower-loader": "^1.1.0", @@ -104,5 +112,9 @@ "power-assert": "^1.6.1", "prettier": "^3.8.3", "typescript": "^5.9.3" + }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c937c73..814f18f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@neteasecloudmusicapienhanced/unblockmusic-utils': - specifier: ^0.3.1 - version: 0.3.1 + specifier: ^0.3.2 + version: 0.3.2 axios: specifier: ^1.16.1 version: 1.16.1 @@ -73,11 +73,11 @@ importers: specifier: 25.5.0 version: 25.5.0 '@typescript-eslint/eslint-plugin': - specifier: ^8.59.4 - version: 8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) + specifier: ^8.60.0 + version: 8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3) '@typescript-eslint/parser': - specifier: ^8.59.4 - version: 8.59.4(eslint@9.39.4)(typescript@5.9.3) + specifier: ^8.60.0 + version: 8.60.0(eslint@9.39.4)(typescript@5.9.3) eslint: specifier: ^9.39.4 version: 9.39.4 @@ -88,8 +88,8 @@ importers: specifier: ^8.1.4 version: 8.1.4 eslint-plugin-prettier: - specifier: ^5.5.5 - version: 5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3) + specifier: ^5.5.6 + version: 5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3) globals: specifier: ^17.6.0 version: 17.6.0 @@ -127,12 +127,12 @@ packages: resolution: {integrity: sha512-W1lG5vUwFvfMd8HVXqdfbuG7RuaSrTCCD8cl8fP8wOivdbtbIg2Db3IWUcgvfxKbbn6ZBGYRW/Zk1MIwK49mgw==} engines: {node: '>=6.9.0'} - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} engines: {node: '>=6.9.0'} - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} '@babel/parser@7.18.4': @@ -222,8 +222,8 @@ packages: '@jridgewell/trace-mapping@0.3.31': resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.1': - resolution: {integrity: sha512-Ul7/5jeUiZKb74GYRmwNj2ekUryTwO4j8CfGSrYKSsJRZlF6/L0FNniQjjpEyQ/6npsrbcmEibMMvn+Z0W4Spw==} + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2': + resolution: {integrity: sha512-H1ckEDXxR+sLUZKzCPhlP8kfSKgEZZp8GSL4+2BZcmCyerwCbSZzMxX9doacHfFjThRjmq90DbsrRnhnKAvrzA==} hasBin: true '@nodelib/fs.scandir@2.1.5': @@ -242,9 +242,9 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@pkgr/core@0.2.9': - resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} - engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} + '@pkgr/core@0.3.6': + resolution: {integrity: sha512-SEeaJLb3qBNF/OaXnaR1NmmBbFYk1zC0ZH/52fATcRPLFg/p791YrcyFFy44Bo9sLaGuSuLp5Q6axbb/O+v/RA==} + engines: {node: ^14.18.0 || >=16.0.0} '@tokenizer/inflate@0.4.1': resolution: {integrity: sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==} @@ -301,63 +301,63 @@ packages: '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@typescript-eslint/eslint-plugin@8.59.4': - resolution: {integrity: sha512-PegsU+XfyJJNjd4+u/k6f9yTyp0lEXXiPopUNobZcIAUJFGICFLN+sP0Rb3JehVmiij1Ph0dFGYqODoRo/2+6A==} + '@typescript-eslint/eslint-plugin@8.60.0': + resolution: {integrity: sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.59.4 + '@typescript-eslint/parser': ^8.60.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.59.4': - resolution: {integrity: sha512-zORHqO/tuhxY1zWuTvMUqddRxpiFJ72xVfcNoWpqdLjs6lfPbuQBJuW4pk+49/uBMy7Ssr4bzgjiKmmDB1UbZQ==} + '@typescript-eslint/parser@8.60.0': + resolution: {integrity: sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.59.4': - resolution: {integrity: sha512-Ly00Vu4oAacfDeHp2Zg85ioNG6l8HG+tN1D7J+xTHSxu9y0awYKJ2zH1rFBn8ZSfuGK+7FxK3Cgl3uAz0aZZLg==} + '@typescript-eslint/project-service@8.60.0': + resolution: {integrity: sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.59.4': - resolution: {integrity: sha512-mUeR/3H1WrTAddJrwut8OoPjfauaztMQmRwV5fQTUyNVJCLiUXXe4lGEyYIL2oFDpP7UtgbGJXCt72wT0z2S3Q==} + '@typescript-eslint/scope-manager@8.60.0': + resolution: {integrity: sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.59.4': - resolution: {integrity: sha512-DLCpnKgD4alVxTBSKulK+gU1KCqOgUXfDRDXh2mZgzokQKa/70ax93I2uVO3m/LLvIAtWZIFoiifudmIqAxpMA==} + '@typescript-eslint/tsconfig-utils@8.60.0': + resolution: {integrity: sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.59.4': - resolution: {integrity: sha512-uonTuPAAKr9XaBGqJ3LjYTh72zy5DyGesljO9gtmk/eFW0W1fRHjnwVYKB35Lm8d5Q5CluEW3gPHjTvZTmgrfA==} + '@typescript-eslint/type-utils@8.60.0': + resolution: {integrity: sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.59.4': - resolution: {integrity: sha512-F1o7WJcCq+bc8dwcO/YsSEOudAH8RDtaOhM6wcAQhcUsFhnWQl81JKy48q1hoxAU0qrzM89+31GYh1515Zde3Q==} + '@typescript-eslint/types@8.60.0': + resolution: {integrity: sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.59.4': - resolution: {integrity: sha512-F+RuOmcDXo4+TPdfd/TCLS3m2nw8gE9XXyZLrA3JBfaA5tz9TtdkyD3YJFmPxulyc2cKbEok/CvFE3MgSLWnag==} + '@typescript-eslint/typescript-estree@8.60.0': + resolution: {integrity: sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.59.4': - resolution: {integrity: sha512-cYXeNAUsG4lJo5dbc1FcKm+JwIWrj1/UpTORsC6tGMjEZ81DYcvIr9/ueikhMa/Y/gDQYGp+YX9/xQrXje5BJw==} + '@typescript-eslint/utils@8.60.0': + resolution: {integrity: sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.59.4': - resolution: {integrity: sha512-U3gxVaDVnuZKhSspW/MzMxE1kq7zOdc072FcSNoqA1I9p8HyKbBFfEHoWckBAMgNMph4MamwS5iTVzFmrnt8TQ==} + '@typescript-eslint/visitor-keys@8.60.0': + resolution: {integrity: sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@unblockneteasemusic/server@0.28.0': @@ -517,11 +517,11 @@ packages: resolution: {integrity: sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==} engines: {node: '>=18'} - brace-expansion@1.1.14: - resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + brace-expansion@1.1.15: + resolution: {integrity: sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==} - brace-expansion@2.1.0: - resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + brace-expansion@2.1.1: + resolution: {integrity: sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==} brace-expansion@5.0.6: resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} @@ -876,8 +876,8 @@ packages: resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} engines: {node: '>= 0.4'} - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} engines: {node: '>= 0.4'} es-set-tostringtag@2.1.0: @@ -951,8 +951,8 @@ packages: resolution: {integrity: sha512-Eno3oPEj3s6AhvDJ5zHhnHPDvXp6LNFXuy3w51fNebOKYuTrfjOHUGwP+mOrGFpR6eOJkO1xkB8ivtbfMjbMjg==} engines: {node: '>=16.0.0'} - eslint-plugin-prettier@5.5.5: - resolution: {integrity: sha512-hscXkbqUZ2sPithAuLm5MXL+Wph+U7wHngPBv9OMWwlP8iaflyxpjTYZkmdgB4/vPIhemRlBEoLrH7UC1n7aUw==} + eslint-plugin-prettier@5.5.6: + resolution: {integrity: sha512-ifetmTcxWfz+4qRW3pH/ujdTq2jQIj59AxJMIN26K5avYgU8dxycUETQonWiW+wPrYXA0j3Try0l1CnwVQtDqQ==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: '@types/eslint': '>=8.0.0' @@ -1311,8 +1311,8 @@ packages: resolution: {integrity: sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ==} engines: {node: '>= 0.4.0'} - hasown@2.0.3: - resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} engines: {node: '>= 0.4'} he@1.2.0: @@ -2373,8 +2373,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - synckit@0.11.12: - resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} + synckit@0.11.13: + resolution: {integrity: sha512-eNRKgb3z66Yp3D2CixVujOUvXLFUTij/zVnV8KRyvFdQwpz7I5DS8UfRkTeLzb64u+dkzDSdelE24izu+zSSUg==} engines: {node: ^14.18.0 || >=16.0.0} tar-fs@2.1.4: @@ -2384,8 +2384,8 @@ packages: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} - tinyexec@1.1.2: - resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + tinyexec@1.2.3: + resolution: {integrity: sha512-g62dB+w1/OEFnPvmX0yd/HnetYITOL+1nJW7kitOycOeAvmbWC/nu0fwmmQ/kupNojqExzyC/T++pST/jRJ2mQ==} engines: {node: '>=18'} tinyglobby@0.2.16: @@ -2469,8 +2469,8 @@ packages: resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} engines: {node: '>= 0.4'} - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + typed-array-length@1.0.8: + resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} typedarray.prototype.slice@1.0.5: @@ -2542,8 +2542,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + which-typed-array@1.1.21: + resolution: {integrity: sha512-zbRA8cVm6io/d5W8uIe2hblzN76/Wm3v/yiythQvr+dpBWeqhPSWIDNj4zOyHi4zKbMK6DN34Xsr9jPHJERAEw==} engines: {node: '>= 0.4'} which@2.0.2: @@ -2655,9 +2655,9 @@ snapshots: '@jridgewell/gen-mapping': 0.3.13 jsesc: 2.5.2 - '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-string-parser@7.29.7': {} - '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-identifier@7.29.7': {} '@babel/parser@7.18.4': dependencies: @@ -2665,8 +2665,8 @@ snapshots: '@babel/types@7.19.0': dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 to-fast-properties: 2.0.0 '@borewit/text-codec@0.2.2': {} @@ -2756,7 +2756,7 @@ snapshots: '@jridgewell/resolve-uri': 3.1.2 '@jridgewell/sourcemap-codec': 1.5.5 - '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.1': + '@neteasecloudmusicapienhanced/unblockmusic-utils@0.3.2': dependencies: '@unblockneteasemusic/server': 0.28.0 axios: 1.16.1 @@ -2782,7 +2782,7 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@pkgr/core@0.2.9': {} + '@pkgr/core@0.3.6': {} '@tokenizer/inflate@0.4.1': dependencies: @@ -2851,14 +2851,14 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 25.5.0 - '@typescript-eslint/eslint-plugin@8.59.4(@typescript-eslint/parser@8.59.4(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.60.0(@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3))(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.59.4(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/type-utils': 8.59.4(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.4(eslint@9.39.4)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/parser': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/type-utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 eslint: 9.39.4 ignore: 7.0.5 natural-compare: 1.4.0 @@ -2867,41 +2867,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.59.4(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/parser@8.60.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.59.4(typescript@5.9.3)': + '@typescript-eslint/project-service@8.60.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3) - '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 debug: 4.4.3 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.59.4': + '@typescript-eslint/scope-manager@8.60.0': dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 - '@typescript-eslint/tsconfig-utils@8.59.4(typescript@5.9.3)': + '@typescript-eslint/tsconfig-utils@8.60.0(typescript@5.9.3)': dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.59.4(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.60.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) - '@typescript-eslint/utils': 8.59.4(eslint@9.39.4)(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) + '@typescript-eslint/utils': 8.60.0(eslint@9.39.4)(typescript@5.9.3) debug: 4.4.3 eslint: 9.39.4 ts-api-utils: 2.5.0(typescript@5.9.3) @@ -2909,14 +2909,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.59.4': {} + '@typescript-eslint/types@8.60.0': {} - '@typescript-eslint/typescript-estree@8.59.4(typescript@5.9.3)': + '@typescript-eslint/typescript-estree@8.60.0(typescript@5.9.3)': dependencies: - '@typescript-eslint/project-service': 8.59.4(typescript@5.9.3) - '@typescript-eslint/tsconfig-utils': 8.59.4(typescript@5.9.3) - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/visitor-keys': 8.59.4 + '@typescript-eslint/project-service': 8.60.0(typescript@5.9.3) + '@typescript-eslint/tsconfig-utils': 8.60.0(typescript@5.9.3) + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/visitor-keys': 8.60.0 debug: 4.4.3 minimatch: 10.2.5 semver: 7.8.1 @@ -2926,20 +2926,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.59.4(eslint@9.39.4)(typescript@5.9.3)': + '@typescript-eslint/utils@8.60.0(eslint@9.39.4)(typescript@5.9.3)': dependencies: '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) - '@typescript-eslint/scope-manager': 8.59.4 - '@typescript-eslint/types': 8.59.4 - '@typescript-eslint/typescript-estree': 8.59.4(typescript@5.9.3) + '@typescript-eslint/scope-manager': 8.60.0 + '@typescript-eslint/types': 8.60.0 + '@typescript-eslint/typescript-estree': 8.60.0(typescript@5.9.3) eslint: 9.39.4 typescript: 5.9.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.59.4': + '@typescript-eslint/visitor-keys@8.60.0': dependencies: - '@typescript-eslint/types': 8.59.4 + '@typescript-eslint/types': 8.60.0 eslint-visitor-keys: 5.0.1 '@unblockneteasemusic/server@0.28.0': @@ -3113,12 +3113,12 @@ snapshots: transitivePeerDependencies: - supports-color - brace-expansion@1.1.14: + brace-expansion@1.1.15: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.1.0: + brace-expansion@2.1.1: dependencies: balanced-match: 1.0.2 @@ -3476,7 +3476,7 @@ snapshots: data-view-byte-offset: 1.0.1 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 es-set-tostringtag: 2.1.0 es-to-primitive: 1.3.0 function.prototype.name: 1.1.8 @@ -3488,7 +3488,7 @@ snapshots: has-property-descriptors: 1.0.2 has-proto: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 internal-slot: 1.1.0 is-array-buffer: 3.0.5 is-callable: 1.2.7 @@ -3517,15 +3517,15 @@ snapshots: typed-array-buffer: 1.0.3 typed-array-byte-length: 1.0.3 typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 + typed-array-length: 1.0.8 unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 + which-typed-array: 1.1.21 es-define-property@1.0.1: {} es-errors@1.3.0: {} - es-object-atoms@1.1.1: + es-object-atoms@1.1.2: dependencies: es-errors: 1.3.0 @@ -3534,7 +3534,7 @@ snapshots: es-errors: 1.3.0 get-intrinsic: 1.3.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 es-to-primitive@1.3.0: dependencies: @@ -3630,12 +3630,12 @@ snapshots: dependencies: htmlparser2: 10.1.0 - eslint-plugin-prettier@5.5.5(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3): + eslint-plugin-prettier@5.5.6(eslint-config-prettier@10.1.8(eslint@9.39.4))(eslint@9.39.4)(prettier@3.8.3): dependencies: eslint: 9.39.4 prettier: 3.8.3 prettier-linter-helpers: 1.0.1 - synckit: 0.11.12 + synckit: 0.11.13 optionalDependencies: eslint-config-prettier: 10.1.8(eslint@9.39.4) @@ -3958,7 +3958,7 @@ snapshots: asynckit: 0.4.0 combined-stream: 1.0.8 es-set-tostringtag: 2.1.0 - hasown: 2.0.3 + hasown: 2.0.4 mime-types: 2.1.35 forwarded@0.2.0: {} @@ -3992,7 +3992,7 @@ snapshots: call-bound: 1.0.4 define-properties: 1.2.1 functions-have-names: 1.2.3 - hasown: 2.0.3 + hasown: 2.0.4 is-callable: 1.2.7 functions-have-names@1.2.3: {} @@ -4008,18 +4008,18 @@ snapshots: call-bind-apply-helpers: 1.0.2 es-define-property: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 function-bind: 1.1.2 get-proto: 1.0.1 gopd: 1.2.0 has-symbols: 1.1.0 - hasown: 2.0.3 + hasown: 2.0.4 math-intrinsics: 1.1.0 get-proto@1.0.1: dependencies: dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-symbol-description@1.1.0: dependencies: @@ -4100,7 +4100,7 @@ snapshots: has@1.0.4: {} - hasown@2.0.3: + hasown@2.0.4: dependencies: function-bind: 1.1.2 @@ -4182,7 +4182,7 @@ snapshots: internal-slot@1.1.0: dependencies: es-errors: 1.3.0 - hasown: 2.0.3 + hasown: 2.0.4 side-channel: 1.1.0 into-stream@6.0.0: @@ -4230,7 +4230,7 @@ snapshots: is-core-module@2.16.2: dependencies: - hasown: 2.0.3 + hasown: 2.0.4 is-core-module@2.9.0: dependencies: @@ -4293,7 +4293,7 @@ snapshots: call-bound: 1.0.4 gopd: 1.2.0 has-tostringtag: 1.0.2 - hasown: 2.0.3 + hasown: 2.0.4 is-set@2.0.3: {} @@ -4314,7 +4314,7 @@ snapshots: is-typed-array@1.1.15: dependencies: - which-typed-array: 1.1.20 + which-typed-array: 1.1.21 is-unicode-supported@0.1.0: {} @@ -4385,7 +4385,7 @@ snapshots: listr2: 9.0.5 picomatch: 4.0.4 string-argv: 0.3.2 - tinyexec: 1.1.2 + tinyexec: 1.2.3 yaml: 2.9.0 listr2@9.0.5: @@ -4469,11 +4469,11 @@ snapshots: minimatch@3.1.5: dependencies: - brace-expansion: 1.1.14 + brace-expansion: 1.1.15 minimatch@9.0.9: dependencies: - brace-expansion: 2.1.0 + brace-expansion: 2.1.1 minimist@1.2.8: {} @@ -4591,7 +4591,7 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-symbols: 1.1.0 object-keys: 1.1.1 @@ -4957,7 +4957,7 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.24.2 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 get-intrinsic: 1.3.0 get-proto: 1.0.1 which-builtin-type: 1.2.1 @@ -5118,7 +5118,7 @@ snapshots: dependencies: dunder-proto: 1.0.1 es-errors: 1.3.0 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 setprototypeof@1.2.0: {} @@ -5268,7 +5268,7 @@ snapshots: define-data-property: 1.1.4 define-properties: 1.2.1 es-abstract: 1.24.2 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 has-property-descriptors: 1.0.2 string.prototype.trimend@1.0.9: @@ -5276,13 +5276,13 @@ snapshots: call-bind: 1.0.9 call-bound: 1.0.4 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string.prototype.trimstart@1.0.8: dependencies: call-bind: 1.0.9 define-properties: 1.2.1 - es-object-atoms: 1.1.1 + es-object-atoms: 1.1.2 string_decoder@1.1.1: dependencies: @@ -5328,9 +5328,9 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - synckit@0.11.12: + synckit@0.11.13: dependencies: - '@pkgr/core': 0.2.9 + '@pkgr/core': 0.3.6 tar-fs@2.1.4: dependencies: @@ -5347,7 +5347,7 @@ snapshots: inherits: 2.0.4 readable-stream: 3.6.2 - tinyexec@1.1.2: {} + tinyexec@1.2.3: {} tinyglobby@0.2.16: dependencies: @@ -5376,7 +5376,7 @@ snapshots: dependencies: gopd: 1.2.0 typedarray.prototype.slice: 1.0.5 - which-typed-array: 1.1.20 + which-typed-array: 1.1.21 ts-api-utils@2.5.0(typescript@5.9.3): dependencies: @@ -5437,7 +5437,7 @@ snapshots: is-typed-array: 1.1.15 reflect.getprototypeof: 1.0.10 - typed-array-length@1.0.7: + typed-array-length@1.0.8: dependencies: call-bind: 1.0.9 for-each: 0.3.5 @@ -5521,7 +5521,7 @@ snapshots: isarray: 2.0.5 which-boxed-primitive: 1.1.1 which-collection: 1.0.2 - which-typed-array: 1.1.20 + which-typed-array: 1.1.21 which-collection@1.0.2: dependencies: @@ -5532,7 +5532,7 @@ snapshots: which-module@2.0.1: {} - which-typed-array@1.1.20: + which-typed-array@1.1.21: dependencies: available-typed-arrays: 1.0.7 call-bind: 1.0.9 diff --git a/public/api.html b/public/api.html index a6f8576..6d09141 100644 --- a/public/api.html +++ b/public/api.html @@ -132,6 +132,7 @@ + diff --git a/public/api_decrypt.html b/public/api_decrypt.html new file mode 100644 index 0000000..a1591de --- /dev/null +++ b/public/api_decrypt.html @@ -0,0 +1,695 @@ + + + + + + API 参数和返回内容解析 + + + + +
+

API 参数和返回内容解析

+ + +
+ 加密方式 + +
+ +
+ 数据类型 +
+
+ + +
+
+ + +
+
+
+ +
+ + +
+ {{ cryptoInfo.hint }} +
+
+ +
+ + + + +
+ +
+
+ + +
+

+    
+ +
+

使用示例

+ 请求示例 + 响应示例 + +
+
+ eapi +

请求参数解密

+
输入 hex 字符串,解密后得到请求 URL 和参数
+ +
+
+ linuxapi +

请求 eparams 解密

+
AES-ECB 解密 linuxapi 的 eparams 参数(hex)
+ +
+
+
+
+ + + + + + + diff --git a/public/docs/_coverpage.md b/public/docs/_coverpage.md index d251cb4..fceae11 100644 --- a/public/docs/_coverpage.md +++ b/public/docs/_coverpage.md @@ -1,13 +1,14 @@ -# 网易云音乐 API Enhanced +# NeteaseCloudMusicAPI Enhanced -> 🔍 网易云音乐API Node.js服务的复兴项目 +> 🎉 全网收集最全的网易云音乐api接口 基于[NeteaseCloudMusicAPI](https://github.com/binaryify/NeteaseCloudMusicApi)的复刻版本 -- 基于原版网易云API新增更多有趣的功能 -- 具备登录接口,多达200多个接口 -- 更完善的文档 +- ⚡ 四种加密模式 · 后端代理 +- 🪛 具备多达200多个接口 +- 📄 更完善的文档 [Github](https://github.com/neteasecloudmusicapienhanced/api-enhanced) -[Get Started](#neteasecloudmusicapienhanced) +[前往本家](https://github.com/binaryify/NeteaseCloudMusicApi) +[快速开始](#neteasecloudmusicapienhanced) -![color](#ffffff) +![color](#ffffff) \ No newline at end of file diff --git a/public/docs/aigen.png b/public/docs/aigen.png new file mode 100644 index 0000000..14a31d9 Binary files /dev/null and b/public/docs/aigen.png differ diff --git a/public/docs/home.md b/public/docs/home.md index b5e232e..dba5c69 100644 --- a/public/docs/home.md +++ b/public/docs/home.md @@ -2,8 +2,6 @@ 网易云音乐 NodeJS API Enhanced -最后更新于: 2026.2.15 - ## 灵感来自 [disoul/electron-cloud-music](https://github.com/disoul/electron-cloud-music) @@ -211,14 +209,19 @@ $ sudo docker run -d -p 3000:3000 netease-music-api ## 调试工具 -- `eapi` 请求参数或返回内容可在 `/eapi_decrypt.html` 里解析 +- 大部分请求参数或返回内容可在 `/api_decrypt.html` 里解析 - 请求参数模式下, 解密结果可直接带到 `/api.html` 继续调试 - 需要返回值加密时, 可传 `e_r=1`, `weapi` 和 `eapi` 都支持 +- 目前支持算法 有 `weapi`, `eapi`, `linuxapi` 和 `xeapi` (xeapi 是一种不加密的特殊算法, 主要用于调试加密前的原始请求参数) ## 接口文档 ### 调用前须知 +AI 生成的图,仅供娱乐() + +![ai generated](./aigen.png) + !> 本项目不提供线上 demo, 只提供在线文档服务, 请不要轻易信任使用他人提供的公开服务,以免发生安全问题,泄露自己的账号和密码 !> 为使用方便,降低门槛, 文档示例接口直接使用了 GET 请求,本项目同时支持 GET/POST 请按实际需求使用 (POST 请求 url 必须添加时间戳,使每次请求 url 不一样,不然请求会被缓存) @@ -261,11 +264,7 @@ $ sudo docker run -d -p 3000:3000 netease-music-api !> ~~因网易增加了网易云盾验证,密码登录暂时不要使用,尽量使用短信验证码登录和二维码登录,否则调用某些接口会触发需要验证的错误~~ -!> ~~二开作者再注: 现在二维码登录也无法使用了, 网易云官方最近查的太严了, 现在尝试调用会提示环境异常, 如果各位有绕过的方法请一定开`Pull Request`~~ - -!> ~~二开作者注: 二维码登录现在是修复了, 但是密码登录和短信登录还是不行, 如果各位有绕过的方法请一定开`Pull Request`~~ - -!> 二开作者注: 在`v4.29.18`版本中修复了密码登录和短信登录的问题, 现在可以正常使用了 +!> 二开作者注: 在`v4.29.18`版本中修复了短信登录的问题, 现在可以正常使用了 #### 1. 手机登录 @@ -1523,7 +1522,7 @@ tags: 歌单标签 1. 歌词行显示开始时间戳 (毫秒) 2. 歌词行显示总时长(毫秒) 3. 逐字显示开始时间戳 (毫秒) -4. 逐字显示时长 (厘秒/0.01s) +4. 逐字显示时长 (毫秒) 5. 未知 6. 文字 @@ -5031,7 +5030,7 @@ let data = encodeURIComponent( **调用例子:** `/vip/sign` -### 黑胶乐签打卡信息 +### 黑胶乐签未来打卡信息 说明: 登录后调用此接口, 获取黑胶乐签打卡信息 @@ -5339,6 +5338,56 @@ let data = encodeURIComponent( 说明 : 调用此接口,可获取城市榜、城市风格榜等指定维度音乐排行榜歌曲列表 +**必选参数 :** + +`chartCode`: 榜单编码,如 `CITY_SONG_CHART`、`CITY_STYLE_SONG_CHART` + +`targetId`: 目标 id,城市榜如 `110000`,城市风格榜如 北京华语流行榜 `110000_1020`。城市风格榜格式通常为 `城市 id_曲风 id`,其中曲风 id 可通过[曲风列表](#曲风列表)接口 `/style/list` 获取。城市榜的城市列表可通过[多级行政区划数据](#多级行政区划数据)接口传入 `bizCode=chart` 获取;城市风格榜的城市列表可通过该接口传空 `bizCode` 获取 + +`targetType`: 目标类型,如 `CITY`、`CITY_STYLE` + +**接口地址 :** `/chart/song/detail` + +**调用例子 :** `/chart/song/detail?chartCode=CITY_STYLE_SONG_CHART&targetId=110000_1020&targetType=CITY_STYLE` + +### 会员任务 - 新版 + +说明 : 登录后调用此接口, 获取会员任务 + +**可选参数** `id`: 用户 id, 传入后可获取指定用户的会员任务, 不传入则获取当前登录用户的会员任务 + +**接口地址 :** `/vip/task/v1` + +**调用例子 :** `/vip/task/v1` `/vip/task/v1?id=32953014` + +### 黑胶乐签详情 + +说明 : 登录后调用此接口, 传入时间戳, 获取黑胶乐签详情 + +**必选参数 :** `timestamp`: 时间戳, 单位毫秒, 如 `1704067200000` 表示 2024 年 12 月 31 日 0 点 (不传入会出现随机的乐签详情) + +**接口地址 :** `/vip/sign/detail` + +**调用例子 :** `/vip/sign/detail` + +### 黑胶乐签历史 + +说明 : 登录后调用此接口, 获取黑胶乐签历史 + +**接口地址 :** `/vip/sign/history` + +**调用例子 :** `/vip/sign/history` + +### 直接获取云盘歌曲下载链接 + +说明 : 调用此接口, 传入云盘歌曲 id, 可直接获取云盘歌曲下载链接 + +**必选参数 :** `id`: 云盘歌曲 id + +**接口地址 :** `/song/cloud/download` + +**调用例子 :** `/song/cloud/download?id=123456789` + ## 离线访问此文档 此文档同时也是 Progressive Web Apps(PWA), 加入了 serviceWorker, 可离线访问 diff --git a/public/docs/icon.png b/public/docs/icon.png deleted file mode 100644 index dc59db5..0000000 Binary files a/public/docs/icon.png and /dev/null differ diff --git a/public/docs/index.html b/public/docs/index.html index 43ca96f..fbf226a 100644 --- a/public/docs/index.html +++ b/public/docs/index.html @@ -108,7 +108,7 @@ name: '网易云音乐 API Enhanced', repo: 'https://github.com/neteasecloudmusicapienhanced/api-enhanced', coverpage: true, - homepage: 'https://cdn.jsdelivr.net/gh/NeteaseCloudMusicApiEnhanced/api-enhanced@main/public/docs/home.md', + homepage: 'home.md', } diff --git a/public/eapi_decrypt.html b/public/eapi_decrypt.html deleted file mode 100644 index fa2d013..0000000 --- a/public/eapi_decrypt.html +++ /dev/null @@ -1,265 +0,0 @@ - - - - - - eapi 参数和返回内容解析 - - - - -
-

eapi 参数和返回内容解析

- -
- - -
- -
-
- - -
-
- - -
-
- - - - -
- -
{{ formatResult(result) }}
-
- -
-

使用示例

- 请求示例 - 响应示例 -
-
- - - - - - - - \ No newline at end of file diff --git a/public/index.html b/public/index.html index de78a17..a9adf3e 100644 --- a/public/index.html +++ b/public/index.html @@ -92,7 +92,7 @@ curl -s {origin}/search?keywords=网易云 听歌识曲 Demo · 云盘上传 · 歌单导入 · - EAPI 解密 · + API 解密 · 一起听示例 · 更新歌单封面示例 · 头像更新示例 diff --git a/public/static/2169.png b/public/static/2169.png deleted file mode 100644 index 10fa00d..0000000 Binary files a/public/static/2169.png and /dev/null differ diff --git a/public/static/2170.png b/public/static/2170.png new file mode 100644 index 0000000..e6d08b3 Binary files /dev/null and b/public/static/2170.png differ diff --git a/server.js b/server.js index a28f081..06d7055 100644 --- a/server.js +++ b/server.js @@ -303,8 +303,13 @@ async function constructServer(moduleDefs) { // 参数注入客户端IP const obj = [...params] const options = obj[2] || {} - if (!options.randomCNIP) { - let ip = req.ip + let ip = '' + + if (options.randomCNIP) { + ip = global.cnIp + // logger.info('Using random Chinese IP for request:', ip) + } else { + ip = req.ip if (ip.substring(0, 7) == '::ffff:') { ip = ip.substring(7) @@ -313,10 +318,11 @@ async function constructServer(moduleDefs) { ip = global.cnIp } // logger.info('Requested from ip:', ip) - obj[2] = { - ...options, - ip, - } + } + + obj[2] = { + ...options, + ip, } return request(...obj) diff --git a/util/config.json b/util/config.json index e9a57f0..189e5fb 100644 --- a/util/config.json +++ b/util/config.json @@ -11,6 +11,7 @@ }, "APP_CONF": { "apiDomain": "https://interface.music.163.com", + "xeapiDomain": "https://interface3.music.163.com", "domain": "https://music.163.com", "encrypt": true, "encryptResponse": false, diff --git a/util/crypto.js b/util/crypto.js index a6301e7..775b1b8 100644 --- a/util/crypto.js +++ b/util/crypto.js @@ -1,4 +1,5 @@ const CryptoJS = require('crypto-js') +const crypto = require('crypto') const forge = require('node-forge') const zlib = require('zlib') const iv = '0102030405060708' @@ -9,6 +10,13 @@ const publicKey = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB -----END PUBLIC KEY-----` const eapiKey = 'e82ckenh8dichen8' +const xeapiStaticKey = Buffer.from( + 'ab1d5a430f6bb04a3f01e81ddd72bd916d5ce591248ac128714806d7f8fb1b84', + 'hex', +) +const xeapiSignKey = + 'mUHCwVNWJbunMqAHf5MImuirT6plvs6VSFW62MGHstFQxhBGdEoIhLItH3djc4+FB/OKty3+lL2rGeoFBpVe5g==' +const x25519SpkiPrefix = Buffer.from('302a300506032b656e032100', 'hex') const aesEncrypt = (text, mode, key, iv, format = 'base64') => { let encrypted = CryptoJS.AES.encrypt( @@ -141,13 +149,172 @@ const decrypt = (cipher) => { return decryptedBytes } +const aesEcbEncrypt = (key, plaintext) => { + const cipher = crypto.createCipheriv(`aes-${key.length * 8}-ecb`, key, null) + return Buffer.concat([cipher.update(Buffer.from(plaintext)), cipher.final()]) +} + +const aesEcbDecrypt = (key, ciphertext) => { + const decipher = crypto.createDecipheriv( + `aes-${key.length * 8}-ecb`, + key, + null, + ) + return Buffer.concat([decipher.update(ciphertext), decipher.final()]) +} + +const createX25519PublicKey = (raw) => { + // Node's crypto API expects X25519 public keys as DER SubjectPublicKeyInfo. + // The Android SDK stores only the 32-byte raw key, so prepend the fixed + // RFC 8410 SPKI header for id-X25519 before importing it. + return crypto.createPublicKey({ + key: Buffer.concat([x25519SpkiPrefix, raw]), + format: 'der', + type: 'spki', + }) +} + +const deriveX25519AesKey = (sharedSecret, ephemeralPublicKey) => { + const prk = crypto + .createHmac('sha256', Buffer.alloc(32)) + .update(sharedSecret.length ? sharedSecret : Buffer.alloc(32)) + .digest() + return crypto + .createHmac('sha256', prk) + .update(Buffer.concat([ephemeralPublicKey, Buffer.from([1])])) + .digest() + .subarray(0, 16) +} + +const xeapiSign = (timestamp, nonce) => { + return crypto + .createHmac('sha256', xeapiSignKey) + .update(String(timestamp) + nonce) + .digest('base64') +} + +const xeapiMidTransform = (ciphertext) => { + const random = crypto.randomBytes(16) + const xored = Buffer.alloc(ciphertext.length) + for (let i = 0; i < ciphertext.length; i++) { + xored[i] = ciphertext[i] ^ random[i & 0x0f] + } + const b64 = Buffer.from(xored.toString('base64')) + const rot = b64.length ? (random[0] & 0x0f) % b64.length : 0 + return Buffer.concat([random, b64.subarray(rot), b64.subarray(0, rot)]) +} + +const xeapiEncryptS = (dynamicKey, publicKeyState, os) => { + const peerRaw = Buffer.from(publicKeyState.publicKey, 'base64') + const peerKey = createX25519PublicKey(peerRaw) + const { publicKey, privateKey } = crypto.generateKeyPairSync('x25519') + const ephemeralRaw = Buffer.from( + publicKey.export({ format: 'der', type: 'spki' }), + ).subarray(-32) + const sharedSecret = crypto.diffieHellman({ + privateKey, + publicKey: peerKey, + }) + const aesKey = deriveX25519AesKey(sharedSecret, ephemeralRaw) + const iv = crypto.randomBytes(12) + const cipher = crypto.createCipheriv('aes-128-gcm', aesKey, iv) + const plaintext = Buffer.from( + `${dynamicKey.toString('base64')}|${os}|${publicKeyState.sk || ''}`, + ) + const encrypted = Buffer.concat([cipher.update(plaintext), cipher.final()]) + return Buffer.concat([ephemeralRaw, iv, encrypted, cipher.getAuthTag()]) +} + +const buildXeapiPlaintext = (uri, data, options = {}) => { + const fields = {} + const contentType = + options.contentType || 'application/x-www-form-urlencoded;charset=utf-8' + const mediaType = contentType.split(';', 1)[0].toLowerCase() + if (mediaType !== 'application/x-www-form-urlencoded') { + fields.contentType = contentType + } + + const method = (options.method || 'POST').toUpperCase() + if (method !== 'POST') fields.method = method + + const url = new URL(uri, 'https://interface.music.163.com') + if (url.search) fields.queryString = url.search.slice(1) + + if (data !== undefined && data !== null) { + const bodyData = { ...data } + delete bodyData.e_r + const body = Buffer.from(new URLSearchParams(bodyData).toString()) + fields.body = body.toString('base64') + } + + if (fields.queryString) { + fields.queryString += '&e_r=true' + } else { + fields.queryString = 'e_r=true' + } + return JSON.stringify(fields) +} + +const xeapi = (uri, data, options = {}) => { + const publicKeyState = options.publicKeyState + if (!publicKeyState) { + throw new Error('xeapi publicKeyState is required') + } + const activeSessionKey = options.sessionKey + ? Buffer.from(String(options.sessionKey)) + : null + const activeSessionId = options.sessionId || '' + const dynamicKey = activeSessionKey || crypto.randomBytes(16) + const plaintext = Buffer.from(buildXeapiPlaintext(uri, data, options)) + + const b = aesEcbEncrypt( + dynamicKey, + xeapiMidTransform(aesEcbEncrypt(xeapiStaticKey, plaintext)), + ) + const s = xeapiEncryptS(dynamicKey, publicKeyState, options.os || 'android') + const r = aesEcbEncrypt( + xeapiStaticKey, + Buffer.from( + `${publicKeyState.version}|${activeSessionKey ? activeSessionId : ''}`, + ), + ) + + return { + B: b.toString('base64'), + S: s.toString('base64'), + R: r.toString('base64'), + } +} + +const xeapiResDecrypt = (body) => { + const decrypted = aesEcbDecrypt(eapiKey, body) + const plaintext = + decrypted[0] === 0x1f && decrypted[1] === 0x8b + ? zlib.gunzipSync(decrypted) + : decrypted + return JSON.parse(plaintext.toString()) +} + +const xeapiDecryptPublicKey = (encryptedData) => { + return JSON.parse( + aesEcbDecrypt( + xeapiStaticKey, + Buffer.from(encryptedData, 'base64'), + ).toString(), + ) +} + module.exports = { weapi, linuxapi, eapi, + xeapi, decrypt, aesEncrypt, aesDecrypt, eapiReqDecrypt, eapiResDecrypt, + xeapiSign, + xeapiResDecrypt, + xeapiDecryptPublicKey, } diff --git a/util/option.js b/util/option.js index a4c2615..4891109 100644 --- a/util/option.js +++ b/util/option.js @@ -5,7 +5,10 @@ const createOption = (query, crypto = '') => { ua: query.ua || '', proxy: query.proxy, realIP: query.realIP, - randomCNIP: query.randomCNIP || false, + randomCNIP: + process.env.ENABLE_RANDOM_CN_IP === 'true' + ? !['false', false].includes(query.randomCNIP) + : ['true', true].includes(query.randomCNIP), e_r: query.e_r || undefined, domain: query.domain || '', checkToken: query.checkToken || false, diff --git a/util/request.js b/util/request.js index a71553d..1b12d2e 100644 --- a/util/request.js +++ b/util/request.js @@ -2,6 +2,7 @@ const encrypt = require('./crypto') const CryptoJS = require('crypto-js') const { default: axios } = require('axios') +const logger = require('./logger') const { PacProxyAgent } = require('pac-proxy-agent') const http = require('http') const https = require('https') @@ -23,6 +24,20 @@ const anonymous_token = fs.readFileSync( path.resolve(tmpPath, './anonymous_token'), 'utf-8', ) +const xeapiPublicKeyPath = path.resolve(tmpPath, './xeapi_public_key') +let xeapi_public_key = null +const loadXeapiPublicKey = () => { + if (!xeapi_public_key && fs.existsSync(xeapiPublicKeyPath)) { + try { + xeapi_public_key = JSON.parse( + fs.readFileSync(xeapiPublicKeyPath, 'utf-8'), + ) + } catch (error) { + console.log('[ERR]', error) + } + } + return xeapi_public_key +} // 预先绑定常用函数和常量 const floor = Math.floor @@ -95,9 +110,13 @@ const userAgentMap = { // 预先定义常量 const DOMAIN = APP_CONF.domain const API_DOMAIN = APP_CONF.apiDomain +const XEAPI_DOMAIN = APP_CONF.xeapiDomain const ENCRYPT_RESPONSE = APP_CONF.encryptResponse const SPECIAL_STATUS_CODES = new Set([201, 302, 400, 502, 800, 801, 802, 803]) +let xeapiSessionId = '' +let xeapiSessionKey = '' + // chooseUserAgent函数 const chooseUserAgent = (crypto, uaType = 'pc') => { return (userAgentMap[crypto] && userAgentMap[crypto][uaType]) || '' @@ -216,6 +235,52 @@ const createRequest = (uri, data, options) => { url = (options.domain || DOMAIN) + '/api/linux/forward' break + case 'xeapi': + const xeapiPublicKey = loadXeapiPublicKey() + if (!xeapiPublicKey) { + throw new Error('xeapi public key is missing') + } + const xeapiOs = cookie.os === 'android' ? cookie.os : 'android' + const xeapiAppver = + cookie.os === 'android' && cookie.appver ? cookie.appver : '9.1.65' + const xeapiOsver = + cookie.os === 'android' && cookie.osver ? cookie.osver : '16' + const xeapiBuildver = cookie.buildver || now().toString().substr(0, 10) + headers['User-Agent'] = options.ua || chooseUserAgent('api', 'android') + headers['X-Client-Enc-State'] = 'ENCRYPTED' + headers['x-aeapi'] = true + headers['content-type'] = + 'application/x-www-form-urlencoded;charset=utf-8' + headers['x-deviceid'] = cookie.deviceId + headers['x-os'] = xeapiOs + headers['x-osver'] = xeapiOsver + headers['x-appver'] = xeapiAppver + headers['x-sdeviceid'] = cookie.sDeviceId || cookie.deviceId + headers['x-buildver'] = xeapiBuildver + if (cookie.MUSIC_U) headers['x-music-u'] = cookie.MUSIC_U + const xeapiCookie = { + ...cookie, + os: xeapiOs, + osver: xeapiOsver, + appver: xeapiAppver, + buildver: xeapiBuildver, + deviceId: cookie.deviceId, + sDeviceId: cookie.sDeviceId || cookie.deviceId, + } + headers['Cookie'] = cookieObjToString(xeapiCookie) + url = (options.domain || XEAPI_DOMAIN) + '/xeapi/' + uri.substr(5) + encryptData = encrypt.xeapi(uri, data, { + ...options, + publicKeyState: xeapiPublicKey, + sessionId: xeapiSessionId, + sessionKey: xeapiSessionKey, + appver: xeapiAppver, + deviceId: cookie.deviceId, + os: xeapiOs, + uid: cookie.uid || cookie.userId || '', + }) + break + case 'eapi': case 'api': // header创建 @@ -259,7 +324,6 @@ const createRequest = (uri, data, options) => { console.log('[ERR]', 'Unknown Crypto:', crypto) break } - // console.log(url); // settings创建 let settings = { method: 'POST', @@ -272,7 +336,8 @@ const createRequest = (uri, data, options) => { // 使用返回值加密 const use_e_r = (crypto === 'eapi' || crypto === 'weapi') && data.e_r - if (use_e_r) { + const use_xeapi = crypto === 'xeapi' + if (use_e_r || use_xeapi) { settings.encoding = null settings.responseType = 'arraybuffer' } @@ -319,8 +384,25 @@ const createRequest = (uri, data, options) => { x.replace(/\s*Domain=[^(;|$)]+;*/, ''), ) + // debug: 统一注释块,需要时取消注释查看请求/返回的原始密文 + + // logger.debug(`[${crypto}]`, uri) + // logger.debug(`[${crypto}] encrypted data:`, JSON.stringify(encryptData)) + // logger.debug( + // `[RAW] [${crypto}]`, + // use_xeapi + // ? Buffer.from(body).toString('base64') + // : body.toString('hex').toUpperCase(), + // ) + try { - if (use_e_r) { + if (use_xeapi) { + if (res.headers['x-encr-ssid'] && res.headers['x-encr-sskey']) { + xeapiSessionId = res.headers['x-encr-ssid'] + xeapiSessionKey = res.headers['x-encr-sskey'] + } + answer.body = encrypt.xeapiResDecrypt(Buffer.from(body)) + } else if (use_e_r) { answer.body = encrypt.eapiResDecrypt( body.toString('hex').toUpperCase(), headers['x-aeapi'], diff --git a/util/xeapiKey.js b/util/xeapiKey.js new file mode 100644 index 0000000..d308df5 --- /dev/null +++ b/util/xeapiKey.js @@ -0,0 +1,24 @@ +const registerXeapiKey = require('../module/register_xeapikey') + +const getXeapiPublicKey = async (currentPublicKey = {}, deviceId = '') => { + const result = await registerXeapiKey( + { + deviceId, + currentKeyVersion: currentPublicKey.version || '', + }, + null, + ) + + const publicKey = result.body + if (!publicKey.sk && currentPublicKey.sk) { + publicKey.sk = currentPublicKey.sk + } + if (!publicKey.sk) { + throw new Error('xeapi public key response missing sk') + } + return publicKey +} + +module.exports = { + getXeapiPublicKey, +}