refactor(sdk): musicSdk 全量迁移至 TypeScript

This commit is contained in:
snowy-yang committed 2026-08-29 00:23:50 +08:00
1 parent f289df4cd7
commit 1dfb7a59ca
83 files changed
+923 -731

No files matched your search

+7
View File
@@ -26,3 +26,10 @@ Thumbs.db
dist/ dist/
coverage/ coverage/
.nyc_output/ .nyc_output/
# TypeScript in-place emit (musicSdk source is .ts)
musicSdk/**/*.js
!musicSdk/**/api-test.js
!musicSdk/**/api-temp.js
!musicSdk/**/temp/*.js
!musicSdk/**/vendors/*.js
-40
View File
@@ -1,40 +0,0 @@
// Support qualitys: 128k 320k flac wav
const sources = [
// {
// id: 'test',
// name: '测试接口',
// disabled: false,
// supportQualitys: {
// kw: ['128k'],
// kg: ['128k'],
// tx: ['128k'],
// wy: ['128k'],
// mg: ['128k'],
// // bd: ['128k'],
// },
// },
// {
// id: 'temp',
// name: '临时接口',
// disabled: false,
// supportQualitys: {
// kw: ['128k'],
// },
// },
{
id: 'v5',
name: 'lx-music-source-v5 聚合源',
disabled: false,
supportQualitys: {
kw: ['128k', '320k', 'flac'],
kg: ['128k', '320k', 'flac'],
tx: ['128k', '320k', 'flac'],
wy: ['128k', '320k', 'flac'],
mg: ['128k', '320k', 'flac'],
// bd: ['128k', '320k', 'flac'],
},
},
]
export default sources
+19 -11
View File
@@ -1,11 +1,14 @@
// 接口源声明:id 与 api-source.ts 中 allApi 的 `<id>_<source>` 键对应
// Support qualitys: 128k 320k flac wav // Support qualitys: 128k 320k flac wav
const sources: Array<{ interface ApiSourceInfo {
id: string id: string
name: string name: string
disabled: boolean disabled: boolean
supportQualitys: Partial<Record<LX.OnlineSource, LX.Quality[]>> supportQualitys: Partial<Record<'kw' | 'kg' | 'tx' | 'wy' | 'mg' | 'bd', string[]>>
}> = [ }
const sources: ApiSourceInfo[] = [
// { // {
// id: 'test', // id: 'test',
// name: '测试接口', // name: '测试接口',
@@ -19,14 +22,19 @@ const sources: Array<{
// // bd: ['128k'], // // bd: ['128k'],
// }, // },
// }, // },
// { {
// id: 'temp', id: 'v5',
// name: '临时接口', name: 'lx-music-source-v5 聚合源',
// disabled: false, disabled: false,
// supportQualitys: { supportQualitys: {
// kw: ['128k'], kw: ['128k', '320k', 'flac'],
// }, kg: ['128k', '320k', 'flac'],
// }, tx: ['128k', '320k', 'flac'],
wy: ['128k', '320k', 'flac'],
mg: ['128k', '320k', 'flac'],
// bd: ['128k', '320k', 'flac'],
},
},
] ]
export default sources export default sources
@@ -1,32 +1,24 @@
import apiSourceInfo from './api-source-info.js' // 接口选择层:按 apiSource 配置把请求路由到 v5 聚合源或服务器内置直连接口
import apiSourceInfo from './api-source-info.ts'
import { apiSource, userApi } from '../src/renderer/store.js' import { apiSource, userApi } from '../src/renderer/store.js'
import internalApi from '../src/apis/index.js' import internalApi from '../src/apis/index.js'
import { createApi as createLxSourceApi } from '../src/apis/lx-source.js' import { createApi as createLxSourceApi } from '../src/apis/lx-source.js'
// import api_temp_kw from './kw/api-temp.js'
// // import api_test_bd from './bd/api-test.js'
// import api_test_tx from './tx/api-test.js'
// import api_test_kg from './kg/api-test.js'
// import api_test_kw from './kw/api-test.js'
// import api_test_mg from './mg/api-test.js'
// import api_test_wy from './wy/api-test.js'
const allApi = { interface SourceApi {
getMusicUrl(songInfo: any, type: string): { promise: Promise<any>; cancelHttp(): void }
[key: string]: any
}
const allApi: Record<string, SourceApi> = {
v5_kw: createLxSourceApi('kw'), v5_kw: createLxSourceApi('kw'),
v5_kg: createLxSourceApi('kg'), v5_kg: createLxSourceApi('kg'),
v5_tx: createLxSourceApi('tx'), v5_tx: createLxSourceApi('tx'),
v5_wy: createLxSourceApi('wy'), v5_wy: createLxSourceApi('wy'),
v5_mg: createLxSourceApi('mg'), v5_mg: createLxSourceApi('mg'),
// temp_kw: api_temp_kw,
// // test_bd: api_test_bd,
// test_tx: api_test_tx,
// test_kg: api_test_kg,
// test_kw: api_test_kw,
// test_mg: api_test_mg,
// test_wy: api_test_wy,
} }
const apiList = {} const apiList: Record<string, SourceApi | undefined> = {}
const supportQuality = {} const supportQuality: Record<string, any> = {}
for (const api of apiSourceInfo) { for (const api of apiSourceInfo) {
supportQuality[api.id] = api.supportQualitys supportQuality[api.id] = api.supportQualitys
@@ -35,9 +27,9 @@ for (const api of apiSourceInfo) {
} }
} }
const getAPI = source => apiList[`${apiSource.value}_api_${source}`] const getAPI = (source: string): SourceApi | undefined => apiList[`${apiSource.value}_api_${source}`]
const apis = source => { export const apis = (source: string): SourceApi => {
if (/^user_api/.test(apiSource.value)) return userApi.apis[source] if (/^user_api/.test(apiSource.value)) return userApi.apis[source]
// 服务器内置直连接口 // 服务器内置直连接口
if (apiSource.value == 'internal') { if (apiSource.value == 'internal') {
@@ -45,9 +37,9 @@ const apis = source => {
if (api) return api if (api) return api
throw new Error('Api is not found') throw new Error('Api is not found')
} }
let api = getAPI(source) const api = getAPI(source)
if (api) return api if (api) return api
throw new Error('Api is not found') throw new Error('Api is not found')
} }
export { apis, supportQuality } export { supportQuality }
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
@@ -1,10 +1,10 @@
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import musicInfo from './musicInfo.js' import musicInfo from './musicInfo.ts'
import songList from './songList.js' import songList from './songList.ts'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
const bd = { const bd = {
leaderboard, leaderboard,
@@ -12,7 +12,7 @@ const bd = {
musicSearch, musicSearch,
hotSearch, hotSearch,
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('bd').getMusicUrl(songInfo, type) return apis('bd').getMusicUrl!(songInfo, type)
}, },
getPic(songInfo) { getPic(songInfo) {
const requestObj = this.getMusicInfo(songInfo) const requestObj = this.getMusicInfo(songInfo)
@@ -1,4 +1,4 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
// import { formatPlayTime } from '../index.js' // import { formatPlayTime } from '../index.js'
@@ -86,8 +86,8 @@ export default {
filterData(rawList) { filterData(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
const types = [] const types: any[] = []
const _types = {} const _types: Record<string, any> = {}
let size = null let size = null
types.push({ type: '128k', size }) types.push({ type: '128k', size })
_types['128k'] = { _types['128k'] = {
@@ -1,4 +1,4 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
export default { export default {
cache: {}, cache: {},
@@ -1,7 +1,7 @@
// import '../../polyfill/array.find.js' // import '../../polyfill/array.find.js'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime } from '../index.js' import { formatPlayTime } from '../index.ts'
// import { debug } from '../../utils/env.js' // import { debug } from '../../utils/env.js'
// import { formatSinger } from './util.js' // import { formatSinger } from './util.js'
@@ -16,13 +16,13 @@ export default {
}, },
handleResult(rawData) { handleResult(rawData) {
let ids = new Set() let ids = new Set()
const list = [] const list: any[] = []
if (!rawData) return list if (!rawData) return list
rawData.forEach(item => { rawData.forEach(item => {
if (ids.has(item.song_id)) return if (ids.has(item.song_id)) return
ids.add(item.song_id) ids.add(item.song_id)
const types = [] const types: any[] = []
const _types = {} const _types: Record<string, any> = {}
let size = null let size = null
let itemTypes = item.all_rate.split(',') let itemTypes = item.all_rate.split(',')
if (itemTypes.includes('128')) { if (itemTypes.includes('128')) {
@@ -1,12 +1,13 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime } from '../index.js' import { formatPlayTime } from '../index.ts'
import { toMD5 } from '../utils.js' import { toMD5 } from '../utils.ts'
import CryptoJS from 'crypto-js' import CryptoJS from 'crypto-js'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
_requestObj_listRecommend: null, _requestObj_listRecommend: null as HttpRequestObject | null,
limit_list: 30, limit_list: 30,
limit_song: 10000, limit_song: 10000,
successCode: 22000, successCode: 22000,
@@ -29,7 +30,7 @@ export default {
let privateKey = toMD5('baidu_taihe_music_secret_key' + timestamp).substr(8, 16) let privateKey = toMD5('baidu_taihe_music_secret_key' + timestamp).substr(8, 16)
let key = CryptoJS.enc.Utf8.parse(privateKey) let key = CryptoJS.enc.Utf8.parse(privateKey)
let iv = CryptoJS.enc.Utf8.parse(privateKey) let iv = CryptoJS.enc.Utf8.parse(privateKey)
let arrData = [] let arrData: string[] = []
let strData = '' let strData = ''
for (let key in jsonData) arrData.push(key) for (let key in jsonData) arrData.push(key)
arrData.sort() arrData.sort()
@@ -40,7 +41,7 @@ export default {
} }
let JsonFormatter = { let JsonFormatter = {
stringify(cipherParams) { stringify(cipherParams) {
let jsonObj = { let jsonObj: Record<string, any> = {
ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64), ct: cipherParams.ciphertext.toString(CryptoJS.enc.Base64),
} }
if (cipherParams.iv) { if (cipherParams.iv) {
@@ -116,7 +117,7 @@ export default {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp() if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.getTagsUrl()) this._requestObj_tags = httpFetch(this.getTagsUrl())
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getTags(++tryNum) if (body.error_code !== this.successCode) return this.getTags(++tryNum)
return { return {
hotTag: this.filterInfoHotTag(body.result.hot), hotTag: this.filterInfoHotTag(body.result.hot),
@@ -150,7 +151,7 @@ export default {
if (this._requestObj_list) this._requestObj_list.cancelHttp() if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_list = httpFetch(this.getListUrl(sortId, tagId, page)) this._requestObj_list = httpFetch(this.getListUrl(sortId, tagId, page))
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
if (body.error_code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum) if (body.error_code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return { return {
list: this.filterList(body.diyInfo), list: this.filterList(body.diyInfo),
@@ -168,8 +169,8 @@ export default {
* @param {*} num * @param {*} num
*/ */
formatPlayCount(num) { formatPlayCount(num) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿' if (num > 100000000) return parseInt((num / 10000000) as any) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万' if (num > 10000) return parseInt((num / 1000) as any) / 10 + '万'
return num return num
}, },
filterList(rawData) { filterList(rawData) {
@@ -215,8 +216,8 @@ export default {
filterData(rawList) { filterData(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
const types = [] const types: any[] = []
const _types = {} const _types: Record<string, any> = {}
let size = null let size = null
let itemTypes = item.all_rate.split(',') let itemTypes = item.all_rate.split(',')
if (itemTypes.includes('128')) { if (itemTypes.includes('128')) {
+64 -54
View File
@@ -1,11 +1,12 @@
import kw from './kw/index.js' import kw from './kw/index.ts'
import kg from './kg/index.js' import kg from './kg/index.ts'
import tx from './tx/index.js' import tx from './tx/index.ts'
import wy from './wy/index.js' import wy from './wy/index.ts'
import mg from './mg/index.js' import mg from './mg/index.ts'
import bd from './bd/index.js' import bd from './bd/index.ts'
import xm from './xm.js' import xm from './xm.ts'
import { supportQuality } from './api-source.js' import { supportQuality } from './api-source.ts'
import type { PlatformModule, SearchResult, SongInfo } from './types.ts'
import { import {
sizeFormate, sizeFormate,
formatPlayTime, formatPlayTime,
@@ -15,11 +16,11 @@ import {
decodeName, decodeName,
} from '../src/index.js' } from '../src/index.js'
// 通用工具函数(SDK 内 `import { ... } from '../index.js'` 的解析目标) // 通用工具函数(SDK 内 `import { ... } from '../index.ts'` 的解析目标)
export { sizeFormate, formatPlayTime, dateFormat, dateFormat2, formatPlayCount, decodeName } export { sizeFormate, formatPlayTime, dateFormat, dateFormat2, formatPlayCount, decodeName }
const SINGERS_SEPARATOR = /、|&|;|;|\/|,|,|\||\]|\[|!|!/g const SINGERS_SEPARATOR = /、|&|;|;|\/|,|,|\||\]|\[|!|!/g
const SUPPORTED_SOURCES = [ const SUPPORTED_SOURCES: Array<{ id: string; name: string; module: PlatformModule }> = [
{ id: 'kw', name: '酷我音乐', module: kw }, { id: 'kw', name: '酷我音乐', module: kw },
{ id: 'kg', name: '酷狗音乐', module: kg }, { id: 'kg', name: '酷狗音乐', module: kg },
{ id: 'tx', name: 'QQ音乐', module: tx }, { id: 'tx', name: 'QQ音乐', module: tx },
@@ -33,48 +34,57 @@ const SOURCE_MODULES = Object.fromEntries(SUPPORTED_SOURCES.map((s) => [s.id, s.
export const sources = SUPPORTED_SOURCES.map(({ id, name }) => ({ id, name })) export const sources = SUPPORTED_SOURCES.map(({ id, name }) => ({ id, name }))
export const init = () => export const init = (): Promise<unknown[]> =>
Promise.all(SUPPORTED_SOURCES.map((s) => s.module.init?.()).filter(Boolean)) Promise.all(SUPPORTED_SOURCES.map((s) => s.module.init?.()).filter(Boolean))
export { supportQuality } export { supportQuality }
const trimStr = (str) => (typeof str === 'string' ? str.trim() : str) const trimStr = (str: unknown): string => (typeof str === 'string' ? str.trim() : str as string)
const filterStr = (str) => const filterStr = (str: unknown): string =>
typeof str === 'string' typeof str === 'string'
? str.replace(/\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[|!|!/g, '') ? str.replace(/\s|'|\.|,|,|&|"|、|\(|\)|(|)|`|~|-|<|>|\||\/|\]|\[|!|!/g, '')
: String(str ?? '') : String(str ?? '')
const getIntervalSeconds = (interval) => { const getIntervalSeconds = (interval: unknown): number => {
if (!interval) return 0 if (!interval) return 0
const parts = String(interval).split(':') const parts = String(interval).split(':')
let seconds = 0 let seconds = 0
let unit = 1 let unit = 1
while (parts.length) { while (parts.length) {
seconds += parseInt(parts.pop()) * unit seconds += parseInt(parts.pop()!) * unit
unit *= 60 unit *= 60
} }
return seconds return seconds
} }
const sortSingle = (singer) => { const sortSingle = (singer: unknown): string => {
if (!singer) return '' if (!singer) return ''
return SINGERS_SEPARATOR.test(singer) const s = String(singer)
? singer.split(SINGERS_SEPARATOR).sort((a, b) => a.localeCompare(b)).join('、') return SINGERS_SEPARATOR.test(s)
: singer ? s.split(SINGERS_SEPARATOR).sort((a, b) => a.localeCompare(b)).join('、')
: s
}
interface MatchCandidate extends SongInfo {
fSinger?: string
fMusicName?: string
fAlbumName?: string
fInterval?: number
} }
/** /**
* 多平台聚合搜索 * 多平台聚合搜索
* @param {object} params * @param params.name 歌名
* @param {string} params.name * @param params.singer 歌手
* @param {string} [params.singer] * @param params.excludeSource 指定平台时,聚合结果会跳过该平台
* @param {string} [params.source] 指定平台时,聚合结果会跳过该平台 * @param params.limit 单平台结果条数
* @param {number} [params.limit=25]
*/ */
export const searchMusic = async ({ name, singer, source: excludeSource, limit = 25 }) => { export const searchMusic = async (
{ name, singer, source: excludeSource, limit = 25 }: { name: string; singer?: string; source?: string; limit?: number },
): Promise<Array<SearchResult | null>> => {
const musicName = trimStr(name) const musicName = trimStr(name)
const tasks = [] const tasks: Array<Promise<SearchResult | null>> = []
for (const source of SUPPORTED_SOURCES) { for (const source of SUPPORTED_SOURCES) {
if ( if (
source.id === 'xm' || source.id === 'xm' ||
@@ -91,9 +101,9 @@ export const searchMusic = async ({ name, singer, source: excludeSource, limit =
return (await Promise.all(tasks)).filter(Boolean) return (await Promise.all(tasks)).filter(Boolean)
} }
const pickMatching = (items, predicate) => { const pickMatching = <T>(items: T[], predicate: (item: T) => boolean): { matched: T[]; rest: T[] } => {
const matched = [] const matched: T[] = []
const rest = [] const rest: T[] = []
for (const item of items) { for (const item of items) {
if (predicate(item)) matched.push(item) if (predicate(item)) matched.push(item)
else rest.push(item) else rest.push(item)
@@ -104,7 +114,9 @@ const pickMatching = (items, predicate) => {
/** /**
* 跨平台精确匹配歌曲(按歌手、歌名、专辑、时长综合排序) * 跨平台精确匹配歌曲(按歌手、歌名、专辑、时长综合排序)
*/ */
export const findMusic = async ({ name, singer, albumName, interval, source: s }) => { export const findMusic = async (
{ name, singer, albumName, interval, source: s }: { name: string; singer?: string; albumName?: string; interval?: string; source?: string },
): Promise<MatchCandidate[]> => {
const lists = await searchMusic({ name, singer, source: s, limit: 25 }) const lists = await searchMusic({ name, singer, source: s, limit: 25 })
const fMusicName = filterStr(name).toLowerCase() const fMusicName = filterStr(name).toLowerCase()
@@ -112,32 +124,34 @@ export const findMusic = async ({ name, singer, albumName, interval, source: s }
const fAlbumName = filterStr(albumName).toLowerCase() const fAlbumName = filterStr(albumName).toLowerCase()
const fInterval = getIntervalSeconds(interval) const fInterval = getIntervalSeconds(interval)
const isEqualsInterval = (intv) => const isEqualsInterval = (intv: number) =>
Math.abs((fInterval || intv) - (intv || fInterval)) < 5 Math.abs((fInterval || intv) - (intv || fInterval)) < 5
const isIncludesName = (n) => fMusicName.includes(n) || n.includes(fMusicName) const isIncludesName = (n: string) => fMusicName.includes(n) || n.includes(fMusicName)
const isIncludesSinger = (sng) => const isIncludesSinger = (sng: string) =>
fSinger ? fSinger.includes(sng) || sng.includes(fSinger) : true fSinger ? fSinger.includes(sng) || sng.includes(fSinger) : true
const isEqualsAlbum = (alb) => (fAlbumName ? fAlbumName === alb : true) const isEqualsAlbum = (alb: string) => (fAlbumName ? fAlbumName === alb : true)
let candidates = [] let candidates: MatchCandidate[] = []
for (const source of lists) { for (const source of lists) {
if (!source) continue
for (const item of source.list) { for (const item of source.list) {
item.name = trimStr(item.name) const cand = item as MatchCandidate
item.singer = trimStr(item.singer) cand.name = trimStr(cand.name)
item.fSinger = filterStr(sortSingle(item.singer).toLowerCase()) cand.singer = trimStr(cand.singer)
item.fMusicName = filterStr(String(item.name ?? '').toLowerCase()) cand.fSinger = filterStr(sortSingle(cand.singer).toLowerCase())
item.fAlbumName = filterStr(String(item.albumName ?? '').toLowerCase()) cand.fMusicName = filterStr(String(cand.name ?? '').toLowerCase())
item.fInterval = getIntervalSeconds(item.interval) cand.fAlbumName = filterStr(String(cand.albumName ?? '').toLowerCase())
if (!isEqualsInterval(item.fInterval)) { cand.fInterval = getIntervalSeconds(cand.interval)
item.name = null if (!isEqualsInterval(cand.fInterval)) {
cand.name = null
continue continue
} }
candidates.push(item) candidates.push(cand)
} }
} }
const result = [] const result: MatchCandidate[] = []
const take = (predicate) => { const take = (predicate: (item: MatchCandidate) => boolean) => {
const { matched, rest } = pickMatching(candidates, predicate) const { matched, rest } = pickMatching(candidates, predicate)
result.push(...matched) result.push(...matched)
candidates = rest candidates = rest
@@ -153,13 +167,7 @@ export const findMusic = async ({ name, singer, albumName, interval, source: s }
take((item) => item.fSinger === fSinger) take((item) => item.fSinger === fSinger)
take((item) => item.fAlbumName === fAlbumName) take((item) => item.fAlbumName === fAlbumName)
for (const item of candidates) { for (const item of [...candidates, ...result]) {
delete item.fSinger
delete item.fMusicName
delete item.fAlbumName
delete item.fInterval
}
for (const item of result) {
delete item.fSinger delete item.fSinger
delete item.fMusicName delete item.fMusicName
delete item.fAlbumName delete item.fAlbumName
@@ -170,7 +178,7 @@ export const findMusic = async ({ name, singer, albumName, interval, source: s }
return result return result
} }
export default { const musicSdk = {
sources, sources,
...SOURCE_MODULES, ...SOURCE_MODULES,
init, init,
@@ -178,3 +186,5 @@ export default {
searchMusic, searchMusic,
findMusic, findMusic,
} }
export default musicSdk
@@ -1,5 +1,5 @@
import { getMusicInfosByList } from './musicInfo.js' import { getMusicInfosByList } from './musicInfo.ts'
import { createHttpFetch } from './util.js' import { createHttpFetch } from './util.ts'
export default { export default {
/** /**
@@ -1,11 +1,12 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName, dateFormat2 } from '../index.js' import { decodeName, dateFormat2 } from '../index.ts'
import { signatureParams } from './util.js' import { signatureParams } from './util.ts'
// import { getMusicInfoRaw } from './musicInfo.js' // import { getMusicInfoRaw } from './musicInfo.js'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
_requestObj2: null, _requestObj2: null as HttpRequestObject | null,
async getComment({ hash }, page = 1, limit = 20) { async getComment({ hash }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
@@ -1,8 +1,9 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName } from '../index.js' import { decodeName } from '../index.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
@@ -12,10 +13,10 @@ export default {
headers: { headers: {
dfid: '1ssiv93oVqMp27cirf2CvoF1', dfid: '1ssiv93oVqMp27cirf2CvoF1',
mid: '156798703528610303473757548878786007104', mid: '156798703528610303473757548878786007104',
clienttime: 1584257267, clienttime: '1584257267',
'x-router': 'msearch.kugou.com', 'x-router': 'msearch.kugou.com',
'user-agent': 'Android9-AndroidPhone-10020-130-0-searchrecommendprotocol-wifi', 'user-agent': 'Android9-AndroidPhone-10020-130-0-searchrecommendprotocol-wifi',
'kg-rc': 1, 'kg-rc': '1',
}, },
}) })
const { body, statusCode } = await _requestObj.promise const { body, statusCode } = await _requestObj.promise
@@ -24,7 +25,7 @@ export default {
return { source: 'kg', list: this.filterList(body.data.list) } return { source: 'kg', list: this.filterList(body.data.list) }
}, },
filterList(rawList) { filterList(rawList) {
const list = [] const list: string[] = []
rawList.forEach(item => { rawList.forEach(item => {
item.keywords.map(k => list.push(decodeName(k.keyword))) item.keywords.map(k => list.push(decodeName(k.keyword)))
}) })
@@ -1,11 +1,11 @@
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import songList from './songList.js' import songList from './songList.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import pic from './pic.js' import pic from './pic.ts'
import lyric from './lyric.js' import lyric from './lyric.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
import comment from './comment.js' import comment from './comment.ts'
// import tipSearch from './tipSearch.js' // import tipSearch from './tipSearch.js'
const kg = { const kg = {
@@ -16,7 +16,7 @@ const kg = {
hotSearch, hotSearch,
comment, comment,
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('kg').getMusicUrl(songInfo, type) return apis('kg').getMusicUrl!(songInfo, type)
}, },
getLyric(songInfo) { getLyric(songInfo) {
return lyric.getLyric(songInfo) return lyric.getLyric(songInfo)
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName, formatPlayTime, sizeFormate } from '../index.js' import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import type { HttpRequestObject, SongType, QualityInfo } from '../types.ts'
let boardList = [{ id: 'kg__8888', name: 'TOP500', bangid: '8888' }, { id: 'kg__6666', name: '飙升榜', bangid: '6666' }, { id: 'kg__59703', name: '蜂鸟流行音乐榜', bangid: '59703' }, { id: 'kg__52144', name: '抖音热歌榜', bangid: '52144' }, { id: 'kg__52767', name: '快手热歌榜', bangid: '52767' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__44412', name: '说唱先锋榜', bangid: '44412' }, { id: 'kg__31308', name: '内地榜', bangid: '31308' }, { id: 'kg__33160', name: '电音榜', bangid: '33160' }, { id: 'kg__31313', name: '香港地区榜', bangid: '31313' }, { id: 'kg__51341', name: '民谣榜', bangid: '51341' }, { id: 'kg__54848', name: '台湾地区榜', bangid: '54848' }, { id: 'kg__31310', name: '欧美榜', bangid: '31310' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__31311', name: '韩国榜', bangid: '31311' }, { id: 'kg__31312', name: '日本榜', bangid: '31312' }, { id: 'kg__49225', name: '80后热歌榜', bangid: '49225' }, { id: 'kg__49223', name: '90后热歌榜', bangid: '49223' }, { id: 'kg__49224', name: '00后热歌榜', bangid: '49224' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__51340', name: '伤感榜', bangid: '51340' }, { id: 'kg__35811', name: '会员专享榜', bangid: '35811' }, { id: 'kg__37361', name: '雷达榜', bangid: '37361' }, { id: 'kg__21101', name: '分享榜', bangid: '21101' }, { id: 'kg__46910', name: '综艺新歌榜', bangid: '46910' }, { id: 'kg__30972', name: '酷狗音乐人原创榜', bangid: '30972' }, { id: 'kg__60170', name: '闽南语榜', bangid: '60170' }, { id: 'kg__65234', name: '儿歌榜', bangid: '65234' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__25028', name: 'Beatport电子舞曲榜', bangid: '25028' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__46868', name: '日本SPACE SHOWER榜', bangid: '46868' }, { id: 'kg__42808', name: 'KKBOX风云榜', bangid: '42808' }, { id: 'kg__60171', name: '越南语榜', bangid: '60171' }, { id: 'kg__60172', name: '泰语榜', bangid: '60172' }, { id: 'kg__59895', name: 'R&B榜', bangid: '59895' }, { id: 'kg__59896', name: '摇滚榜', bangid: '59896' }, { id: 'kg__59897', name: '爵士榜', bangid: '59897' }, { id: 'kg__59898', name: '乡村音乐榜', bangid: '59898' }, { id: 'kg__59900', name: '纯音乐榜', bangid: '59900' }, { id: 'kg__59899', name: '古典榜', bangid: '59899' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }] let boardList = [{ id: 'kg__8888', name: 'TOP500', bangid: '8888' }, { id: 'kg__6666', name: '飙升榜', bangid: '6666' }, { id: 'kg__59703', name: '蜂鸟流行音乐榜', bangid: '59703' }, { id: 'kg__52144', name: '抖音热歌榜', bangid: '52144' }, { id: 'kg__52767', name: '快手热歌榜', bangid: '52767' }, { id: 'kg__24971', name: 'DJ热歌榜', bangid: '24971' }, { id: 'kg__23784', name: '网络红歌榜', bangid: '23784' }, { id: 'kg__44412', name: '说唱先锋榜', bangid: '44412' }, { id: 'kg__31308', name: '内地榜', bangid: '31308' }, { id: 'kg__33160', name: '电音榜', bangid: '33160' }, { id: 'kg__31313', name: '香港地区榜', bangid: '31313' }, { id: 'kg__51341', name: '民谣榜', bangid: '51341' }, { id: 'kg__54848', name: '台湾地区榜', bangid: '54848' }, { id: 'kg__31310', name: '欧美榜', bangid: '31310' }, { id: 'kg__33162', name: 'ACG新歌榜', bangid: '33162' }, { id: 'kg__31311', name: '韩国榜', bangid: '31311' }, { id: 'kg__31312', name: '日本榜', bangid: '31312' }, { id: 'kg__49225', name: '80后热歌榜', bangid: '49225' }, { id: 'kg__49223', name: '90后热歌榜', bangid: '49223' }, { id: 'kg__49224', name: '00后热歌榜', bangid: '49224' }, { id: 'kg__33165', name: '粤语金曲榜', bangid: '33165' }, { id: 'kg__33166', name: '欧美金曲榜', bangid: '33166' }, { id: 'kg__33163', name: '影视金曲榜', bangid: '33163' }, { id: 'kg__51340', name: '伤感榜', bangid: '51340' }, { id: 'kg__35811', name: '会员专享榜', bangid: '35811' }, { id: 'kg__37361', name: '雷达榜', bangid: '37361' }, { id: 'kg__21101', name: '分享榜', bangid: '21101' }, { id: 'kg__46910', name: '综艺新歌榜', bangid: '46910' }, { id: 'kg__30972', name: '酷狗音乐人原创榜', bangid: '30972' }, { id: 'kg__60170', name: '闽南语榜', bangid: '60170' }, { id: 'kg__65234', name: '儿歌榜', bangid: '65234' }, { id: 'kg__4681', name: '美国BillBoard榜', bangid: '4681' }, { id: 'kg__25028', name: 'Beatport电子舞曲榜', bangid: '25028' }, { id: 'kg__4680', name: '英国单曲榜', bangid: '4680' }, { id: 'kg__38623', name: '韩国Melon音乐榜', bangid: '38623' }, { id: 'kg__42807', name: 'joox本地热歌榜', bangid: '42807' }, { id: 'kg__36107', name: '小语种热歌榜', bangid: '36107' }, { id: 'kg__4673', name: '日本公信榜', bangid: '4673' }, { id: 'kg__46868', name: '日本SPACE SHOWER榜', bangid: '46868' }, { id: 'kg__42808', name: 'KKBOX风云榜', bangid: '42808' }, { id: 'kg__60171', name: '越南语榜', bangid: '60171' }, { id: 'kg__60172', name: '泰语榜', bangid: '60172' }, { id: 'kg__59895', name: 'R&B榜', bangid: '59895' }, { id: 'kg__59896', name: '摇滚榜', bangid: '59896' }, { id: 'kg__59897', name: '爵士榜', bangid: '59897' }, { id: 'kg__59898', name: '乡村音乐榜', bangid: '59898' }, { id: 'kg__59900', name: '纯音乐榜', bangid: '59900' }, { id: 'kg__59899', name: '古典榜', bangid: '59899' }, { id: 'kg__22603', name: '5sing音乐榜', bangid: '22603' }, { id: 'kg__21335', name: '繁星音乐榜', bangid: '21335' }, { id: 'kg__33161', name: '古风新歌榜', bangid: '33161' }]
@@ -72,18 +73,18 @@ export default {
limit: /pagesize: '(\d+)',/, limit: /pagesize: '(\d+)',/,
listData: /global\.features = (\[.+\]);/, listData: /global\.features = (\[.+\]);/,
}, },
_requestBoardsObj: null, _requestBoardsObj: null as HttpRequestObject | null,
getBoardsData() { getBoardsData() {
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v5/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1') this._requestBoardsObj = httpFetch('http://mobilecdnbj.kugou.com/api/v5/rank/list?version=9108&plat=0&showtype=2&parentid=0&apiver=6&area_code=1&withsong=1')
return this._requestBoardsObj.promise return this._requestBoardsObj!.promise
}, },
getData(url) { getData(url) {
const requestDataObj = httpFetch(url) const requestDataObj = httpFetch(url)
return requestDataObj.promise return requestDataObj.promise
}, },
getSinger(singers) { getSinger(singers) {
let arr = [] let arr: string[] = []
singers.forEach(singer => { singers.forEach(singer => {
arr.push(singer.author_name) arr.push(singer.author_name)
}) })
@@ -92,8 +93,8 @@ export default {
filterData(rawList) { filterData(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
const types = [] const types: Array<SongType & { hash?: string }> = []
const _types = {} const _types: Record<string, QualityInfo & { hash?: string }> = {}
if (item.filesize !== 0) { if (item.filesize !== 0) {
let size = sizeFormate(item.filesize) let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash }) types.push({ type: '128k', size, hash: item.hash })
@@ -147,7 +148,7 @@ export default {
filterBoardsData(rawList) { filterBoardsData(rawList) {
// console.log(rawList) // console.log(rawList)
let list = [] let list: Array<{ id: string; name: string; bangid: string }> = []
for (const board of rawList) { for (const board of rawList) {
if (board.isvol != 1) continue if (board.isvol != 1) continue
list.push({ list.push({
@@ -1,4 +1,4 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeKrc } from '../../src/common/lyricUtils/kg.js' import { decodeKrc } from '../../src/common/lyricUtils/kg.js'
export default { export default {
@@ -11,7 +11,7 @@ export default {
intv += (intvArr.pop()) * unit intv += (intvArr.pop()) * unit
unit *= 60 unit *= 60
} }
return parseInt(intv) return parseInt(intv as unknown as string)
}, },
// getLyric(songInfo, tryNum = 0) { // getLyric(songInfo, tryNum = 0) {
// let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, { // let requestObj = httpFetch(`http://m.kugou.com/app/i/krc.php?cmd=100&keyword=${encodeURIComponent(songInfo.name)}&hash=${songInfo.hash}&timelength=${songInfo._interval || this.getIntv(songInfo.interval)}&d=0.38664927426725626`, {
@@ -38,12 +38,12 @@ export default {
searchLyric(name, hash, time, tryNum = 0) { searchLyric(name, hash, time, tryNum = 0) {
let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}&lrctxt=1`, { let requestObj = httpFetch(`http://lyrics.kugou.com/search?ver=1&man=yes&client=pc&keyword=${encodeURIComponent(name)}&hash=${hash}&timelength=${time}&lrctxt=1`, {
headers: { headers: {
'KG-RC': 1, 'KG-RC': '1',
'KG-THash': 'expand_search_manager.cpp:852736169:451', 'KG-THash': 'expand_search_manager.cpp:852736169:451',
'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
}, },
}) })
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { requestObj.promise = (requestObj.promise as any).then(({ body, statusCode }) => {
if (statusCode !== 200) { if (statusCode !== 200) {
if (tryNum > 5) return Promise.reject(new Error('歌词获取失败')) if (tryNum > 5) return Promise.reject(new Error('歌词获取失败'))
let tryRequestObj = this.searchLyric(name, hash, time, ++tryNum) let tryRequestObj = this.searchLyric(name, hash, time, ++tryNum)
@@ -61,15 +61,15 @@ export default {
getLyricDownload(id, accessKey, fmt, tryNum = 0) { getLyricDownload(id, accessKey, fmt, tryNum = 0) {
let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=${fmt}&charset=utf8`, { let requestObj = httpFetch(`http://lyrics.kugou.com/download?ver=1&client=pc&id=${id}&accesskey=${accessKey}&fmt=${fmt}&charset=utf8`, {
headers: { headers: {
'KG-RC': 1, 'KG-RC': '1',
'KG-THash': 'expand_search_manager.cpp:852736169:451', 'KG-THash': 'expand_search_manager.cpp:852736169:451',
'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
}, },
}) })
requestObj.promise = requestObj.promise.then(({ body, statusCode }) => { requestObj.promise = (requestObj.promise as any).then(({ body, statusCode }) => {
if (statusCode !== 200) { if (statusCode !== 200) {
if (tryNum > 5) return Promise.reject(new Error('歌词获取失败')) if (tryNum > 5) return Promise.reject(new Error('歌词获取失败'))
let tryRequestObj = this.getLyric(id, accessKey, fmt, ++tryNum) let tryRequestObj = (this.getLyric as any)(id, accessKey, fmt, ++tryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise return tryRequestObj.promise
} }
@@ -97,12 +97,12 @@ export default {
requestObj.promise = requestObj.promise.then(result => { requestObj.promise = requestObj.promise.then(result => {
if (!result) return Promise.reject(new Error('Get lyric failed')) if (!result) return Promise.reject(new Error('Get lyric failed'))
let requestObj2 = this.getLyricDownload(result.id, result.accessKey, result.fmt) let requestObj2 = this.getLyricDownload((result as any).id, (result as any).accessKey, (result as any).fmt)
requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2) requestObj.cancelHttp = requestObj2.cancelHttp.bind(requestObj2)
return requestObj2.promise return requestObj2.promise
}) }) as any
return requestObj return requestObj
}, },
} }
@@ -1,7 +1,8 @@
import { decodeName, formatPlayTime, sizeFormate } from '../index.js' import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
import { createHttpFetch } from './util.js' import { createHttpFetch } from './util.ts'
import type { SongInfo, SongType, QualityInfo } from '../types.ts'
const createGetMusicInfosTask = (hashs) => { const createGetMusicInfosTask = (hashs: Array<{ hash: string }>): Array<Promise<any>> => {
let data = { let data = {
area_code: '1', area_code: '1',
show_privilege: 1, show_privilege: 1,
@@ -16,7 +17,7 @@ const createGetMusicInfosTask = (hashs) => {
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname,classification', fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname,classification',
} }
let list = hashs let list = hashs
let tasks = [] let tasks: Array<{ data: Array<{ hash: string }> }> = []
while (list.length) { while (list.length) {
tasks.push(Object.assign({ data: list.slice(0, 100) }, data)) tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
if (list.length < 100) break if (list.length < 100) break
@@ -37,16 +38,16 @@ const createGetMusicInfosTask = (hashs) => {
}).then(data => data.map(s => s[0]))) }).then(data => data.map(s => s[0])))
} }
export const filterMusicInfoList = (rawList) => { export const filterMusicInfoList = (rawList: any[]): SongInfo[] => {
// console.log(rawList) // console.log(rawList)
let ids = new Set() let ids = new Set()
let list = [] let list: SongInfo[] = []
rawList.forEach(item => { rawList.forEach(item => {
if (!item) return if (!item) return
if (ids.has(item.audio_info.audio_id)) return if (ids.has(item.audio_info.audio_id)) return
ids.add(item.audio_info.audio_id) ids.add(item.audio_info.audio_id)
const types = [] const types: Array<SongType & { hash?: string }> = []
const _types = {} const _types: Record<string, QualityInfo & { hash?: string }> = {}
if (item.audio_info.filesize !== '0') { if (item.audio_info.filesize !== '0') {
let size = sizeFormate(parseInt(item.audio_info.filesize)) let size = sizeFormate(parseInt(item.audio_info.filesize))
types.push({ type: '128k', size, hash: item.audio_info.hash }) types.push({ type: '128k', size, hash: item.audio_info.hash })
@@ -99,18 +100,18 @@ export const filterMusicInfoList = (rawList) => {
return list return list
} }
export const getMusicInfos = async(hashs) => { export const getMusicInfos = async(hashs: Array<{ hash: string }>): Promise<SongInfo[]> => {
return filterMusicInfoList(await Promise.all(createGetMusicInfosTask(hashs)).then(data => data.flat())) return filterMusicInfoList(await Promise.all(createGetMusicInfosTask(hashs)).then(data => data.flat()))
} }
export const getMusicInfoRaw = async(hash) => { export const getMusicInfoRaw = async(hash: string): Promise<any> => {
return Promise.all(createGetMusicInfosTask([{ hash }])).then(data => data.flat()[0]) return Promise.all(createGetMusicInfosTask([{ hash }])).then(data => data.flat()[0])
} }
export const getMusicInfo = async(hash) => { export const getMusicInfo = async(hash: string): Promise<SongInfo | undefined> => {
return getMusicInfos([{ hash }]).then(data => data[0]) return getMusicInfos([{ hash }]).then(data => data[0])
} }
export const getMusicInfosByList = (list) => { export const getMusicInfosByList = (list: any[]): Promise<SongInfo[]> => {
return getMusicInfos(list.map(item => ({ hash: item.hash }))) return getMusicInfos(list.map(item => ({ hash: item.hash })))
} }
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName, formatPlayTime, sizeFormate } from '../index.js' import { decodeName, formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import type { SongType, QualityInfo } from '../types.ts'
export default { export default {
@@ -13,8 +14,8 @@ export default {
return searchRequest.promise.then(({ body }) => body) return searchRequest.promise.then(({ body }) => body)
}, },
filterData(rawData) { filterData(rawData) {
const types = [] const types: Array<SongType & { hash?: string }> = []
const _types = {} const _types: Record<string, QualityInfo & { hash?: string }> = {}
if (rawData.FileSize !== 0) { if (rawData.FileSize !== 0) {
let size = sizeFormate(rawData.FileSize) let size = sizeFormate(rawData.FileSize)
types.push({ type: '128k', size, hash: rawData.FileHash }) types.push({ type: '128k', size, hash: rawData.FileHash })
@@ -67,7 +68,7 @@ export default {
}, },
handleResult(rawData) { handleResult(rawData) {
let ids = new Set() let ids = new Set()
const list = [] const list: any[] = []
rawData.forEach(item => { rawData.forEach(item => {
const key = item.Audioid + item.FileHash const key = item.Audioid + item.FileHash
if (ids.has(key)) return if (ids.has(key)) return
+2 -2
View File
@@ -1,4 +1,4 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
export default { export default {
getPic(songInfo) { getPic(songInfo) {
@@ -7,7 +7,7 @@ export default {
{ {
method: 'POST', method: 'POST',
headers: { headers: {
'KG-RC': 1, 'KG-RC': '1',
'KG-THash': 'expand_search_manager.cpp:852736169:451', 'KG-THash': 'expand_search_manager.cpp:852736169:451',
'User-Agent': 'KuGou2012-9020-ExpandSearchManager', 'User-Agent': 'KuGou2012-9020-ExpandSearchManager',
}, },
@@ -1,5 +1,5 @@
import { getMusicInfosByList } from './musicInfo.js' import { getMusicInfosByList } from './musicInfo.ts'
import { createHttpFetch } from './util.js' import { createHttpFetch } from './util.ts'
export default { export default {
/** /**
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.js' import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
import { toMD5 } from '../utils.js' import { toMD5 } from '../utils.ts'
import { signatureParams } from './util.js' import { signatureParams } from './util.ts'
import type { HttpRequestObject, SongInfo, SongType, QualityInfo } from '../types.ts'
// 原版使用 @renderer/utils/musicSdk/kg/vendors/infSign.min(浏览器签名库), // 原版使用 @renderer/utils/musicSdk/kg/vendors/infSign.min(浏览器签名库),
// 其 H5 签名算法为:{srcappid, clientver, clienttime, mid, uuid, dfid} + 业务参数, // 其 H5 签名算法为:{srcappid, clientver, clienttime, mid, uuid, dfid} + 业务参数,
@@ -27,10 +28,10 @@ const handleSignature = (id, page, limit) => {
} }
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_listInfo: null, _requestObj_listInfo: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
_requestObj_listRecommend: null, _requestObj_listRecommend: null as HttpRequestObject | null,
listDetailLimit: 10000, listDetailLimit: 10000,
currentTagInfo: { currentTagInfo: {
id: undefined, id: undefined,
@@ -119,7 +120,7 @@ export default {
if (index < 0) return null if (index < 0) return null
return decodeName(afterStr.substring(0, index)) return decodeName(afterStr.substring(0, index))
}, },
async getListDetailBySpecialId(id, page, tryNum = 0) { async getListDetailBySpecialId(id, page?, tryNum = 0) {
if (tryNum > 2) throw new Error('try max num') if (tryNum > 2) throw new Error('try max num')
const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise const { body } = await httpFetch(this.getSongListDetailUrl(id)).promise
@@ -172,7 +173,7 @@ export default {
// }, // },
// } // }
}, },
getInfoUrl(tagId) { getInfoUrl(tagId?) {
return tagId return tagId
? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}` ? `http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&cdn=cdn&t=5&c=${tagId}`
: 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&' : 'http://www2.kugou.kugou.com/yueku/v9/special/getSpecial?is_smarty=1&'
@@ -186,7 +187,7 @@ export default {
}, },
filterInfoHotTag(rawData) { filterInfoHotTag(rawData) {
const result = [] const result: any[] = []
if (rawData.status !== 1) return result if (rawData.status !== 1) return result
for (const key of Object.keys(rawData.data)) { for (const key of Object.keys(rawData.data)) {
let tag = rawData.data[key] let tag = rawData.data[key]
@@ -199,7 +200,7 @@ export default {
return result return result
}, },
filterTagInfo(rawData) { filterTagInfo(rawData) {
const result = [] const result: any[] = []
for (const name of Object.keys(rawData)) { for (const name of Object.keys(rawData)) {
result.push({ result.push({
name, name,
@@ -221,7 +222,7 @@ export default {
this._requestObj_list = httpFetch( this._requestObj_list = httpFetch(
this.getSongListUrl(sortId, tagId, page), this.getSongListUrl(sortId, tagId, page),
) )
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
if (!body || body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum) if (!body || body.status !== 1) return this.getSongList(sortId, tagId, page, ++tryNum)
return this.filterList(body.special_db) return this.filterList(body.special_db)
}) })
@@ -249,7 +250,7 @@ export default {
}, },
}, },
) )
return this._requestObj_listRecommend.promise.then(({ body }) => { return this._requestObj_listRecommend!.promise.then(({ body }) => {
if (body.status !== 1) return this.getSongListRecommend(++tryNum) if (body.status !== 1) return this.getSongListRecommend(++tryNum)
return this.filterList(body.data.special_list) return this.filterList(body.data.special_list)
}) })
@@ -308,7 +309,7 @@ export default {
fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname', fields: 'album_info,author_name,audio_info,ori_audio_name,base,songname',
} }
let list = hashs let list = hashs
let tasks = [] let tasks: any[] = []
while (list.length) { while (list.length) {
tasks.push(Object.assign({ data: list.slice(0, 100) }, data)) tasks.push(Object.assign({ data: list.slice(0, 100) }, data))
if (list.length < 100) break if (list.length < 100) break
@@ -452,7 +453,7 @@ export default {
async getUserListDetailByLink({ info }, link) { async getUserListDetailByLink({ info }, link) {
let listInfo = info['0'] let listInfo = info['0']
let total = listInfo.count let total = listInfo.count
let tasks = [] let tasks: any[] = []
let page = 0 let page = 0
while (total) { while (total) {
const limit = total > 90 ? 90 : total const limit = total > 90 ? 90 : total
@@ -484,7 +485,7 @@ export default {
} }
}, },
createGetListDetail2Task(id, total) { createGetListDetail2Task(id, total) {
let tasks = [] let tasks: any[] = []
let page = 0 let page = 0
while (total) { while (total) {
const limit = total > 300 ? 300 : total const limit = total > 300 ? 300 : total
@@ -653,32 +654,33 @@ export default {
}, },
}) })
const { headers: { location }, statusCode, body } = await requestObj_listDetailLink.promise const { headers: { location }, statusCode, body } = await requestObj_listDetailLink.promise
const loc = location as string | undefined
// console.log(body, location) // console.log(body, location)
if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum) if (statusCode > 400) return this.getUserListDetail(link, page, ++retryNum)
if (location) { if (loc) {
// console.log(location) // console.log(location)
if (location.includes('global_collection_id')) return this.getUserListDetail2(location.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1')) if (loc.includes('global_collection_id')) return this.getUserListDetail2(loc.replace(/^.*?global_collection_id=(\w+)(?:&.*$|#.*$|$)/, '$1'))
if (location.includes('gcid_')) { if (loc.includes('gcid_')) {
let gcid = link.match(/gcid_\w+/)?.[0] let gcid = link.match(/gcid_\w+/)?.[0]
if (gcid) { if (gcid) {
const global_collection_id = await this.decodeGcid(gcid) const global_collection_id = await this.decodeGcid(gcid)
if (global_collection_id) return this.getUserListDetail2(global_collection_id) if (global_collection_id) return this.getUserListDetail2(global_collection_id)
} }
} }
if (location.includes('chain=')) return this.getUserListDetail3(location.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page) if (loc.includes('chain=')) return this.getUserListDetail3(loc.replace(/^.*?chain=(\w+)(?:&.*$|#.*$|$)/, '$1'), page)
if (location.includes('.html')) { if (loc.includes('.html')) {
if (location.includes('zlist.html')) { if (loc.includes('zlist.html')) {
let link = location.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list') let link = loc.replace(/^(.*)zlist\.html/, 'https://m3ws.kugou.com/zlist/list')
if (link.includes('pagesize')) { if (link.includes('pagesize')) {
link = link.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page) link = link.replace('pagesize=30', 'pagesize=' + this.listDetailLimit).replace('page=1', 'page=' + page)
} else { } else {
link += `&pagesize=${this.listDetailLimit}&page=${page}` link += `&pagesize=${this.listDetailLimit}&page=${page}`
} }
return this.getUserListDetail(link, page, ++retryNum) return this.getUserListDetail(link, page, ++retryNum)
} else return this.getUserListDetail3(location.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page) } else return this.getUserListDetail3(loc.replace(/.+\/(\w+).html(?:\?.*|&.*$|#.*$|$)/, '$1'), page)
} }
// console.log('location', location) // console.log('location', location)
return this.getUserListDetail(location, page, ++retryNum) return this.getUserListDetail(loc, page, ++retryNum)
} }
if (typeof body == 'string') { if (typeof body == 'string') {
let global_collection_id = body.match(/"global_collection_id":"(\w+)"/)?.[1] let global_collection_id = body.match(/"global_collection_id":"(\w+)"/)?.[1]
@@ -713,8 +715,8 @@ export default {
filterData(rawList) { filterData(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
const types = [] const types: Array<{ type: string; size: string; hash?: string }> = []
const _types = {} const _types: Record<string, QualityInfo & { hash?: string }> = {}
if (item.filesize !== 0) { if (item.filesize !== 0) {
let size = sizeFormate(item.filesize) let size = sizeFormate(item.filesize)
types.push({ type: '128k', size, hash: item.hash }) types.push({ type: '128k', size, hash: item.hash })
@@ -831,13 +833,13 @@ export default {
filterData2(rawList) { filterData2(rawList) {
// console.log(rawList) // console.log(rawList)
let ids = new Set() let ids = new Set()
let list = [] let list: SongInfo[] = []
rawList.forEach(item => { rawList.forEach(item => {
if (!item) return if (!item) return
if (ids.has(item.audio_info.audio_id)) return if (ids.has(item.audio_info.audio_id)) return
ids.add(item.audio_info.audio_id) ids.add(item.audio_info.audio_id)
const types = [] const types: Array<SongType & { hash?: string }> = []
const _types = {} const _types: Record<string, QualityInfo & { hash?: string }> = {}
if (item.audio_info.filesize !== '0') { if (item.audio_info.filesize !== '0') {
let size = sizeFormate(parseInt(item.audio_info.filesize)) let size = sizeFormate(parseInt(item.audio_info.filesize))
types.push({ type: '128k', size, hash: item.audio_info.hash }) types.push({ type: '128k', size, hash: item.audio_info.hash })
@@ -895,7 +897,7 @@ export default {
if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp() if (this._requestObj_listInfo) this._requestObj_listInfo.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId)) this._requestObj_listInfo = httpFetch(this.getInfoUrl(tagId))
return this._requestObj_listInfo.promise.then(({ body }) => { return this._requestObj_listInfo!.promise.then(({ body }) => {
if (body.status !== 1) return this.getListInfo(tagId, ++tryNum) if (body.status !== 1) return this.getListInfo(tagId, ++tryNum)
return { return {
limit: body.data.params.pagesize, limit: body.data.params.pagesize,
@@ -933,7 +935,7 @@ export default {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp() if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.getInfoUrl()) this._requestObj_tags = httpFetch(this.getInfoUrl())
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
if (body.status !== 1) return this.getTags(++tryNum) if (body.status !== 1) return this.getTags(++tryNum)
return { return {
hotTag: this.filterInfoHotTag(body.data.hotTag), hotTag: this.filterInfoHotTag(body.data.hotTag),
@@ -1,7 +1,7 @@
import { createHttpFetch } from './util.js' import { createHttpFetch } from './util.ts'
export default { export default {
requestObj: null, requestObj: null as any,
cancelTipSearch() { cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp() if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
}, },
+5 -4
View File
@@ -1,5 +1,6 @@
import { toMD5 } from '../utils.js' import { toMD5 } from '../utils.ts'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { HttpOptions } from '../types.ts'
// s.content[0].lyricContent.forEach(([str]) => { // s.content[0].lyricContent.forEach(([str]) => {
// console.log(str) // console.log(str)
@@ -10,7 +11,7 @@ import { httpFetch } from '../request.js'
* @param {*} params * @param {*} params
* @param {*} apiver * @param {*} apiver
*/ */
export const signatureParams = (params, platform = 'android', body = '') => { export const signatureParams = (params: string, platform = 'android', body = ''): string => {
let keyparam = 'OIlwieks28dk2k092lksi2UIkp' let keyparam = 'OIlwieks28dk2k092lksi2UIkp'
if (platform === 'web') keyparam = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt' if (platform === 'web') keyparam = 'NVPh5oo715z5DIWAeQlhMDsWXXQV4hwt'
let param_list = params.split('&') let param_list = params.split('&')
@@ -25,7 +26,7 @@ export const signatureParams = (params, platform = 'android', body = '') => {
* @param {*} options * @param {*} options
* @param {*} retryNum * @param {*} retryNum
*/ */
export const createHttpFetch = async(url, options, retryNum = 0) => { export const createHttpFetch = async(url: string, options?: HttpOptions, retryNum = 0): Promise<any> => {
if (retryNum > 2) throw new Error('try max num') if (retryNum > 2) throw new Error('try max num')
let result let result
try { try {
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeName } from '../index.js' import { decodeName } from '../index.ts'
import { formatSinger, objStr2JSON } from './util.js' import { formatSinger, objStr2JSON } from './util.ts'
import type { Quality } from '../types.ts'
// let requestObj_list // let requestObj_list
export default { export default {
@@ -11,8 +12,8 @@ export default {
// console.log(rawList.length, rawList2.length) // console.log(rawList.length, rawList2.length)
return rawList.map((item, inedx) => { return rawList.map((item, inedx) => {
let formats = item.formats.split('|') let formats = item.formats.split('|')
let types = [] let types: Array<{ type: Quality; size: string | null }> = []
let _types = {} let _types: Partial<Record<Quality, { size: string | null }>> = {}
if (formats.includes('MP3128')) { if (formats.includes('MP3128')) {
types.push({ type: '128k', size: null }) types.push({ type: '128k', size: null })
_types['128k'] = { _types['128k'] = {
@@ -71,9 +72,9 @@ export default {
* 格式化播放数量 * 格式化播放数量
* @param {*} num * @param {*} num
*/ */
formatPlayCount(num) { formatPlayCount(num: any) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿' if (num > 100000000) return parseInt((num / 10000000) as any) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万' if (num > 10000) return parseInt((num / 1000) as any) / 10 + '万'
return num return num
}, },
getAlbumListDetail(id, page, retryNum = 0) { getAlbumListDetail(id, page, retryNum = 0) {
@@ -1,9 +1,10 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { dateFormat2 } from '../index.js' import { dateFormat2 } from '../index.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
_requestObj2: null, _requestObj2: null as HttpRequestObject | null,
async getComment({ songmid }, page = 1, limit = 20) { async getComment({ songmid }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
+17 -16
View File
@@ -1,19 +1,20 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import tipSearch from './tipSearch.js' import tipSearch from './tipSearch.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import { formatSinger } from './util.js' import { formatSinger } from './util.ts'
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import lyric from './lyric.js' import lyric from './lyric.ts'
import pic from './pic.js' import pic from './pic.ts'
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import songList from './songList.js' import songList from './songList.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
import comment from './comment.js' import comment from './comment.ts'
import type { HttpRequestObject } from '../types.ts'
const kw = { const kw = {
_musicInfoRequestObj: null, _musicInfoRequestObj: null as HttpRequestObject | null,
_musicInfoPromiseCancelFn: null, _musicInfoPromiseCancelFn: null,
_musicPicRequestObj: null, _musicPicRequestObj: null as HttpRequestObject | null,
_musicPicPromiseCancelFn: null, _musicPicPromiseCancelFn: null,
// context: null, // context: null,
@@ -60,19 +61,19 @@ const kw = {
}, },
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('kw').getMusicUrl(songInfo, type) return apis('kw').getMusicUrl!(songInfo, type)
}, },
getMusicInfo(songInfo) { getMusicInfo(songInfo) {
if (this._musicInfoRequestObj) this._musicInfoRequestObj.cancelHttp() if (this._musicInfoRequestObj) this._musicInfoRequestObj.cancelHttp()
this._musicInfoRequestObj = httpFetch(`http://www.kuwo.cn/api/www/music/musicInfo?mid=${songInfo.songmid}`) this._musicInfoRequestObj = httpFetch(`http://www.kuwo.cn/api/www/music/musicInfo?mid=${songInfo.songmid}`)
return this._musicInfoRequestObj.promise.then(({ body }) => { return this._musicInfoRequestObj!.promise.then(({ body }) => {
return body.code === 200 ? body.data : Promise.reject(new Error(body.msg)) return body.code === 200 ? body.data : Promise.reject(new Error(body.msg))
}) })
}, },
getMusicUrls(musicInfo, cb) { getMusicUrls(musicInfo, cb) {
let tasks = [] let tasks: Array<Promise<any>> = []
let songId = musicInfo.songmid let songId = musicInfo.songmid
musicInfo.types.forEach(type => { musicInfo.types.forEach(type => {
tasks.push(kw.getMusicUrl(songId, type.type).promise) tasks.push(kw.getMusicUrl(songId, type.type).promise)
@@ -1,19 +1,20 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime, decodeName } from '../index.js' import { formatPlayTime, decodeName } from '../index.ts'
import { formatSinger, wbdCrypto } from './util.js' import { formatSinger, wbdCrypto } from './util.ts'
import type { HttpRequestObject, Quality, QualityInfo, SongType } from '../types.ts'
const boardList = [{ id: 'kw__93', name: '飙升榜', bangid: '93' }, { id: 'kw__17', name: '新歌榜', bangid: '17' }, { id: 'kw__16', name: '热歌榜', bangid: '16' }, { id: 'kw__158', name: '抖音热歌榜', bangid: '158' }, { id: 'kw__292', name: '铃声榜', bangid: '292' }, { id: 'kw__284', name: '热评榜', bangid: '284' }, { id: 'kw__290', name: 'ACG新歌榜', bangid: '290' }, { id: 'kw__286', name: '台湾KKBOX榜', bangid: '286' }, { id: 'kw__279', name: '冬日暖心榜', bangid: '279' }, { id: 'kw__281', name: '巴士随身听榜', bangid: '281' }, { id: 'kw__255', name: 'KTV点唱榜', bangid: '255' }, { id: 'kw__280', name: '家务进行曲榜', bangid: '280' }, { id: 'kw__282', name: '熬夜修仙榜', bangid: '282' }, { id: 'kw__283', name: '枕边轻音乐榜', bangid: '283' }, { id: 'kw__278', name: '古风音乐榜', bangid: '278' }, { id: 'kw__264', name: 'Vlog音乐榜', bangid: '264' }, { id: 'kw__242', name: '电音榜', bangid: '242' }, { id: 'kw__187', name: '流行趋势榜', bangid: '187' }, { id: 'kw__204', name: '现场音乐榜', bangid: '204' }, { id: 'kw__186', name: 'ACG神曲榜', bangid: '186' }, { id: 'kw__185', name: '最强翻唱榜', bangid: '185' }, { id: 'kw__26', name: '经典怀旧榜', bangid: '26' }, { id: 'kw__104', name: '华语榜', bangid: '104' }, { id: 'kw__182', name: '粤语榜', bangid: '182' }, { id: 'kw__22', name: '欧美榜', bangid: '22' }, { id: 'kw__184', name: '韩语榜', bangid: '184' }, { id: 'kw__183', name: '日语榜', bangid: '183' }, { id: 'kw__145', name: '会员畅听榜', bangid: '145' }, { id: 'kw__153', name: '网红新歌榜', bangid: '153' }, { id: 'kw__64', name: '影视金曲榜', bangid: '64' }, { id: 'kw__176', name: 'DJ嗨歌榜', bangid: '176' }, { id: 'kw__106', name: '真声音', bangid: '106' }, { id: 'kw__12', name: 'Billboard榜', bangid: '12' }, { id: 'kw__49', name: 'iTunes音乐榜', bangid: '49' }, { id: 'kw__180', name: 'beatport电音榜', bangid: '180' }, { id: 'kw__13', name: '英国UK榜', bangid: '13' }, { id: 'kw__164', name: '百大DJ榜', bangid: '164' }, { id: 'kw__246', name: 'YouTube音乐排行榜', bangid: '246' }, { id: 'kw__265', name: '韩国Genie榜', bangid: '265' }, { id: 'kw__14', name: '韩国M-net榜', bangid: '14' }, { id: 'kw__8', name: '香港电台榜', bangid: '8' }, { id: 'kw__15', name: '日本公信榜', bangid: '15' }, { id: 'kw__151', name: '腾讯音乐人原创榜', bangid: '151' }] const boardList = [{ id: 'kw__93', name: '飙升榜', bangid: '93' }, { id: 'kw__17', name: '新歌榜', bangid: '17' }, { id: 'kw__16', name: '热歌榜', bangid: '16' }, { id: 'kw__158', name: '抖音热歌榜', bangid: '158' }, { id: 'kw__292', name: '铃声榜', bangid: '292' }, { id: 'kw__284', name: '热评榜', bangid: '284' }, { id: 'kw__290', name: 'ACG新歌榜', bangid: '290' }, { id: 'kw__286', name: '台湾KKBOX榜', bangid: '286' }, { id: 'kw__279', name: '冬日暖心榜', bangid: '279' }, { id: 'kw__281', name: '巴士随身听榜', bangid: '281' }, { id: 'kw__255', name: 'KTV点唱榜', bangid: '255' }, { id: 'kw__280', name: '家务进行曲榜', bangid: '280' }, { id: 'kw__282', name: '熬夜修仙榜', bangid: '282' }, { id: 'kw__283', name: '枕边轻音乐榜', bangid: '283' }, { id: 'kw__278', name: '古风音乐榜', bangid: '278' }, { id: 'kw__264', name: 'Vlog音乐榜', bangid: '264' }, { id: 'kw__242', name: '电音榜', bangid: '242' }, { id: 'kw__187', name: '流行趋势榜', bangid: '187' }, { id: 'kw__204', name: '现场音乐榜', bangid: '204' }, { id: 'kw__186', name: 'ACG神曲榜', bangid: '186' }, { id: 'kw__185', name: '最强翻唱榜', bangid: '185' }, { id: 'kw__26', name: '经典怀旧榜', bangid: '26' }, { id: 'kw__104', name: '华语榜', bangid: '104' }, { id: 'kw__182', name: '粤语榜', bangid: '182' }, { id: 'kw__22', name: '欧美榜', bangid: '22' }, { id: 'kw__184', name: '韩语榜', bangid: '184' }, { id: 'kw__183', name: '日语榜', bangid: '183' }, { id: 'kw__145', name: '会员畅听榜', bangid: '145' }, { id: 'kw__153', name: '网红新歌榜', bangid: '153' }, { id: 'kw__64', name: '影视金曲榜', bangid: '64' }, { id: 'kw__176', name: 'DJ嗨歌榜', bangid: '176' }, { id: 'kw__106', name: '真声音', bangid: '106' }, { id: 'kw__12', name: 'Billboard榜', bangid: '12' }, { id: 'kw__49', name: 'iTunes音乐榜', bangid: '49' }, { id: 'kw__180', name: 'beatport电音榜', bangid: '180' }, { id: 'kw__13', name: '英国UK榜', bangid: '13' }, { id: 'kw__164', name: '百大DJ榜', bangid: '164' }, { id: 'kw__246', name: 'YouTube音乐排行榜', bangid: '246' }, { id: 'kw__265', name: '韩国Genie榜', bangid: '265' }, { id: 'kw__14', name: '韩国M-net榜', bangid: '14' }, { id: 'kw__8', name: '香港电台榜', bangid: '8' }, { id: 'kw__15', name: '日本公信榜', bangid: '15' }, { id: 'kw__151', name: '腾讯音乐人原创榜', bangid: '151' }]
const sortQualityArray = array => { const sortQualityArray = (array: SongType[]): SongType[] => {
const qualityMap = { const qualityMap = {
flac24bit: 4, flac24bit: 4,
flac: 3, flac: 3,
'320k': 2, '320k': 2,
'128k': 1, '128k': 1,
} }
const rawQualityArray = [] const rawQualityArray: Array<{ type: number; index: number }> = []
const newQualityArray = [] const newQualityArray: SongType[] = []
array.forEach((item, index) => { array.forEach((item, index) => {
const type = qualityMap[item.type] const type = qualityMap[item.type]
@@ -30,6 +31,12 @@ const sortQualityArray = array => {
return newQualityArray return newQualityArray
} }
interface BoardItem {
id: string
name: string
bangid: number | string
}
export default { export default {
list: [ list: [
{ {
@@ -87,27 +94,27 @@ export default {
name: '日语榜', name: '日语榜',
bangid: 183, bangid: 183,
}, },
], ] as BoardItem[],
// getUrl: (p, l, id) => `http://kbangserver.kuwo.cn/ksong.s?from=pc&fmt=json&pn=${p - 1}&rn=${l}&type=bang&data=content&id=${id}&show_copyright_off=0&pcmp4=1&isbang=1`, // getUrl: (p, l, id) => `http://kbangserver.kuwo.cn/ksong.s?from=pc&fmt=json&pn=${p - 1}&rn=${l}&type=bang&data=content&id=${id}&show_copyright_off=0&pcmp4=1&isbang=1`,
regExps: { regExps: {
mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/, mInfo: /level:(\w+),bitrate:(\d+),format:(\w+),size:([\w.]+)/,
}, },
limit: 100, limit: 100,
_requestBoardsObj: null, _requestBoardsObj: null as HttpRequestObject | null,
getBoardsData() { getBoardsData() {
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
this._requestBoardsObj = httpFetch('http://qukudata.kuwo.cn/q.k?op=query&cont=tree&node=2&pn=0&rn=1000&fmt=json&level=2') this._requestBoardsObj = httpFetch('http://qukudata.kuwo.cn/q.k?op=query&cont=tree&node=2&pn=0&rn=1000&fmt=json&level=2')
return this._requestBoardsObj.promise return this._requestBoardsObj!.promise
}, },
getData(url) { getData(url: string) {
const requestDataObj = httpFetch(url) const requestDataObj = httpFetch(url)
return requestDataObj.promise return requestDataObj.promise
}, },
filterData(rawList) { filterData(rawList) {
return rawList.map(item => { return rawList.map(item => {
let types = [] let types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
const qualitys = new Set() const qualitys = new Set()
item.n_minfo.split(';').forEach(i => { item.n_minfo.split(';').forEach(i => {
@@ -161,7 +168,7 @@ export default {
filterBoardsData(rawList) { filterBoardsData(rawList) {
// console.log(rawList) // console.log(rawList)
let list = [] let list: any[] = []
for (const board of rawList) { for (const board of rawList) {
if (board.source != '1') continue if (board.source != '1') continue
list.push({ list.push({
+10 -10
View File
@@ -1,6 +1,6 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { decodeLyric, lrcTools } from './util.js' import { decodeLyric, lrcTools } from './util.ts'
import { decodeName } from '../index.js' import { decodeName } from '../index.ts'
/* /*
export default { export default {
@@ -69,7 +69,7 @@ export default {
const buf_key = Buffer.from('yeelion') const buf_key = Buffer.from('yeelion')
const buf_key_len = buf_key.length const buf_key_len = buf_key.length
const buildParams = (id, isGetLyricx) => { const buildParams = (id: string, isGetLyricx: boolean): string => {
let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}` let params = `user=12345,web,web,web&requester=localhost&req=1&rid=MUSIC_${id}`
if (isGetLyricx) params += '&lrcx=1' if (isGetLyricx) params += '&lrcx=1'
const buf_str = Buffer.from(params) const buf_str = Buffer.from(params)
@@ -148,8 +148,8 @@ export default {
}, */ }, */
sortLrcArr(arr) { sortLrcArr(arr) {
const lrcSet = new Set() const lrcSet = new Set()
let lrc = [] let lrc: any[] = []
let lrcT = [] let lrcT: any[] = []
let isLyricx = false let isLyricx = false
for (const item of arr) { for (const item of arr) {
@@ -186,10 +186,10 @@ export default {
transformLrc(tags, lrclist) { transformLrc(tags, lrclist) {
return `${tags.join('\n')}\n${lrclist ? lrclist.map(l => `[${l.time}]${l.text}\n`).join('') : '暂无歌词'}` return `${tags.join('\n')}\n${lrclist ? lrclist.map(l => `[${l.time}]${l.text}\n`).join('') : '暂无歌词'}`
}, },
parseLrc(lrc) { parseLrc(lrc: string) {
const lines = lrc.split(/\r\n|\r|\n/) const lines = lrc.split(/\r\n|\r|\n/)
let tags = [] let tags: string[] = []
let lrcArr = [] let lrcArr: Array<{ time: string; text: string }> = []
for (let i = 0; i < lines.length; i++) { for (let i = 0; i < lines.length; i++) {
const line = lines[i].trim() const line = lines[i].trim()
let result = timeExp.exec(line) let result = timeExp.exec(line)
@@ -238,7 +238,7 @@ export default {
const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`) const requestObj = httpFetch(`http://newlyric.kuwo.cn/newlyric.lrc?${buildParams(musicInfo.songmid, isGetLyricx)}`)
requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => { requestObj.promise = requestObj.promise.then(({ statusCode, body, raw }) => {
if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body))) if (statusCode != 200) return Promise.reject(new Error(JSON.stringify(body)))
return decodeLyric({ lrcBase64: raw.toString('base64'), isGetLyricx }).then(base64Data => { return decodeLyric({ lrcBase64: raw!.toString('base64'), isGetLyricx }).then(base64Data => {
// let lrcInfo // let lrcInfo
// try { // try {
// lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString()) // lrcInfo = this.parseLrc(Buffer.from(base64Data, 'base64').toString())
@@ -1,9 +1,10 @@
// import '../../polyfill/array.find.js' // import '../../polyfill/array.find.js'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime, decodeName } from '../index.js' import { formatPlayTime, decodeName } from '../index.ts'
// import { debug } from '../../utils/env.js' // import { debug } from '../../utils/env.js'
import { formatSinger } from './util.js' import { formatSinger } from './util.ts'
import type { Quality, QualityInfo, SongInfo, SongType } from '../types.ts'
export default { export default {
regExps: { regExps: {
@@ -14,7 +15,7 @@ export default {
page: 0, page: 0,
allPage: 1, allPage: 1,
// cancelFn: null, // cancelFn: null,
musicSearch(str, page, limit) { musicSearch(str: string, page: number, limit: number) {
const musicSearchRequestObj = httpFetch(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`) const musicSearchRequestObj = httpFetch(`http://search.kuwo.cn/r.s?client=kt&all=${encodeURIComponent(str)}&pn=${page - 1}&rn=${limit}&uid=794762570&ver=kwplayer_ar_9.2.2.1&vipver=1&show_copyright_off=1&newver=1&ft=music&cluster=0&strategy=2012&encoding=utf8&rformat=json&vermerge=1&mobi=1&issubtitle=1`)
return musicSearchRequestObj.promise return musicSearchRequestObj.promise
}, },
@@ -24,8 +25,8 @@ export default {
// getLrc(songId) { // getLrc(songId) {
// return httpGet(`http://mobile.kuwo.cn/mpage/html5/songinfoandlrc?mid=${songId}&flag=0`) // return httpGet(`http://mobile.kuwo.cn/mpage/html5/songinfoandlrc?mid=${songId}&flag=0`)
// }, // },
handleResult(rawData) { handleResult(rawData: any) {
const result = [] const result: SongInfo[] = []
if (!rawData) return result if (!rawData) return result
// console.log(rawData) // console.log(rawData)
for (let i = 0; i < rawData.length; i++) { for (let i = 0; i < rawData.length; i++) {
@@ -38,8 +39,8 @@ export default {
return null return null
} }
const types = [] const types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
let infoArr = info.N_MINFO.split(';') let infoArr = info.N_MINFO.split(';')
for (let info of infoArr) { for (let info of infoArr) {
@@ -99,7 +100,7 @@ export default {
// console.log(result) // console.log(result)
return result return result
}, },
search(str, page = 1, limit, retryNum = 0) { search(str: string, page = 1, limit?: number, retryNum = 0) {
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
if (limit == null) limit = this.limit if (limit == null) limit = this.limit
// http://newlyric.kuwo.cn/newlyric.lrc?62355680 // http://newlyric.kuwo.cn/newlyric.lrc?62355680
+3 -2
View File
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { SongInfo } from '../types.ts'
export default { export default {
getPic({ songmid }) { getPic({ songmid }: SongInfo) {
const requestObj = httpFetch(`http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=${songmid}`) const requestObj = httpFetch(`http://artistpicserver.kuwo.cn/pic.web?corp=kuwo&type=rid_pic&pictype=500&size=500&rid=${songmid}`)
requestObj.promise = requestObj.promise.then(({ body }) => /^http/.test(body) ? body : null) requestObj.promise = requestObj.promise.then(({ body }) => /^http/.test(body) ? body : null)
return requestObj.promise return requestObj.promise
@@ -1,12 +1,13 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime, decodeName } from '../index.js' import { formatPlayTime, decodeName } from '../index.ts'
import { formatSinger, objStr2JSON } from './util.js' import { formatSinger, objStr2JSON } from './util.ts'
import album from './album.js' import album from './album.ts'
import type { HttpRequestObject, Quality, QualityInfo, SongType } from '../types.ts'
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_hotTags: null, _requestObj_hotTags: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
limit_list: 36, limit_list: 36,
limit_song: 1000, limit_song: 1000,
successCode: 200, successCode: 200,
@@ -28,7 +29,7 @@ export default {
}, },
tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576', tagsUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getTagList?cmd=rcm_keyword_playlist&user=0&prod=kwplayer_pc_9.0.5.0&vipver=9.0.5.0&source=kwplayer_pc_9.0.5.0&loginUid=0&loginSid=0&appUid=76039576',
hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576', hotTagUrl: 'http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmTagList?loginUid=0&loginSid=0&appUid=76039576',
getListUrl({ sortId, id, type, page }) { getListUrl({ sortId, id, type, page }: { sortId: string; id: string | null; type: string; page: number }) {
if (!id) return `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&&pn=${page}&rn=${this.limit_list}&order=${sortId}` if (!id) return `http://wapi.kuwo.cn/api/pc/classify/playlist/getRcmPlayList?loginUid=0&loginSid=0&appUid=76039576&&pn=${page}&rn=${this.limit_list}&order=${sortId}`
switch (type) { switch (type) {
case '10000': return `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&id=${id}&rn=${this.limit_list}` case '10000': return `http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&pn=${page}&id=${id}&rn=${this.limit_list}`
@@ -36,7 +37,7 @@ export default {
} }
// http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&id=173&pn=1&rn=100 // http://wapi.kuwo.cn/api/pc/classify/playlist/getTagPlayList?loginUid=0&loginSid=0&appUid=76039576&id=173&pn=1&rn=100
}, },
getListDetailUrl(id, page) { getListDetailUrl(id: string, page: number) {
// http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2858093057&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1 // http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=2858093057&pn=0&rn=100&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1
return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1` return `http://nplserver.kuwo.cn/pl.svc?op=getlistinfo&pid=${id}&pn=${page - 1}&rn=${this.limit_song}&encode=utf8&keyset=pl2012&identity=kuwo&pcmp4=1&vipver=MUSIC_9.0.5.0_W1&newver=1`
// http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=140&prod=pc // http://mobileinterfaces.kuwo.cn/er.s?type=get_pc_qz_data&f=web&id=140&prod=pc
@@ -48,7 +49,7 @@ export default {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp() if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.tagsUrl) this._requestObj_tags = httpFetch(this.tagsUrl)
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTag(++tryNum) if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body.data) return this.filterTagInfo(body.data)
}) })
@@ -58,7 +59,7 @@ export default {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp() if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_hotTags = httpFetch(this.hotTagUrl) this._requestObj_hotTags = httpFetch(this.hotTagUrl)
return this._requestObj_hotTags.promise.then(({ body }) => { return this._requestObj_hotTags!.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getHotTag(++tryNum) if (body.code !== this.successCode) return this.getHotTag(++tryNum)
return this.filterInfoHotTag(body.data[0].data) return this.filterInfoHotTag(body.data[0].data)
}) })
@@ -84,20 +85,21 @@ export default {
}, },
// 获取列表数据 // 获取列表数据
getList(sortId, tagId, page, tryNum = 0) { getList(sortId: string, tagId: string | undefined, page: number, tryNum = 0) {
if (this._requestObj_list) this._requestObj_list.cancelHttp() if (this._requestObj_list) this._requestObj_list.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
let id let id: string | null
let type let type: string
if (tagId) { if (tagId) {
let arr = tagId.split('-') let arr = tagId.split('-')
id = arr[0] id = arr[0]
type = arr[1] type = arr[1] ?? ''
} else { } else {
id = null id = null
type = ''
} }
this._requestObj_list = httpFetch(this.getListUrl({ sortId, id, type, page })) this._requestObj_list = httpFetch(this.getListUrl({ sortId, id, type, page } as any) as any)
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
if (!id || type == '10000') { if (!id || type == '10000') {
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum) if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return { return {
@@ -125,9 +127,9 @@ export default {
* 格式化播放数量 * 格式化播放数量
* @param {*} num * @param {*} num
*/ */
formatPlayCount(num) { formatPlayCount(num: any) {
if (num > 100000000) return parseInt(num / 10000000) / 10 + '亿' if (num > 100000000) return parseInt((num / 10000000) as any) / 10 + '亿'
if (num > 10000) return parseInt(num / 1000) / 10 + '万' if (num > 10000) return parseInt((num / 1000) as any) / 10 + '万'
return num return num
}, },
filterList(rawData) { filterList(rawData) {
@@ -146,7 +148,7 @@ export default {
}, },
filterList2(rawData) { filterList2(rawData) {
// console.log(rawData) // console.log(rawData)
const list = [] const list: any[] = []
rawData.forEach(item => { rawData.forEach(item => {
if (!item.label) return if (!item.label) return
list.push(...item.list.map(item => ({ list.push(...item.list.map(item => ({
@@ -225,8 +227,8 @@ export default {
filterBDListDetail(rawList) { filterBDListDetail(rawList) {
return rawList.map(item => { return rawList.map(item => {
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
for (let info of item.audios) { for (let info of item.audios) {
info.size = info.size?.toLocaleUpperCase() info.size = info.size?.toLocaleUpperCase()
switch (info.bitrate) { switch (info.bitrate) {
@@ -288,7 +290,7 @@ export default {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
plat: 'h5', plat: 'h5',
}, },
}).promise.catch(() => ({ code: 0 })) }).promise.catch(() => ({ code: 0 } as any))
if (infoData.code != 200) return null if (infoData.code != 200) return null
@@ -306,7 +308,7 @@ export default {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36',
plat: 'h5', plat: 'h5',
}, },
}).promise.catch(() => ({ code: 0 })) }).promise.catch(() => ({ code: 0 } as any))
if (infoData.code != 200) return null if (infoData.code != 200) return null
@@ -346,7 +348,7 @@ export default {
const source = /source=(\d+)/.exec(id)?.[1] const source = /source=(\d+)/.exec(id)?.[1]
if (!listId) return Promise.reject(new Error('failed')) if (!listId) return Promise.reject(new Error('failed'))
const task = [this.getListDetailMusicListByBDList(listId, source, page)] const task: Array<Promise<any>> = [this.getListDetailMusicListByBDList(listId, source, page)]
switch (source) { switch (source) {
case '4': case '4':
task.push(this.getListDetailMusicListByBDListInfo(listId, source)) task.push(this.getListDetailMusicListByBDListInfo(listId, source))
@@ -392,8 +394,8 @@ export default {
// console.log(rawData) // console.log(rawData)
return rawData.map(item => { return rawData.map(item => {
let infoArr = item.N_MINFO.split(';') let infoArr = item.N_MINFO.split(';')
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
for (let info of infoArr) { for (let info of infoArr) {
info = info.match(this.regExps.mInfo) info = info.match(this.regExps.mInfo)
if (info) { if (info) {
@@ -1,32 +1,35 @@
// import { decodeName } from '../index.js' // import { decodeName } from '../index.ts'
// import { tokenRequest } from './util.js' // import { tokenRequest } from './util.ts'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
regExps: { regExps: {
relWord: /RELWORD=(.+)/, relWord: /RELWORD=(.+)/,
}, },
requestObj: null, requestObj: null as HttpRequestObject | null,
async tipSearchBySong(str) { async tipSearchBySong(str: string) {
// 报错403,加了referer还是有问题(直接换一个 // 报错403,加了referer还是有问题(直接换一个
// this.requestObj = await tokenRequest(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`) // this.requestObj = await tokenRequest(`http://www.kuwo.cn/api/www/search/searchKey?key=${encodeURIComponent(str)}`)
this.cancelTipSearch() this.cancelTipSearch()
this.requestObj = httpFetch(`https://tips.kuwo.cn/t.s?corp=kuwo&newver=3&p2p=1&notrace=0&c=mbox&w=${encodeURIComponent(str)}&encoding=utf8&rformat=json`, { this.requestObj = httpFetch(`https://tips.kuwo.cn/t.s?corp=kuwo&newver=3&p2p=1&notrace=0&c=mbox&w=${encodeURIComponent(str)}&encoding=utf8&rformat=json`, {
headers: {
Referer: 'http://www.kuwo.cn/', Referer: 'http://www.kuwo.cn/',
},
}) })
return this.requestObj.promise.then(({ body, statusCode }) => { return this.requestObj!.promise.then(({ body, statusCode }) => {
if (statusCode != 200 || !body.WORDITEMS) return Promise.reject(new Error('请求失败')) if (statusCode != 200 || !body.WORDITEMS) return Promise.reject(new Error('请求失败'))
return body.WORDITEMS return body.WORDITEMS
}) })
}, },
handleResult(rawData) { handleResult(rawData: any) {
return rawData.map(item => item.RELWORD) return rawData.map(item => item.RELWORD)
}, },
cancelTipSearch() { cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp() if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
}, },
async search(str) { async search(str: string) {
return this.tipSearchBySong(str).then(result => this.handleResult(result)) return this.tipSearchBySong(str).then(result => this.handleResult(result))
}, },
} }
+26 -18
View File
@@ -1,8 +1,8 @@
// import { httpGet, httpFetch } from '../request.js' // import { httpGet, httpFetch } from '../request.ts'
import { WIN_MAIN_RENDERER_EVENT_NAME } from '../../src/common/ipcNames.js' import { WIN_MAIN_RENDERER_EVENT_NAME } from '../../src/common/ipcNames.js'
import { rendererInvoke } from '../../src/common/rendererIpc.js' import { rendererInvoke } from '../../src/common/rendererIpc.js'
import { createCipheriv, createDecipheriv } from 'crypto' import { createCipheriv, createDecipheriv } from 'crypto'
import { toMD5 } from '../utils.js' import { toMD5 } from '../utils.ts'
// const kw_token = { // const kw_token = {
// token: null, // token: null,
@@ -30,14 +30,14 @@ import { toMD5 } from '../utils.js'
// return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s])) // return JSON.parse(str.replace(/(^{'|'}\n$|'}$|':'|','|':\[{'|'}\],'|':{'|'},'|'},{'|':\['|':\[\],'|':{},'|'}]})/g, s => translationMap[s]))
// } // }
export const objStr2JSON = str => { export const objStr2JSON = (str: string): any => {
return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"')) return JSON.parse(str.replace(/('(?=(,\s*')))|('(?=:))|((?<=([:,]\s*))')|((?<={)')|('(?=}))/g, '"'))
} }
export const formatSinger = rawData => rawData.replace(/&/g, '、') export const formatSinger = (rawData: string): string => rawData.replace(/&/g, '、')
export const matchToken = headers => { export const matchToken = (headers: Record<string, any>): string | null => {
try { try {
return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1] return headers['set-cookie'][0].match(/kw_token=(\w+)/)[1]
} catch (err) { } catch (err) {
@@ -63,7 +63,8 @@ export const matchToken = headers => {
// }) // })
// }) // })
export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data) export const decodeLyric = (base64Data: { lrcBase64: string; isGetLyricx: boolean }): Promise<any> =>
rendererInvoke(WIN_MAIN_RENDERER_EVENT_NAME.handle_kw_decode_lyric, base64Data)
// export const tokenRequest = async(url, options = {}) => { // export const tokenRequest = async(url, options = {}) => {
// let token = kw_token.token // let token = kw_token.token
@@ -86,6 +87,13 @@ export const decodeLyric = base64Data => rendererInvoke(WIN_MAIN_RENDERER_EVENT_
// return requestObj // return requestObj
// } // }
interface LrcWordInfo {
startTime: number
endTime: number
timeStr: string
newTimeStr?: string
}
export const lrcTools = { export const lrcTools = {
rxps: { rxps: {
wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/, wordLine: /^(\[\d{1,2}:.*\d{1,4}\])\s*(\S+(?:\s+\S+)*)?\s*/,
@@ -96,9 +104,9 @@ export const lrcTools = {
offset: 1, offset: 1,
offset2: 1, offset2: 1,
isOK: false, isOK: false,
lines: [], lines: [] as string[],
tags: [], tags: [] as string[],
getWordInfo(str, str2, prevWord) { getWordInfo(str: string, str2: string, prevWord?: LrcWordInfo): LrcWordInfo {
const offset = parseInt(str) const offset = parseInt(str)
const offset2 = parseInt(str2) const offset2 = parseInt(str2)
let startTime = Math.abs((offset + offset2) / (this.offset * 2)) let startTime = Math.abs((offset + offset2) / (this.offset * 2))
@@ -120,7 +128,7 @@ export const lrcTools = {
timeStr: `<${startTime},${endTime - startTime}>`, timeStr: `<${startTime},${endTime - startTime}>`,
} }
}, },
parseLine(line) { parseLine(line: string): void {
if (line.length < 6) return if (line.length < 6) return
let result = this.rxps.wordLine.exec(line) let result = this.rxps.wordLine.exec(line)
if (result) { if (result) {
@@ -135,7 +143,7 @@ export const lrcTools = {
let preTimeInfo let preTimeInfo
for (const timeStr of wordTimes) { for (const timeStr of wordTimes) {
const result = this.rxps.wordTime.exec(timeStr) const result = this.rxps.wordTime.exec(timeStr)
const wordInfo = this.getWordInfo(result[1], result[2], preTimeInfo) const wordInfo = this.getWordInfo(result![1], result![2], preTimeInfo)
words = words.replace(timeStr, wordInfo.timeStr) words = words.replace(timeStr, wordInfo.timeStr)
if (preTimeInfo?.newTimeStr) words = words.replace(preTimeInfo.timeStr, preTimeInfo.newTimeStr) if (preTimeInfo?.newTimeStr) words = words.replace(preTimeInfo.timeStr, preTimeInfo.newTimeStr)
preTimeInfo = wordInfo preTimeInfo = wordInfo
@@ -160,7 +168,7 @@ export const lrcTools = {
this.tags.push(line) this.tags.push(line)
} }
}, },
parse(lrc) { parse(lrc: string): string {
// console.log(lrc) // console.log(lrc)
const lines = lrc.split(/\r\n|\r|\n/) const lines = lrc.split(/\r\n|\r|\n/)
const tools = Object.create(this) const tools = Object.create(this)
@@ -183,30 +191,30 @@ export const lrcTools = {
} }
const createAesEncrypt = (buffer, mode, key, iv) => { const createAesEncrypt = (buffer: Buffer, mode: string, key: Buffer | string, iv: string | Buffer): Buffer => {
const cipher = createCipheriv(mode, key, iv) const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()]) return Buffer.concat([cipher.update(buffer), cipher.final()])
} }
const createAesDecrypt = (buffer, mode, key, iv) => { const createAesDecrypt = (buffer: Buffer, mode: string, key: Buffer | string, iv: string | Buffer): Buffer => {
const cipher = createDecipheriv(mode, key, iv) const cipher = createDecipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()]) return Buffer.concat([cipher.update(buffer), cipher.final()])
} }
export const wbdCrypto = { export const wbdCrypto = {
aesMode: 'aes-128-ecb', aesMode: 'aes-128-ecb',
aesKey: Buffer.from([112, 87, 39, 61, 199, 250, 41, 191, 57, 68, 45, 114, 221, 94, 140, 228], 'binary'), aesKey: Buffer.from([112, 87, 39, 61, 199, 250, 41, 191, 57, 68, 45, 114, 221, 94, 140, 228] as any, 'binary'),
aesIv: '', aesIv: '',
appId: 'y67sprxhhpws', appId: 'y67sprxhhpws',
decodeData(base64Result) { decodeData(base64Result: string): any {
const data = Buffer.from(decodeURIComponent(base64Result), 'base64') const data = Buffer.from(decodeURIComponent(base64Result), 'base64')
return JSON.parse(createAesDecrypt(data, this.aesMode, this.aesKey, this.aesIv).toString()) return JSON.parse(createAesDecrypt(data, this.aesMode, this.aesKey, this.aesIv).toString())
}, },
createSign(data, time) { createSign(data: string, time: number): string {
const str = `${this.appId}${data}${time}` const str = `${this.appId}${data}${time}`
return toMD5(str).toUpperCase() return toMD5(str).toUpperCase()
}, },
buildParam(jsonData) { buildParam(jsonData: unknown): string {
const data = Buffer.from(JSON.stringify(jsonData)) const data = Buffer.from(JSON.stringify(jsonData))
const time = Date.now() const time = Date.now()
File renamed without changes.
@@ -1,11 +1,12 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import getSongId from './songId.js' import getSongId from './songId.ts'
import { dateFormat2 } from '../index.js' import { dateFormat2 } from '../index.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
_requestObj2: null, _requestObj2: null as HttpRequestObject | null,
_requestObj3: null, _requestObj3: null as HttpRequestObject | null,
lastCommentIds: new Map(), lastCommentIds: new Map(),
async getComment(musicInfo, page = 1, limit = 20) { async getComment(musicInfo, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
+10 -10
View File
@@ -1,12 +1,12 @@
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import songList from './songList.js' import songList from './songList.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import pic from './pic.js' import pic from './pic.ts'
import lyric from './lyric.js' import lyric from './lyric.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
import comment from './comment.js' import comment from './comment.ts'
// import tipSearch from './tipSearch.js' // import tipSearch from './tipSearch.ts'
const mg = { const mg = {
// tipSearch, // tipSearch,
@@ -16,7 +16,7 @@ const mg = {
hotSearch, hotSearch,
comment, comment,
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('mg').getMusicUrl(songInfo, type) return apis('mg').getMusicUrl!(songInfo, type)
}, },
getLyric(songInfo) { getLyric(songInfo) {
return lyric.getLyric(songInfo) return lyric.getLyric(songInfo)
@@ -1,5 +1,6 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { filterMusicInfoList } from './musicInfo.js' import { filterMusicInfoList } from './musicInfo.ts'
import type { Source, HttpRequestObject } from '../types.ts'
// const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }] // const boardList = [{ id: 'mg__27553319', name: '咪咕尖叫新歌榜', bangid: '27553319' }, { id: 'mg__27186466', name: '咪咕尖叫热歌榜', bangid: '27186466' }, { id: 'mg__27553408', name: '咪咕尖叫原创榜', bangid: '27553408' }, { id: 'mg__23189800', name: '咪咕港台榜', bangid: '23189800' }, { id: 'mg__23189399', name: '咪咕内地榜', bangid: '23189399' }, { id: 'mg__19190036', name: '咪咕欧美榜', bangid: '19190036' }, { id: 'mg__23189813', name: '咪咕日韩榜', bangid: '23189813' }, { id: 'mg__23190126', name: '咪咕彩铃榜', bangid: '23190126' }, { id: 'mg__15140045', name: '咪咕KTV榜', bangid: '15140045' }, { id: 'mg__15140034', name: '咪咕网络榜', bangid: '15140034' }, { id: 'mg__23217754', name: 'MV榜', bangid: '23217754' }, { id: 'mg__23218151', name: '新专辑榜', bangid: '23218151' }, { id: 'mg__21958042', name: 'iTunes榜', bangid: '21958042' }, { id: 'mg__21975570', name: 'billboard榜', bangid: '21975570' }, { id: 'mg__22272815', name: '台湾Hito中文榜', bangid: '22272815' }, { id: 'mg__22272904', name: '中国TOP排行榜', bangid: '22272904' }, { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943' }, { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437' }]
@@ -20,7 +21,15 @@ import { filterMusicInfoList } from './musicInfo.js'
// // { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' }, // // { id: 'mg__22272943', name: '韩国Melon榜', bangid: '22272943', webId: 'mnet' },
// // { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' }, // // { id: 'mg__22273437', name: '英国UK榜', bangid: '22273437', webId: 'uk' },
// ] // ]
const boardList = [ interface BoardItem {
id: string
name: string
bangid: string
source: Source
// 旧数据字段,部分条目缺失(缺失时 getDetailPageUrl 拼出 undefined,与原实现一致)
webId?: string
}
const boardList: BoardItem[] = [
{ {
id: 'mg__27553319', id: 'mg__27553319',
name: '新歌榜', name: '新歌榜',
@@ -141,9 +150,9 @@ export default {
// return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}` // return `http://m.music.migu.cn/migu/remoting/cms_list_tag?nid=${id}&pageSize=${this.limit}&pageNo=${page - 1}`
}, },
successCode: '000000', successCode: '000000',
requestBoardsObj: null, requestBoardsObj: null as HttpRequestObject | null,
getBoardsData() { getBoardsData() {
if (this.requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this.requestBoardsObj) (this as any)._requestBoardsObj.cancelHttp()
this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/pc/bmw/rank/rank-index/v1.0', { this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/pc/bmw/rank/rank-index/v1.0', {
// this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/rank-list/release', { // this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM3.0/v1.0/template/rank-list/release', {
// this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM2.0/v2.0/content/indexrank.do?templateVersion=8', { // this.requestBoardsObj = httpFetch('https://app.c.nf.migu.cn/MIGUM2.0/v2.0/content/indexrank.do?templateVersion=8', {
@@ -153,7 +162,7 @@ export default {
channel: '0146921', channel: '0146921',
}, },
}) })
return this.requestBoardsObj.promise return this.requestBoardsObj!.promise
}, },
getData(url) { getData(url) {
const requestObj = httpFetch(url) const requestObj = httpFetch(url)
@@ -1,6 +1,6 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { getMusicInfo } from './musicInfo.js' import { getMusicInfo } from './musicInfo.ts'
import { decrypt } from './utils/mrc.js' import { decrypt } from './utils/mrc.ts'
const mrcTools = { const mrcTools = {
rxps: { rxps: {
@@ -11,8 +11,8 @@ const mrcTools = {
parseLyric(str) { parseLyric(str) {
str = str.replace(/\r/g, '') str = str.replace(/\r/g, '')
const lines = str.split('\n') const lines = str.split('\n')
const lxlrcLines = [] const lxlrcLines: string[] = []
const lrcLines = [] const lrcLines: string[] = []
for (const line of lines) { for (const line of lines) {
if (line.length < 6) continue if (line.length < 6) continue
@@ -20,12 +20,12 @@ const mrcTools = {
if (!result) continue if (!result) continue
const startTime = parseInt(result[1]) const startTime = parseInt(result[1])
let time = startTime let time: any = startTime
let ms = time % 1000 let ms = time % 1000
time /= 1000 time /= 1000
let m = parseInt(time / 60).toString().padStart(2, '0') let m = parseInt((time / 60) as unknown as string).toString().padStart(2, '0')
time %= 60 time %= 60
let s = parseInt(time).toString().padStart(2, '0') let s = parseInt(time as unknown as string).toString().padStart(2, '0')
time = `${m}:${s}.${ms}` time = `${m}:${s}.${ms}`
let words = line.replace(this.rxps.lineTime, '') let words = line.replace(this.rxps.lineTime, '')
@@ -36,7 +36,7 @@ const mrcTools = {
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+)\)/.exec(time) const result = /\((\d+),(\d+)\)/.exec(time)
return `<${parseInt(result[1]) - startTime},${result[2]}>` return `<${parseInt(result![1]) - startTime},${result![2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('') const newWords = times.map((time, index) => `${time}${wordArr[index]}`).join('')
@@ -1,10 +1,11 @@
import { sizeFormate, formatPlayTime } from '../index.js' import { sizeFormate, formatPlayTime } from '../index.ts'
import { createHttpFetch } from './utils/index.js' import { createHttpFetch } from './utils/index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import type { SongInfo, SongType, QualityInfo, Quality } from '../types.ts'
const createGetMusicInfosTask = (ids) => { const createGetMusicInfosTask = (ids: Array<string | undefined>) => {
let list = ids let list = ids
let tasks = [] const tasks: Array<Array<string | undefined>> = []
while (list.length) { while (list.length) {
tasks.push(list.slice(0, 100)) tasks.push(list.slice(0, 100))
if (list.length < 100) break if (list.length < 100) break
@@ -19,15 +20,15 @@ const createGetMusicInfosTask = (ids) => {
}).then(data => data.resource))) }).then(data => data.resource)))
} }
export const filterMusicInfoList = (rawList) => { export const filterMusicInfoList = (rawList: any[]): SongInfo[] => {
// console.log(rawList) // console.log(rawList)
let ids = new Set() let ids = new Set()
const list = [] const list: any[] = []
rawList.forEach(item => { rawList.forEach(item => {
if (!item.songId || ids.has(item.songId)) return if (!item.songId || ids.has(item.songId)) return
ids.add(item.songId) ids.add(item.songId)
const types = [] const types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
item.newRateFormats?.forEach(type => { item.newRateFormats?.forEach(type => {
let size let size
switch (type.formatType) { switch (type.formatType) {
@@ -90,12 +91,12 @@ export const filterMusicInfoList = (rawList) => {
export const filterMusicInfoListV5 = (rawList) => { export const filterMusicInfoListV5 = (rawList) => {
// console.log(rawList) // console.log(rawList)
let ids = new Set() let ids = new Set()
const list = [] const list: any[] = []
rawList.forEach(item => { rawList.forEach(item => {
if (!item.songId || ids.has(item.songId)) return if (!item.songId || ids.has(item.songId)) return
ids.add(item.songId) ids.add(item.songId)
const types = [] const types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
item.audioFormats?.forEach(type => { item.audioFormats?.forEach(type => {
let size let size
switch (type.formatType) { switch (type.formatType) {
@@ -153,10 +154,10 @@ export const filterMusicInfoListV5 = (rawList) => {
return list return list
} }
export const getMusicInfo = async(copyrightId) => { export const getMusicInfo = async(copyrightId?: string): Promise<SongInfo | undefined> => {
return getMusicInfos([copyrightId]).then(data => data[0]) return getMusicInfos([copyrightId]).then(data => data[0])
} }
export const getMusicInfos = async(copyrightIds) => { export const getMusicInfos = async(copyrightIds: Array<string | undefined>): Promise<SongInfo[]> => {
return filterMusicInfoList((await createGetMusicInfosTask(copyrightIds)).flat()) return filterMusicInfoList((await createGetMusicInfosTask(copyrightIds)).flat())
} }
@@ -1,8 +1,9 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { sizeFormate, formatPlayTime } from '../index.js' import { sizeFormate, formatPlayTime } from '../index.ts'
import { toMD5, formatSingerName } from '../utils.js' import { toMD5, formatSingerName } from '../utils.ts'
import type { SongType, QualityInfo, Quality } from '../types.ts'
export const createSignature = (time, str) => { export const createSignature = (time: string, str: string): { sign: string, deviceId: string } => {
const deviceId = '963B7AA0D21511ED807EE5846EC87D20' const deviceId = '963B7AA0D21511ED807EE5846EC87D20'
const signatureMd5 = '6cdc72a439cef99a3418d2a78aa28c73' const signatureMd5 = '6cdc72a439cef99a3418d2a78aa28c73'
const sign = toMD5(`${str}${signatureMd5}yyapp2d16148780a1dcc7408e06336b98cfd50${deviceId}${time}`) const sign = toMD5(`${str}${signatureMd5}yyapp2d16148780a1dcc7408e06336b98cfd50${deviceId}${time}`)
@@ -126,7 +127,7 @@ export default {
}, },
filterData(rawData) { filterData(rawData) {
// console.log(rawData) // console.log(rawData)
const list = [] const list: any[] = []
const ids = new Set() const ids = new Set()
rawData.forEach(item => { rawData.forEach(item => {
@@ -134,8 +135,8 @@ export default {
if (!data.songId || !data.copyrightId || ids.has(data.copyrightId)) return if (!data.songId || !data.copyrightId || ids.has(data.copyrightId)) return
ids.add(data.copyrightId) ids.add(data.copyrightId)
const types = [] const types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
data.audioFormats && data.audioFormats.forEach(type => { data.audioFormats && data.audioFormats.forEach(type => {
let size let size
switch (type.formatType) { switch (type.formatType) {
+3 -3
View File
@@ -1,5 +1,5 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import getSongId from './songId.js' import getSongId from './songId.ts'
export default { export default {
async getPicUrl(songId, tryNum = 0) { async getPicUrl(songId, tryNum = 0) {
@@ -11,7 +11,7 @@ export default {
requestObj.promise.then(({ body }) => { requestObj.promise.then(({ body }) => {
if (body.returnCode !== '000000') { if (body.returnCode !== '000000') {
if (tryNum > 5) return Promise.reject(new Error('图片获取失败')) if (tryNum > 5) return Promise.reject(new Error('图片获取失败'))
let tryRequestObj = this.getPic(songId, ++tryNum) let tryRequestObj = (this as any).getPic(songId, ++tryNum)
requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj) requestObj.cancelHttp = tryRequestObj.cancelHttp.bind(tryRequestObj)
return tryRequestObj.promise return tryRequestObj.promise
} }
@@ -1,10 +1,11 @@
// import { httpFetch } from '../request.js' // import { httpFetch } from '../request.js'
import { getMusicInfo } from './musicInfo.js' import { getMusicInfo } from './musicInfo.ts'
import type { SongInfo } from '../types.ts'
const getSongId = async(mInfo) => { const getSongId = async(mInfo: SongInfo): Promise<string | undefined> => {
if (mInfo.songmid != mInfo.copyrightId) return mInfo.songmid if (mInfo.songmid != mInfo.copyrightId) return mInfo.songmid
const musicInfo = await getMusicInfo(mInfo.copyrightId) const musicInfo = await getMusicInfo(mInfo.copyrightId)
return musicInfo.songmid return musicInfo!.songmid
} }
@@ -1,14 +1,15 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayCount } from '../index.js' import { formatPlayCount } from '../index.ts'
import { filterMusicInfoListV5 } from './musicInfo.js' import { filterMusicInfoListV5 } from './musicInfo.ts'
import { createSignature } from './musicSearch.js' import { createSignature } from './musicSearch.ts'
import { createHttpFetch } from './utils/index.js' import { createHttpFetch } from './utils/index.ts'
import type { HttpRequestObject } from '../types.ts'
// const tagData = { code: '000000', info: 'SUCCESS', columnInfo: { columnTitle: '分类', columnId: '15244430', columnPid: '15031270', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 6, columnStatus: 1, columnCreateTime: '2016-11-10 10:53:05.077', columntype: 2011, contents: [{ contentId: '18464615', relationType: 2011, objectInfo: { columnTitle: '热门', columnId: '18464615', columnPid: '15244430', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 8, columnStatus: 1, columnCreateTime: '2017-02-20 16:09:13.400', columntype: 2011, contents: [{ contentId: '1000001672', relationType: 4034, objectInfo: { tagId: '1000001672', tagName: '流行', resourceType: '2034' }, relationSort: 9 }, { contentId: '1003449727', relationType: 4034, objectInfo: { tagId: '1003449727', tagName: '厂牌', resourceType: '2034' }, relationSort: 8 }, { contentId: '1000001795', relationType: 4034, objectInfo: { tagId: '1000001795', tagName: '伤感', resourceType: '2034' }, relationSort: 7 }, { contentId: '1001076080', relationType: 4034, objectInfo: { tagId: '1001076080', tagName: '电影', resourceType: '2034' }, relationSort: 6 }, { contentId: '1000001675', relationType: 4034, objectInfo: { tagId: '1000001675', tagName: '中国风', resourceType: '2034' }, relationSort: 5 }, { contentId: '1000001635', relationType: 4034, objectInfo: { tagId: '1000001635', tagName: '经典老歌', resourceType: '2034' }, relationSort: 4 }, { contentId: '1000001831', relationType: 4034, objectInfo: { tagId: '1000001831', tagName: '翻唱', resourceType: '2034' }, relationSort: 3 }, { contentId: '1000001762', relationType: 4034, objectInfo: { tagId: '1000001762', tagName: '国语', resourceType: '2034' }, relationSort: 1 }], dataVersion: '1620410266029', customizedPicUrls: [] }, relationSort: 6 }, { contentId: '15244503', relationType: 2011, objectInfo: { columnTitle: '主题', columnId: '15244503', columnPid: '15244430', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 23, columnStatus: 1, columnCreateTime: '2016-11-10 10:54:10.261', columntype: 2011, contents: [{ contentId: '1003449727', relationType: 4034, objectInfo: { tagId: '1003449727', tagName: '厂牌', resourceType: '2034' }, relationSort: 29 }, { contentId: '1001076080', relationType: 4034, objectInfo: { tagId: '1001076080', tagName: '电影', resourceType: '2034' }, relationSort: 28 }, { contentId: '1001076078', relationType: 4034, objectInfo: { tagId: '1001076078', tagName: '电视剧', resourceType: '2034' }, relationSort: 27 }, { contentId: '1001076083', relationType: 4034, objectInfo: { tagId: '1001076083', tagName: '综艺', resourceType: '2034' }, relationSort: 26 }, { contentId: '1000001827', relationType: 4034, objectInfo: { tagId: '1000001827', tagName: 'KTV', resourceType: '2034' }, relationSort: 23 }, { contentId: '1000001698', relationType: 4034, objectInfo: { tagId: '1000001698', tagName: '爱情', resourceType: '2034' }, relationSort: 22 }, { contentId: '1000001635', relationType: 4034, objectInfo: { tagId: '1000001635', tagName: '经典老歌', resourceType: '2034' }, relationSort: 21 }, { contentId: '1001076096', relationType: 4034, objectInfo: { tagId: '1001076096', tagName: '网络热歌', resourceType: '2034' }, relationSort: 20 }, { contentId: '1000001780', relationType: 4034, objectInfo: { tagId: '1000001780', tagName: '儿童歌曲', resourceType: '2034' }, relationSort: 19 }, { contentId: '1000587702', relationType: 4034, objectInfo: { tagId: '1000587702', tagName: '广场舞', resourceType: '2034' }, relationSort: 18 }, { contentId: '1000587717', relationType: 4034, objectInfo: { tagId: '1000587717', tagName: '70后', resourceType: '2034' }, relationSort: 17 }, { contentId: '1000587718', relationType: 4034, objectInfo: { tagId: '1000587718', t Line truncated // const tagData = { code: '000000', info: 'SUCCESS', columnInfo: { columnTitle: '分类', columnId: '15244430', columnPid: '15031270', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 6, columnStatus: 1, columnCreateTime: '2016-11-10 10:53:05.077', columntype: 2011, contents: [{ contentId: '18464615', relationType: 2011, objectInfo: { columnTitle: '热门', columnId: '18464615', columnPid: '15244430', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 8, columnStatus: 1, columnCreateTime: '2017-02-20 16:09:13.400', columntype: 2011, contents: [{ contentId: '1000001672', relationType: 4034, objectInfo: { tagId: '1000001672', tagName: '流行', resourceType: '2034' }, relationSort: 9 }, { contentId: '1003449727', relationType: 4034, objectInfo: { tagId: '1003449727', tagName: '厂牌', resourceType: '2034' }, relationSort: 8 }, { contentId: '1000001795', relationType: 4034, objectInfo: { tagId: '1000001795', tagName: '伤感', resourceType: '2034' }, relationSort: 7 }, { contentId: '1001076080', relationType: 4034, objectInfo: { tagId: '1001076080', tagName: '电影', resourceType: '2034' }, relationSort: 6 }, { contentId: '1000001675', relationType: 4034, objectInfo: { tagId: '1000001675', tagName: '中国风', resourceType: '2034' }, relationSort: 5 }, { contentId: '1000001635', relationType: 4034, objectInfo: { tagId: '1000001635', tagName: '经典老歌', resourceType: '2034' }, relationSort: 4 }, { contentId: '1000001831', relationType: 4034, objectInfo: { tagId: '1000001831', tagName: '翻唱', resourceType: '2034' }, relationSort: 3 }, { contentId: '1000001762', relationType: 4034, objectInfo: { tagId: '1000001762', tagName: '国语', resourceType: '2034' }, relationSort: 1 }], dataVersion: '1620410266029', customizedPicUrls: [] }, relationSort: 6 }, { contentId: '15244503', relationType: 2011, objectInfo: { columnTitle: '主题', columnId: '15244503', columnPid: '15244430', opNumItem: { playNum: 0, playNumDesc: '0', keepNum: 0, keepNumDesc: '0', commentNum: 0, commentNumDesc: '0', shareNum: 0, shareNumDesc: '0', orderNumByWeek: 0, orderNumByWeekDesc: '0', orderNumByTotal: 0, orderNumByTotalDesc: '0', thumbNum: 0, thumbNumDesc: '0', followNum: 0, followNumDesc: '0', subscribeNum: 0, subscribeNumDesc: '0', livePlayNum: 0, livePlayNumDesc: '0', popularNum: 0, popularNumDesc: '0', bookingNum: 0, bookingNumDesc: '0' }, contentsCount: 23, columnStatus: 1, columnCreateTime: '2016-11-10 10:54:10.261', columntype: 2011, contents: [{ contentId: '1003449727', relationType: 4034, objectInfo: { tagId: '1003449727', tagName: '厂牌', resourceType: '2034' }, relationSort: 29 }, { contentId: '1001076080', relationType: 4034, objectInfo: { tagId: '1001076080', tagName: '电影', resourceType: '2034' }, relationSort: 28 }, { contentId: '1001076078', relationType: 4034, objectInfo: { tagId: '1001076078', tagName: '电视剧', resourceType: '2034' }, relationSort: 27 }, { contentId: '1001076083', relationType: 4034, objectInfo: { tagId: '1001076083', tagName: '综艺', resourceType: '2034' }, relationSort: 26 }, { contentId: '1000001827', relationType: 4034, objectInfo: { tagId: '1000001827', tagName: 'KTV', resourceType: '2034' }, relationSort: 23 }, { contentId: '1000001698', relationType: 4034, objectInfo: { tagId: '1000001698', tagName: '爱情', resourceType: '2034' }, relationSort: 22 }, { contentId: '1000001635', relationType: 4034, objectInfo: { tagId: '1000001635', tagName: '经典老歌', resourceType: '2034' }, relationSort: 21 }, { contentId: '1001076096', relationType: 4034, objectInfo: { tagId: '1001076096', tagName: '网络热歌', resourceType: '2034' }, relationSort: 20 }, { contentId: '1000001780', relationType: 4034, objectInfo: { tagId: '1000001780', tagName: '儿童歌曲', resourceType: '2034' }, relationSort: 19 }, { contentId: '1000587702', relationType: 4034, objectInfo: { tagId: '1000587702', tagName: '广场舞', resourceType: '2034' }, relationSort: 18 }, { contentId: '1000587717', relationType: 4034, objectInfo: { tagId: '1000587717', tagName: '70后', resourceType: '2034' }, relationSort: 17 }, { contentId: '1000587718', relationType: 4034, objectInfo: { tagId: '1000587718', t Line truncated
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
limit_list: 30, limit_list: 30,
limit_song: 50, limit_song: 50,
successCode: '000000', successCode: '000000',
@@ -200,7 +201,7 @@ export default {
// source: 'mg', // source: 'mg',
// } // }
// }) // })
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
// console.log(body) // console.log(body)
// if (body.retCode !== '000000') return this.getList(sortId, tagId, page, ++tryNum) // if (body.retCode !== '000000') return this.getList(sortId, tagId, page, ++tryNum)
if (body.code !== '000000') return this.getList(sortId, tagId, page, ++tryNum) if (body.code !== '000000') return this.getList(sortId, tagId, page, ++tryNum)
@@ -214,7 +215,7 @@ export default {
} }
}) })
}, },
filterList2(listData, list = [], ids = new Set()) { filterList2(listData, list: any[] = [], ids = new Set()) {
for (const item of listData) { for (const item of listData) {
if (item.contents) this.filterList2(item.contents, list, ids) if (item.contents) this.filterList2(item.contents, list, ids)
else if (item.resType == '2021' && !ids.has(item.resId)) { else if (item.resType == '2021' && !ids.has(item.resId)) {
@@ -255,7 +256,7 @@ export default {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp() if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.tagsUrl, { headers: this.defaultHeaders }) this._requestObj_tags = httpFetch(this.tagsUrl, { headers: this.defaultHeaders })
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTag(++tryNum) if (body.code !== this.successCode) return this.getTag(++tryNum)
// console.log(body) // console.log(body)
return this.filterTagInfo(body.data) return this.filterTagInfo(body.data)
@@ -314,7 +315,7 @@ export default {
}, },
filterSongListResult(raw) { filterSongListResult(raw) {
const list = [] const list: any[] = []
raw.forEach(item => { raw.forEach(item => {
if (!item.id) return if (!item.id) return
@@ -1,7 +1,7 @@
import { createHttpFetch } from './utils/index.js' import { createHttpFetch } from './utils/index.ts'
export default { export default {
requestObj: null, requestObj: null as any,
cancelTipSearch() { cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp() if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
}, },
@@ -1,4 +1,5 @@
import { httpFetch } from '../../request.js' import { httpFetch } from '../../request.ts'
import type { HttpOptions } from '../../types.ts'
/** /**
* 创建一个适用于MG的Http请求 * 创建一个适用于MG的Http请求
@@ -6,7 +7,7 @@ import { httpFetch } from '../../request.js'
* @param {*} options * @param {*} options
* @param {*} retryNum * @param {*} retryNum
*/ */
export const createHttpFetch = async(url, options, retryNum = 0) => { export const createHttpFetch = async(url: string, options?: HttpOptions, retryNum = 0): Promise<any> => {
if (retryNum > 2) throw new Error('try max num') if (retryNum > 2) throw new Error('try max num')
let result let result
try { try {
@@ -16,7 +16,7 @@ const keyArr = [
] ]
const teaDecrypt = (data, key) => { const teaDecrypt = (data: bigint[], key: bigint[]): bigint[] => {
const length = data.length const length = data.length
const lengthBitint = BigInt(length) const lengthBitint = BigInt(length)
if (length >= 1) { if (length >= 1) {
@@ -31,14 +31,14 @@ const teaDecrypt = (data, key) => {
while (true) { while (true) {
j6-- j6--
if (j6 > 0n) { if (j6 > 0n) {
let j7 = data[(j6 - 1n)] let j7 = data[(j6 - 1n) as unknown as number]
let i = j6 let i = j6 as unknown as number
j2 = toLong(data[i] - (toLong(toLong(j2 ^ j4) + toLong(j7 ^ key[toLong(toLong(3n & j6) ^ j5)])) ^ toLong(toLong(toLong(j7 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j7 << 4n))))) j2 = toLong(data[i] - (toLong(toLong(j2 ^ j4) + toLong(j7 ^ key[toLong(toLong(3n & j6) ^ j5) as unknown as number])) ^ toLong(toLong(toLong(j7 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j7 << 4n)))))
data[i] = j2 data[i] = j2
} else break } else break
} }
let j8 = data[lengthBitint - 1n] let j8 = data[lengthBitint - 1n as unknown as number]
j2 = toLong(data[0n] - toLong(toLong(toLong(key[toLong(toLong(j6 & 3n) ^ j5)] ^ j8) + toLong(j2 ^ j4)) ^ toLong(toLong(toLong(j8 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j8 << 4n))))) j2 = toLong(data[0n as unknown as number] - toLong(toLong(toLong(key[toLong(toLong(j6 & 3n) ^ j5) as unknown as number] ^ j8) + toLong(j2 ^ j4)) ^ toLong(toLong(toLong(j8 >> 5n) ^ toLong(j2 << 2n)) + toLong(toLong(j2 >> 3n) ^ toLong(j8 << 4n)))))
data[0] = j2 data[0] = j2
j3 = toLong(j4 - DELTA) j3 = toLong(j4 - DELTA)
} }
@@ -46,26 +46,26 @@ const teaDecrypt = (data, key) => {
return data return data
} }
const longArrToString = (data) => { const longArrToString = (data: bigint[]): string => {
const arrayList = [] const arrayList: string[] = []
for (const j of data) arrayList.push(longToBytes(j).toString('utf16le')) for (const j of data) arrayList.push(longToBytes(j).toString('utf16le'))
return arrayList.join('') return arrayList.join('')
} }
// https://stackoverflow.com/a/29132118 // https://stackoverflow.com/a/29132118
const longToBytes = (l) => { const longToBytes = (l: bigint): Buffer => {
const result = Buffer.alloc(8) const result = Buffer.alloc(8)
for (let i = 0; i < 8; i++) { for (let i = 0; i < 8; i++) {
result[i] = parseInt(l & 0xFFn) result[i] = parseInt((l & 0xFFn) as unknown as string)
l >>= 8n l >>= 8n
} }
return result return result
} }
const toBigintArray = (data) => { const toBigintArray = (data: string): bigint[] => {
const length = Math.floor(data.length / 16) const length = Math.floor(data.length / 16)
const jArr = Array(length) const jArr: bigint[] = Array(length)
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
jArr[i] = toLong(data.substring(i * 16, (i * 16) + 16)) jArr[i] = toLong(data.substring(i * 16, (i * 16) + 16))
} }
@@ -75,14 +75,14 @@ const toBigintArray = (data) => {
// https://github.com/lyswhut/lx-music-desktop/issues/445#issuecomment-1139338682 // https://github.com/lyswhut/lx-music-desktop/issues/445#issuecomment-1139338682
const MAX = 9223372036854775807n const MAX = 9223372036854775807n
const MIN = -9223372036854775808n const MIN = -9223372036854775808n
const toLong = str => { const toLong = (str: string | bigint): bigint => {
const num = typeof str == 'string' ? BigInt('0x' + str) : str const num = typeof str == 'string' ? BigInt('0x' + str) : str
if (num > MAX) return toLong(num - (1n << 64n)) if (num > MAX) return toLong(num - (1n << 64n))
else if (num < MIN) return toLong(num + (1n << 64n)) else if (num < MIN) return toLong(num + (1n << 64n))
return num return num
} }
export const decrypt = (data) => { export const decrypt = (data: string | null): string | null => {
// console.log(data.length) // console.log(data.length)
// -3551594764563790630 // -3551594764563790630
// console.log(toLongArrayFromArr(Buffer.from(key))) // console.log(toLongArrayFromArr(Buffer.from(key)))
+2 -2
View File
@@ -1,9 +1,9 @@
// SDK 公共请求常量
export const bHh = '624868746c' export const bHh = '624868746c'
export const headers = { export const headers: Record<string, string | string[]> = {
'User-Agent': 'lx-music request', 'User-Agent': 'lx-music request',
[bHh]: [bHh], [bHh]: [bHh],
} }
export const timeout = 15000 export const timeout = 15000
-4
View File
@@ -1,4 +0,0 @@
// SDK 根请求模块(转接至 src/common/request.js)
// musicSdk 内 `import { httpFetch } from '../request.js'` 的解析目标
export { httpFetch } from '../src/common/request.js'
+8
View File
@@ -0,0 +1,8 @@
// SDK 根请求模块(转接至 src/common/request.js)
// musicSdk 内 `import { ... } from '../request.js'` 的解析目标
import { httpFetch as rawHttpFetch } from '../src/common/request.js'
import type { HttpRequestObject, HttpOptions } from './types.ts'
export const httpFetch: (url: string, options?: HttpOptions) => HttpRequestObject =
rawHttpFetch as (url: string, options?: HttpOptions) => HttpRequestObject
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject } from '../types.ts'
import { dateFormat2 } from '../index.js' import { httpFetch } from '../request.ts'
import getMusicInfo from './musicInfo.js' import { dateFormat2 } from '../index.ts'
import getMusicInfo from './musicInfo.ts'
const emojis = { const emojis = {
e400846: '😘', e400846: '😘',
@@ -75,15 +76,16 @@ const songIdMap = new Map()
const promises = new Map() const promises = new Map()
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
_requestObj2: null, _requestObj2: null as HttpRequestObject | null,
async getSongId({ songId, songmid }) { async getSongId({ songId, songmid }) {
if (songId) return songId if (songId) return songId
if (songIdMap.has(songmid)) return songIdMap.get(songmid) if (songIdMap.has(songmid)) return songIdMap.get(songmid)
if (promises.has(songmid)) return (await promises.get(songmid)).songId if (promises.has(songmid)) return (await promises.get(songmid)).songId
const promise = getMusicInfo(songmid) const promise = getMusicInfo(songmid)
promises.set(promise) promises.set(songmid, promise)
const info = await promise const info = await promise
if (!info) throw new Error('获取歌曲信息失败')
songIdMap.set(songmid, info.songId) songIdMap.set(songmid, info.songId)
promises.delete(songmid) promises.delete(songmid)
return info.songId return info.songId
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject } from '../types.ts'
import { httpFetch } from '../request.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
@@ -1,11 +1,11 @@
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import lyric from './lyric.js' import lyric from './lyric.ts'
import songList from './songList.js' import songList from './songList.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
import comment from './comment.js' import comment from './comment.ts'
// import tipSearch from './tipSearch.js' // import tipSearch from './tipSearch.ts'
const tx = { const tx = {
// tipSearch, // tipSearch,
@@ -16,7 +16,7 @@ const tx = {
comment, comment,
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('tx').getMusicUrl(songInfo, type) return apis('tx')!.getMusicUrl(songInfo, type)
}, },
getLyric(songInfo) { getLyric(songInfo) {
// let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer // let singer = songInfo.singer.indexOf('、') > -1 ? songInfo.singer.split('、')[0] : songInfo.singer
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject, SongInfo, SongType, Quality, QualityInfo } from '../types.ts'
import { formatPlayTime, sizeFormate } from '../index.js' import { httpFetch } from '../request.ts'
import { formatSingerName } from '../utils.js' import { formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.ts'
let boardList = [{ id: 'tx__4', name: '流行指数榜', bangid: '4' }, { id: 'tx__26', name: '热歌榜', bangid: '26' }, { id: 'tx__27', name: '新歌榜', bangid: '27' }, { id: 'tx__62', name: '飙升榜', bangid: '62' }, { id: 'tx__58', name: '说唱榜', bangid: '58' }, { id: 'tx__57', name: '喜力电音榜', bangid: '57' }, { id: 'tx__28', name: '网络歌曲榜', bangid: '28' }, { id: 'tx__5', name: '内地榜', bangid: '5' }, { id: 'tx__3', name: '欧美榜', bangid: '3' }, { id: 'tx__59', name: '香港地区榜', bangid: '59' }, { id: 'tx__16', name: '韩国榜', bangid: '16' }, { id: 'tx__60', name: '抖快榜', bangid: '60' }, { id: 'tx__29', name: '影视金曲榜', bangid: '29' }, { id: 'tx__17', name: '日本榜', bangid: '17' }, { id: 'tx__52', name: '腾讯音乐人原创榜', bangid: '52' }, { id: 'tx__36', name: 'K歌金曲榜', bangid: '36' }, { id: 'tx__61', name: '台湾地区榜', bangid: '61' }, { id: 'tx__63', name: 'DJ舞曲榜', bangid: '63' }, { id: 'tx__64', name: '综艺新歌榜', bangid: '64' }, { id: 'tx__65', name: '国风热歌榜', bangid: '65' }, { id: 'tx__67', name: '听歌识曲榜', bangid: '67' }, { id: 'tx__72', name: '动漫音乐榜', bangid: '72' }, { id: 'tx__73', name: '游戏音乐榜', bangid: '73' }, { id: 'tx__75', name: '有声榜', bangid: '75' }, { id: 'tx__131', name: '校园音乐人排行榜', bangid: '131' }] let boardList = [{ id: 'tx__4', name: '流行指数榜', bangid: '4' }, { id: 'tx__26', name: '热歌榜', bangid: '26' }, { id: 'tx__27', name: '新歌榜', bangid: '27' }, { id: 'tx__62', name: '飙升榜', bangid: '62' }, { id: 'tx__58', name: '说唱榜', bangid: '58' }, { id: 'tx__57', name: '喜力电音榜', bangid: '57' }, { id: 'tx__28', name: '网络歌曲榜', bangid: '28' }, { id: 'tx__5', name: '内地榜', bangid: '5' }, { id: 'tx__3', name: '欧美榜', bangid: '3' }, { id: 'tx__59', name: '香港地区榜', bangid: '59' }, { id: 'tx__16', name: '韩国榜', bangid: '16' }, { id: 'tx__60', name: '抖快榜', bangid: '60' }, { id: 'tx__29', name: '影视金曲榜', bangid: '29' }, { id: 'tx__17', name: '日本榜', bangid: '17' }, { id: 'tx__52', name: '腾讯音乐人原创榜', bangid: '52' }, { id: 'tx__36', name: 'K歌金曲榜', bangid: '36' }, { id: 'tx__61', name: '台湾地区榜', bangid: '61' }, { id: 'tx__63', name: 'DJ舞曲榜', bangid: '63' }, { id: 'tx__64', name: '综艺新歌榜', bangid: '64' }, { id: 'tx__65', name: '国风热歌榜', bangid: '65' }, { id: 'tx__67', name: '听歌识曲榜', bangid: '67' }, { id: 'tx__72', name: '动漫音乐榜', bangid: '72' }, { id: 'tx__73', name: '游戏音乐榜', bangid: '73' }, { id: 'tx__75', name: '有声榜', bangid: '75' }, { id: 'tx__131', name: '校园音乐人排行榜', bangid: '131' }]
@@ -10,57 +11,57 @@ export default {
{ {
id: 'txlxzsb', id: 'txlxzsb',
name: '流行榜', name: '流行榜',
bangid: 4, bangid: '4',
}, },
{ {
id: 'txrgb', id: 'txrgb',
name: '热歌榜', name: '热歌榜',
bangid: 26, bangid: '26',
}, },
{ {
id: 'txwlhgb', id: 'txwlhgb',
name: '网络榜', name: '网络榜',
bangid: 28, bangid: '28',
}, },
{ {
id: 'txdyb', id: 'txdyb',
name: '抖音榜', name: '抖音榜',
bangid: 60, bangid: '60',
}, },
{ {
id: 'txndb', id: 'txndb',
name: '内地榜', name: '内地榜',
bangid: 5, bangid: '5',
}, },
{ {
id: 'txxgb', id: 'txxgb',
name: '香港榜', name: '香港榜',
bangid: 59, bangid: '59',
}, },
{ {
id: 'txtwb', id: 'txtwb',
name: '台湾榜', name: '台湾榜',
bangid: 61, bangid: '61',
}, },
{ {
id: 'txoumb', id: 'txoumb',
name: '欧美榜', name: '欧美榜',
bangid: 3, bangid: '3',
}, },
{ {
id: 'txhgb', id: 'txhgb',
name: '韩国榜', name: '韩国榜',
bangid: 16, bangid: '16',
}, },
{ {
id: 'txrbb', id: 'txrbb',
name: '日本榜', name: '日本榜',
bangid: 17, bangid: '17',
}, },
{ {
id: 'txtybb', id: 'txtybb',
name: 'YouTube榜', name: 'YouTube榜',
bangid: 128, bangid: '128',
}, },
], ],
listDetailRequest(id, period, limit) { listDetailRequest(id, period, limit) {
@@ -95,7 +96,7 @@ export default {
}, },
periods: {}, periods: {},
periodUrl: 'https://c.y.qq.com/node/pc/wk_v15/top.html', periodUrl: 'https://c.y.qq.com/node/pc/wk_v15/top.html',
_requestBoardsObj: null, _requestBoardsObj: null as HttpRequestObject | null,
getBoardsData() { getBoardsData() {
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
this._requestBoardsObj = httpFetch('https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg?g_tk=1928093487&inCharset=utf-8&outCharset=utf-8&notice=0&format=json&uin=0&needNewCode=1&platform=h5') this._requestBoardsObj = httpFetch('https://c.y.qq.com/v8/fcg-bin/fcg_myqq_toplist.fcg?g_tk=1928093487&inCharset=utf-8&outCharset=utf-8&notice=0&format=json&uin=0&needNewCode=1&platform=h5')
@@ -108,8 +109,8 @@ export default {
filterData(rawList) { filterData(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
if (item.file.size_128mp3 !== 0) { if (item.file.size_128mp3 !== 0) {
let size = sizeFormate(item.file.size_128mp3) let size = sizeFormate(item.file.size_128mp3)
types.push({ type: '128k', size }) types.push({ type: '128k', size })
@@ -180,7 +181,7 @@ export default {
}, },
filterBoardsData(rawList) { filterBoardsData(rawList) {
// console.log(rawList) // console.log(rawList)
let list = [] let list: any[] = []
for (const board of rawList) { for (const board of rawList) {
// 排除 MV榜 // 排除 MV榜
if (board.id == 201) continue if (board.id == 201) continue
+17 -15
View File
@@ -1,7 +1,8 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject } from '../types.ts'
import getMusicInfo from './musicInfo.js' import { httpFetch } from '../request.ts'
import { rendererInvoke } from '../../src/common/rendererIpc.js' import getMusicInfo from './musicInfo.ts'
import { WIN_MAIN_RENDERER_EVENT_NAME } from '../../src/common/ipcNames.js' import { rendererInvoke } from '../../src/common/rendererIpc.ts'
import { WIN_MAIN_RENDERER_EVENT_NAME } from '../../src/common/ipcNames.ts'
const songIdMap = new Map() const songIdMap = new Map()
const promises = new Map() const promises = new Map()
@@ -21,9 +22,9 @@ const parseTools = {
if (Number.isNaN(timeMs)) return '' if (Number.isNaN(timeMs)) return ''
let ms = timeMs % 1000 let ms = timeMs % 1000
timeMs /= 1000 timeMs /= 1000
let m = parseInt(timeMs / 60).toString().padStart(2, '0') let m = Math.floor(timeMs / 60).toString().padStart(2, '0')
timeMs %= 60 timeMs %= 60
let s = parseInt(timeMs).toString().padStart(2, '0') let s = Math.floor(timeMs).toString().padStart(2, '0')
return `[${m}:${s}.${String(ms).padStart(3, '0')}]` return `[${m}:${s}.${String(ms).padStart(3, '0')}]`
}, },
parseLyric(lrc) { parseLyric(lrc) {
@@ -32,8 +33,8 @@ const parseTools = {
if (!lrc) return { lyric: '', lxlyric: '' } if (!lrc) return { lyric: '', lxlyric: '' }
const lines = lrc.split('\n') const lines = lrc.split('\n')
const lxlrcLines = [] const lxlrcLines: string[] = []
const lrcLines = [] const lrcLines: string[] = []
for (let line of lines) { for (let line of lines) {
line = line.trim() line = line.trim()
@@ -61,7 +62,7 @@ const parseTools = {
let times = words.match(this.rxps.wordTimeAll) let times = words.match(this.rxps.wordTimeAll)
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+)\)/.exec(time) const result = /\((\d+),(\d+)\)/.exec(time)!
return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
@@ -79,7 +80,7 @@ const parseTools = {
if (!lrc) return { lyric: '', lxlyric: '' } if (!lrc) return { lyric: '', lxlyric: '' }
const lines = lrc.split('\n') const lines = lrc.split('\n')
const lrcLines = [] const lrcLines: string[] = []
for (let line of lines) { for (let line of lines) {
line = line.trim() line = line.trim()
@@ -112,8 +113,8 @@ const parseTools = {
// console.log(rlrc) // console.log(rlrc)
const rlrcLines = rlrc.split('\n') const rlrcLines = rlrc.split('\n')
let lrcLines = lrc.split('\n') let lrcLines = lrc.split('\n')
// let temp = [] // let temp: string[] = []
let newLrc = [] let newLrc: string[] = []
rlrcLines.forEach((line) => { rlrcLines.forEach((line) => {
const result = this.rxps.lineTime2.exec(line) const result = this.rxps.lineTime2.exec(line)
if (!result) return if (!result) return
@@ -142,8 +143,8 @@ const parseTools = {
// console.log(tlrc) // console.log(tlrc)
const tlrcLines = tlrc.split('\n') const tlrcLines = tlrc.split('\n')
let lrcLines = lrc.split('\n') let lrcLines = lrc.split('\n')
// let temp = [] // let temp: string[] = []
let newLrc = [] let newLrc: string[] = []
tlrcLines.forEach((line) => { tlrcLines.forEach((line) => {
const result = this.rxps.lineTime2.exec(line) const result = this.rxps.lineTime2.exec(line)
if (!result) return if (!result) return
@@ -203,8 +204,9 @@ export default {
if (songIdMap.has(songmid)) return songIdMap.get(songmid) if (songIdMap.has(songmid)) return songIdMap.get(songmid)
if (promises.has(songmid)) return (await promises.get(songmid)).songId if (promises.has(songmid)) return (await promises.get(songmid)).songId
const promise = getMusicInfo(songmid) const promise = getMusicInfo(songmid)
promises.set(promise) promises.set(songmid, promise)
const info = await promise const info = await promise
if (!info) throw new Error('获取歌曲信息失败')
songIdMap.set(songmid, info.songId) songIdMap.set(songmid, info.songId)
promises.delete(songmid) promises.delete(songmid)
return info.songId return info.songId
@@ -1,8 +1,9 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
import { formatPlayTime, sizeFormate } from '../index.js' import { httpFetch } from '../request.ts'
import { formatPlayTime, sizeFormate } from '../index.ts'
const getSinger = (singers) => { const getSinger = (singers) => {
let arr = [] let arr: string[] = []
singers.forEach(singer => { singers.forEach(singer => {
arr.push(singer.name) arr.push(singer.name)
}) })
@@ -37,8 +38,8 @@ export default (songmid) => {
const item = body.req.data.track_info const item = body.req.data.track_info
if (!item.file?.media_mid) return null if (!item.file?.media_mid) return null
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
const file = item.file const file = item.file
if (file.size_128mp3 != 0) { if (file.size_128mp3 != 0) {
let size = sizeFormate(file.size_128mp3) let size = sizeFormate(file.size_128mp3)
@@ -1,6 +1,7 @@
import { formatPlayTime, sizeFormate } from '../index.js' import { formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import { signRequest } from './utils/index.js' import { signRequest } from './utils/index.ts'
import type { SongInfo, SongType, Quality, QualityInfo } from '../types.ts'
export default { export default {
limit: 50, limit: 50,
@@ -81,12 +82,12 @@ export default {
handleResult(rawList) { handleResult(rawList) {
// console.log(rawList) // console.log(rawList)
if (!rawList || !Array.isArray(rawList)) return [] if (!rawList || !Array.isArray(rawList)) return []
const list = [] const list: SongInfo[] = []
rawList.forEach(item => { rawList.forEach(item => {
if (!item.file?.media_mid) return if (!item.file?.media_mid) return
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
const file = item.file const file = item.file
if (file.size_128mp3 != 0) { if (file.size_128mp3 != 0) {
let size = sizeFormate(file.size_128mp3) let size = sizeFormate(file.size_128mp3)
@@ -1,11 +1,12 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
import { httpFetch } from '../request.ts'
import { formatPlayTime, sizeFormate } from '../index.js' import { formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
export const filterMusicInfoItem = item => { export const filterMusicInfoItem = item => {
const types = [] const types: SongType[] = []
const _types = {} const _types: Partial<Record<Quality, QualityInfo>> = {}
if (item.file.size_128mp3 != 0) { if (item.file.size_128mp3 != 0) {
let size = sizeFormate(item.file.size_128mp3) let size = sizeFormate(item.file.size_128mp3)
types.push({ type: '128k', size }) types.push({ type: '128k', size })
@@ -65,7 +66,7 @@ export const filterMusicInfoItem = item => {
* @param {*} options * @param {*} options
* @param {*} retryNum * @param {*} retryNum
*/ */
const createMusicuFetch = async(data, options, retryNum = 0) => { const createMusicuFetch = async(data, options?, retryNum = 0) => {
if (retryNum > 2) throw new Error('try max num') if (retryNum > 2) throw new Error('try max num')
let result let result
@@ -1,11 +1,12 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject, SongType, Quality, QualityInfo } from '../types.ts'
import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.js' import { httpFetch } from '../request.ts'
import { formatSingerName } from '../utils.js' import { decodeName, formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
import { formatSingerName } from '../utils.ts'
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_hotTags: null, _requestObj_hotTags: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
limit_list: 36, limit_list: 36,
limit_song: 100000, limit_song: 100000,
successCode: 0, successCode: 0,
@@ -68,7 +69,7 @@ export default {
if (this._requestObj_tags) this._requestObj_tags.cancelHttp() if (this._requestObj_tags) this._requestObj_tags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_tags = httpFetch(this.tagsUrl) this._requestObj_tags = httpFetch(this.tagsUrl)
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getTag(++tryNum) if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body.tags.data.v_group) return this.filterTagInfo(body.tags.data.v_group)
}) })
@@ -78,14 +79,14 @@ export default {
if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp() if (this._requestObj_hotTags) this._requestObj_hotTags.cancelHttp()
if (tryNum > 2) return Promise.reject(new Error('try max num')) if (tryNum > 2) return Promise.reject(new Error('try max num'))
this._requestObj_hotTags = httpFetch(this.hotTagUrl) this._requestObj_hotTags = httpFetch(this.hotTagUrl)
return this._requestObj_hotTags.promise.then(({ statusCode, body }) => { return this._requestObj_hotTags!.promise.then(({ statusCode, body }) => {
if (statusCode !== 200) return this.getHotTag(++tryNum) if (statusCode !== 200) return this.getHotTag(++tryNum)
return this.filterInfoHotTag(body) return this.filterInfoHotTag(body)
}) })
}, },
filterInfoHotTag(html) { filterInfoHotTag(html) {
let hotTag = html.match(this.regExps.hotTagHtml) let hotTag = html.match(this.regExps.hotTagHtml)
const hotTags = [] const hotTags: any[] = []
if (!hotTag) return hotTags if (!hotTag) return hotTags
hotTag.forEach(tagHtml => { hotTag.forEach(tagHtml => {
@@ -120,7 +121,7 @@ export default {
this.getListUrl(sortId, tagId, page), this.getListUrl(sortId, tagId, page),
) )
// console.log(this.getListUrl(sortId, tagId, page)) // console.log(this.getListUrl(sortId, tagId, page))
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum) if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return tagId ? this.filterList2(body.playlist.data, page) : this.filterList(body.playlist.data, page) return tagId ? this.filterList2(body.playlist.data, page) : this.filterList(body.playlist.data, page)
}) })
@@ -226,8 +227,8 @@ export default {
filterListDetail(rawList) { filterListDetail(rawList) {
// console.log(rawList) // console.log(rawList)
return rawList.map(item => { return rawList.map(item => {
let types = [] let types: SongType[] = []
let _types = {} let _types: Partial<Record<Quality, QualityInfo>> = {}
if (item.file.size_128mp3 !== 0) { if (item.file.size_128mp3 !== 0) {
let size = sizeFormate(item.file.size_128mp3) let size = sizeFormate(item.file.size_128mp3)
types.push({ type: '128k', size }) types.push({ type: '128k', size })
@@ -1,10 +1,11 @@
import { httpFetch } from '../request.js' import type { HttpRequestObject } from '../types.ts'
import { httpFetch } from '../request.ts'
export default { export default {
// regExps: { // regExps: {
// relWord: /RELWORD=(.+)/, // relWord: /RELWORD=(.+)/,
// }, // },
requestObj: null, requestObj: null as HttpRequestObject | null,
tipSearch(str) { tipSearch(str) {
this.cancelTipSearch() this.cancelTipSearch()
this.requestObj = httpFetch(`https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?is_xml=0&format=json&key=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`, { this.requestObj = httpFetch(`https://c.y.qq.com/splcloud/fcgi-bin/smartbox_new.fcg?is_xml=0&format=json&key=${encodeURIComponent(str)}&loginUin=0&hostUin=0&format=json&inCharset=utf8&outCharset=utf-8&notice=0&platform=yqq&needNewCode=0`, {
File renamed without changes.
@@ -1,6 +1,7 @@
import type { HttpRequestObject } from '../../types.ts'
import { httpFetch } from '../../request.js' import { httpFetch } from '../../request.ts'
import { zzcSign } from './crypto.js' import { zzcSign } from './crypto.ts'
export const signRequest = async(data) => { export const signRequest = async(data) => {
// console.log(data) // console.log(data)
+146
View File
@@ -0,0 +1,146 @@
// SDK 共享类型定义
// 所有平台模块与根模块的类型锚点。类型只描述实际返回的数据形状,不改变运行时行为。
/** 音乐平台标识 */
export type Source = 'kw' | 'kg' | 'tx' | 'wy' | 'mg' | 'bd' | 'xm'
/** 音质标识(wav 仅部分接口声明使用) */
export type Quality = '128k' | '320k' | 'flac' | 'flac24bit' | 'wav'
/** 单音质资源描述(搜索结果 types 数组项) */
export interface SongType {
type: Quality
size: string
}
/** 单音质详情(搜索结果 _types 映射项) */
export interface QualityInfo {
size: string
}
/** 歌曲信息(搜索结果项 / 取流入参) */
export interface SongInfo {
songmid?: string
hash?: string
copyrightId?: string
songStringId?: string
albumId?: string
name?: string | null
singer?: string
source?: Source
interval?: string | number
albumName?: string
img?: string | null
lrc?: string | null
otherSource?: string | null
types?: SongType[]
_types?: Partial<Record<Quality, QualityInfo>>
typeUrl?: Partial<Record<Quality, string>>
[key: string]: unknown
}
/** httpFetch 响应(与 src/common/request.js 契约一致) */
export interface HttpResponse {
statusCode: number
statusMessage?: string
headers: Record<string, string | string[]>
body: any
raw?: Buffer
}
/** httpFetch 返回的可取消请求对象 */
export interface HttpRequestObject {
promise: Promise<HttpResponse>
cancelHttp(): void
}
/** httpFetch 选项(src/common/request.js 子集) */
export interface HttpOptions {
method?: string
headers?: Record<string, string>
body?: string | object
form?: Record<string, unknown>
timeout?: number
lookup?: unknown
family?: number
}
/** 带取消句柄的异步结果(getMusicUrl/getLyric 等的统一返回形态) */
export interface RequestObj<T> {
promise: Promise<T>
cancelHttp(): void
}
/** 取流结果 */
export interface MusicUrlResult {
type: string
url: string
}
/** 歌词结果 */
export interface LyricInfo {
lyric: string
tlyric: string
rlyric?: string
lxlyric?: string
}
/** 搜索结果 */
export interface SearchResult {
list: SongInfo[]
allPage: number
total: number
limit: number
source: Source
}
/** 搜索模块(平台 musicSearch 子模块) */
export interface MusicSearchModule {
search(str: string, page?: number, limit?: number, retryNum?: number): Promise<SearchResult>
[key: string]: unknown
}
/** 歌词模块 */
export interface LyricModule {
getLyric(songInfo: SongInfo, ...rest: unknown[]): Promise<LyricInfo> | RequestObj<LyricInfo>
[key: string]: unknown
}
/** 封面模块 */
export interface PicModule {
getPic(songInfo: SongInfo, ...rest: unknown[]): Promise<string> | RequestObj<string>
[key: string]: unknown
}
/** 热搜模块 */
export interface HotSearchModule {
getList(): Promise<any>
[key: string]: unknown
}
/** 评论模块 */
export interface CommentModule {
getComment(songInfo: any, page?: number, limit?: number): Promise<any>
getHotComment(songInfo: any, page?: number, limit?: number): Promise<any>
[key: string]: unknown
}
/** 平台模块(各平台默认导出需满足的最小形状)
* 注:SDK 内部实现大量返回 httpFetch 原生的 HttpRequestObject,
* 实际 promise 会在 .then() 中被改写为对应结果类型;此处只约束调用侧可见形态。
*/
export interface PlatformModule {
init?(): Promise<unknown> | void
musicSearch?: MusicSearchModule
leaderboard?: any
songList?: any
hotSearch?: HotSearchModule
comment?: CommentModule
tipSearch?: any
getMusicUrl?(songInfo: SongInfo, type: Quality): HttpRequestObject
getLyric?(songInfo: SongInfo, ...rest: unknown[]): HttpRequestObject
getPic?(songInfo: SongInfo, ...rest: unknown[]): HttpRequestObject | Promise<any>
getMusicInfo?(songInfo: SongInfo): HttpRequestObject | Promise<any>
getMusicDetailPageUrl?(songInfo: SongInfo): string
[key: string]: any
}
+23 -13
View File
@@ -2,47 +2,57 @@ import crypto from 'crypto'
import dns from 'dns' import dns from 'dns'
import { decodeName } from '../src/common/utils.js' import { decodeName } from '../src/common/utils.js'
export const toMD5 = str => crypto.createHash('md5').update(str).digest('hex') export const toMD5 = (str: string): string => crypto.createHash('md5').update(str).digest('hex')
interface CachedIp {
address: string
family: number
}
const ipMap = new Map() const ipMap = new Map<string, CachedIp | true>()
export const getHostIp = hostname => {
export const getHostIp = (hostname: string): CachedIp | undefined => {
const result = ipMap.get(hostname) const result = ipMap.get(hostname)
if (typeof result === 'object') return result if (typeof result === 'object') return result
if (result === true) return if (result === true) return
ipMap.set(hostname, true) ipMap.set(hostname, true)
// console.log(hostname)
dns.lookup(hostname, { dns.lookup(hostname, {
// family: 4, // family: 4,
all: false, all: false,
}, (err, address, family) => { }, (err, address, family) => {
if (err) return console.log(err) if (err) return console.log(err)
// console.log(address, family)
ipMap.set(hostname, { address, family }) ipMap.set(hostname, { address, family })
}) })
} }
export const dnsLookup = (hostname, options, callback) => { export const dnsLookup = (
hostname: string,
options: dns.LookupOptions,
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void,
): void => {
const result = getHostIp(hostname) const result = getHostIp(hostname)
if (result) return callback(null, result.address, result.family) if (result) return callback(null, result.address, result.family)
dns.lookup(hostname, options, callback) dns.lookup(hostname, options, callback as any)
} }
/** /**
* 格式化歌手 * 格式化歌手
* @param singers 歌手数组 * @param singers 歌手数组或字符串
* @param nameKey 歌手名键值 * @param nameKey 歌手名键值
* @param join 歌手分割字符 * @param join 歌手分割字符
*/ */
export const formatSingerName = (singers, nameKey = 'name', join = '、') => { export const formatSingerName = (
singers: Array<Record<string, unknown>> | string | null | undefined,
nameKey = 'name',
join = '、',
): string => {
if (Array.isArray(singers)) { if (Array.isArray(singers)) {
const singer = [] const singer: string[] = []
singers.forEach(item => { singers.forEach(item => {
let name = item[nameKey] const name = item[nameKey]
if (!name) return if (!name) return
singer.push(name) singer.push(String(name))
}) })
return decodeName(singer.join(join)) return decodeName(singer.join(join))
} }
@@ -1,6 +1,7 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { weapi } from './utils/crypto.js' import { weapi } from './utils/crypto.ts'
import { dateFormat2 } from '../index.js' import { dateFormat2 } from '../index.ts'
import type { HttpRequestObject } from '../types.ts'
const emojis = [ const emojis = [
['大笑', '😃'], ['大笑', '😃'],
@@ -65,13 +66,19 @@ const emojis = [
['叉', '❌'], ['叉', '❌'],
] ]
const applyEmoji = text => { const applyEmoji = (text: string): string => {
for (const e of emojis) text = text.replaceAll(`[${e[0]}]`, e[1]) for (const e of emojis) text = text.replaceAll(`[${e[0]}]`, e[1])
return text return text
} }
let cursorTools = { let cursorTools = {
cache: {}, cache: {} as Record<string, {
page?: number
prevCursor?: number
cursor?: number
orderType?: number
offset?: number
}>,
getCursor(id, page, limit) { getCursor(id, page, limit) {
let cacheData = this.cache[id] let cacheData = this.cache[id]
if (!cacheData) cacheData = this.cache[id] = {} if (!cacheData) cacheData = this.cache[id] = {}
@@ -115,8 +122,8 @@ let cursorTools = {
} }
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
_requestObj2: null, _requestObj2: null as HttpRequestObject | null,
async getComment({ songmid }, page = 1, limit = 20) { async getComment({ songmid }, page = 1, limit = 20) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
@@ -1,7 +1,8 @@
import { eapiRequest } from './utils/index.js' import { eapiRequest } from './utils/index.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj: null, _requestObj: null as HttpRequestObject | null,
async getList(retryNum = 0) { async getList(retryNum = 0) {
if (this._requestObj) this._requestObj.cancelHttp() if (this._requestObj) this._requestObj.cancelHttp()
if (retryNum > 2) return Promise.reject(new Error('try max num')) if (retryNum > 2) return Promise.reject(new Error('try max num'))
@@ -1,11 +1,11 @@
import leaderboard from './leaderboard.js' import leaderboard from './leaderboard.ts'
import { apis } from '../api-source.js' import { apis } from '../api-source.ts'
import getLyric from './lyric.js' import getLyric from './lyric.ts'
import getMusicInfo from './musicInfo.js' import getMusicInfo from './musicInfo.ts'
import musicSearch from './musicSearch.js' import musicSearch from './musicSearch.ts'
import songList from './songList.js' import songList from './songList.ts'
import hotSearch from './hotSearch.js' import hotSearch from './hotSearch.ts'
import comment from './comment.js' import comment from './comment.ts'
// import tipSearch from './tipSearch.js' // import tipSearch from './tipSearch.js'
const wy = { const wy = {
@@ -16,7 +16,7 @@ const wy = {
hotSearch, hotSearch,
comment, comment,
getMusicUrl(songInfo, type) { getMusicUrl(songInfo, type) {
return apis('wy').getMusicUrl(songInfo, type) return apis('wy').getMusicUrl!(songInfo, type)
}, },
getLyric(songInfo) { getLyric(songInfo) {
return getLyric(songInfo.songmid) return getLyric(songInfo.songmid)
@@ -1,6 +1,7 @@
import { weapi } from './utils/crypto.js' import { weapi } from './utils/crypto.ts'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import musicDetailApi from './musicDetail.js' import musicDetailApi from './musicDetail.ts'
import type { HttpRequestObject } from '../types.ts'
const topList = [{ id: 'wy__19723756', name: '飙升榜', bangid: '19723756' }, const topList = [{ id: 'wy__19723756', name: '飙升榜', bangid: '19723756' },
{ id: 'wy__3779629', name: '新歌榜', bangid: '3779629' }, { id: 'wy__3779629', name: '新歌榜', bangid: '3779629' },
@@ -106,14 +107,14 @@ export default {
regExps: { regExps: {
list: /<textarea id="song-list-pre-data" style="display:none;">(.+?)<\/textarea>/, list: /<textarea id="song-list-pre-data" style="display:none;">(.+?)<\/textarea>/,
}, },
_requestBoardsObj: null, _requestBoardsObj: null as HttpRequestObject | null,
getBoardsData() { getBoardsData() {
if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp() if (this._requestBoardsObj) this._requestBoardsObj.cancelHttp()
this._requestBoardsObj = httpFetch('https://music.163.com/weapi/toplist', { this._requestBoardsObj = httpFetch('https://music.163.com/weapi/toplist', {
method: 'post', method: 'post',
form: weapi({}), form: weapi({}),
}) })
return this._requestBoardsObj.promise return this._requestBoardsObj!.promise
}, },
getData(id) { getData(id) {
const requestBoardsDetailObj = httpFetch('https://music.163.com/weapi/v3/playlist/detail', { const requestBoardsDetailObj = httpFetch('https://music.163.com/weapi/v3/playlist/detail', {
@@ -129,7 +130,7 @@ export default {
filterBoardsData(rawList) { filterBoardsData(rawList) {
// console.log(rawList) // console.log(rawList)
let list = [] let list: any[] = []
for (const board of rawList) { for (const board of rawList) {
// 排除 MV榜 // 排除 MV榜
// if (board.id == 201) continue // if (board.id == 201) continue
@@ -171,7 +172,7 @@ export default {
let resp let resp
try { try {
resp = await this.getData(bangid) resp = await this.getData(bangid)
} catch (err) { } catch (err: any) {
if (err.message == 'try max num') { if (err.message == 'try max num') {
throw err throw err
} else { } else {
@@ -182,8 +183,8 @@ export default {
// console.log(resp.body) // console.log(resp.body)
let musicDetail let musicDetail
try { try {
musicDetail = await musicDetailApi.getList(resp.body.playlist.trackIds.map(trackId => trackId.id)) musicDetail = await (musicDetailApi as any).getList(resp.body.playlist.trackIds.map(trackId => trackId.id))
} catch (err) { } catch (err: any) {
console.log(err) console.log(err)
if (err.message == 'try max num') { if (err.message == 'try max num') {
throw err throw err
+15 -14
View File
@@ -1,5 +1,6 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { eapi } from './utils/crypto.js' import { eapi } from './utils/crypto.ts'
import type { LyricInfo, HttpRequestObject } from '../types.ts'
// import { decodeName } from '../...js' // import { decodeName } from '../...js'
// const parseLyric = (str, lrc) => { // const parseLyric = (str, lrc) => {
@@ -34,7 +35,7 @@ import { eapi } from './utils/crypto.js'
// return lxlyric.trim() // return lxlyric.trim()
// } // }
const eapiRequest = (url, data) => { const eapiRequest = (url: string, data: object) => {
return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', { return httpFetch('https://interface3.music.163.com/eapi/song/lyric/v1', {
method: 'post', method: 'post',
headers: { headers: {
@@ -65,14 +66,14 @@ const parseTools = {
if (Number.isNaN(timeMs)) return '' if (Number.isNaN(timeMs)) return ''
let ms = timeMs % 1000 let ms = timeMs % 1000
timeMs /= 1000 timeMs /= 1000
let m = parseInt(timeMs / 60).toString().padStart(2, '0') let m = parseInt((timeMs / 60) as any).toString().padStart(2, '0')
timeMs %= 60 timeMs %= 60
let s = parseInt(timeMs).toString().padStart(2, '0') let s = parseInt(timeMs as any).toString().padStart(2, '0')
return `[${m}:${s}.${ms}]` return `[${m}:${s}.${ms}]`
}, },
parseLyric(lines) { parseLyric(lines) {
const lxlrcLines = [] const lxlrcLines: string[] = []
const lrcLines = [] const lrcLines: string[] = []
for (let line of lines) { for (let line of lines) {
line = line.trim() line = line.trim()
@@ -97,7 +98,7 @@ const parseTools = {
if (!times) continue if (!times) continue
times = times.map(time => { times = times.map(time => {
const result = /\((\d+),(\d+),\d+\)/.exec(time) const result = /\((\d+),(\d+),\d+\)/.exec(time)
return `<${Math.max(parseInt(result[1]) - startMsTime, 0)},${result[2]}>` return `<${Math.max(parseInt(result![1]) - startMsTime, 0)},${result![2]}>`
}) })
const wordArr = words.split(this.rxps.wordTime) const wordArr = words.split(this.rxps.wordTime)
wordArr.shift() wordArr.shift()
@@ -137,8 +138,8 @@ const parseTools = {
let lrcLines = lrc.split('\n') let lrcLines = lrc.split('\n')
const targetlrcLines = targetlrc.split('\n') const targetlrcLines = targetlrc.split('\n')
const timeRxp = /^\[([\d:.]+)\]/ const timeRxp = /^\[([\d:.]+)\]/
let temp = [] let temp: string[] = []
let newLrc = [] let newLrc: string[] = []
targetlrcLines.forEach((line) => { targetlrcLines.forEach((line) => {
const result = timeRxp.exec(line) const result = timeRxp.exec(line)
if (!result) return if (!result) return
@@ -147,7 +148,7 @@ const parseTools = {
const t1 = this.getIntv(result[1]) const t1 = this.getIntv(result[1])
while (lrcLines.length) { while (lrcLines.length) {
const lrcLine = lrcLines.shift() const lrcLine = lrcLines.shift()!
const lrcLineResult = timeRxp.exec(lrcLine) const lrcLineResult = timeRxp.exec(lrcLine)
if (!lrcLineResult) continue if (!lrcLineResult) continue
const t2 = this.getIntv(lrcLineResult[1]) const t2 = this.getIntv(lrcLineResult[1])
@@ -248,7 +249,7 @@ const parseTools = {
// } // }
// https://github.com/lyswhut/lx-music-mobile/issues/370 // https://github.com/lyswhut/lx-music-mobile/issues/370
const fixTimeLabel = (lrc, tlrc, romalrc) => { const fixTimeLabel = (lrc: string, tlrc?: string, romalrc?: string) => {
if (lrc) { if (lrc) {
let newLrc = lrc.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') let newLrc = lrc.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]')
let newTlrc = tlrc?.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') ?? tlrc let newTlrc = tlrc?.replace(/\[(\d{2}:\d{2}):(\d{2})]/g, '[$1.$2]') ?? tlrc
@@ -263,7 +264,7 @@ const fixTimeLabel = (lrc, tlrc, romalrc) => {
} }
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/lyric_new.js // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/lyric_new.js
export default songmid => { export default (songmid: string): HttpRequestObject => {
const requestObj = eapiRequest('/api/song/lyric/v1', { const requestObj = eapiRequest('/api/song/lyric/v1', {
id: songmid, id: songmid,
cp: false, cp: false,
@@ -283,6 +284,6 @@ export default songmid => {
// console.log(info) // console.log(info)
if (!info.lyric) return Promise.reject(new Error('Get lyric failed')) if (!info.lyric) return Promise.reject(new Error('Get lyric failed'))
return info return info
}) }) as any
return requestObj return requestObj
} }
@@ -1,11 +1,11 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { weapi } from './utils/crypto.js' import { weapi } from './utils/crypto.ts'
import { formatPlayTime, sizeFormate } from '../index.js' import { formatPlayTime, sizeFormate } from '../index.ts'
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
export default { export default {
getSinger(singers) { getSinger(singers) {
let arr = [] let arr: any[] = []
singers?.forEach(singer => { singers?.forEach(singer => {
arr.push(singer.name) arr.push(singer.name)
}) })
@@ -13,10 +13,10 @@ export default {
}, },
filterList({ songs, privileges }) { filterList({ songs, privileges }) {
// console.log(songs, privileges) // console.log(songs, privileges)
const list = [] const list: any[] = []
songs.forEach((item, index) => { songs.forEach((item, index) => {
const types = [] const types: Array<{ type: string, size: string | null }> = []
const _types = {} const _types: Record<string, { size: string | null }> = {}
let size let size
let privilege = privileges[index] let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id) if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
@@ -1,8 +1,9 @@
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/song_detail.js
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { weapi } from './utils/crypto.js' import { weapi } from './utils/crypto.ts'
import type { RequestObj } from '../types.ts'
export default songmid => { export default (songmid: string): RequestObj<any> => {
const requestObj = httpFetch('https://music.163.com/weapi/v3/song/detail', { const requestObj = httpFetch('https://music.163.com/weapi/v3/song/detail', {
method: 'post', method: 'post',
headers: { headers: {
@@ -22,4 +23,3 @@ export default songmid => {
}) })
return requestObj return requestObj
} }
@@ -1,8 +1,8 @@
// import { httpFetch } from '../request.js' // import { httpFetch } from '../request.js'
// import { weapi } from './utils/crypto.js' // import { weapi } from './utils/crypto.js'
import { sizeFormate, formatPlayTime } from '../index.js' import { sizeFormate, formatPlayTime } from '../index.ts'
// import musicDetailApi from './musicDetail.js' // import musicDetailApi from './musicDetail.js'
import { eapiRequest } from './utils/index.js' import { eapiRequest } from './utils/index.ts'
export default { export default {
limit: 30, limit: 30,
@@ -29,7 +29,7 @@ export default {
return searchRequest.promise.then(({ body }) => body) return searchRequest.promise.then(({ body }) => body)
}, },
getSinger(singers) { getSinger(singers) {
let arr = [] let arr: any[] = []
singers.forEach(singer => { singers.forEach(singer => {
arr.push(singer.name) arr.push(singer.name)
}) })
@@ -40,8 +40,8 @@ export default {
if (!rawList) return [] if (!rawList) return []
return rawList.map(item => { return rawList.map(item => {
item = item.baseInfo.simpleSongData item = item.baseInfo.simpleSongData
const types = [] const types: Array<{ type: string, size: string | null }> = []
const _types = {} const _types: Record<string, { size: string | null }> = {}
let size let size
if (item.privilege.maxBrLevel == 'hires') { if (item.privilege.maxBrLevel == 'hires') {
+12 -12
View File
@@ -1,6 +1,6 @@
import { eapiRequest } from './utils/index.js' import { eapiRequest } from './utils/index.ts'
import { formatPlayTime, sizeFormate } from '../index.js' import { formatPlayTime, sizeFormate } from '../index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
export default { export default {
/** /**
@@ -8,7 +8,7 @@ export default {
* @param {*} id * @param {*} id
*/ */
getInfo(id) { getInfo(id) {
return eapiRequest('/api/artist/head/info/get', { id }).then(({ body }) => { return (eapiRequest('/api/artist/head/info/get', { id }) as any).then(({ body }) => {
if (!body || body.code != 200) throw new Error('get singer info faild.') if (!body || body.code != 200) throw new Error('get singer info faild.')
return { return {
source: 'wy', source: 'wy',
@@ -34,11 +34,11 @@ export default {
*/ */
getSongList(id, page = 1, limit = 100) { getSongList(id, page = 1, limit = 100) {
if (page === 1) page = 0 if (page === 1) page = 0
return eapiRequest('/api/v2/artist/songs', { return (eapiRequest('/api/v2/artist/songs', {
id, id,
limit, limit,
offset: limit * page, offset: limit * page,
}).then(({ body }) => { }) as any).then(({ body }) => {
if (!body.songs || body.code != 200) throw new Error('get singer song list faild.') if (!body.songs || body.code != 200) throw new Error('get singer song list faild.')
const list = this.filterSongList(body.songs) const list = this.filterSongList(body.songs)
@@ -59,10 +59,10 @@ export default {
*/ */
getAlbumList(id, page = 1, limit = 10) { getAlbumList(id, page = 1, limit = 10) {
if (page === 1) page = 0 if (page === 1) page = 0
return eapiRequest(`/api/artist/albums/${id}`, { return (eapiRequest(`/api/artist/albums/${id}`, {
limit, limit,
offset: limit * page, offset: limit * page,
}).then(({ body }) => { }) as any).then(({ body }) => {
if (!body.hotAlbums || body.code != 200) throw new Error('get singer album list faild.') if (!body.hotAlbums || body.code != 200) throw new Error('get singer album list faild.')
const list = this.filterAlbumList(body.hotAlbums) const list = this.filterAlbumList(body.hotAlbums)
@@ -76,7 +76,7 @@ export default {
}) })
}, },
filterAlbumList(raw) { filterAlbumList(raw) {
const list = [] const list: any[] = []
raw.forEach(item => { raw.forEach(item => {
if (!item.id) return if (!item.id) return
list.push({ list.push({
@@ -93,12 +93,12 @@ export default {
return list return list
}, },
filterSongList(raw) { filterSongList(raw) {
const list = [] const list: any[] = []
raw.forEach(item => { raw.forEach(item => {
if (!item.id) return if (!item.id) return
const types = [] const types: Array<{ type: string, size: string | null }> = []
const _types = {} const _types: Record<string, { size: string | null }> = {}
let size let size
item.privilege.chargeInfoList.forEach(i => { item.privilege.chargeInfoList.forEach(i => {
switch (i.rate) { switch (i.rate) {
@@ -3,17 +3,18 @@
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/top_playlist.js // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/top_playlist.js
// https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_detail.js // https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/module/playlist_detail.js
import { weapi, linuxapi } from './utils/crypto.js' import { weapi, linuxapi } from './utils/crypto.ts'
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.js' import { formatPlayTime, sizeFormate, dateFormat, formatPlayCount } from '../index.ts'
import musicDetailApi from './musicDetail.js' import musicDetailApi from './musicDetail.ts'
import { eapiRequest } from './utils/index.js' import { eapiRequest } from './utils/index.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
_requestObj_tags: null, _requestObj_tags: null as HttpRequestObject | null,
_requestObj_hotTags: null, _requestObj_hotTags: null as HttpRequestObject | null,
_requestObj_list: null, _requestObj_list: null as HttpRequestObject | null,
limit_list: 30, limit_list: 30,
limit_song: 100000, limit_song: 100000,
successCode: 200, successCode: 200,
@@ -97,8 +98,8 @@ export default {
list = this.filterListDetail(body) list = this.filterListDetail(body)
} else { } else {
try { try {
list = (await musicDetailApi.getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))).list list = (await (musicDetailApi as any).getList(body.playlist.trackIds.slice(rangeStart, limit * page).map(trackId => trackId.id))).list
} catch (err) { } catch (err: any) {
console.log(err) console.log(err)
if (err.message == 'try max num') { if (err.message == 'try max num') {
throw err throw err
@@ -125,10 +126,10 @@ export default {
}, },
filterListDetail({ playlist: { tracks }, privileges }) { filterListDetail({ playlist: { tracks }, privileges }) {
// console.log(tracks, privileges) // console.log(tracks, privileges)
const list = [] const list: any[] = []
tracks.forEach((item, index) => { tracks.forEach((item, index) => {
const types = [] const types: Array<{ type: string, size: string | null }> = []
const _types = {} const _types: Record<string, { size: string | null }> = {}
let size let size
let privilege = privileges[index] let privilege = privileges[index]
if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id) if (privilege.id !== item.id) privilege = privileges.find(p => p.id === item.id)
@@ -216,7 +217,7 @@ export default {
total: true, total: true,
}), }),
}) })
return this._requestObj_list.promise.then(({ body }) => { return this._requestObj_list!.promise.then(({ body }) => {
// console.log(body) // console.log(body)
if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum) if (body.code !== this.successCode) return this.getList(sortId, tagId, page, ++tryNum)
return { return {
@@ -252,7 +253,7 @@ export default {
method: 'post', method: 'post',
form: weapi({}), form: weapi({}),
}) })
return this._requestObj_tags.promise.then(({ body }) => { return this._requestObj_tags!.promise.then(({ body }) => {
// console.log(JSON.stringify(body)) // console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum) if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterTagInfo(body) return this.filterTagInfo(body)
@@ -271,7 +272,7 @@ export default {
}) })
} }
const list = [] const list: any[] = []
for (const key of Object.keys(categories)) { for (const key of Object.keys(categories)) {
list.push({ list.push({
name: categories[key], name: categories[key],
@@ -290,7 +291,7 @@ export default {
method: 'post', method: 'post',
form: weapi({}), form: weapi({}),
}) })
return this._requestObj_hotTags.promise.then(({ body }) => { return this._requestObj_hotTags!.promise.then(({ body }) => {
// console.log(JSON.stringify(body)) // console.log(JSON.stringify(body))
if (body.code !== this.successCode) return this.getTag(++tryNum) if (body.code !== this.successCode) return this.getTag(++tryNum)
return this.filterHotTagInfo(body.tags) return this.filterHotTagInfo(body.tags)
@@ -1,9 +1,10 @@
import { httpFetch } from '../request.js' import { httpFetch } from '../request.ts'
import { weapi } from './utils/crypto.js' import { weapi } from './utils/crypto.ts'
import { formatSingerName } from '../utils.js' import { formatSingerName } from '../utils.ts'
import type { HttpRequestObject } from '../types.ts'
export default { export default {
requestObj: null, requestObj: null as HttpRequestObject | null,
cancelTipSearch() { cancelTipSearch() {
if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp() if (this.requestObj && this.requestObj.cancelHttp) this.requestObj.cancelHttp()
}, },
@@ -19,7 +20,7 @@ export default {
s: str, s: str,
}), }),
}) })
return this.requestObj.promise.then(({ statusCode, body }) => { return this.requestObj!.promise.then(({ statusCode, body }) => {
if (statusCode != 200 || body.code != 200) return Promise.reject(new Error('请求失败')) if (statusCode != 200 || body.code != 200) return Promise.reject(new Error('请求失败'))
return body.result.songs return body.result.songs
}) })
@@ -7,31 +7,31 @@ const base62 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----' const publicKey = '-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDgtQn2JZ34ZC28NWYpAUd98iZ37BUrX/aKzmFbt7clFSs6sXqHauqKWqdtLkF2KexO40H1YTX8z2lSgBBOAxLsvaklV8k4cBFK9snQXE9/DDaFt6Rr7iVZMldczhC0JNgTz+SHXT6CBHuX3e9SdB1Ua44oncaTWz7OBGLbCiK45wIDAQAB\n-----END PUBLIC KEY-----'
const eapiKey = 'e82ckenh8dichen8' const eapiKey = 'e82ckenh8dichen8'
const aesEncrypt = (buffer, mode, key, iv) => { const aesEncrypt = (buffer: Buffer, mode: string, key: Buffer | string, iv: Buffer | string): Buffer => {
const cipher = createCipheriv(mode, key, iv) const cipher = createCipheriv(mode, key, iv)
return Buffer.concat([cipher.update(buffer), cipher.final()]) return Buffer.concat([cipher.update(buffer), cipher.final()])
} }
const aesDecrypt = function(cipherBuffer, mode, key, iv) { const aesDecrypt = function(cipherBuffer: Buffer, mode: string, key: Buffer | string, iv: Buffer | string): Buffer {
let decipher = createDecipheriv(mode, key, iv) let decipher = createDecipheriv(mode, key, iv)
return Buffer.concat([decipher.update(cipherBuffer), decipher.final()]) return Buffer.concat([decipher.update(cipherBuffer), decipher.final()])
} }
const rsaEncrypt = (buffer, key) => { const rsaEncrypt = (buffer: Buffer, key: string): Buffer => {
buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer]) buffer = Buffer.concat([Buffer.alloc(128 - buffer.length), buffer])
return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer) return publicEncrypt({ key, padding: constants.RSA_NO_PADDING }, buffer)
} }
export const weapi = object => { export const weapi = (object: object): { params: string, encSecKey: string } => {
const text = JSON.stringify(object) const text = JSON.stringify(object)
const secretKey = randomBytes(16).map(n => (base62.charAt(n % 62).charCodeAt())) const secretKey = randomBytes(16).map(n => ((base62.charAt(n % 62) as any).charCodeAt())) as Buffer
return { return {
params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'aes-128-cbc', presetKey, iv).toString('base64')), 'aes-128-cbc', secretKey, iv).toString('base64'), params: aesEncrypt(Buffer.from(aesEncrypt(Buffer.from(text), 'aes-128-cbc', presetKey, iv).toString('base64')), 'aes-128-cbc', secretKey, iv).toString('base64'),
encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'), encSecKey: rsaEncrypt(secretKey.reverse(), publicKey).toString('hex'),
} }
} }
export const linuxapi = object => { export const linuxapi = (object: object): { eparams: string } => {
const text = JSON.stringify(object) const text = JSON.stringify(object)
return { return {
eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(), eparams: aesEncrypt(Buffer.from(text), 'aes-128-ecb', linuxapiKey, '').toString('hex').toUpperCase(),
@@ -39,7 +39,7 @@ export const linuxapi = object => {
} }
export const eapi = (url, object) => { export const eapi = (url: string, object: object | string): { params: string } => {
const text = typeof object === 'object' ? JSON.stringify(object) : object const text = typeof object === 'object' ? JSON.stringify(object) : object
const message = `nobody${url}use${text}md5forencrypt` const message = `nobody${url}use${text}md5forencrypt`
const digest = createHash('md5').update(message).digest('hex') const digest = createHash('md5').update(message).digest('hex')
@@ -49,6 +49,6 @@ export const eapi = (url, object) => {
} }
} }
export const eapiDecrypt = cipherBuffer => { export const eapiDecrypt = (cipherBuffer: Buffer): string => {
return aesDecrypt(cipherBuffer, 'aes-128-ecb', eapiKey, '').toString() return aesDecrypt(cipherBuffer, 'aes-128-ecb', eapiKey, '').toString()
} }
@@ -1,7 +1,8 @@
import { httpFetch } from '../../request.js' import { httpFetch } from '../../request.ts'
import { eapi } from './crypto.js' import { eapi } from './crypto.ts'
import type { HttpRequestObject } from '../../types.ts'
export const eapiRequest = (url, data) => { export const eapiRequest = (url: string, data: object): HttpRequestObject => {
return httpFetch('http://interface.music.163.com/eapi/batch', { return httpFetch('http://interface.music.163.com/eapi/batch', {
method: 'post', method: 'post',
headers: { headers: {
-55
View File
@@ -1,55 +0,0 @@
// import { apis } from '../api-source.js'
// import leaderboard from './leaderboard.js'
// import songList from './songList.js'
// import musicSearch from './musicSearch.js'
// import pic from './pic.js'
// import lyric from './lyric.js'
// import hotSearch from './hotSearch.js'
// import comment from './comment.js'
// import musicInfo from './musicInfo.js'
// import { closeVerifyModal } from './util.js'
const xm = {
// songList,
// musicSearch,
// leaderboard,
// hotSearch,
// closeVerifyModal,
comment: {
getComment() {
return Promise.reject(new Error('fail'))
},
getHotComment() {
return Promise.reject(new Error('fail'))
},
},
getMusicUrl(songInfo, type) {
return {
promise: Promise.reject(new Error('fail')),
}
// return apis('xm').getMusicUrl(songInfo, type)
},
getLyric(songInfo) {
return {
promise: Promise.reject(new Error('fail')),
}
// return lyric.getLyric(songInfo)
},
getPic(songInfo) {
return Promise.reject(new Error('fail'))
// return pic.getPic(songInfo)
},
// getMusicDetailPageUrl(songInfo) {
// if (songInfo.songStringId) return `https://www.xiami.com/song/${songInfo.songStringId}`
// musicInfo.getMusicInfo(songInfo).then(({ data }) => {
// songInfo.songStringId = data.songStringId
// })
// return `https://www.xiami.com/song/${songInfo.songmid}`
// },
// init() {
// getToken()
// },
}
export default xm
+30
View File
@@ -0,0 +1,30 @@
// 虾米音乐 —— 服务已关停,全部接口返回失败(保留桩实现)
import type { PlatformModule, SongInfo, Quality } from './types.ts'
const xm: PlatformModule = {
comment: {
getComment(_songInfo: SongInfo) {
return Promise.reject(new Error('fail'))
},
getHotComment(_songInfo: SongInfo) {
return Promise.reject(new Error('fail'))
},
},
getMusicUrl(_songInfo: SongInfo, _type: Quality) {
return {
promise: Promise.reject(new Error('fail')),
cancelHttp() {},
}
},
getLyric(_songInfo: SongInfo) {
return {
promise: Promise.reject(new Error('fail')),
cancelHttp() {},
}
},
getPic(_songInfo: SongInfo) {
return Promise.reject(new Error('fail'))
},
}
export default xm
+1 -1
View File
@@ -5,7 +5,7 @@
// promise resolve → { type, url } // promise resolve → { type, url }
import { httpFetch } from '../common/request.js' import { httpFetch } from '../common/request.js'
import { eapi } from '../../musicSdk/wy/utils/crypto.js' import { eapi } from '../../musicSdk/wy/utils/crypto.ts'
const levelMap = { const levelMap = {
'128k': 'standard', '128k': 'standard',
+5 -2
View File
@@ -12,11 +12,14 @@
"resolveJsonModule": true, "resolveJsonModule": true,
"types": ["node"], "types": ["node"],
"noImplicitAny": false, "noImplicitAny": false,
"noEmitOnError": false "noEmitOnError": false,
"allowImportingTsExtensions": true,
"rewriteRelativeImportExtensions": true
}, },
"include": [ "include": [
"src/**/*.ts", "src/**/*.ts",
"src/types/**/*.d.ts" "src/types/**/*.d.ts",
"musicSdk/**/*.ts"
], ],
"exclude": [ "exclude": [
"node_modules", "node_modules",