Compare commits

..
15 Commits
Author SHA1 Message Date
MoEFusion ae1197f552 Add files via upload 2026-05-30 22:28:24 +08:00
MoeFurina 3e8273780a upd doc 2026-05-22 20:01:01 +08:00
MoeFurina e866ead286 更新 README 2026-02-12 22:56:48 +08:00
MoeFurina eee3daa2d9 更新style 2026-02-12 22:43:42 +08:00
ElyPrism 8efe78a3a3 更换cdn地址 2026-02-12 22:37:15 +08:00
ElyPrism 04510ab4ae Update index.html 2026-02-06 19:12:11 +08:00
ElyPrism 36411fb613 upd doc 2025-10-07 23:14:24 +08:00
ImFurina 355057c56e upd doc 一劳永逸 2025-10-06 14:12:11 +08:00
ElyPrism da9d8822fc 更新 home.md 2025-10-05 21:35:08 +08:00
ImFurina 2d8b2e27e4 upd doc 2025-10-01 21:53:47 +08:00
ImFurina f108803509 upd doc 2025-09-23 20:27:40 +08:00
ImFurina 563c491dad update doc 2025-09-07 13:18:45 +08:00
传奇账号耐活王 6a460caba5 Create CNAME 2025-08-28 16:56:13 +08:00
传奇账号耐活王 890ce471a5 Create README.md 2025-08-28 16:54:39 +08:00
ImFurina 64aa95532e init 2025-08-28 16:53:36 +08:00
504 changed files with 35 additions and 61433 deletions
-15
View File
@@ -1,15 +0,0 @@
/**
!/module
!/plugins
!/public
!/static
!/util
!/app.js
!/server.js
!/package.json
!/index.js
!/generateConfig.js
!/main.js
!/data
!/.env
!/pnpm-lock.yaml
-34
View File
@@ -1,34 +0,0 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*.{js,py}]
charset = utf-8
# 4 space indentation
[*.py]
indent_style = space
indent_size = 4
# Tab indentation (no size specified)
[Makefile]
indent_style = tab
# Indentation override for all JS under lib directory
[*.{js,ts}]
indent_style = space
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_style = space
indent_size = 2
-27
View File
@@ -1,27 +0,0 @@
### CORS资源共享设置
## 如果你需要配置, 请取消下面的注释并设置具体的域名
# CORS_ALLOW_ORIGIN = "*"
### 代理设置
## 启用反向代理
ENABLE_PROXY = false
## 代理配置
PROXY_URL = "https://your-proxy-url.com/?proxy="
### 随机IP设置
## 启用随机中国IP(默认关闭);启用后,所有请求默认使用随机中国IP,除非请求参数randomCNIP显式关闭
ENABLE_RANDOM_CN_IP = false
### UnblockNeteaseMusic 设置项
## 启用全局解灰, 无论是否调用参数都会使用解灰(不推荐开启)
ENABLE_GENERAL_UNBLOCK = false
## 歌曲启用无损音质
ENABLE_FLAC = true
## 启用无损音质时,是否选择音质最高的
SELECT_MAX_BR = false
## 严格按照配置音源设置顺序进行匹配
FOLLOW_SOURCE_ORDER = true
## Cookie设置项; 推荐在Vercel等平台进行环境变量的设置或者设置github secret
# 网易云 cookie
# 格式:MUSIC_U=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
# NETEASE_COOKIE = ""
-1
View File
@@ -1 +0,0 @@
custom: ["https://rewards.focalors.ltd"]
-117
View File
@@ -1,117 +0,0 @@
name: 🐛 Bug报告
description: 报告API其中的错误或问题
title: "[Bug] "
labels: ["bug"]
assignees: []
body:
- type: markdown
attributes:
value: |
感谢您报告Bug!请填写以下信息帮助我们快速定位和修复问题。
- type: checkboxes
id: terms
attributes:
label: 确认事项
description: |
在提交Bug报告前,请确认以下事项:
- 我已经搜索了现有的issues,确认这不是重复问题
- 我使用的是最新版本的API, 而且是官方发布的版本,而不是fork或修改版
- 不处理别人搭建的线上服务的问题,此项目提供任何线上服务不保证质量
- 如果不是提建议,提 issues 如果不照着模版来将不会优先处理或放着不管
- 维护项目都是业余时间,精力有限,我只能挑容易解决的issues处理,为了节约双方时间,请尽可能提供足够的有用的信息,给的信息不够我只能根据精力和时间看情况处理,如果模板信息看都不看就删掉,我不会进行任何回复,并且一个月后close掉issue
options:
- label: 我已确认以上事项
required: true
- type: input
id: version
attributes:
label: API版本
description: 请提供您使用的API版本以及nodejs版本
placeholder: "v4.2x.x, Node.js v22.x.x"
validations:
required: true
- type: dropdown
id: deployment
attributes:
label: 部署方式
description: 此项目支持本地部署和云平台部署,您使用的是哪种方式?
options:
- 本机直接运行 (node / pm2)
- 本机 Docker 部署
- Vercel
- Heroku
- Railway
- 其他云平台 (请在描述中说明)
validations:
required: true
- type: textarea
id: bug-description
attributes:
label: Bug描述
description: 请详细描述您遇到的问题
placeholder: |
清晰简洁地描述bug的具体表现...
validations:
required: true
- type: textarea
id: reproduction-steps
attributes:
label: 重现步骤
description: 请提供详细的重现步骤
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: 期望行为
description: 请描述您期望发生的行为
placeholder: 清晰简洁地描述您期望应该发生什么...
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: 实际行为
description: 请描述实际发生的行为
placeholder: 清晰简洁地描述实际发生了什么...
validations:
required: true
- type: textarea
id: error-output
attributes:
label: 错误输出日志
description: 请提供完整的错误信息日志
render: shell
placeholder: |
错误信息输出...
- type: textarea
id: additional-context
attributes:
label: 额外信息
description: 添加任何其他有助于理解问题的信息
placeholder: |
- 截图
- 相关配置文件
- 其他相关信息
- type: checkboxes
id: help
attributes:
label: 愿意协助
description: 您是否愿意帮助修复这个bug?
options:
- label: 我愿意提交PR来修复这个bug
- label: 我可以提供更多测试用例
- label: 我可以帮助测试修复方案
-8
View File
@@ -1,8 +0,0 @@
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 发起者均已了解此处的内容
@@ -1,72 +0,0 @@
name: ✨ 接口添加请求
description: 希望添加新接口
title: "[Feature] "
labels: ["enhancement"]
assignees: []
body:
- type: markdown
attributes:
value: |
感谢您提出新功能建议!请填写以下信息帮助我们理解您的需求。
- type: checkboxes
id: terms
attributes:
label: 确认事项
description: 在提交功能请求前,请确认以下事项
options:
- label: 我已经搜索了现有的issues,确认这不是重复请求
required: true
- label: 我已经查看了项目文档
required: true
- label: 该项目符合该项目的目标和范围
required: true
- label: 我确认我提供的接口请求是合理且有意义的
required: true
- type: textarea
id: proposed-solution
attributes:
label: 功能描述
description: 请详细描述您希望添加的功能,最好贴上对应客户端界面的截图
placeholder: |
请详细描述您期望的功能:
- 新的网易云音乐api 接口?
validations:
required: true
- type: dropdown
id: priority
attributes:
label: 优先级
description: 您认为这个功能的优先级如何?
options:
- 低 (可有可无的改进)
- 中 (会显著改善使用体验)
- 高 (严重影响了我使用)
- 紧急 (阻塞了重要工作)
validations:
required: true
- type: textarea
id: compatibility
attributes:
label: 兼容性考虑
description: 这个功能是否会影响现有代码的兼容性?
placeholder: |
- 是否是破坏性变更?
- 是否需要语法变更?
- 对性能的影响?
- type: checkboxes
id: implementation
attributes:
label: 实现意愿
description: 您是否愿意参与这个功能的实现?
options:
- label: 我愿意提交PR来实现这个功能
- label: 我可以协助设计API接口
- label: 我可以提供测试用例
- label: 我可以帮助编写文档
-41
View File
@@ -1,41 +0,0 @@
# .github/dependabot.yml
version: 2
updates:
# 更新 npm (JavaScript) 依赖
- package-ecosystem: "npm"
directory: "/" # package.json 所在的位置
schedule:
interval: "weekly" # 每周一检查
# 默认情况下,Dependabot 会自动获取最新版本。
# 你可以使用 `ignore` 来排除某些特定更新
ignore:
- dependency-name: "eslint"
# 对于 ESLint,忽略主版本 8 以上的所有更新
versions: ["> 8"]
# 自动合并小版本更新的 PR (可选)
# 可以设置针对 patch 或 minor 版本更新自动合并
commit-message:
prefix: "build(packages):" # PR 合并提交信息的前缀
# 更新 GitHub Actions 依赖
- package-ecosystem: "github-actions"
directory: "/" # .github/workflows/ 目录所在的位置
schedule:
interval: "monthly"
# 更新 Python (pip) 依赖,如果你的项目有 requirements.txt
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
# 如果你有多个目录(例如一个 monorepo),可以为每个目录单独配置
# - package-ecosystem: "npm"
# directory: "/packages/frontend"
# schedule:
# interval: "weekly"
# - package-ecosystem: "npm"
# directory: "/packages/backend"
# schedule:
# interval: "weekly"
-53
View File
@@ -1,53 +0,0 @@
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
-79
View File
@@ -1,79 +0,0 @@
name: Issue Management
permissions:
issues: write
contents: read
on:
issues:
types: [opened, labeled]
issue_comment:
types: [created]
jobs:
welcome-new-issues:
if: github.event.action == 'opened'
runs-on: ubuntu-latest
steps:
- name: Welcome new issue
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ github.event.issue.number }}
body: |
## 快速链接
在我们处理您的问题之前,您可以先查看以下资源:
- 📖 查看[项目文档](https://neteasecloudmusicapienhanced.js.org)
- 💬 加入[QQ交流群](https://qm.qq.com/q/TpeP9Uv2yk)
- 🔍 搜索[现有issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues) 看是否有类似问题
handle-help-wanted:
if: contains(github.event.issue.labels.*.name, 'question')
runs-on: ubuntu-latest
steps:
- name: Add help resources
uses: peter-evans/create-or-update-comment@v5
with:
issue-number: ${{ github.event.issue.number }}
body: |
🆘 需要帮助?
这里有一些可能对您有用的资源:
📚 **文档资源**
- [项目文档](https://neteasecloudmusicapienhanced.js.org)
🔍 **常见问题**
- 搜索[已关闭的issues](https://github.com/neteasecloudmusicapienhanced/api-enhanced/issues?q=is%3Aissue+is%3Aclosed) 看看是否有类似的问题已经被解答。
💬 **即时帮助**
- 加入QQ群:https://qm.qq.com/q/TpeP9Uv2yk
如果以上资源无法解决您的问题,请提供更多详细信息,我们会尽快为您解答!
stale-issues:
if: github.event.action == 'opened'
runs-on: ubuntu-latest
steps:
- name: Mark stale issues
uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: |
这个issue已经30天没有活动了。
如果这个问题仍然存在,请回复此评论说明当前状态。
否则,这个issue将在7天后自动关闭。
您可以随时重新打开已关闭的issue。
stale-pr-message: |
这个PR已经30天没有活动了。
如果您仍在处理这个PR,请回复此评论。
否则,这个PR将在7天后自动关闭。
days-before-stale: 30
days-before-close: 7
stale-issue-label: "stale"
stale-pr-label: "stale"
exempt-issue-labels: "pinned,enhancement,priority:high,ongoing,fatal"
exempt-pr-labels: "pinned,priority:high,ongoing,fatal"
@@ -1,315 +0,0 @@
name: Create Release on Version Change
on:
push:
branches:
- main
paths:
- package.json
workflow_dispatch:
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
jobs:
detect:
name: Detect Version Change
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.value }}
should_release: ${{ steps.check.outputs.should_release }}
tag_exists: ${{ steps.tagcheck.outputs.exists }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Extract version from package.json
id: version
run: |
echo "value=$(jq -r .version package.json)" >> $GITHUB_OUTPUT
- name: Check if version changed
id: check
shell: bash
run: |
if [ "${{ github.event_name }}" == "workflow_dispatch" ]; then
echo "should_release=true" >> $GITHUB_OUTPUT
exit 0
fi
if git diff "${{ github.event.before }}" "${{ github.sha }}" -- package.json | grep -q '"version"'; then
echo "should_release=true" >> $GITHUB_OUTPUT
else
echo "should_release=false" >> $GITHUB_OUTPUT
fi
- name: Check if tag already exists
id: tagcheck
shell: bash
run: |
VERSION="${{ steps.version.outputs.value }}"
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "exists=false" >> $GITHUB_OUTPUT
fi
build:
name: Build ${{ matrix.platform }}
needs: detect
if: |
needs.detect.outputs.should_release == 'true' &&
needs.detect.outputs.tag_exists != 'true'
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux
target: node18-linux-x64
output: ncm-api-linux-x64
- os: windows-latest
platform: windows
target: node18-win-x64
output: ncm-api-win-x64.exe
- os: macos-latest
platform: macos
target: node18-macos-x64
output: ncm-api-macos-x64
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Setup pnpm
uses: pnpm/action-setup@v6
with:
version: 9
- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build binary
shell: bash
env:
PKG_TARGET: ${{ matrix.target }}
run: |
mkdir -p release-artifacts
case "${{ matrix.platform }}" in
linux)
npm run pkglinux
mv precompiled/app "release-artifacts/${{ matrix.output }}"
;;
windows)
npm run pkgwin
mv precompiled/app.exe "release-artifacts/${{ matrix.output }}"
;;
macos)
npm run pkgmacos
mv precompiled/app "release-artifacts/${{ matrix.output }}"
;;
esac
- name: Upload build artifact
uses: actions/upload-artifact@v7
with:
name: ${{ matrix.platform }}-binary
path: release-artifacts/*
release:
name: Create GitHub Release
needs:
- detect
- build
if: |
needs.detect.outputs.should_release == 'true' &&
needs.detect.outputs.tag_exists != 'true'
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
fetch-tags: true
- name: Download build artifacts
uses: actions/download-artifact@v8
with:
path: release-artifacts
merge-multiple: true
- name: List flattened artifacts
shell: bash
run: |
mkdir -p final-artifacts
cp release-artifacts/* final-artifacts/
ls -lah final-artifacts
- name: Generate release notes
shell: bash
run: |
VERSION="${{ needs.detect.outputs.version }}"
PREV_TAG=$(git tag --sort=-v:refname | head -1)
{
echo "# Release v${VERSION}"
echo ""
echo "## 更新内容 / Changelog"
echo ""
if [ -n "$PREV_TAG" ]; then
echo "从 \`$PREV_TAG\` 到 \`v$VERSION\` 的提交记录:"
echo ""
git log "$PREV_TAG..HEAD" \
--no-merges \
--pretty=format:"- %s (%h)"
else
echo "首次发布,包含以下提交:"
echo ""
git log \
--no-merges \
--pretty=format:"- %s (%h)"
fi
echo ""
echo ""
echo "---"
echo "自动发布 via GitHub Actions"
} > release-notes.md
cat release-notes.md
- name: Create Git tag
run: |
git tag "v${{ needs.detect.outputs.version }}"
git push origin "v${{ needs.detect.outputs.version }}"
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ needs.detect.outputs.version }}
name: Release v${{ needs.detect.outputs.version }}
body_path: release-notes.md
files: final-artifacts/*
draft: false
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@v4
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Log in to GitHub Container Registry
uses: docker/login-action@v4
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
-36
View File
@@ -1,36 +0,0 @@
name: Upstream Sync
permissions:
contents: write
on:
schedule:
- cron: "0 4 * * *" # At 12PM UTC+8
workflow_dispatch:
jobs:
sync_latest_from_upstream:
name: Sync latest commits from upstream repo
runs-on: ubuntu-latest
if: ${{ github.event.repository.fork }}
steps:
# Step 1: run a standard checkout action
- name: Checkout target repo
uses: actions/checkout@v6
# Step 2: run the sync action
- name: Sync upstream changes
id: sync
uses: aormsby/Fork-Sync-With-Upstream-action@v3.4.3
with:
upstream_sync_repo: NeteaseCloudMusicApiEnhanced/api-enhanced
upstream_sync_branch: main
target_sync_branch: main
target_repo_token: ${{ secrets.GITHUB_TOKEN }}
- name: Sync check
if: failure()
run: |
echo "[Error] Due to a change in the workflow file of the upstream repository, GitHub has automatically suspended the scheduled automatic update. You need to manually sync your fork."
exit 1
+1 -12
View File
@@ -1,12 +1 @@
.DS_Store node_modules/
node_modules
*.log
.idea
.vscode
.history
examples/moddef.json
bin
anonymous_token
.vercel
.env
IFLOW.md
View File
-1
View File
@@ -1 +0,0 @@
node_modules
-5
View File
@@ -1,5 +0,0 @@
{
"semi": false,
"trailingComma": "all",
"singleQuote": true
}
-4
View File
@@ -1,4 +0,0 @@
language: node_js
node_js:
- 22
-23
View File
@@ -1,23 +0,0 @@
# Agent Instructions for NeteaseCloudMusicApiEnhanced
## Quick Start
- **Package Manager**: Use `pnpm` (not npm or yarn).
- **Node Version**: Requires Node.js 18 or later.
## Developer Commands
- **Install dependencies**: `pnpm i`
- **Start server**: `pnpm start` or `node app.js`
- **Start dev server**: `pnpm dev` (uses nodemon)
- **Run tests**: `pnpm test` (uses Mocha)
- **Linting**: `pnpm lint` (check) or `pnpm lint-fix` (auto-fix)
## Architecture & Entrypoints
- **Executable Server**: `app.js` is the main entrypoint for running the API server.
- **Module Exports**: `main.js` is the entrypoint when the project is imported as a Node.js dependency.
- **API Endpoints**: Located in the `module/` directory. Each file typically corresponds to an API route.
- **Core Utilities**: Request handling, encryption, and core utilities are found in the `util/` directory.
## Important Gotchas & Quirks
- **Environment Variables**: The server defaults to port 3000 but can be overridden with the `PORT` environment variable.
- **Proxy Variables**: Be very careful with proxy environment variables (`http_proxy`, `https_proxy`, `no_proxy`). The request library (like axios) will automatically pick these up. If they point to an unavailable proxy (especially common in Docker environments), requests will fail silently or throw connection errors.
- **Code Style**: The project uses ESLint and Prettier. Always run `pnpm lint-fix` before committing changes to ensure formatting consistency.
+1
View File
@@ -0,0 +1 @@
neteasecloudmusicapienhanced.js.org
-16
View File
@@ -1,16 +0,0 @@
FROM node:lts-alpine
RUN apk add --no-cache tini
ENV NODE_ENV production
USER node
WORKDIR /app
COPY --chown=node:node . ./
RUN yarn --network-timeout=100000
EXPOSE 3000
CMD [ "/sbin/tini", "--", "node", "app.js" ]
-21
View File
@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2013-2022 Binaryify
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-253
View File
@@ -1,253 +0,0 @@
<p align="center">
<img src="./public/static/neteaselogo.png" alt="网易云音乐 API Enhanced Logo" />
</p>
<p align="center">
<a href="https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api">
<img src="https://img.shields.io/npm/v/@neteasecloudmusicapienhanced/api?style=flat-square" alt="NPM Version" />
</a>
<a href="https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api">
<img src="https://img.shields.io/npm/dm/@neteasecloudmusicapienhanced/api?style=flat-square" alt="NPM Downloads" />
</a>
<a href="https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced">
<img src="https://img.shields.io/github/license/NeteaseCloudMusicApiEnhanced/api-enhanced?style=flat-square" alt="License" />
</a>
<a href="https://nodejs.org/">
<img src="https://img.shields.io/node/v/@neteasecloudmusicapienhanced/api?style=flat-square" alt="Node Version" />
</a>
<a href="https://hub.docker.com/r/moefurina/ncm-api">
<img src="https://img.shields.io/docker/v/moefurina/ncm-api?style=flat-square" alt="Docker Version" />
</a>
<a href="https://hub.docker.com/r/moefurina/ncm-api">
<img src="https://img.shields.io/docker/pulls/moefurina/ncm-api?style=flat-square" alt="Docker Pulls" />
</a>
<a href="https://www.typescriptlang.org/">
<img src="https://img.shields.io/badge/TypeScript-Ready-blue?style=flat-square&logo=typescript" alt="TypeScript" />
</a>
</p>
## 项目简介
网易云音乐第三方 Node.js API, 支持丰富的音乐相关接口,适合自建服务、二次开发和多平台部署
> [!IMPORTANT]
>
> ## 注意
>
> - 本项目是自由项目, 不带任何担保, 在基于`MIT License`的许可下, 任何人都可以自由使用、修改和分发本项目的代码, 包括用于商业目的.
> - 该项目是个合作计划, 有很多人参与了此次贡献, 前往 [Contributors](https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced/graphs/contributors) 页面查看所有贡献者
> - 原作者项目 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 并非完全停止维护, 你可以在 [NeteaseCloudMusicApi 的 NPMJS 页面](https://www.npmjs.com/package/NeteaseCloudMusicApi) 查看最新版本信息。
> - 感谢尊重和理解!
## 快速开始
### 环境要求
- Node.js 18 及以上
- 推荐使用 pnpm 进行依赖管理
### 安装
```bash
git clone https://github.com/neteasecloudmusicapienhanced/api-enhanced.git
cd api
pnpm i
```
### 启动服务
```bash
# 默认端口 3000
node app.js
# 指定端口(如 4000)
PORT=4000 node app.js # Mac/Linux
set PORT=4000 && node app.js # Windows
```
### 重要提示
- 调用前请务必阅读文档的 [调用前须知](https://neteasecloudmusicapienhanced.js.org/#/?id=%e8%b0%83%e7%94%a8%e5%89%8d%e9%a1%bb%e7%9f%a5) 部分。
- 推荐将敏感信息(如 cookie)通过部署平台的环境变量进行配置。
## 在线体验与文档
- [在线文档](https://neteasecloudmusicapienhanced.js.org/)
- [NPM 包地址](https://www.npmjs.com/package/@neteasecloudmusicapienhanced/api)
- [Dockerhub 主页](https://hub.docker.com/r/moefurina/ncm-api)
## 常见部署方式
- 推荐参考[在线文档](https://neteasecloudmusicapienhanced.js.org/)
## Docker 部署说明
本项目支持通过 Docker 一键部署,无需手动安装 Node.js 或依赖。
> 注意: 在 docker 中运行的时候, 由于使用了 request 来发请求, 所以会检查几个 proxy 相关的环境变量(如下所列), 这些环境变量 会影响到 request 的代理, 详情请参考[request 的文档](https://github.com/request/request#proxies), 如果这些环境变量 指向的代理不可用, 那么就会造成错误, 所以在使用 docker 的时候一定要注意这些环境变量. 不过, 要是你在 query 中加上了 proxy 参数, 那么环境变量会被覆盖, 就会用你通过 proxy 参数提供的代理了.
request 相关的环境变量
1. http_proxy
2. https_proxy
3. HTTP_PROXY
4. HTTPS_PROXY
5. no_proxy
6. NO_PROXY
```shell
docker pull moefurina/ncm-api:latest
docker run -d -p 3000:3000 --name ncm-api moefurina/ncm-api:latest
// 或者
docker run -d -p 3000:3000 moefurina/ncm-api:latest
// 去掉或者设置相关的环境变量
docker run -d -p 3000:3000 --name ncm-api -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= moefurina/ncm-api:latest
// 或者
docker run -d -p 3000:3000 -e http_proxy= -e https_proxy= -e no_proxy= -e HTTP_PROXY= -e HTTPS_PROXY= -e NO_PROXY= moefurina/ncm-api:latest
```
> 以下是自行 build docker 镜像方式
```shell
$ git clone https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced && cd api-enhanced
$ sudo docker build . -t ncm-api
$ sudo docker run -d -p 3000:3000 ncm-api
```
## 3. 环境变量
| 变量名 | 默认值 | 说明 |
|----------------------------|--------------------------------------|----------------------------------------------------|
| **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` | 启用无损音质时,是否选择最高码率音质。 |
| **FOLLOW_SOURCE_ORDER** | `true` | 是否严格按照音源列表顺序进行匹配。 |
---
### 4. 更新镜像
更新到最新版本:
```bash
docker pull moefurina/ncm-api:latest
docker stop ncm-api && docker rm ncm-api
docker run -d -p 3000:3000 moefurina/ncm-api:latest
```
## Vercel 一键部署
1. fork 本项目
2. 在 Vercel 官网新建项目,导入 fork 的仓库
3. 直接 Deploy
## 腾讯云 Serverless 部署
1. fork 本项目
2. 在腾讯云 serverless 控制台新建 Web 应用,选择 Express 框架
3. 代码仓库选择 fork 的项目,启动文件填写:
```bash
#!/bin/bash
export PORT=9000
/var/lang/node16/bin/node app.js
```
4. 完成部署后,访问 API 网关的 URL 即可
## Node.js 方式调用
支持直接在 Node.js 项目中引入和调用,返回 Promise:
```js
const {
login_cellphone,
user_cloud,
} = require('@neteasecloudmusicapienhanced/api')
async function main() {
const result = await login_cellphone({ phone: '手机号', password: '密码' })
console.log(result)
const result2 = await user_cloud({ cookie: result.body.cookie })
console.log(result2.body)
}
main()
```
## TypeScript 支持
```ts
import { banner } from '@neteasecloudmusicapienhanced/api'
banner({ type: 0 }).then((res) => console.log(res))
```
## 单元测试
```bash
pnpm test
```
## 主要功能特性
- 登录/注册/验证码
- 用户信息、歌单、动态、播放记录
- 歌曲、专辑、歌手、MV、歌词、评论、排行榜
- 搜索、推荐、私人 FM、签到、云盘
- 歌曲解锁(解灰)
## 贡献与社区
- 欢迎提交 PR、Issue 参与维护
- 要贡献新接口, 请参考[这篇文章](https://www.focalors.ltd/post/how-to-contribute-ncm-api)
### 致谢
原作者 [Binaryify/NeteaseCloudMusicApi](https://github.com/binaryify/NeteaseCloudMusicApi) 项目为本项目基础 (该项目在`npmjs`网站上仍持续维护, 但 github 仓库已不再更新)
感谢大佬们为逆向eapi, weapi, xeapi等加密算法所做的贡献
项目参考:
[trazyn/ieaseMusic](https://github.com/trazyn/ieaseMusic)
[listen1/listen1_chrome_extension](https://github.com/listen1/listen1_chrome_extension)
[chaunsin/netease-cloud-music](https://github.com/chaunsin/netease-cloud-music)
[folltoshe/netease-report-listen-song](https://github.com/folltoshe/netease-report-listen-song)
### SDK 生态
| 语言 | 作者 | 地址 | 类型 |
|--------|---------------------------------------------|------------------------------------------------------------------------------------------|-----|
| Java | [JackuXL](https://github.com/JackuXL) | [NeteaseCloudMusicApi-SDK](https://github.com/JackuXL/NeteaseCloudMusicApi-SDK) | 第三方 |
| Java | [1015770492](https://github.com/1015770492) | [yumbo-music-utils](https://github.com/1015770492/yumbo-music-utils) | 第三方 |
| Python | [盧瞳](https://github.com/2061360308) | [NeteaseCloudMusic_PythonSDK](https://github.com/2061360308/NeteaseCloudMusic_PythonSDK) | 第三方 |
| Swift | [Lincb522](https://github.com/Lincb522) | [NeteaseCloudMusicApi-Swift](https://github.com/Lincb522/NeteaseCloudMusicAPI-Swift) | 第三方 |
| Rust | [SPlayer-Dev](https://github.com/splayer-dev) | [ncm-api-rs](https://github.com/SPlayer-Dev/ncm-api-rs) | 第三方 |
### 依赖此项目的优秀开源项目
- [qier2222/YesPlayMusic](https://github.com/qier222/YesPlayMusic)
- [MaigoLabs/amaoke.app](https://github.com/MaigoLabs/amaoke.app)
- [Yueby/music-together](https://github.com/Yueby/music-together)
- [chthollyphlie/folia-major](https://github.com/chthollyphile/folia-major)
## License
[MIT License](https://github.com/MoeFurina/NeteaseCloudMusicApiEnhanced/blob/main/LICENSE)
+4
View File
@@ -0,0 +1,4 @@
## 这是什么
这是网易云音乐api enhanced的文档页面
## [前往](https://docs-neteasecloudmusicapi.focalors.ltd)
View File

Before

Width:  |  Height:  |  Size: 2.4 MiB

After

Width:  |  Height:  |  Size: 2.4 MiB

-18
View File
@@ -1,18 +0,0 @@
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const tmpPath = require('os').tmpdir()
async function start() {
// 检测是否存在 anonymous_token 文件,没有则生成
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
}
// 启动时更新anonymous_token
const generateConfig = require('./generateConfig')
await generateConfig()
require('./server').serveNcmApi({
checkVersion: true,
})
}
start()
File diff suppressed because it is too large Load Diff
-24641
View File
File diff suppressed because it is too large Load Diff
-91
View File
@@ -1,91 +0,0 @@
const { defineConfig, globalIgnores } = require('eslint/config')
const html = require('eslint-plugin-html')
const globals = require('globals')
const tsParser = require('@typescript-eslint/parser')
const js = require('@eslint/js')
const { FlatCompat } = require('@eslint/eslintrc')
const compat = new FlatCompat({
baseDirectory: __dirname,
recommendedConfig: js.configs.recommended,
allConfig: js.configs.all,
})
module.exports = defineConfig([
{
languageOptions: {
ecmaVersion: 2020,
sourceType: 'module',
parserOptions: {
parser: 'babel-eslint',
},
globals: {
...globals.browser,
...globals.node,
},
},
plugins: {
html,
},
extends: compat.extends('plugin:prettier/recommended'),
rules: {
'prettier/prettier': [
'error',
{
endOfLine: 'auto',
},
],
indent: [
'error',
2,
{
SwitchCase: 1,
},
],
'space-infix-ops': [
'error',
{
int32Hint: false,
},
],
'key-spacing': [
2,
{
beforeColon: false,
afterColon: true,
},
],
'no-octal': 2,
'no-redeclare': 2,
'comma-spacing': 2,
'no-new-object': 2,
'arrow-spacing': 2,
quotes: [
2,
'single',
{
avoidEscape: true,
allowTemplateLiterals: true,
},
],
},
},
globalIgnores(['**/public/']),
{
files: ['**/*.ts'],
languageOptions: {
parser: tsParser,
},
extends: compat.extends('plugin:@typescript-eslint/recommended'),
},
])
-23
View File
@@ -1,23 +0,0 @@
const fsPromises = require('fs/promises')
const path = require('path')
const server = require('../server')
const logger = require('../util/logger.js')
const exportFile = path.join(__dirname, 'moddef.json')
async function main() {
const def = await server.getModulesDefinitions(
path.join(__dirname, '..', 'module'),
{
'daily_signin.js': '/daily_signin',
'fm_trash.js': '/fm_trash',
'personal_fm.js': '/personal_fm',
},
false,
)
fsPromises.writeFile(exportFile, JSON.stringify(def, null, 4))
logger.info(`👍 Get your own definition at: ${exportFile}`)
}
main()

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

-41
View File
@@ -1,41 +0,0 @@
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() {
global.cnIp = generateRandomChineseIP()
try {
const res = await register_anonimous()
const cookie = res.body.cookie
if (cookie) {
const cookieObj = cookieToJson(cookie)
fs.writeFileSync(
path.resolve(tmpPath, 'anonymous_token'),
cookieObj.MUSIC_A,
'utf-8',
)
}
} 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
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 858 KiB

+29 -15
View File
@@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="KEYWords" contect="网易云音乐,网易云音乐 api,网易云音乐 nodejs,网易云音乐 node.js"> <meta name="KEYWords" contect="网易云音乐,网易云音乐 api,网易云音乐 nodejs,网易云音乐 node.js">
@@ -7,7 +8,8 @@
<title>网易云音乐 NodeJS API Enhanced</title> <title>网易云音乐 NodeJS API Enhanced</title>
<link rel="icon" href="netease.png"> <link rel="icon" href="netease.png">
<meta name="description" content="Description"> <meta name="description" content="Description">
<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"> <meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta name="referrer" content="never"> <meta name="referrer" content="never">
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css" id="theme-css"> <link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css" id="theme-css">
<style> <style>
@@ -19,8 +21,8 @@
width: 44px; width: 44px;
height: 44px; height: 44px;
border-radius: 50%; border-radius: 50%;
border: 1px solid rgba(255,255,255,0.1); border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(30,30,46,0.75); background: rgba(30, 30, 46, 0.75);
backdrop-filter: blur(12px); backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
color: #fff; color: #fff;
@@ -29,27 +31,37 @@
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
box-shadow: 0 2px 12px rgba(0,0,0,0.15); box-shadow: 0 2px 12px rgba(0, 0, 0, 0.15);
transition: all 0.3s cubic-bezier(0.4,0,0.2,1); transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
outline: none; outline: none;
padding: 0; padding: 0;
line-height: 1; line-height: 1;
user-select: none; user-select: none;
} }
.theme-toggle:hover { .theme-toggle:hover {
transform: scale(1.12) rotate(12deg); transform: scale(1.12) rotate(12deg);
box-shadow: 0 4px 20px rgba(0,0,0,0.25); box-shadow: 0 4px 20px rgba(0, 0, 0, 0.25);
} }
.theme-toggle:active { .theme-toggle:active {
transform: scale(0.92); transform: scale(0.92);
} }
.theme-toggle.light { .theme-toggle.light {
background: rgba(255,255,255,0.85); background: rgba(255, 255, 255, 0.85);
color: #333; color: #333;
border-color: rgba(0,0,0,0.08); border-color: rgba(0, 0, 0, 0.08);
} }
html, body, .sidebar, .content, .markdown-section,
.sidebar-nav, .search, .docsify-pagination-container { html,
body,
.sidebar,
.content,
.markdown-section,
.sidebar-nav,
.search,
.docsify-pagination-container {
transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease; transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
} }
</style> </style>
@@ -61,12 +73,13 @@
}); });
</script> </script>
</head> </head>
<body> <body>
<div id="app"></div> <div id="app"></div>
<button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">🌙</button> <button class="theme-toggle" id="themeToggle" aria-label="Toggle theme">🌙</button>
</body> </body>
<script> <script>
(function() { (function () {
var STORAGE_KEY = 'docsify-theme'; var STORAGE_KEY = 'docsify-theme';
var link = document.getElementById('theme-css'); var link = document.getElementById('theme-css');
var btn = document.getElementById('themeToggle'); var btn = document.getElementById('themeToggle');
@@ -95,12 +108,12 @@
btn.addEventListener('click', toggleTheme); btn.addEventListener('click', toggleTheme);
try { try {
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function (e) {
if (!localStorage.getItem(STORAGE_KEY)) { if (!localStorage.getItem(STORAGE_KEY)) {
setTheme(e.matches ? 'dark' : 'light'); setTheme(e.matches ? 'dark' : 'light');
} }
}); });
} catch(e) {} } catch (e) { }
})(); })();
</script> </script>
<script> <script>
@@ -108,7 +121,7 @@
name: '网易云音乐 API Enhanced', name: '网易云音乐 API Enhanced',
repo: 'https://github.com/neteasecloudmusicapienhanced/api-enhanced', repo: 'https://github.com/neteasecloudmusicapienhanced/api-enhanced',
coverpage: true, coverpage: true,
homepage: 'home.md', homepage: 'https://cdn.jsdelivr.net/gh/NeteaseCloudMusicApiEnhanced/api-enhanced@main/public/docs/home.md',
} }
</script> </script>
<script src="https://unpkg.com/docsify@4.11.3/lib/docsify.min.js"></script> <script src="https://unpkg.com/docsify@4.11.3/lib/docsify.min.js"></script>
@@ -126,4 +139,5 @@
gtag('config', 'G-BPRGR23JEG'); gtag('config', 'G-BPRGR23JEG');
</script> </script>
</html>
</html>
-1
View File
@@ -1 +0,0 @@
require('./app.js')
-1
View File
@@ -1 +0,0 @@
import './app.js'
-2564
View File
File diff suppressed because it is too large Load Diff
-63
View File
@@ -1,63 +0,0 @@
const fs = require('fs')
const path = require('path')
const tmpPath = require('os').tmpdir()
const { cookieToJson } = require('./util')
const anonymousTokenPath = path.resolve(tmpPath, 'anonymous_token')
if (!fs.existsSync(anonymousTokenPath)) {
fs.writeFileSync(anonymousTokenPath, '', 'utf-8')
}
/** @type {Record<string, any>} */
let obj = {}
const modulePath = path.join(__dirname, 'module')
const moduleFiles = fs.readdirSync(modulePath).reverse()
let requestModule = null
moduleFiles.forEach((file) => {
if (!file.endsWith('.js')) return
const filePath = path.join(modulePath, file)
let fileModule = require(filePath)
let fn = file.split('.').shift() || ''
obj[fn] = function (data = {}) {
const cookie =
typeof data.cookie === 'string'
? cookieToJson(data.cookie)
: data.cookie || {}
return fileModule(
{
...data,
cookie,
},
async (...args) => {
if (!requestModule) {
requestModule = require('./util/request')
}
return requestModule(...args)
},
)
}
})
let serverModule = null
/**
* @type {Record<string, any> & import("./server")}
*/
module.exports = {
get server() {
if (!serverModule) {
serverModule = require('./server')
}
return serverModule
},
...obj,
}
Object.assign(module.exports, require('./server'))
-18
View File
@@ -1,18 +0,0 @@
const assert = require('assert')
const main = require('./main')
describe('methods in server.js', () => {
it('has serveNcmApi', () => {
assert.strictEqual(typeof main.serveNcmApi, 'function')
})
it('has getModulesDefinitions', () => {
assert.strictEqual(typeof main.getModulesDefinitions, 'function')
})
})
describe('methods in module', () => {
it('has activate_init_profile', () => {
assert.strictEqual(typeof main.activate_init_profile, 'function')
})
})
-9
View File
@@ -1,9 +0,0 @@
// 初始化名字
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
nickname: query.nickname,
}
return request(`/api/activate/initProfile`, data, createOption(query))
}
-28
View File
@@ -1,28 +0,0 @@
// 私人 DJ
// 实际请求参数如下, 部分内容省略, 敏感信息已进行混淆
// 可按需修改此 API 的代码
/* {"extInfo":"{\"lastRequestTimestamp\":1692358373509,\"lbsInfoList\":[{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692358543},{\"lat\":40.23076381,\"lon\":129.07545186,\"time\":1692055283}],\"listenedTs\":false,\"noAidjToAidj\":true}","header":"{}"} */
const logger = require('../util/logger.js')
const createOption = require('../util/option.js')
module.exports = (query, request) => {
var extInfo = {}
if (query.latitude != undefined) {
extInfo.lbsInfoList = [
{
lat: query.latitude,
lon: query.longitude,
time: Date.parse(new Date()) / 1000,
},
]
}
extInfo.noAidjToAidj = false
extInfo.lastRequestTimestamp = new Date().getTime()
extInfo.listenedTs = false
const data = {
extInfo: JSON.stringify(extInfo),
}
// logger.info(data)
return request(`/api/aidj/content/rcmd/info`, data, createOption(query))
}
-6
View File
@@ -1,6 +0,0 @@
// 专辑内容
const createOption = require('../util/option.js')
module.exports = (query, request) => {
return request(`/api/v1/album/${query.id}`, {}, createOption(query, 'weapi'))
}
-12
View File
@@ -1,12 +0,0 @@
// 数字专辑详情
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(
`/api/vipmall/albumproduct/detail`,
data,
createOption(query, 'weapi'),
)
}
-12
View File
@@ -1,12 +0,0 @@
// 专辑动态信息
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(
`/api/album/detail/dynamic`,
data,
createOption(query, 'weapi'),
)
}
-16
View File
@@ -1,16 +0,0 @@
// 数字专辑-新碟上架
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 30,
offset: query.offset || 0,
total: true,
area: query.area || 'ALL', //ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本
type: query.type,
}
return request(
`/api/vipmall/albumproduct/list`,
data,
createOption(query, 'weapi'),
)
}
-15
View File
@@ -1,15 +0,0 @@
// 数字专辑-语种风格馆
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 10,
offset: query.offset || 0,
total: true,
area: query.area || 'Z_H', //Z_H:华语,E_A:欧美,KR:韩国,JP:日本
}
return request(
`/api/vipmall/appalbum/album/style`,
data,
createOption(query, 'weapi'),
)
}
-11
View File
@@ -1,11 +0,0 @@
// 全部新碟
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 30,
offset: query.offset || 0,
total: true,
area: query.area || 'ALL', //ALL:全部,ZH:华语,EA:欧美,KR:韩国,JP:日本
}
return request(`/api/album/new`, data, createOption(query, 'weapi'))
}
-6
View File
@@ -1,6 +0,0 @@
// 最新专辑
const createOption = require('../util/option.js')
module.exports = (query, request) => {
return request(`/api/discovery/newAlbum`, {}, createOption(query, 'weapi'))
}
-9
View File
@@ -1,9 +0,0 @@
// 获取专辑歌曲的音质
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/album/privilege`, data, createOption(query))
}
-19
View File
@@ -1,19 +0,0 @@
// 数字专辑&数字单曲-榜单
const createOption = require('../util/option.js')
module.exports = (query, request) => {
let data = {
albumType: query.albumType || 0, //0为数字专辑,1为数字单曲
}
const type = query.type || 'daily' // daily,week,year,total
if (type === 'year') {
data = {
...data,
year: query.year,
}
}
return request(
`/api/feealbum/songsaleboard/${type}/type`,
data,
createOption(query, 'weapi'),
)
}
-10
View File
@@ -1,10 +0,0 @@
// 收藏/取消收藏专辑
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.t = query.t == 1 ? 'sub' : 'unsub'
const data = {
id: query.id,
}
return request(`/api/album/${query.t}`, data, createOption(query, 'weapi'))
}
-11
View File
@@ -1,11 +0,0 @@
// 已收藏专辑列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 25,
offset: query.offset || 0,
total: true,
}
return request(`/api/album/sublist`, data, createOption(query, 'weapi'))
}
-21
View File
@@ -1,21 +0,0 @@
const { cookieToJson } = require('../util/index')
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const uri = query.uri
let data = {}
try {
data =
typeof query.data === 'string' ? JSON.parse(query.data) : query.data || {}
if (typeof data.cookie === 'string') {
data.cookie = cookieToJson(data.cookie)
query.cookie = data.cookie
}
} catch (e) {
data = {}
}
const crypto = query.crypto || ''
const res = request(uri, data, createOption(query, crypto))
return res
}
-15
View File
@@ -1,15 +0,0 @@
// 歌手专辑列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 30,
offset: query.offset || 0,
total: true,
}
return request(
`/api/artist/albums/${query.id}`,
data,
createOption(query, 'weapi'),
)
}
-9
View File
@@ -1,9 +0,0 @@
// 歌手介绍
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/artist/introduction`, data, createOption(query, 'weapi'))
}
-10
View File
@@ -1,10 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
return request(
`/api/artist/head/info/get`,
{
id: query.id,
},
createOption(query),
)
}
-9
View File
@@ -1,9 +0,0 @@
// 歌手动态信息
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/artist/detail/dynamic`, data, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 歌手粉丝
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
limit: query.limit || 20,
offset: query.offset || 0,
}
return request(`/api/artist/fans/get`, data, createOption(query, 'weapi'))
}
-13
View File
@@ -1,13 +0,0 @@
// 歌手粉丝数量
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(
`/api/artist/follow/count/get`,
data,
createOption(query, 'weapi'),
)
}
-33
View File
@@ -1,33 +0,0 @@
// 歌手分类
/*
type 取值
1:男歌手
2:女歌手
3:乐队
area 取值
-1:全部
7华语
96欧美
8:日本
16韩国
0:其他
initial 取值 a-z/A-Z
*/
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
initial: isNaN(query.initial)
? (query.initial || '').toUpperCase().charCodeAt() || undefined
: query.initial,
offset: query.offset || 0,
limit: query.limit || 30,
total: true,
type: query.type || '1',
area: query.area,
}
return request(`/api/v1/artist/list`, data, createOption(query, 'weapi'))
}
-12
View File
@@ -1,12 +0,0 @@
// 歌手相关MV
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
artistId: query.id,
limit: query.limit,
offset: query.offset,
total: true,
}
return request(`/api/artist/mvs`, data, createOption(query, 'weapi'))
}
-12
View File
@@ -1,12 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 20,
startTimestamp: query.before || Date.now(),
}
return request(
`/api/sub/artist/new/works/mv/list`,
data,
createOption(query, 'weapi'),
)
}
-12
View File
@@ -1,12 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 20,
startTimestamp: query.before || Date.now(),
}
return request(
`/api/sub/artist/new/works/song/list`,
data,
createOption(query, 'weapi'),
)
}
-12
View File
@@ -1,12 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
private_cloud: 'true',
work_type: 1,
order: query.order || 'hot', //hot,time
offset: query.offset || 0,
limit: query.limit || 100,
}
return request(`/api/v1/artist/songs`, data, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 收藏与取消收藏歌手
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.t = query.t == 1 ? 'sub' : 'unsub'
const data = {
artistId: query.id,
artistIds: '[' + query.id + ']',
}
return request(`/api/artist/${query.t}`, data, createOption(query, 'weapi'))
}
-11
View File
@@ -1,11 +0,0 @@
// 关注歌手列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 25,
offset: query.offset || 0,
total: true,
}
return request(`/api/artist/sublist`, data, createOption(query, 'weapi'))
}
-8
View File
@@ -1,8 +0,0 @@
// 歌手热门 50 首歌曲
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
id: query.id,
}
return request(`/api/artist/top/song`, data, createOption(query, 'weapi'))
}
-15
View File
@@ -1,15 +0,0 @@
// 歌手相关视频
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
artistId: query.id,
page: JSON.stringify({
size: query.size || 10,
cursor: query.cursor || 0,
}),
tab: 0,
order: query.order || 0,
}
return request(`/api/mlog/artist/video`, data, createOption(query, 'weapi'))
}
-6
View File
@@ -1,6 +0,0 @@
// 歌手单曲
const createOption = require('../util/option.js')
module.exports = (query, request) => {
return request(`/api/v1/artist/${query.id}`, {}, createOption(query, 'weapi'))
}
-19
View File
@@ -1,19 +0,0 @@
const { default: axios } = require('axios')
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
const res = await axios({
method: 'get',
url: `https://interface.music.163.com/api/music/audio/match?sessionId=0123456789abcdef&algorithmCode=shazam_v2&duration=${
query.duration
}&rawdata=${encodeURIComponent(query.audioFP)}&times=1&decrypt=1`,
data: null,
})
return {
status: 200,
body: {
code: 200,
data: res.data.data,
},
}
}
-22
View File
@@ -1,22 +0,0 @@
const uploadPlugin = require('../plugins/upload')
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
const uploadInfo = await uploadPlugin(query, request)
const res = await request(
`/api/user/avatar/upload/v1`,
{
imgid: uploadInfo.imgId,
},
createOption(query),
)
return {
status: 200,
body: {
code: 200,
data: {
...uploadInfo,
...res.body,
},
},
}
}
-16
View File
@@ -1,16 +0,0 @@
// 首页轮播图
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const type =
{
0: 'pc',
1: 'android',
2: 'iphone',
3: 'ipad',
}[query.type || 0] || 'pc'
return request(
`/api/v2/banner/get`,
{ clientType: type },
createOption(query),
)
}
-12
View File
@@ -1,12 +0,0 @@
// 批量请求接口
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {}
Object.keys(query).forEach((i) => {
if (/^\/api\//.test(i)) {
data[i] = query[i]
}
})
return request(`/api/batch`, data, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 广播电台 - 分类/地区信息
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {}
return request(
`/api/voice/broadcast/category/region/get`,
data,
createOption(query),
)
}
-12
View File
@@ -1,12 +0,0 @@
// 广播电台 - 我的收藏
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
contentType: 'BROADCAST',
limit: query.limit || '99999',
timeReverseOrder: 'true',
startDate: '4762584922000',
}
return request(`/api/content/channel/collect/list`, data, createOption(query))
}
-13
View File
@@ -1,13 +0,0 @@
// 广播电台 - 电台信息
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
channelId: query.id,
}
return request(
`/api/voice/broadcast/channel/currentinfo`,
data,
createOption(query),
)
}
-13
View File
@@ -1,13 +0,0 @@
// 广播电台 - 全部电台
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
categoryId: query.categoryId || '0',
regionId: query.regionId || '0',
limit: query.limit || '20',
lastId: query.lastId || '0',
score: query.score || '-1',
}
return request(`/api/voice/broadcast/channel/list`, data, createOption(query))
}
-12
View File
@@ -1,12 +0,0 @@
// 广播电台 - 收藏/取消收藏电台
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.t = query.t == 1 ? 'false' : 'true'
const data = {
contentType: 'BROADCAST',
contentId: query.id,
cancelCollect: query.t,
}
return request(`/api/content/interact/collect`, data, createOption(query))
}
-8
View File
@@ -1,8 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
startTime: query.startTime || Date.now(),
endTime: query.endTime || Date.now(),
}
return request(`/api/mcalendar/detail`, data, createOption(query, 'weapi'))
}
-11
View File
@@ -1,11 +0,0 @@
// 发送验证码
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ctcode: query.ctcode || '86',
secrete: 'music_middleuser_pclogin',
cellphone: query.phone,
}
return request(`/api/sms/captcha/sent`, data, createOption(query, 'weapi'))
}
-11
View File
@@ -1,11 +0,0 @@
// 校验验证码
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ctcode: query.ctcode || '86',
cellphone: query.phone,
captcha: query.captcha,
}
return request(`/api/sms/captcha/verify`, data, createOption(query, 'weapi'))
}
-10
View File
@@ -1,10 +0,0 @@
// 检测手机号码是否已注册
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
cellphone: query.phone,
countrycode: query.countrycode,
}
return request(`/api/cellphone/existence/check`, data, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 获取指定维度音乐排行榜详情
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
chartCode: query.chartCode,
targetId: query.targetId,
targetType: query.targetType,
}
return request(`/api/chart/detail`, data, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 获取指定维度音乐排行榜列表
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
chartCode: query.chartCode,
targetId: query.targetId,
targetType: query.targetType,
}
return request(`/api/chart/song/detail`, data, createOption(query))
}
-30
View File
@@ -1,30 +0,0 @@
// 歌曲可用性
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
ids: '[' + parseInt(query.id) + ']',
br: parseInt(query.br || 999000),
}
return request(
`/api/song/enhance/player/url`,
data,
createOption(query, 'weapi'),
).then((response) => {
let playable = false
if (response.body.code == 200) {
if (response.body.data[0].code == 200) {
playable = true
}
}
if (playable) {
response.body = { code: 200, success: true, message: 'ok' }
return response
} else {
// response.status = 404
response.body = { code: 200, success: false, message: '亲爱的,暂无版权' }
return response
// return Promise.reject(response)
}
})
}
-164
View File
@@ -1,164 +0,0 @@
const uploadPlugin = require('../plugins/songUpload')
const createOption = require('../util/option.js')
const logger = require('../util/logger.js')
const {
isTempFile,
getFileSize,
getFileMd5,
cleanupTempFile,
getFileExtension,
sanitizeFilename,
} = require('../util/fileHelper')
let mm
module.exports = async (query, request) => {
mm = require('music-metadata')
query.songFile.name = Buffer.from(query.songFile.name, 'latin1').toString(
'utf-8',
)
const ext = getFileExtension(query.songFile.name)
const filename = sanitizeFilename(query.songFile.name)
const bitrate = 999000
if (!query.songFile) {
return Promise.reject({
status: 500,
body: {
msg: '请上传音乐文件',
code: 500,
},
})
}
const useTemp = isTempFile(query.songFile)
let fileSize = await getFileSize(query.songFile)
let fileMd5 = await getFileMd5(query.songFile)
query.songFile.md5 = fileMd5
query.songFile.size = fileSize
try {
const res = await request(
`/api/cloud/upload/check`,
{
bitrate: String(bitrate),
ext: '',
length: fileSize,
md5: fileMd5,
songId: '0',
version: 1,
},
createOption(query),
)
let artist = ''
let album = ''
let songName = ''
try {
let metadata
if (useTemp) {
metadata = await mm.parseFile(query.songFile.tempFilePath)
} else {
metadata = await mm.parseBuffer(
query.songFile.data,
query.songFile.mimetype,
)
}
const info = metadata.common
if (info.title) songName = info.title
if (info.album) album = info.album
if (info.artist) artist = info.artist
} catch (error) {
logger.info('元数据解析错误:', error.message)
}
const tokenRes = await request(
`/api/nos/token/alloc`,
{
bucket: '',
ext: ext,
filename: filename,
local: false,
nos_product: 3,
type: 'audio',
md5: fileMd5,
},
createOption(query),
)
if (!tokenRes.body.result || !tokenRes.body.result.resourceId) {
logger.error('Token分配失败:', tokenRes.body)
return Promise.reject({
status: 500,
body: {
code: 500,
msg: '获取上传token失败',
detail: tokenRes.body,
},
})
}
if (res.body.needUpload) {
logger.info('需要上传,开始上传流程...')
try {
const uploadInfo = await uploadPlugin(query, request)
logger.info('上传完成:', uploadInfo?.body?.result?.resourceId)
} catch (uploadError) {
logger.error('上传失败:', uploadError)
return Promise.reject(uploadError)
}
} else {
logger.info('文件已存在,跳过上传')
}
const res2 = await request(
`/api/upload/cloud/info/v2`,
{
md5: fileMd5,
songid: res.body.songId,
filename: query.songFile.name,
song: songName || filename,
album: album || '未知专辑',
artist: artist || '未知艺术家',
bitrate: String(bitrate),
resourceId: tokenRes.body.result.resourceId,
},
createOption(query),
)
if (res2.body.code !== 200) {
logger.error('云盘信息上传失败:', res2.body)
return Promise.reject({
status: res2.status || 500,
body: {
code: res2.body.code || 500,
msg: res2.body.msg || '上传云盘信息失败',
detail: res2.body,
},
})
}
const res3 = await request(
`/api/cloud/pub/v2`,
{
songid: res2.body.songId,
},
createOption(query),
)
return {
status: 200,
body: {
...res.body,
...res3.body,
},
cookie: res.cookie,
}
} finally {
if (useTemp) {
await cleanupTempFile(query.songFile.tempFilePath)
}
}
}
-39
View File
@@ -1,39 +0,0 @@
// 云盘导入歌曲
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
query.id = query.id || -2
query.artist = query.artist || '未知'
query.album = query.album || '未知'
const checkData = {
uploadType: 0,
songs: JSON.stringify([
{
md5: query.md5,
songId: query.id,
bitrate: query.bitrate,
fileSize: query.fileSize,
},
]),
}
const res = await request(
`/api/cloud/upload/check/v2`,
checkData,
createOption(query),
)
//res.body.data[0].upload 0:文件可导入,1:文件已在云盘,2:不能导入
//只能用song决定云盘文件名,且上传后的文件名后缀固定为mp3
const importData = {
uploadType: 0,
songs: JSON.stringify([
{
songId: res.body.data[0].songId,
bitrate: query.bitrate,
song: query.song,
artist: query.artist,
album: query.album,
fileName: query.song + '.' + query.fileType,
},
]),
}
return request(`/api/cloud/user/song/import`, importData, createOption(query))
}
-11
View File
@@ -1,11 +0,0 @@
// 获取云盘歌词
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
userId: query.uid,
songId: query.sid,
lv: -1,
kv: -1,
}
return request(`/api/cloud/lyric/get`, data, createOption(query, 'eapi'))
}
-13
View File
@@ -1,13 +0,0 @@
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
userId: query.uid,
songId: query.sid,
adjustSongId: query.asid,
}
return request(
`/api/cloud/user/song/match`,
data,
createOption(query, 'weapi'),
)
}
-72
View File
@@ -1,72 +0,0 @@
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
const {
songId,
resourceId,
md5,
filename,
song,
artist,
album,
bitrate = 999000,
} = query
if (!songId || !resourceId || !md5 || !filename) {
return Promise.reject({
status: 400,
body: {
code: 400,
msg: '缺少必要参数: songId, resourceId, md5, filename',
},
})
}
const songName = song || filename.replace(/\.[^.]+$/, '')
const res2 = await request(
`/api/upload/cloud/info/v2`,
{
md5: md5,
songid: songId,
filename: filename,
song: songName,
album: album || '未知专辑',
artist: artist || '未知艺术家',
bitrate: String(bitrate),
resourceId: resourceId,
},
createOption(query),
)
if (res2.body.code !== 200) {
return Promise.reject({
status: res2.status || 500,
body: {
code: res2.body.code || 500,
msg: res2.body.msg || '上传云盘信息失败',
detail: res2.body,
},
})
}
const res3 = await request(
`/api/cloud/pub/v2`,
{
songid: res2.body.songId,
},
createOption(query),
)
return {
status: 200,
body: {
code: 200,
data: {
songId: res2.body.songId,
...res3.body,
},
},
cookie: res2.cookie,
}
}
-111
View File
@@ -1,111 +0,0 @@
const { default: axios } = require('axios')
const createOption = require('../util/option.js')
module.exports = async (query, request) => {
const { md5, fileSize, filename, bitrate = 999000 } = query
if (!md5 || !fileSize || !filename) {
return Promise.reject({
status: 400,
body: {
code: 400,
msg: '缺少必要参数: md5, fileSize, filename',
},
})
}
const ext = filename.includes('.') ? filename.split('.').pop() : 'mp3'
const checkRes = await request(
`/api/cloud/upload/check`,
{
bitrate: String(bitrate),
ext: '',
length: fileSize,
md5: md5,
songId: '0',
version: 1,
},
createOption(query),
)
const bucket = 'jd-musicrep-privatecloud-audio-public'
const tokenRes = await request(
`/api/nos/token/alloc`,
{
bucket: bucket,
ext: ext,
filename: filename
.replace(/\.[^.]+$/, '')
.replace(/\s/g, '')
.replace(/\./g, '_'),
local: false,
nos_product: 3,
type: 'audio',
md5: md5,
},
createOption(query, 'weapi'),
)
if (!tokenRes.body.result || !tokenRes.body.result.objectKey) {
return Promise.reject({
status: 500,
body: {
code: 500,
msg: '获取上传token失败',
detail: tokenRes.body,
},
})
}
let lbs
try {
lbs = (
await axios({
method: 'get',
url: `https://wanproxy.127.net/lbs?version=1.0&bucketname=${bucket}`,
timeout: 10000,
})
).data
} catch (error) {
return Promise.reject({
status: 500,
body: {
code: 500,
msg: '获取上传服务器地址失败',
detail: error.message,
},
})
}
if (!lbs || !lbs.upload || !lbs.upload[0]) {
return Promise.reject({
status: 500,
body: {
code: 500,
msg: '获取上传服务器地址无效',
detail: lbs,
},
})
}
return {
status: 200,
body: {
code: 200,
data: {
needUpload: checkRes.body.needUpload,
songId: checkRes.body.songId,
uploadToken: tokenRes.body.result.token,
objectKey: tokenRes.body.result.objectKey,
resourceId: tokenRes.body.result.resourceId,
uploadUrl: `${lbs.upload[0]}/${bucket}/${tokenRes.body.result.objectKey.replace(/\//g, '%2F')}?offset=0&complete=true&version=1.0`,
bucket: bucket,
md5: md5,
fileSize: fileSize,
filename: filename,
},
},
cookie: checkRes.cookie,
}
}
-13
View File
@@ -1,13 +0,0 @@
// 搜索
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
s: query.keywords,
type: query.type || 1, // 1: 单曲, 10: 专辑, 100: 歌手, 1000: 歌单, 1002: 用户, 1004: MV, 1006: 歌词, 1009: 电台, 1014: 视频
limit: query.limit || 30,
offset: query.offset || 0,
total: true,
}
return request(`/api/cloudsearch/pc`, data, createOption(query))
}
-30
View File
@@ -1,30 +0,0 @@
const { resourceTypeMap } = require('../util/config.json')
// 发送与删除评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.t = {
1: 'add',
0: 'delete',
2: 'reply',
}[query.t]
query.type = resourceTypeMap[query.type]
const data = {
threadId: query.type + query.id,
}
if (query.type == 'A_EV_2_') {
data.threadId = query.threadId
}
if (query.t == 'add') data.content = query.content
else if (query.t == 'delete') data.commentId = query.commentId
else if (query.t == 'reply') {
data.commentId = query.commentId
data.content = query.content
}
return request(
`/api/resource/comments/${query.t}`,
data,
createOption(query, 'weapi'),
)
}
-16
View File
@@ -1,16 +0,0 @@
// 专辑评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
rid: query.id,
limit: query.limit || 20,
offset: query.offset || 0,
beforeTime: query.before || 0,
}
return request(
`/api/v1/resource/comments/R_AL_3_${query.id}`,
data,
createOption(query, 'weapi'),
)
}
-16
View File
@@ -1,16 +0,0 @@
// 电台评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
rid: query.id,
limit: query.limit || 20,
offset: query.offset || 0,
beforeTime: query.before || 0,
}
return request(
`/api/v1/resource/comments/A_DJ_1_${query.id}`,
data,
createOption(query, 'weapi'),
)
}
-15
View File
@@ -1,15 +0,0 @@
// 获取动态评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
const data = {
limit: query.limit || 20,
offset: query.offset || 0,
beforeTime: query.before || 0,
}
return request(
`/api/v1/resource/comments/${query.threadId}`,
data,
createOption(query, 'weapi'),
)
}
-16
View File
@@ -1,16 +0,0 @@
const { resourceTypeMap } = require('../util/config.json')
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.type = resourceTypeMap[query.type]
const data = {
parentCommentId: query.parentCommentId,
threadId: query.type + query.id,
time: query.time || -1,
limit: query.limit || 20,
}
return request(
`/api/resource/comment/floor/get`,
data,
createOption(query, 'weapi'),
)
}
-18
View File
@@ -1,18 +0,0 @@
const { resourceTypeMap } = require('../util/config.json')
// 热门评论
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.type = resourceTypeMap[query.type]
const data = {
rid: query.id,
limit: query.limit || 20,
offset: query.offset || 0,
beforeTime: query.before || 0,
}
return request(
`/api/v1/resource/hotcomments/${query.type}${query.id}`,
data,
createOption(query, 'weapi'),
)
}
-20
View File
@@ -1,20 +0,0 @@
const { resourceTypeMap } = require('../util/config.json')
const createOption = require('../util/option.js')
module.exports = (query, request) => {
query.type = resourceTypeMap[query.type || 0]
const threadId = query.type + query.sid
const data = {
targetUserId: query.uid,
commentId: query.cid,
cursor: query.cursor || '-1',
threadId: threadId,
pageNo: query.page || 1,
idCursor: query.idCursor || -1,
pageSize: query.pageSize || 100,
}
return request(
`/api/v2/resource/comments/hug/list`,
data,
createOption(query),
)
}
-30
View File
@@ -1,30 +0,0 @@
// 评论统计数据
// type: 0=歌曲 1=MV 2=歌单 3=专辑 4=电台节目 5=视频 6=动态 7=电台
// ids: 资源 ID 列表,多个用逗号分隔,如 "123,456"
const { resourceTypeMap } = require('../util/config.json')
const createOption = require('../util/option.js')
// 从 resourceTypeMap 的前缀值中提取网易云内部资源类型编号
// 例如 "R_SO_4_" -> "4", "A_DR_14_" -> "14"
const resourceTypeIdMap = Object.fromEntries(
Object.entries(resourceTypeMap).map(([key, prefix]) => [
key,
prefix.replace(/_$/, '').split('_').pop(),
]),
)
module.exports = (query, request) => {
const ids = String(query.ids || query.id || '')
.split(',')
.map((id) => id.trim())
.filter(Boolean)
return request(
`/api/resource/commentInfo/list`,
{
resourceType: resourceTypeIdMap[String(query.type || 0)],
resourceIds: JSON.stringify(ids),
},
createOption(query, 'weapi'),
)
}

Some files were not shown because too many files have changed in this diff Show More