Compare commits

..

No commits in common. "6fc4231142e2a91913cdf54f27cc9722391e30c0" and "395a80e74b9e69b8bfadfc19d77d57121f68087d" have entirely different histories.

9 changed files with 260 additions and 134 deletions

13
app.js
View File

@ -8,11 +8,16 @@ async function start() {
if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) { if (!fs.existsSync(path.resolve(tmpPath, 'anonymous_token'))) {
fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8') fs.writeFileSync(path.resolve(tmpPath, 'anonymous_token'), '', 'utf-8')
} }
// 启动时更新anonymous_token // 启动时更新anonymous_tokenVercel 构建环境下跳过网络请求喵~
const generateConfig = require('./generateConfig') if (!process.env.VERCEL_ENV) {
await generateConfig() const generateConfig = require('./generateConfig')
await generateConfig()
}
require('./server').serveNcmApi({ require('./server').serveNcmApi({
checkVersion: true, checkVersion: true,
}) })
} }
start() start().catch((err) => {
console.error('[FATAL] 启动失败:', err)
process.exit(1)
})

View File

@ -4,38 +4,159 @@ const { register_anonimous } = require('./main')
const { cookieToJson, generateRandomChineseIP } = require('./util/index') const { cookieToJson, generateRandomChineseIP } = require('./util/index')
const { getXeapiPublicKey } = require('./util/xeapiKey') const { getXeapiPublicKey } = require('./util/xeapiKey')
const tmpPath = require('os').tmpdir() const tmpPath = require('os').tmpdir()
const logger = require('./util/logger')
async function generateConfig() { const MAX_RETRIES = 10
global.cnIp = generateRandomChineseIP() const RETRY_DELAY_MS = 1000
try {
const res = await register_anonimous() function sleep(ms) {
const cookie = res.body.cookie return new Promise((resolve) => setTimeout(resolve, ms))
if (cookie) { }
const cookieObj = cookieToJson(cookie)
function isRetryableError(err) {
const status =
(err && err.status) || (err && err.response && err.response.status)
if (status && status >= 500) {
return true
}
return false
}
/** @returns {{ success: boolean, error?: Error }} */
async function fetchAnonymousToken() {
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
try {
const res = await register_anonimous()
const body = res.body
if (body.code && body.code !== 200) {
const err = new Error(`匿名注册返回错误码 ${body.code}`)
err.status = 502
throw err
}
const cookie = body.cookie
if (cookie) {
const cookieObj = cookieToJson(cookie)
fs.writeFileSync(
path.resolve(tmpPath, 'anonymous_token'),
cookieObj.MUSIC_A,
'utf-8',
)
logger.success('[generateConfig] 匿名 token 注册成功')
return { success: true }
}
logger.warn(
`[generateConfig] 匿名注册返回了空 cookie (attempt ${attempt})`,
)
return {
success: false,
error: new Error('empty cookie from anonymous register'),
}
} catch (err) {
if (isRetryableError(err) && attempt < MAX_RETRIES) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
logger.warn(
`[generateConfig] 获取匿名 token 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
)
await sleep(delay)
continue
}
if (attempt >= MAX_RETRIES) {
logger.error(
`[generateConfig] 获取匿名 token 已达最大重试次数 (${MAX_RETRIES}):`,
err.message,
)
} else {
logger.error(
`[generateConfig] 获取匿名 token 失败 (不可重试):`,
err.message,
)
}
return { success: false, error: err }
}
}
return { success: false, error: new Error('unreachable') }
}
/**
* 获取 xeapi public key带重试
* @returns {{ success: boolean, error?: Error }}
*/
async function fetchXeapiPublicKey() {
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
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( fs.writeFileSync(
path.resolve(tmpPath, 'anonymous_token'), path.resolve(tmpPath, 'xeapi_public_key'),
cookieObj.MUSIC_A, JSON.stringify(publicKey),
'utf-8', 'utf-8',
) )
logger.success('[generateConfig] xeapi public key 获取成功')
return { success: true }
} catch (err) {
if (isRetryableError(err) && attempt < MAX_RETRIES) {
const delay = RETRY_DELAY_MS * Math.pow(2, attempt - 1)
logger.warn(
`[generateConfig] 获取 xeapi public key 失败 (attempt ${attempt}/${MAX_RETRIES}), ${delay}ms 后重试...`,
)
await sleep(delay)
continue
}
if (attempt >= MAX_RETRIES) {
logger.error(
`[generateConfig] 获取 xeapi public key 已达最大重试次数 (${MAX_RETRIES}):`,
err.message,
)
} else {
logger.error(
`[generateConfig] 获取 xeapi public key 失败 (不可重试):`,
err.message,
)
}
return { success: false, error: err }
} }
} catch (error) {
console.log(error)
} }
try { return { success: false, error: new Error('unreachable') }
let currentPublicKey = {} }
try {
currentPublicKey = JSON.parse( /**
fs.readFileSync(path.resolve(tmpPath, 'xeapi_public_key'), 'utf-8'), * 生成配置匿名 token + xeapi public key带容错重试
) * @returns {{ tokenOk: boolean, keyOk: boolean }}
} catch (_) {} */
const publicKey = await getXeapiPublicKey(currentPublicKey, global.deviceId) async function generateConfig() {
fs.writeFileSync( global.cnIp = generateRandomChineseIP()
path.resolve(tmpPath, 'xeapi_public_key'),
JSON.stringify(publicKey), // 两个任务并行执行,互不影响喵~
'utf-8', const [tokenResult, keyResult] = await Promise.all([
) fetchAnonymousToken(),
} catch (error) { fetchXeapiPublicKey(),
console.log(error) ])
if (!tokenResult.success) {
logger.warn('[generateConfig] 匿名 token 获取失败')
}
if (!keyResult.success) {
logger.warn('[generateConfig] xeapi public key 获取失败')
}
if (tokenResult.success && keyResult.success) {
logger.success('[generateConfig] 配置初始化完成')
}
return {
tokenOk: tokenResult.success,
keyOk: keyResult.success,
} }
} }
module.exports = generateConfig module.exports = generateConfig

View File

@ -1 +0,0 @@
import './app.js'

View File

@ -26,7 +26,6 @@ function cloudmusic_dll_encode_id(some_id) {
module.exports = async (query, request) => { module.exports = async (query, request) => {
const deviceId = generateDeviceId() const deviceId = generateDeviceId()
logger.info(`Successfully registered anonimous token, deviceId: ${deviceId}`)
global.deviceId = deviceId global.deviceId = deviceId
const encodedId = CryptoJS.enc.Base64.stringify( const encodedId = CryptoJS.enc.Base64.stringify(
CryptoJS.enc.Utf8.parse( CryptoJS.enc.Utf8.parse(

View File

@ -11,6 +11,8 @@
"prepare": "husky install", "prepare": "husky install",
"docs:format": "node scripts/format-docs.js", "docs:format": "node scripts/format-docs.js",
"docs:check": "node scripts/format-docs.js --check", "docs:check": "node scripts/format-docs.js --check",
"docs:toc": "node scripts/format-docs.js --toc",
"docs:all": "node scripts/format-docs.js --dir public/docs --toc --verbose",
"pkgwin": "pkg . -t node18-win-x64 -C GZip -o precompiled/app", "pkgwin": "pkg . -t node18-win-x64 -C GZip -o precompiled/app",
"pkglinux": "pkg . -t node18-linux-x64 -C GZip -o precompiled/app", "pkglinux": "pkg . -t node18-linux-x64 -C GZip -o precompiled/app",
"pkgmacos": "pkg . -t node18-macos-x64 -C GZip -o precompiled/app" "pkgmacos": "pkg . -t node18-macos-x64 -C GZip -o precompiled/app"
@ -52,7 +54,7 @@
}, },
"homepage": "https://neteasecloudmusicapienhanced.js.org/", "homepage": "https://neteasecloudmusicapienhanced.js.org/",
"engines": { "engines": {
"node": ">=12" "node": ">=22"
}, },
"lint-staged": { "lint-staged": {
"*.js": [ "*.js": [

View File

@ -1,3 +1,9 @@
## 目录
- [NeteaseCloudMusicAPI Enhanced](#neteasecloudmusicapi-enhanced)
---
# NeteaseCloudMusicAPI Enhanced # NeteaseCloudMusicAPI Enhanced
> 🎉 全网收集最全的网易云音乐api接口 基于[NeteaseCloudMusicAPI](https://github.com/binaryify/NeteaseCloudMusicApi)的复刻版本 > 🎉 全网收集最全的网易云音乐api接口 基于[NeteaseCloudMusicAPI](https://github.com/binaryify/NeteaseCloudMusicApi)的复刻版本

View File

@ -1,14 +1,14 @@
#!/usr/bin/env node #!/usr/bin/env node
/** /**
* 📝 Markdown 文档格式化工具喵~ * 📝 Markdown 文档格式化工具喵~
* 支持格式化标题代码块空行和缩进 * 支持格式化标题代码块空行缩进还能生成目录
* *
* 用法: * 用法:
* node scripts/format-docs.js # 格式化默认文档 (public/docs/home.md) * node scripts/format-docs.js # 格式化默认文档 (public/docs/home.md)
* node scripts/format-docs.js <文件路径> # 格式化指定文件 * node scripts/format-docs.js <文件路径> # 格式化指定文件
* node scripts/format-docs.js --dir <目录> # 格式化整个目录的 .md 文件 * node scripts/format-docs.js --dir <目录> # 格式化整个目录的 .md 文件
* node scripts/format-docs.js --check # 只检查不写入 (dry-run) * node scripts/format-docs.js --check # 只检查不写入 (dry-run)
* * node scripts/format-docs.js --toc # 同时生成目录
*/ */
const fs = require('fs') const fs = require('fs')
@ -16,13 +16,14 @@ const path = require('path')
// ======================== 配置 ======================== // ======================== 配置 ========================
const CONFIG = { const CONFIG = {
maxConsecutiveBlankLines: 2, // 最大连续空行数 maxConsecutiveBlankLines: 2, // 最大连续空行数
codeBlockLang: true, // 代码块是否保留语言标记 codeBlockLang: true, // 代码块是否保留语言标记
headingSpaceBefore: true, // 标题前是否确保空行 headingSpaceBefore: true, // 标题前是否确保空行
headingSpaceAfter: true, // 标题后是否确保空行 headingSpaceAfter: true, // 标题后是否确保空行
listIndent: 2, // 列表缩进空格数 listIndent: 2, // 列表缩进空格数
encodeSpecialChars: false, // 是否编码特殊字符 encodeSpecialChars: false, // 是否编码特殊字符
removeTrailingSpaces: true, // 是否删除行尾空格 removeTrailingSpaces: true, // 是否删除行尾空格
tocMaxLevel: 3, // 目录最大标题层级
} }
const DEFAULT_FILE = path.resolve(__dirname, '..', 'public', 'docs', 'home.md') const DEFAULT_FILE = path.resolve(__dirname, '..', 'public', 'docs', 'home.md')
@ -106,10 +107,7 @@ function parseBlocks(lines) {
i++ i++
while (i < lines.length) { while (i < lines.length) {
const t = lines[i].trim() const t = lines[i].trim()
if (/-->/.test(t) || /<\/\w+>/.test(t)) { if (/-->/.test(t) || /<\/\w+>/.test(t)) { i++; break }
i++
break
}
i++ i++
} }
const htmlLines = lines.slice(start, i) const htmlLines = lines.slice(start, i)
@ -125,31 +123,17 @@ function parseBlocks(lines) {
i++ i++
} }
const quoteLines = lines.slice(start, i) const quoteLines = lines.slice(start, i)
blocks.push({ blocks.push({ type: 'blockquote', lines: quoteLines, raw: quoteLines.join('\n') })
type: 'blockquote',
lines: quoteLines,
raw: quoteLines.join('\n'),
})
continue continue
} }
// 表格 // 表格
if ( if (/\|/.test(trimmed) && lines[i + 1] && /^\|[\s\-:]+\|/.test(lines[i + 1].trim())) {
/\|/.test(trimmed) &&
lines[i + 1] &&
/^\|[\s\-:]+\|/.test(lines[i + 1].trim())
) {
const start = i const start = i
i += 2 i += 2
while (i < lines.length && /\|/.test(lines[i].trim())) { while (i < lines.length && /\|/.test(lines[i].trim())) { i++ }
i++
}
const tableLines = lines.slice(start, i) const tableLines = lines.slice(start, i)
blocks.push({ blocks.push({ type: 'table', lines: tableLines, raw: tableLines.join('\n') })
type: 'table',
lines: tableLines,
raw: tableLines.join('\n'),
})
continue continue
} }
@ -159,19 +143,10 @@ function parseBlocks(lines) {
i++ i++
while (i < lines.length) { while (i < lines.length) {
const t = lines[i].trim() const t = lines[i].trim()
if (t === '') { if (t === '') { i++; break }
i++ if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])) { i++; continue }
break
}
if (/^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])) {
i++
continue
}
// 缩进 continuation // 缩进 continuation
if (/^\s{2,}/.test(lines[i])) { if (/^\s{2,}/.test(lines[i])) { i++; continue }
i++
continue
}
break break
} }
const listLines = lines.slice(start, i) const listLines = lines.slice(start, i)
@ -185,23 +160,13 @@ function parseBlocks(lines) {
while (i < lines.length && lines[i].trim() !== '') { while (i < lines.length && lines[i].trim() !== '') {
// 如果遇到新的块元素则停止 // 如果遇到新的块元素则停止
const t = lines[i].trim() const t = lines[i].trim()
if ( if (/^#{1,6}\s/.test(t) || /^```/.test(t) || /^~~~/.test(t) || /^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])) break
/^#{1,6}\s/.test(t) ||
/^```/.test(t) ||
/^~~~/.test(t) ||
/^(\s*[-*+]\s|\s*\d+\.\s)/.test(lines[i])
)
break
// 分割线 // 分割线
if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(t)) break if (/^(-{3,}|\*{3,}|_{3,})\s*$/.test(t)) break
i++ i++
} }
const paraLines = lines.slice(start, i) const paraLines = lines.slice(start, i)
blocks.push({ blocks.push({ type: 'paragraph', lines: paraLines, raw: paraLines.join('\n') })
type: 'paragraph',
lines: paraLines,
raw: paraLines.join('\n'),
})
} }
return blocks return blocks
@ -225,20 +190,12 @@ function formatMarkdown(input, options = {}) {
const newLines = [...block.lines] const newLines = [...block.lines]
// 标题前加空行(如果不是第一个块且前一个不是空行) // 标题前加空行(如果不是第一个块且前一个不是空行)
if ( if (cfg.headingSpaceBefore && idx > 0 && blocks[idx - 1].type !== 'empty') {
cfg.headingSpaceBefore &&
idx > 0 &&
blocks[idx - 1].type !== 'empty'
) {
newLines.unshift('') newLines.unshift('')
} }
// 标题后加空行(如果不是最后一个块且后一个不是空行) // 标题后加空行(如果不是最后一个块且后一个不是空行)
if ( if (cfg.headingSpaceAfter && idx < blocks.length - 1 && blocks[idx + 1].type !== 'empty') {
cfg.headingSpaceAfter &&
idx < blocks.length - 1 &&
blocks[idx + 1].type !== 'empty'
) {
newLines.push('') newLines.push('')
} }
@ -263,18 +220,10 @@ function formatMarkdown(input, options = {}) {
newLines[0] = lang ? `${indent}${marker}${lang}` : `${indent}${marker}` newLines[0] = lang ? `${indent}${marker}${lang}` : `${indent}${marker}`
// 确保代码块前后有空行(插在标准化之后,因为只动第一行) // 确保代码块前后有空行(插在标准化之后,因为只动第一行)
if ( if (idx > 0 && blocks[idx - 1].type !== 'empty' && blocks[idx - 1].type !== 'code') {
idx > 0 &&
blocks[idx - 1].type !== 'empty' &&
blocks[idx - 1].type !== 'code'
) {
newLines.unshift('') newLines.unshift('')
} }
if ( if (idx < blocks.length - 1 && blocks[idx + 1].type !== 'empty' && blocks[idx + 1].type !== 'code') {
idx < blocks.length - 1 &&
blocks[idx + 1].type !== 'empty' &&
blocks[idx + 1].type !== 'code'
) {
newLines.push('') newLines.push('')
} }
@ -304,8 +253,7 @@ function formatMarkdown(input, options = {}) {
const indent = line.length - line.trimStart().length const indent = line.length - line.trimStart().length
// 如果是顶层列表项确保缩进为0 // 如果是顶层列表项确保缩进为0
if (indent % cfg.listIndent !== 0) { if (indent % cfg.listIndent !== 0) {
const normalizedIndent = const normalizedIndent = Math.round(indent / cfg.listIndent) * cfg.listIndent
Math.round(indent / cfg.listIndent) * cfg.listIndent
return ' '.repeat(normalizedIndent) + trimmed return ' '.repeat(normalizedIndent) + trimmed
} }
} }
@ -319,8 +267,7 @@ function formatMarkdown(input, options = {}) {
// 处理文件开头和结尾的空行 // 处理文件开头和结尾的空行
while (resultLines.length > 0 && resultLines[0] === '') resultLines.shift() while (resultLines.length > 0 && resultLines[0] === '') resultLines.shift()
while (resultLines.length > 0 && resultLines[resultLines.length - 1] === '') while (resultLines.length > 0 && resultLines[resultLines.length - 1] === '') resultLines.pop()
resultLines.pop()
resultLines.push('') // 文件结尾留一个空行 resultLines.push('') // 文件结尾留一个空行
return resultLines.join('\n') return resultLines.join('\n')
@ -348,6 +295,41 @@ function compressEmptyLines(blocks, maxBlank) {
return result return result
} }
// ======================== 目录生成 ========================
/**
* 从文档中提取标题生成目录
*/
function generateTOC(input, maxLevel = 3) {
const lines = input.split('\n')
const headings = []
for (const line of lines) {
const match = line.match(/^(#{1,6})\s+(.+)$/)
if (match) {
const level = match[1].length
if (level <= maxLevel) {
headings.push({ level, text: match[2].trim() })
}
}
}
if (headings.length === 0) return ''
let toc = '\n## 目录\n\n'
for (const h of headings) {
const indent = ' '.repeat(h.level - 1)
const anchor = h.text
.toLowerCase()
.replace(/[^\w\u4e00-\u9fa5]+/g, '-')
.replace(/^-|-$/g, '')
toc += `${indent}- [${h.text}](#${anchor})\n`
}
toc += '\n---\n'
return toc
}
// ======================== 统计信息 ======================== // ======================== 统计信息 ========================
function getStats(input) { function getStats(input) {
@ -435,7 +417,20 @@ function processFile(filePath, options) {
} }
if (hasChanges) { if (hasChanges) {
writeFile(filePath, formatted) // 如果指定了 --toc在文档开头插入目录
let output = formatted
if (options.toc) {
const tocPlaceholder = '<!-- TOC -->'
if (output.includes(tocPlaceholder)) {
output = output.replace(
new RegExp(`${tocPlaceholder}[\\s\\S]*?${tocPlaceholder}`),
`${tocPlaceholder}\n${generateTOC(output, options.tocMaxLevel || CONFIG.tocMaxLevel)}${tocPlaceholder}`
)
} else {
output = generateTOC(output, options.tocMaxLevel || CONFIG.tocMaxLevel) + '\n' + output
}
}
writeFile(filePath, output)
console.log(green(' ✨ 格式化完成!')) console.log(green(' ✨ 格式化完成!'))
if (options.verbose) { if (options.verbose) {
printStats(statsAfter) printStats(statsAfter)
@ -459,9 +454,7 @@ function processDirectory(dirPath, options) {
return 0 return 0
} }
console.log( console.log(bold(`\n📁 扫描目录: ${cyan(dirPath)} (${files.length} 个文件)\n`))
bold(`\n📁 扫描目录: ${cyan(dirPath)} (${files.length} 个文件)\n`),
)
for (const file of files) { for (const file of files) {
const ret = processFile(file, options) const ret = processFile(file, options)
@ -474,8 +467,7 @@ function processDirectory(dirPath, options) {
// ======================== CLI ======================== // ======================== CLI ========================
function printHelp() { function printHelp() {
console.log( console.log(bold(`
bold(`
📝 Markdown 文档格式化工具 v1.0.0 📝 Markdown 文档格式化工具 v1.0.0
${cyan('用法:')} ${cyan('用法:')}
@ -487,6 +479,7 @@ ${cyan('参数:')}
${cyan('选项:')} ${cyan('选项:')}
--dir, -d <目录> 格式化整个目录下的所有 .md 文件 --dir, -d <目录> 格式化整个目录下的所有 .md 文件
--check, -c dry-run 模式,只检查不写入 --check, -c dry-run 模式,只检查不写入
--toc, -t 在文档中生成目录
--verbose, -v 显示详细统计信息 --verbose, -v 显示详细统计信息
--help, -h 显示帮助信息 --help, -h 显示帮助信息
@ -495,8 +488,8 @@ ${cyan('示例:')}
node scripts/format-docs.js README.md node scripts/format-docs.js README.md
node scripts/format-docs.js --dir docs/ node scripts/format-docs.js --dir docs/
node scripts/format-docs.js --check node scripts/format-docs.js --check
`), node scripts/format-docs.js --toc --verbose
) `))
} }
function parseArgs() { function parseArgs() {
@ -505,6 +498,7 @@ function parseArgs() {
file: null, file: null,
dir: null, dir: null,
check: false, check: false,
toc: false,
verbose: false, verbose: false,
help: false, help: false,
} }
@ -519,6 +513,10 @@ function parseArgs() {
case '-c': case '-c':
options.check = true options.check = true
break break
case '--toc':
case '-t':
options.toc = true
break
case '--verbose': case '--verbose':
case '-v': case '-v':
options.verbose = true options.verbose = true
@ -574,4 +572,4 @@ if (require.main === module) {
process.exit(exitCode) process.exit(exitCode)
} }
module.exports = { formatMarkdown, getStats, parseBlocks } module.exports = { formatMarkdown, generateTOC, getStats, parseBlocks }

View File

@ -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 (
@ -445,10 +448,7 @@ 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

View File

@ -16,9 +16,5 @@
"Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version" "Access-Control-Allow-Headers": "X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version"
} }
} }
], ]
"env": {
"NODE_ENV": "production",
"ENABLE_FLAC": "true"
}
} }