Files
MusicServer/musicSdk/index.js
T

181 lines
5.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import kw from './kw/index.js'
import kg from './kg/index.js'
import tx from './tx/index.js'
import wy from './wy/index.js'
import mg from './mg/index.js'
import bd from './bd/index.js'
import xm from './xm.js'
import { supportQuality } from './api-source.js'
import {
sizeFormate,
formatPlayTime,
dateFormat,
dateFormat2,
formatPlayCount,
decodeName,
} from '../src/index.js'
// 通用工具函数(SDK 内 `import { ... } from '../index.js'` 的解析目标)
export { sizeFormate, formatPlayTime, dateFormat, dateFormat2, formatPlayCount, decodeName }
const SINGERS_SEPARATOR = /、|&|;|;|\/|,|,|\||\]|\[|!|!/g
const SUPPORTED_SOURCES = [
{ id: 'kw', name: '酷我音乐', module: kw },
{ id: 'kg', name: '酷狗音乐', module: kg },
{ id: 'tx', name: 'QQ音乐', module: tx },
{ id: 'wy', name: '网易音乐', module: wy },
{ id: 'mg', name: '咪咕音乐', module: mg },
{ id: 'xm', name: '虾米音乐', module: xm },
// { id: 'bd', name: '百度音乐', module: bd },
]
const SOURCE_MODULES = Object.fromEntries(SUPPORTED_SOURCES.map((s) => [s.id, s.module]))
export const sources = SUPPORTED_SOURCES.map(({ id, name }) => ({ id, name }))
export const init = () =>
Promise.all(SUPPORTED_SOURCES.map((s) => s.module.init?.()).filter(Boolean))
export { supportQuality }
const trimStr = (str) => (typeof str === 'string' ? str.trim() : str)
const filterStr = (str) =>
typeof str === 'string'
? str.replace(/\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[|!|!/g, '')
: String(str ?? '')
const getIntervalSeconds = (interval) => {
if (!interval) return 0
const parts = String(interval).split(':')
let seconds = 0
let unit = 1
while (parts.length) {
seconds += parseInt(parts.pop()) * unit
unit *= 60
}
return seconds
}
const sortSingle = (singer) => {
if (!singer) return ''
return SINGERS_SEPARATOR.test(singer)
? singer.split(SINGERS_SEPARATOR).sort((a, b) => a.localeCompare(b)).join('、')
: singer
}
/**
* 多平台聚合搜索
* @param {object} params
* @param {string} params.name
* @param {string} [params.singer]
* @param {string} [params.source] 指定平台时,聚合结果会跳过该平台
* @param {number} [params.limit=25]
*/
export const searchMusic = async ({ name, singer, source: excludeSource, limit = 25 }) => {
const musicName = trimStr(name)
const tasks = []
for (const source of SUPPORTED_SOURCES) {
if (
source.id === 'xm' ||
source.id === excludeSource ||
!source.module.musicSearch
)
continue
tasks.push(
source.module.musicSearch
.search(`${musicName} ${singer || ''}`.trim(), 1, limit)
.catch(() => null)
)
}
return (await Promise.all(tasks)).filter(Boolean)
}
const pickMatching = (items, predicate) => {
const matched = []
const rest = []
for (const item of items) {
if (predicate(item)) matched.push(item)
else rest.push(item)
}
return { matched, rest }
}
/**
* 跨平台精确匹配歌曲(按歌手、歌名、专辑、时长综合排序)
*/
export const findMusic = async ({ name, singer, albumName, interval, source: s }) => {
const lists = await searchMusic({ name, singer, source: s, limit: 25 })
const fMusicName = filterStr(name).toLowerCase()
const fSinger = filterStr(sortSingle(singer)).toLowerCase()
const fAlbumName = filterStr(albumName).toLowerCase()
const fInterval = getIntervalSeconds(interval)
const isEqualsInterval = (intv) =>
Math.abs((fInterval || intv) - (intv || fInterval)) < 5
const isIncludesName = (n) => fMusicName.includes(n) || n.includes(fMusicName)
const isIncludesSinger = (sng) =>
fSinger ? fSinger.includes(sng) || sng.includes(fSinger) : true
const isEqualsAlbum = (alb) => (fAlbumName ? fAlbumName === alb : true)
let candidates = []
for (const source of lists) {
for (const item of source.list) {
item.name = trimStr(item.name)
item.singer = trimStr(item.singer)
item.fSinger = filterStr(sortSingle(item.singer).toLowerCase())
item.fMusicName = filterStr(String(item.name ?? '').toLowerCase())
item.fAlbumName = filterStr(String(item.albumName ?? '').toLowerCase())
item.fInterval = getIntervalSeconds(item.interval)
if (!isEqualsInterval(item.fInterval)) {
item.name = null
continue
}
candidates.push(item)
}
}
const result = []
const take = (predicate) => {
const { matched, rest } = pickMatching(candidates, predicate)
result.push(...matched)
candidates = rest
}
take((item) => item.fSinger === fSinger && item.fMusicName === fMusicName && item.interval === interval)
take((item) => item.fMusicName === fMusicName && item.fSinger === fSinger && item.fAlbumName === fAlbumName)
take((item) => item.fSinger === fSinger && item.fMusicName === fMusicName)
take((item) => item.fMusicName === fMusicName && item.interval === interval)
take((item) => item.fSinger === fSinger && item.interval === interval)
take((item) => item.interval === interval)
take((item) => item.fMusicName === fMusicName)
take((item) => item.fSinger === fSinger)
take((item) => item.fAlbumName === fAlbumName)
for (const item of candidates) {
delete item.fSinger
delete item.fMusicName
delete item.fAlbumName
delete item.fInterval
}
for (const item of result) {
delete item.fSinger
delete item.fMusicName
delete item.fAlbumName
delete item.fInterval
}
result.push(...candidates)
return result
}
export default {
sources,
...SOURCE_MODULES,
init,
supportQuality,
searchMusic,
findMusic,
}