mirror of
https://github.com/NeteaseCloudMusicApiEnhanced/api-enhanced.git
synced 2026-06-27 21:25:08 +00:00
Compare commits
2 Commits
6fc4231142
...
60ee927e32
| Author | SHA1 | Date | |
|---|---|---|---|
| 60ee927e32 | |||
| eb07a525eb |
23
AGENTS.md
Normal file
23
AGENTS.md
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# 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,8 +1,40 @@
|
|||||||
// 听歌打卡
|
// 听歌打卡
|
||||||
|
|
||||||
const createOption = require('../util/option.js')
|
const createOption = require('../util/option.js')
|
||||||
module.exports = (query, request) => {
|
module.exports = async (query, request) => {
|
||||||
const data = {
|
// 注入 os=osx 的 cookie
|
||||||
|
let cookie = query.cookie || ''
|
||||||
|
if (typeof cookie === 'object') {
|
||||||
|
cookie = Object.assign({ os: 'osx' }, cookie)
|
||||||
|
} else if (typeof cookie === 'string') {
|
||||||
|
if (cookie.indexOf('os=') > -1) {
|
||||||
|
cookie = cookie.replace(/os=[^;]+/g, 'os=osx')
|
||||||
|
} else {
|
||||||
|
cookie = cookie + '; os=osx'
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
cookie = 'os=osx'
|
||||||
|
}
|
||||||
|
query.cookie = cookie
|
||||||
|
|
||||||
|
// 1) startplay → 进「最近播放」
|
||||||
|
const startplayData = {
|
||||||
|
logs: JSON.stringify([
|
||||||
|
{
|
||||||
|
action: 'startplay',
|
||||||
|
json: {
|
||||||
|
id: query.id,
|
||||||
|
type: 'song',
|
||||||
|
mainsite: '1',
|
||||||
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) play → 涨「听歌排行」计数
|
||||||
|
const playData = {
|
||||||
logs: JSON.stringify([
|
logs: JSON.stringify([
|
||||||
{
|
{
|
||||||
action: 'play',
|
action: 'play',
|
||||||
@ -15,12 +47,30 @@ module.exports = (query, request) => {
|
|||||||
type: 'song',
|
type: 'song',
|
||||||
wifi: 0,
|
wifi: 0,
|
||||||
source: 'list',
|
source: 'list',
|
||||||
mainsite: 1,
|
mainsite: '1',
|
||||||
content: '',
|
mainsiteWeb: '1',
|
||||||
|
content: `id=${query.sourceid}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
]),
|
]),
|
||||||
}
|
}
|
||||||
|
|
||||||
return request(`/api/feedback/weblog`, data, createOption(query, 'weapi'))
|
const option = createOption(query, 'eapi')
|
||||||
|
option.domain = 'https://clientlog.music.163.com'
|
||||||
|
|
||||||
|
// 发送两次请求
|
||||||
|
const res1 = await request(`/api/feedback/weblog`, startplayData, option)
|
||||||
|
const res2 = await request(`/api/feedback/weblog`, playData, option)
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: 200,
|
||||||
|
body: {
|
||||||
|
code: 200,
|
||||||
|
data: 'success',
|
||||||
|
details: {
|
||||||
|
startplay: res1.body,
|
||||||
|
play: res2.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@neteasecloudmusicapienhanced/api",
|
"name": "@neteasecloudmusicapienhanced/api",
|
||||||
"version": "4.35.1",
|
"version": "4.35.2",
|
||||||
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
|
"description": "全网最全的网易云音乐API接口 || A revival project for NeteaseCloudMusicApi Node.js Services (Half Refactor & Enhanced) || 网易云音乐 API 备份 + 增强 || 本项目自原版v4.28.0版本后开始自行维护",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nodemon app.js",
|
"dev": "nodemon app.js",
|
||||||
|
|||||||
21
server.js
21
server.js
@ -10,6 +10,7 @@ const { cookieToJson } = require('./util/index')
|
|||||||
const fileUpload = require('express-fileupload')
|
const fileUpload = require('express-fileupload')
|
||||||
const decode = require('safe-decode-uri-component')
|
const decode = require('safe-decode-uri-component')
|
||||||
const logger = require('./util/logger.js')
|
const logger = require('./util/logger.js')
|
||||||
|
const { APP_CONF } = require('./util/config.json')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version check result.
|
* The version check result.
|
||||||
@ -299,15 +300,15 @@ async function constructServer(moduleDefs) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
let usedCrypto = ''
|
||||||
const moduleResponse = await moduleDef.module(query, (...params) => {
|
const moduleResponse = await moduleDef.module(query, (...params) => {
|
||||||
// 参数注入客户端IP
|
|
||||||
const obj = [...params]
|
const obj = [...params]
|
||||||
const options = obj[2] || {}
|
const options = obj[2] || {}
|
||||||
|
usedCrypto = options.crypto || ''
|
||||||
let ip = ''
|
let ip = ''
|
||||||
|
|
||||||
if (options.randomCNIP) {
|
if (options.randomCNIP) {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
// logger.info('Using random Chinese IP for request:', ip)
|
|
||||||
} else {
|
} else {
|
||||||
ip = req.ip
|
ip = req.ip
|
||||||
|
|
||||||
@ -317,7 +318,6 @@ async function constructServer(moduleDefs) {
|
|||||||
if (ip == '::1') {
|
if (ip == '::1') {
|
||||||
ip = global.cnIp
|
ip = global.cnIp
|
||||||
}
|
}
|
||||||
// logger.info('Requested from ip:', ip)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obj[2] = {
|
obj[2] = {
|
||||||
@ -327,7 +327,10 @@ async function constructServer(moduleDefs) {
|
|||||||
|
|
||||||
return request(...obj)
|
return request(...obj)
|
||||||
})
|
})
|
||||||
logger.info(`Request Success: ${decode(req.originalUrl)}`)
|
const displayCrypto = usedCrypto || (APP_CONF.encrypt ? 'eapi' : 'api')
|
||||||
|
logger.info(
|
||||||
|
`Request Success: [${displayCrypto}] ${decode(req.originalUrl)}`,
|
||||||
|
)
|
||||||
|
|
||||||
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
// 夹带私货部分:如果开启了通用解锁,并且是获取歌曲URL的接口,则尝试解锁(如果需要的话)ヾ(≧▽≦*)o
|
||||||
if (
|
if (
|
||||||
@ -422,7 +425,7 @@ async function serveNcmApi(options) {
|
|||||||
options.checkVersion &&
|
options.checkVersion &&
|
||||||
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
checkVersion().then(({ npmVersion, ourVersion, status }) => {
|
||||||
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
if (status == VERSION_CHECK_RESULT.NOT_LATEST) {
|
||||||
logger.info(
|
logger.warn(
|
||||||
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
`最新版本: ${npmVersion}, 当前版本: ${ourVersion}, 请及时更新`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -444,11 +447,9 @@ async function serveNcmApi(options) {
|
|||||||
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
╠═╣╠═╝║ ║╣ ║║║╠═╣╠═╣║║║║ ║╣ ║║
|
||||||
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
╩ ╩╩ ╩ ╚═╝╝╚╝╩ ╩╩ ╩╝╚╝╚═╝╚═╝═╩╝
|
||||||
`)
|
`)
|
||||||
logger.info(`
|
logger.info(
|
||||||
- Server started successfully @ http://${host ? host : 'localhost'}:${port}
|
`Server started successfully @ http://${host ? host : 'localhost'}:${port}`,
|
||||||
- Environment: ${process.env.NODE_ENV || 'development'}
|
)
|
||||||
- Node Version: ${process.version}
|
|
||||||
- Process ID: ${process.pid}`)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return appExt
|
return appExt
|
||||||
|
|||||||
@ -62,9 +62,9 @@ const chinaIPRanges = (function loadChinaIPRanges() {
|
|||||||
// attach total for convenience
|
// attach total for convenience
|
||||||
arr.totalCount = total
|
arr.totalCount = total
|
||||||
|
|
||||||
logger.info(
|
// logger.info(
|
||||||
`Loaded ${arr.length} Chinese IP ranges from china_ip_ranges.txt, total ${total} IPs`,
|
// `Loaded ${arr.length} Chinese IP ranges from china_ip_ranges.txt, total ${total} IPs`,
|
||||||
)
|
// )
|
||||||
return arr
|
return arr
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Failed to load china_ip_ranges.txt:', error.message)
|
logger.error('Failed to load china_ip_ranges.txt:', error.message)
|
||||||
|
|||||||
@ -88,6 +88,12 @@ const osMap = {
|
|||||||
osver: '16.2',
|
osver: '16.2',
|
||||||
channel: 'distribution',
|
channel: 'distribution',
|
||||||
},
|
},
|
||||||
|
osx: {
|
||||||
|
os: 'osx',
|
||||||
|
appver: '3.1.10.5100',
|
||||||
|
osver: '15.5',
|
||||||
|
channel: 'netease',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// 预先定义userAgentMap
|
// 预先定义userAgentMap
|
||||||
@ -306,7 +312,11 @@ const createRequest = (uri, data, options) => {
|
|||||||
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
if (cookie.MUSIC_A) header['MUSIC_A'] = cookie.MUSIC_A
|
||||||
|
|
||||||
headers['Cookie'] = createHeaderCookie(header)
|
headers['Cookie'] = createHeaderCookie(header)
|
||||||
headers['User-Agent'] = options.ua || chooseUserAgent('api', 'iphone')
|
headers['User-Agent'] =
|
||||||
|
options.ua ||
|
||||||
|
(cookie.os === 'osx'
|
||||||
|
? 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36'
|
||||||
|
: chooseUserAgent('api', 'iphone'))
|
||||||
|
|
||||||
if (crypto === 'eapi') {
|
if (crypto === 'eapi') {
|
||||||
// headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
|
// headers['x-aeapi'] = true // 服务器会使用gzip压缩返回值
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user