1 Star 0 Fork 0

RIVE2012 / Less Player Desktop Plugins

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
音乐平台-QQ音乐-WhoamI-v1.0.0.js 54.91 KB
一键复制 编辑 原始数据 按行查看 历史
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524
/**
* @name 音乐平台 - QQ音乐
* @version 1.0.0
* @author WhoamI
* @about
* @repository
*/
/* 默认提供的插件API */
const { common, utils, crypto, events, nets, permissions } = lessAPI
const { Category, Playlist, Track, Album, Lyric } = common
const { toTrimString, nextInt, getImageUrlByQuality, } = utils
const { base64Stringify, base64Parse, hexDecode } = crypto
const { APIEvents, register, unregister } = events
const { getDoc, getJson, getRaw, postJson } = nets
const { APIPermissions, access } = permissions
//@param ignore 是否忽略&字符
const escapeHtml = (text, ignore) => {
if (!text) return null
const regex = ignore ? (/#\d+;/g) : (/[&]#\d+;/g)
return text.replace(regex, '')
.replace(/'/g, "'")
}
const moduleReq = (module, method, param) => {
return { module, method, param }
}
const changeImageSize = (url, oSize, nSize) => {
oSize = oSize || 300
nSize = nSize || 600
if (!url) return null
return url.replace(`/${oSize}?n=1`, `/${nSize}?n=1`)
}
const getArtistCover = (artistmid, size) => {
if (!artistmid) return null
size = size || 500
return `http://y.gtimg.cn/music/photo_new/T001R${size}x${size}M000${artistmid}.jpg`
}
const getAlbumCover = (albummid, size) => {
if (!albummid) return null
size = size || 500
return `https://y.qq.com/music/photo_new/T002R${size}x${size}M000${albummid}.jpg?max_age=2592000`
}
const getCoverByQuality = ({ artistMid, albumMid }) => {
if (artistMid) {
return getImageUrlByQuality([
getArtistCover(artistMid, 300),
getArtistCover(artistMid),
getArtistCover(artistMid, 800)
])
} else if (albumMid) {
return getImageUrlByQuality([
getAlbumCover(albumMid, 300),
getAlbumCover(albumMid),
getAlbumCover(albumMid, 800)
])
}
return null
}
const getTrackTypeMeta = (typeName) => {
return {
m4a: {
prefix: 'C400',
ext: '.m4a',
},
128: {
prefix: 'M500',
ext: '.mp3',
},
320: {
prefix: 'M800',
ext: '.mp3',
},
ape: {
prefix: 'A000',
ext: '.ape',
},
flac: {
prefix: 'F000',
ext: '.flac',
}
}[typeName]
}
//TODO
const vkeyReqData = (trackInfo, type) => {
const { mid: mediaId, type: songtype } = trackInfo
const filename = [type].map(item => {
const { prefix, ext } = getTrackTypeMeta(item)
return `${prefix}${mediaId}${mediaId}${ext}`
})
const guid = nextInt(10000000).toFixed(0)
const uin = "0"
return {
comm: {
uin,
format: 'json',
ct: 24,
cv: 0
},
req_1: moduleReq('vkey.GetVkeyServer', 'CgiGetVkey',
{
filename,
guid,
songmid: [mediaId],
songtype: [songtype],
uin,
loginflag: 1,
platform: "20"
})
}
}
const vkeyReqBody = (trackInfo, type) => {
return {
'-': 'getplaysongvkey',
'g_tk': 5381,
loginUin: 0,
hostUin: 0,
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8',
notice: 1,
platform: 'yqq.json',
needNewCode: 0,
data: JSON.stringify(vkeyReqData(trackInfo, type))
}
}
const artistHotSongReqBody = (id, offset, limit) => {
return {
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('music.web_singer_info_svr', 'get_singer_detail_info', {
sort: 5,
singermid: id,
sin: offset,
num: limit
})
})
}
}
const artistAlbumReqBody = (id, offset, limit) => {
return {
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('music.web_singer_info_svr', 'get_singer_album',
{
singermid: id,
order: "time",
begin: offset,
num: limit,
exstatus: 1
})
})
}
}
const albumAllSongsReqBody = (id, offset, limit) => {
return {
data: JSON.stringify({
comm: {
ct: 24,
cv: 10000
},
req_1: moduleReq('music.musichallAlbum.AlbumSongList', 'GetAlbumSongList',
{
albumMid: id,
albumID: 0,
begin: offset,
num: limit,
order: 2
})
})
}
}
const searchParam = (keyword, type, offset, limit, page) => {
const types = {
0: 'song',
2: 'songlist',
7: 'lyric',
8: 'album',
9: 'singer',
12: 'mv'
}
keyword = toTrimString(keyword)
return {
format: 'json',
n: limit,
p: page,
w: keyword,
cr: 1,
g_tk: 5381,
t: type
}
}
const searchParam_v1 = (keyword, type, offset, limit, page) => {
keyword = toTrimString(keyword)
return {
comm: {
ct: '6',
cv: '80500'
},
req_1: moduleReq('music.search.SearchCgiService', 'DoSearchForQQMusicDesktop',
{
num_per_page: 30,
page_num: page,
query: keyword,
search_type: type,
grp: 1
})
}
}
const topListReqBody = () => {
return {
_: Date.now(),
uin: 0,
format: 'json',
inCharset: "utf8",
outCharset: "utf8",
notice: 0,
platform: "yqq.json",
needNewCode: 1,
g_tk: 5381,
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('musicToplist.ToplistInfoServer', 'GetAll', {})
})
}
}
const topListDetailReqBody = (id, offset, limit, page) => {
return {
g_tk: 5381,
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('musicToplist.ToplistInfoServer', 'GetDetail',
{
topid: id,
offset,
num: 100,
period: getPerid(id)
})
})
}
}
const playlistRadiosReqBody = () => {
return {
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8',
notice: 0,
platform: 'yqq.json',
needNewCode: 1,
loginUin: 0,
hostUin: 0,
g_tk: 5381,
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('pf.radiosvr', 'GetRadiolist', { ct: 24 })
})
}
}
const radioSonglistReqBody = (id, firstplay) => {
if (typeof id == 'string') id = parseInt(id.trim())
return {
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8',
notice: 0,
platform: 'yqq.json',
needNewCode: 1,
loginUin: 0,
hostUin: 0,
g_tk: 5381,
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('pf.radiosvr', 'GetRadiosonglist',
{
id,
firstplay, //数字:0或1
num: 10
})
})
}
}
//TODO 目前部分周期计算不准确
/* 获取更新周期 */
const getPerid = (id) => {
const date = new Date()
const yyyy = date.getFullYear()
let mm = date.getMonth() + 1
let dd = date.getDate()
const day = date.getDay()
mm = mm < 10 ? ('0' + mm) : mm
const d0 = dd < 10 ? ('0' + dd) : dd
let period = yyyy + "-" + mm + "-" + d0
let week = 1
//默认每天
switch (id) {
//每天
case 27:
case 62:
break
//每周几?
case 4:
case 52:
case 67:
let d2 = day < 6 ? (dd - day + 1) : dd
d2 = d2 < 10 ? ('0' + d2) : d2
period = yyyy + "-" + mm + "-" + d2
break
//
case 130:
break
//每n周?
case 131:
week = getWeek(period) - 8
week = week < 10 ? ('0' + week) : week
period = date.getFullYear() + "_" + week;
break
//每周
default:
week = getWeek(period) - 1
week = week < 10 ? ('0' + week) : week
period = date.getFullYear() + "_" + week;
break
}
return period
}
const getWeek = (dt) => {
let d1 = new Date(dt)
let d2 = new Date(d1.getFullYear() + "-" + "01-01")
let millis = d1 - d2
let days = Math.ceil(millis / (24 * 60 * 60 * 1000))
let num = Math.ceil(days / 7)
return num
}
//新版本歌词信息
const lyricExtReqBody = (id, track) => {
const { title, artist, album, duration, songID } = track
const songName = base64Stringify(title)
const singerName = base64Stringify(artist[0].name)
const albumName = base64Stringify(album.name)
const interval = parseInt(duration / 1000)
return {
data: JSON.stringify({
comm: {
"tmeAppID": "qqmusic",
"authst": "",
"uid": "5019772269",
"gray": "1",
"OpenUDID": "2057708153c9fc13f0e801c14d39af5fccdfdc60",
"ct": "6",
"patch": "2",
"sid": "202304202127285019772269",
"wid": "2722428046011261952",
"cv": "80605",
//"gzip" : "1",
"qq": "",
"nettype": "2"
},
req_1: moduleReq('music.musichallSong.PlayLyricInfo', 'GetPlayLyricInfo',
{
"trans_t": 0,
"roma_t": 0,
"crypt": 0,
"lrc_t": 0,
interval,
"trans": 1,
"ct": 6,
singerName,
"type": 0,
"qrc_t": 0,
"cv": 80605,
"roma": 1,
songID,
"qrc": 0,
albumName,
songName
})
})
}
}
//旧版API,参考: https://github.com/jsososo/QQMusicApi/
class QQ {
static CODE = "qq"
static DEFAULT_CATE = 10000000
static NEW_CODE = 22222222
static TOPLIST_CODE = 99999999
static RADIO_CODE = 88888888
static TOPLIST_PREFIX = "TOP_"
static RADIO_CACHE = { channel: 0, data: [] }
//全部分类
static categories() {
return QQ.categories_v1()
}
//全部分类
static categories_v0() {
return new Promise((resolve, reject) => {
const url = "https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_tag_conf.fcg"
const reqBody = {
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8'
}
const result = { platform: QQ.CODE, data: [], orders: [] }
getJson(url, reqBody).then(json => {
const cateNameCached = []
const list = json.data.categories
list.forEach(cate => {
const cateName = cate.categoryGroupName
const category = new Category(cateName)
const items = cate.items
items.forEach(item => {
const name = item.categoryName
const id = item.categoryId
category.add(name, id)
})
if (cateNameCached.includes(cateName)) return
result.data.push(category)
cateNameCached.push(cateName)
})
const firstCate = result.data[0]
firstCate.data.splice(1, 0, { key: '最新', value: QQ.NEW_CODE })
firstCate.data.splice(2, 0, { key: '排行榜', value: QQ.TOPLIST_CODE })
firstCate.data.splice(3, 0, { key: '电台', value: QQ.RADIO_CODE })
resolve(result)
})
})
}
//全部分类
static categories_v1() {
return new Promise((resolve, reject) => {
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = JSON.stringify({
req_1: moduleReq('music.playlist.PlaylistSquare', 'GetAllTag', { qq: '' }),
comm: {
g_tk: 5381,
uin: '0',
format: 'json',
ct: 6,
cv: 80605,
platform: 'wk_v17',
uid: '5019772269',
guid: '2057708153c9fc13f0e801c14d39af5fccdfdc60',
mesh_devops: 'DevopsBase'
}
})
const result = { platform: QQ.CODE, data: [], orders: [] }
postJson(url, reqBody).then(json => {
const recommandTagNames = [
'国语', '英语', '粤语',
'轻音乐', '校园', '民谣',
'思念', '学习工作', '治愈',
'古典', '摇滚', '爵士',
'运动', '乡村', '乐器',
'婚礼', '00年代', '90年代']
const ignoreTagNames = ['AI歌单']
const recommandCategory = new Category('推荐', 0)
result.data.push(recommandCategory)
const list = json.req_1.data.v_group
list.forEach(cate => {
const cateName = cate.group_name
const cateCode = cate.group_id
const category = new Category(cateName, cateCode)
const items = cate.v_item
items.forEach(item => {
const { id, name } = item
if (ignoreTagNames.includes(name)) return
if (recommandTagNames.includes(name)) recommandCategory.add(name, id)
category.add(name, id)
})
result.data.push(category)
})
const firstCate = result.data[0]
firstCate.data.splice(0, 0, { key: '默认', value: QQ.DEFAULT_CATE })
firstCate.data.splice(1, 0, { key: '排行榜', value: QQ.TOPLIST_CODE })
firstCate.data.splice(2, 0, { key: '电台', value: QQ.RADIO_CODE })
//firstCate.data.splice(4, 0, { key: '最新', value: QQ.NEW_CODE })
resolve(result)
})
})
}
//排行榜列表
static toplist(cate, offset, limit, page, order) {
return new Promise((resolve, reject) => {
let result = { platform: QQ.CODE, cate, offset: 0, limit: 100, page: 1, total: 0, data: [] }
if (page > 1) {
resolve(result)
return
}
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = topListReqBody()
getJson(url, reqBody).then(json => {
const groupList = json.req_1.data.group
groupList.forEach(group => {
group.toplist.forEach(item => {
const id = QQ.TOPLIST_PREFIX + item.topId
const cover = item.frontPicUrl || item.headPicUrl
const detail = new Playlist(id, QQ.CODE, cover, item.title)
detail.about = item.intro
result.data.push(detail)
})
})
resolve(result)
})
})
}
//排行榜详情
static toplistDetail(id, offset, limit, page) {
return new Promise((resolve, reject) => {
const result = new Playlist()
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const topid = parseInt(id.replace(QQ.TOPLIST_PREFIX, ''))
const reqBody = topListDetailReqBody(topid, offset, limit, page)
getJson(url, reqBody).then(json => {
const playlist = json.req_1.data.data
result.id = playlist.topId
result.platform = QQ.CODE
result.title = playlist.title
result.cover = playlist.frontPicUrl || playlist.headPicUrl
result.about = playlist.intro
let songs = json.req_1.data.songInfoList || []
if(songs.length < 1) songs = json.req_1.data.data.song || []
songs.forEach(song => {
let artist = []
if(song.singer) {
artist = song.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
} else if(song.singerMid) {
artist.push({
id: song.singerMid,
name: song.singerName,
_id: song.singerId
})
}
let album = { id: null, name: '' }
if(song.album) {
album = { id: song.album.mid, name: song.album.name }
} else if(song.albumMid) {
Object.assign(album, { id: song.albumMid, name: song.albumName})
}
const duration = (song.interval || 0) * 1000
const cover = getCoverByQuality({ albumMid: album.id }) || song.cover
const tId = song.mid || song.songMid
const tTitle = song.name || song.title
const track = new Track(tId, QQ.CODE, tTitle, artist, album, duration, cover)
track.pid = id
track.songID = song.id || song.songId
result.addTrack(track)
})
resolve(result)
})
})
}
//歌单电台列表
static playlistRadios(cate, offset, limit, page) {
return new Promise((resolve, reject) => {
const result = { platform: QQ.CODE, cate, offset, limit, page, total: 0, data: [] }
if (page > 1) {
resolve(result)
return
}
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = playlistRadiosReqBody()
getJson(url, reqBody).then(json => {
const radioList = json.req_1.data.radio_list
radioList.forEach(group => {
group.list.forEach(item => {
const cid = item.id
const title = group.title + '' + item.title
const playlist = new Playlist(cid, QQ.CODE, item.pic_url, title)
//playlist.isRadioType = true
playlist.type = Playlist.NORMAL_RADIO_TYPE
result.data.push(playlist)
})
})
resolve(result)
})
})
}
//电台:下一首歌曲
static nextPlaylistRadioTrack(channel, track) {
return new Promise((resolve, reject) => {
let result = null
const firstplay = !track ? 1 : 0
//是否命中缓存
if (channel == QQ.RADIO_CACHE.channel) {
const index = (firstplay == 1) ? 0 :
QQ.RADIO_CACHE.data.findIndex(item => item.id == track.id)
const length = QQ.RADIO_CACHE.data.length
if (length > 0 && index > -1 && index < (length - 1)) {
result = QQ.RADIO_CACHE.data[index + 1]
resolve(result)
return
}
}
//不命中,重置缓存
QQ.RADIO_CACHE.channel = channel
QQ.RADIO_CACHE.data.length = 0
//拉取数据
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = radioSonglistReqBody(channel, firstplay)
getJson(url, reqBody).then(json => {
const list = json.req_1.data.track_list
list.forEach(item => {
const artist = item.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
const album = { id: item.album.mid, name: item.album.name }
const duration = item.interval * 1000
const cover = getCoverByQuality({ albumMid: item.album.mid })
const cache = new Track(item.mid, QQ.CODE, item.title, artist, album, duration, cover)
cache.type = Playlist.NORMAL_RADIO_TYPE
cache.channel = channel
QQ.RADIO_CACHE.data.push(cache)
})
result = QQ.RADIO_CACHE.data[0]
resolve(result)
})
})
}
//歌单广场(列表)
static square(cate, offset, limit, page) {
return QQ.square_v1(cate, offset, limit, page)
}
//歌单广场(列表)
static square_v0(cate, offset, limit, page) {
const originCate = cate || QQ.DEFAULT_CATE
let resolvedCate = cate
if (typeof resolvedCate == 'string') resolvedCate = parseInt(resolvedCate.trim())
resolvedCate = resolvedCate > 0 ? resolvedCate : QQ.DEFAULT_CATE
//榜单
if (resolvedCate == QQ.TOPLIST_CODE) return QQ.toplist(cate, offset, limit, page)
//电台
if (resolvedCate == QQ.RADIO_CODE) return QQ.playlistRadios(cate, offset, limit, page)
//普通歌单
let sortId = 5 //最热
if (resolvedCate == QQ.NEW_CODE) {
sortId = 2 //最新
resolvedCate = QQ.DEFAULT_CATE
}
return new Promise((resolve, reject) => {
const result = { platform: QQ.CODE, cate: originCate, offset, limit, page, total: 0, data: [] }
const url = "https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg"
const reqBody = {
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8',
sortId: sortId, //5 => 最热, 2 => 最新
categoryId: resolvedCate,
sin: offset,
ein: (offset + limit - 1)
}
getJson(url, reqBody).then(json => {
if (json && json.data) {
result.total = Math.ceil(json.data.sum / limit)
const list = json.data.list
list.forEach(item => {
const cover = item.imgurl
const playlist = new Playlist(item.dissid, QQ.CODE, cover, item.dissname)
playlist.about = item.introduction
playlist.playCount = item.listennum
result.data.push(playlist)
})
}
resolve(result)
})
})
}
//歌单广场(列表)
static square_v1(cate, offset, limit, page) {
const originCate = cate || 0
let resolvedCate = cate
if (typeof resolvedCate == 'string') resolvedCate = parseInt(resolvedCate.trim())
resolvedCate = resolvedCate > 0 ? resolvedCate : QQ.DEFAULT_CATE
//榜单
if (resolvedCate == QQ.TOPLIST_CODE) return QQ.toplist(cate, offset, limit, page)
//电台
if (resolvedCate == QQ.RADIO_CODE) return QQ.playlistRadios(cate, offset, limit, page)
//普通歌单
if (resolvedCate == QQ.DEFAULT_CATE || resolvedCate == QQ.NEW_CODE) {
return QQ.square_v0(cate, offset, limit, page)
}
return new Promise((resolve, reject) => {
const result = { platform: QQ.CODE, cate: originCate, offset, limit, page, total: 0, data: [] }
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = JSON.stringify({
req_1: moduleReq('playlist.PlayListCategoryServer',
'get_category_content',
{
caller: "0",
category_id: resolvedCate,
page: (page - 1),
use_page: 1,
size: limit
})
})
postJson(url, reqBody).then(json => {
const { content } = json.req_1.data
result.total = Math.ceil(content.total_cnt / limit)
const list = content.v_item
list.forEach(lItem => {
const item = lItem.basic
const cover = getImageUrlByQuality([item.cover.small_url, item.cover.medium_url, item.cover.big_url])
const playlist = new Playlist(item.tid, QQ.CODE, cover, item.title)
playlist.about = item.desc
playlist.playCount = item.play_cnt
playlist.total = item.song_cnt
result.data.push(playlist)
})
resolve(result)
})
})
}
//歌单详情
static playlistDetail(id, offset, limit, page) {
if (id.toString().startsWith(QQ.TOPLIST_PREFIX)) {
return QQ.toplistDetail(id, offset, limit, page)
}
return new Promise((resolve, reject) => {
const result = new Playlist(id, QQ.CODE)
const url = "http://c.y.qq.com/qzone/fcg-bin/fcg_ucc_getcdinfo_byids_cp.fcg"
const reqBody = {
format: 'json',
type: 1,
utf8: 1,
disstid: id, // 歌单的id
loginUin: 0,
}
getJson(url, reqBody).then(json => {
const playlist = json.cdlist[0]
result.dissid = playlist.dissid
result.title = playlist.dissname
result.cover = getImageUrlByQuality([
playlist.logo,
playlist.logo,
changeImageSize(playlist.logo, 300, 600) //size=800不存在
])
result.about = playlist.desc
const songs = playlist.songlist
songs.forEach(song => {
const artist = song.singer.map(e => ({ id: e.mid, name: e.name, _id: e.id }))
const album = { id: song.albummid, name: song.albumname, _id: song.albumid }
const duration = song.interval * 1000
const cover = getCoverByQuality({ albumMid: song.albummid })
const track = new Track(song.songmid, QQ.CODE, song.songname, artist, album, duration, cover)
track.mv = song.vid
track.pid = id
track.payPlay = (song.pay.payplay == 1)
track.payDownload = (song.pay.paydownload == 1)
track.songID = song.songid
track.strMediaMid = song.strMediaMid
result.addTrack(track)
})
resolve(result)
})
})
}
//歌曲播放详情:url、cover、lyric等
static playDetail(id, track) {
return new Promise((resolve, reject) => {
const result = new Track(id, QQ.CODE)
const url = "http://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = {
format: 'json',
data: JSON.stringify({
req_1: moduleReq('music.pf_song_detail_svr', 'get_song_detail_yqq', { song_mid: id })
})
}
getJson(url, reqBody).then(async (json) => {
const trackInfo = json.req_1.data.track_info
const types = ['320', '128', 'm4a']
for (var i = 0; i < types.length; i++) {
const vkeyJson = await QQ.getVKeyJson(trackInfo, types[i])
const { midurlinfo, sip } = vkeyJson.req_1.data
const urlInfo = midurlinfo[0]
const { vkey } = urlInfo
if ((vkey || '').trim().length > 0) {
result.url = sip[0] + urlInfo.purl
break
}
}
resolve(result)
/*
QQ.getVKeyJson(trackInfo).then(json => {
const { data } = json.req_1
const urlInfo = data.midurlinfo[0]
const vkey = urlInfo.vkey.trim()
if (vkey.length > 0) {
result.url = data.sip[0] + urlInfo.purl
}
resolve(result)
})
*/
})
})
}
//获取VKey、purl和sip服务器等信息
static getVKeyJson(trackInfo, type) {
return new Promise((resolve, reject) => {
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = vkeyReqBody(trackInfo, type)
getJson(url, reqBody).then(json => resolve(json))
})
}
//歌词
static lyric(id, track) {
if (id) return QQ.lyricExt(id, track)
return new Promise((resolve, reject) => {
const result = { id, platform: QQ.CODE, lyric: null, trans: null }
const url = "http://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg"
const reqBody = {
songmid: id,
pcachetime: Date.now(),
g_tk: 5381,
loginUin: 0,
hostUin: 0,
format: 'json',
inCharset: 'utf8',
outCharset: 'utf8',
notice: 0,
platform: 'yqq',
needNewCode: 0
}
getJson(url, reqBody).then(json => {
const { lyric, trans } = json
//lyric = escapeHtml(lyric)
Object.assign(result, { lyric: Lyric.parseFromText(base64Parse(lyric)) })
if (trans) {
Object.assign(result, { trans: Lyric.parseFromText(base64Parse(trans)) })
}
resolve(result)
})
})
}
//新版歌词 - 翻译、罗马发音等
static lyricExt(id, track) {
return new Promise((resolve, reject) => {
const url = "http://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = lyricExtReqBody(id, track)
const result = { id, platform: QQ.CODE, lyric: null, trans: null }
getJson(url, reqBody).then(json => {
const { lyric, roma, trans } = json.req_1.data
Object.assign(result, { lyric: Lyric.parseFromText(base64Parse(lyric)) })
if (roma) { //TODO
Object.assign(result, { roma: Lyric.parseFromText(hexDecode(roma)) })
}
if (trans) {
Object.assign(result, { trans: Lyric.parseFromText(base64Parse(trans)) })
}
resolve(result)
})
})
}
//歌手详情:Name、Cover、简介(如果有)、热门歌曲等
static artistDetail_v0(id) {
return new Promise((resolve, reject) => {
const result = { id, title: '未知歌手', cover: '', data: [], about: '' }
const url = `https://y.qq.com/n/ryqq/singer/${id}`
getDoc(url).then(doc => {
const scriptEls = doc.querySelectorAll("script")
const key = "window.__INITIAL_DATA__"
let scriptText = null
for (var scriptEl of scriptEls) {
scriptText = scriptEl.textContent
if (!scriptText) continue
scriptText = scriptText.trim()
if (scriptText.includes(key)) break
}
if (scriptText) {
scriptText = scriptText.split(key)[1].trim().substring(1)
scriptText = scriptText.replace(/:undefined,/g, ':"",')
const json = JSON.parse(scriptText)
const detail = json.singerDetail
result.title = detail.basic_info.name
result.cover = getCoverByQuality({ artistMid: detail.basic_info.singer_mid }) || detail.pic.pic
result.about = detail.descstr
}
resolve(result)
})
})
}
//歌手详情:Name、Cover、简介(如果有)、热门歌曲等
static artistDetail(id) {
return new Promise((resolve, reject) => {
const result = { id, title: '未知歌手', cover: '', data: [], about: '' }
const url = 'https://u6.y.qq.com/cgi-bin/musicu.fcg'
const reqBody = JSON.stringify({
comm: {
cv: 4747474,
ct: 24,
format: 'json',
inCharset: 'utf-8',
outCharset: 'utf-8',
notice: 0,
platform: 'yqq.json',
needNewCode: 1,
uin: 0,
g_tk_new_20200303: 5381,
g_tk: 5381
},
req_1: moduleReq('music.musichallSinger.SingerInfoInter', 'GetSingerDetail', {
singer_mids: [id],
ex_singer: 1,
wiki_singer: 1,
group_singer: 0,
pic: 1,
photos: 0
})
})
postJson(url, reqBody).then(json => {
const singer = json.req_1.data.singer_list[0]
const { basic_info, ex_info, wiki } = singer || {}
Object.assign(result, {
title: basic_info && basic_info.name,
about: ex_info && ex_info.desc,
cover: getCoverByQuality({ artistMid: id })
})
resolve(result)
})
})
}
//@Deprecated 弃用
static artistDetailDesc(id) {
return new Promise((resolve, reject) => {
const url = "http://c.y.qq.com/splcloud/fcgi-bin/fcg_get_singer_desc.fcg"
const reqBody = {
singermid: id,
format: 'xml',
utf8: 1,
outCharset: 'utf8'
}
getRaw(url, reqBody).then(xml => {
const result = { id, name: '', cover: '', data: [] }
resolve(result)
})
})
}
//歌手详情:热门歌曲
static artistDetailHotSongs(id) {
return new Promise((resolve, reject) => {
const url = "http://u.y.qq.com/cgi-bin/musicu.fcg"
const offset = 0
const limit = 365
const page = 1
const reqBody = artistHotSongReqBody(id, offset, limit)
getJson(url, reqBody).then(json => {
const result = { id, offset, limit, page, data: [] }
const songList = json.req_1.data.songlist
songList.forEach(item => {
const artist = item.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
const album = { id: item.album.mid, name: item.album.name }
const duration = item.interval * 1000
const cover = getCoverByQuality({ albumMid: item.album.mid })
const track = new Track(item.mid, QQ.CODE, item.title,
artist, album, duration, cover)
track.mv = item.mv.vid
track.payPlay = (item.pay.pay_play == 1)
track.payDownload = (item.pay.pay_down == 1)
result.data.push(track)
})
resolve(result)
})
})
}
//歌手详情: 专辑
static artistDetailAlbums(id, offset, limit, page) {
return new Promise((resolve, reject) => {
const url = "http://u.y.qq.com/cgi-bin/musicu.fcg"
const reqBody = artistAlbumReqBody(id, offset, limit)
getJson(url, reqBody).then(json => {
const result = { id, platform: QQ.CODE, offset, limit, page, data: [] }
const albumList = json.req_1.data.list
albumList.forEach(item => {
const artist = item.singers.map(ar => ({ id: ar.singer_mid, name: ar.singer_name }))
const cover = getCoverByQuality({ albumMid: item.album_mid })
const album = new Album(item.album_mid, QQ.CODE, item.album_name, cover, artist)
album.publishTime = item.pub_time
result.data.push(album)
})
resolve(result)
})
})
}
//专辑详情
static albumDetail_v0(id) {
return new Promise((resolve, reject) => {
const url = "https://y.qq.com/n/ryqq/albumDetail/" + id
getDoc(url).then(doc => {
const scriptEls = doc.querySelectorAll("script")
const key = "window.__INITIAL_DATA__"
let scriptText = null
for (var scriptEl of scriptEls) {
scriptText = scriptEl.textContent
if (!scriptText) continue
scriptText = scriptText.trim()
if (scriptText.includes(key)) break
}
const result = new Album(id, QQ.CODE)
if (scriptText) {
scriptText = scriptText.split(key)[1].trim().substring(1)
scriptText = scriptText.replace(/:undefined,/g, ':"",')
const json = JSON.parse(scriptText)
const detail = json.detail
//const cover = detail.picurl.startsWith("//") ? ("https:" + detail.picurl) : detail.picurl
result.title = detail.albumName
result.cover = getCoverByQuality({ albumMid: detail.albumMid })
result.artist = detail.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
result.publishTime = detail.ctime
result.company = detail.company
result.about = detail.desc
}
resolve(result)
})
})
}
//专辑详情
static albumDetail(id) {
return new Promise((resolve, reject) => {
const url = `https://c6.y.qq.com/v8/fcg-bin/musicmall.fcg?_=1709281071876&cv=4747474&ct=24&format=json&inCharset=utf-8&outCharset=utf-8&notice=0&platform=yqq.json&needNewCode=1&uin=0&g_tk_new_20200303=5381&g_tk=5381&cmd=get_album_buy_page&albummid=${id}&albumid=0`
getJson(url).then(json => {
const result = new Album(id, QQ.CODE)
const detail = json.data
result.title = detail.album_name
result.cover = getCoverByQuality({ albumMid: id })
result.artist = detail.singerinfo.map(ar => ({ id: ar.singermid, name: ar.singername, _id: ar.singerid }))
result.publishTime = detail.publictime
result.company = detail.companyname
result.about = detail.desc
resolve(result)
})
})
}
//专辑详情: 歌曲
static albumDetailAllSongs(id, offset, limit, page) {
return new Promise((resolve, reject) => {
const url = "https://u.y.qq.com/cgi-bin/musicu.fcg?g_tk=5381&format=json&inCharset=utf8&outCharset=utf8"
const reqBody = albumAllSongsReqBody(id, offset, limit)
getJson(url, reqBody).then(json => {
const result = new Album(id)
const songList = json.req_1.data.songList
songList.forEach(item => {
const song = item.songInfo
const artist = song.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
const album = { id, name: song.album.name }
const cover = getCoverByQuality({ albumMid: id })
const duration = song.interval * 1000
const track = new Track(song.mid, QQ.CODE, song.name, artist, album, duration, cover)
track.mv = song.mv.vid
track.payPlay = (song.pay.pay_play == 1)
track.payDownload = (song.pay.pay_down == 1)
result.addTrack(track)
})
resolve(result)
})
})
}
static doMultiPageSearch_old({ keyword, type, offset, limit, page, count }, { getList, mapItem }) {
return new Promise(async (resolve, reject) => {
count = count || 2
const result = { platform: QQ.CODE, offset, limit, page, data: [] }
const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
for (var i = 0; i < count; i++) {
const reqBody = JSON.stringify(searchParam_v1(keyword, type, offset, limit, (page + i)))
const json = await postJson(url, reqBody)
const { list } = getList(json)
const hitNum = list ? list.length : 0
if (hitNum < 1) break
const data = list.map(item => mapItem(item))
const dataSize = data ? data.length : 0
if (dataSize > 0) result.data.push(...data)
if (dataSize < 30) break
}
resolve(result)
})
}
static doMultiPageSearch({ keyword, type, offset, limit, page, count }, { getList, mapItem, beforeResolve }) {
return new Promise((resolve, reject) => {
count = count || 2
const result = { platform: QQ.CODE, offset, limit, page, data: [] }
const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
const tasks = []
for (var i = 0; i < count; i++) {
const reqBody = JSON.stringify(searchParam_v1(keyword, type, offset, limit, (page + i)))
tasks.push(postJson(url, reqBody))
}
Promise.all(tasks).then(jsons => {
for (var i = 0; i < jsons.length; i++) {
const { list } = getList(jsons[i])
const hitNum = list ? list.length : 0
if (hitNum < 1) continue
const data = list.map(item => mapItem(item))
const dataSize = data ? data.length : 0
if (dataSize > 0) result.data.push(...data)
//if (dataSize < 30) break
}
if (beforeResolve && (typeof beforeResolve == 'function')) beforeResolve(result)
resolve(result)
}).catch(error => resolve(result))
})
}
//搜索: 歌曲
static searchSongs(keyword, offset, limit, page) {
//const url = "https://u.y.qq.com/cgi-bin/musicu.fcg"
return QQ.doMultiPageSearch({ keyword, type: 0, offset, limit, page },
{
getList: json => json.req_1.data.body.song,
mapItem: item => {
const artist = item.singer.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
const album = { id: item.album.mid, name: item.album.name }
const duration = item.interval * 1000
const cover = getCoverByQuality({ albumMid: album.id })
const track = new Track(item.mid, QQ.CODE, item.name, artist, album, duration, cover)
track.mv = item.mv.vid
track.payPlay = (item.pay.pay_play == 1)
track.payDownload = (item.pay.pay_down == 1)
return track
}
})
}
//搜索: 歌单
static searchPlaylists(keyword, offset, limit, page) {
return QQ.doMultiPageSearch({ keyword, type: 3, offset, limit, page },
{
getList: json => json.req_1.data.body.songlist,
mapItem: item => {
let cover = item.imgurl
cover = getImageUrlByQuality([
cover,
cover,
changeImageSize(cover, 300, 600) //size=800不存在
])
const playlist = new Playlist(item.dissid, QQ.CODE, cover, item.dissname)
//playlist.about = item.introduction
return playlist
}
})
}
//搜索: 专辑
static searchAlbums(keyword, offset, limit, page) {
return QQ.doMultiPageSearch({ keyword, type: 2, offset, limit, page },
{
getList: json => json.req_1.data.body.album,
mapItem: item => {
//const cover = item.albumPic
const artist = item.singer_list.map(ar => ({ id: ar.mid, name: ar.name, _id: ar.id }))
const cover = getCoverByQuality({ albumMid: item.albumMID })
const album = new Album(item.albumMID, QQ.CODE, item.albumName, cover, artist)
album.publishTime = item.publicTime
return album
}
})
}
//搜索: 歌手
static searchArtists(keyword, offset, limit, page) {
return QQ.doMultiPageSearch({ keyword, type: 1, offset, limit, page },
{
getList: json => json.req_1.data.body.singer,
mapItem: item => ({
id: item.singerMID,
platform: QQ.CODE,
title: item.singerName,
//cover: item.singerPic
cover: getCoverByQuality({ artistMid: item.singerMID })
})
})
}
//搜索: MV视频
static searchVideos(keyword, offset, limit, page) {
return QQ.doMultiPageSearch({ keyword, type: 4, offset, limit, page },
{
getList: json => json.req_1.data.body.mv,
mapItem: item => ({
id: item.mv_id,
vid: item.v_id,
mvid: item.mv_id,
watchid: item.watchid,
platform: QQ.CODE,
title: item.mv_name,
subtitle: item.singer_name,
cover: item.mv_pic_url,
type: Playlist.VIDEO_TYPE,
publicTime: item.publish_date,
pay: (item.pay > 0),
duration: (item.duration * 1000),
playCount: item.play_count
})
})
}
//歌手分类名称映射
static artistTagsMap() {
return {
index: '字母',
area: '地区',
sex: '性别',
genre: '流派',
}
}
//歌手分类
static artistCategories() {
return new Promise((resolve, reject) => {
const result = { platform: QQ.CODE, data: [], alphabet: new Category('字母') }
const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
const reqBody = {
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('Music.SingerListServer', 'get_singer_list', {
area: -100,
sex: -100,
genre: -100,
index: -100,
sin: 0,
cur_page: 1
})
})
}
getJson(url, reqBody).then(json => {
const tags = json.req_1.data.tags
const artistTagsMap = QQ.artistTagsMap()
const keys = ['area', 'sex', 'genre']
keys.forEach(key => {
const category = new Category(artistTagsMap[key])
const list = tags[key]
list.forEach(item => {
category.add(item.name, item.id)
})
result.data.push(category)
})
//字母表
tags.index.forEach(item => {
result.alphabet.add(item.name, item.id)
})
resolve(result)
})
})
}
//提取分类
static parseArtistCate(cate) {
const result = { area: -100, sex: -100, genre: -100, index: -100 }
const source = {}
const artistTagsMap = QQ.artistTagsMap()
for (var key in cate) {
for (var tag in artistTagsMap) {
if (key == artistTagsMap[tag]) {
source[tag] = cate[key].item.value
}
}
}
return Object.assign(result, source)
}
//歌手(列表)广场
static artistSquare(cate, offset, limit, page) {
limit = 80
offset = (page - 1) * limit
return new Promise((resolve, reject) => {
const result = { platform: QQ.CODE, cate, offset, limit, page, total: 0, data: [] }
const url = 'https://u.y.qq.com/cgi-bin/musicu.fcg'
const resolvedCate = QQ.parseArtistCate(cate)
const reqBody = {
data: JSON.stringify({
comm: {
ct: 24,
cv: 0
},
req_1: moduleReq('Music.SingerListServer', 'get_singer_list', {
area: resolvedCate.area,
sex: resolvedCate.sex,
genre: resolvedCate.genre,
index: resolvedCate.index,
sin: offset,
cur_page: page
})
})
}
getJson(url, reqBody).then(json => {
const list = json.req_1.data.singerlist
list.forEach(item => {
const id = item.singer_mid
const title = item.singer_name
//const cover = item.singer_pic
const cover = getCoverByQuality({ artistMid: id })
const artist = { id, platform: QQ.CODE, title, cover }
result.data.push(artist)
})
resolve(result)
})
})
}
static videoDetail_v0(id, video) {
return new Promise((resolve, reject) => {
const url = 'http://u.y.qq.com/cgi-bin/musicu.fcg'
const reqBody = {
data: JSON.stringify({
req_1: {
module: 'gosrf.Stream.MvUrlProxy',
method: 'GetMvUrls',
param: {
vids: [id],
request_type: 10001,
/*
"request_type": 10003,
"addrtype": 3,
"format": 265,
"maxFiletype": 60
*/
}
}
})
}
getJson(url, reqBody).then(json => {
const data = json.req_1.data
const mvData = {}
Object.keys(data).forEach(vid => {
const mp4Arr = data[vid].mp4 || []
mvData[vid] = mp4Arr.map(item => {
return item.freeflow_url[0] || ''
})
})
const result = { id, platform: QQ.CODE, url: '' }
const mvUrls = mvData[id]
//TODO 其实应该全部返回给客户端,由客户端决定播放url,播放失败时也方便重试
result.url = mvUrls.length > 0 ? mvUrls[mvUrls.length - 1] : ''
resolve(result)
})
})
}
static videoDetail(id, video) {
return new Promise((resolve, reject) => {
const url = 'http://u.y.qq.com/cgi-bin/musicu.fcg'
const reqBody = {
data: JSON.stringify({
req_1: {
module: 'music.stream.MvUrlProxy',
method: 'GetMvUrls',
param: {
vids: [id],
request_type: 10003
}
}
})
}
const result = { id, platform: QQ.CODE, url: '' }
getJson(url, reqBody).then(json => {
const data = json.req_1.data
if (!data) return resolve(result)
const mvData = {}
Object.keys(data).forEach(vid => {
let keyHits = 0
for (const [key, value] of Object.entries(data[vid])) {
if (keyHits >= 2) break
if (key === 'mp4' || key === 'hls') {
++keyHits
mvData[key] = []
value.forEach(item => {
const url = item.freeflow_url[0]
if (!url || url.trim().length < 1) return
mvData[key].push(url)
})
}
}
})
for (const [key, value] of Object.entries(mvData)) {
if (value.length > 0) {
result.url = value[value.length - 1]
break
}
}
//TODO 其实应该全部返回给客户端,由客户端决定播放url,播放失败时也方便重试
resolve(result)
})
})
}
}
/* 插件接入规范区 */
//插件启用
export const activate = async () => {
//获取权限
access(APIPermissions.ADD_PLATFORM, {
code: QQ.CODE,
vendor: QQ,
name: 'QQ音乐',
shortName: 'QQ',
online: true,
types: ['playlists', 'artists', 'albums', 'videos'],
scopes: ['playlists', 'artists', 'albums', 'search', 'userhome', 'random', 'united'],
artistTabs: [ 'hot-songs', 'albums','about' ],
searchTabs: [ 'all-songs', 'playlists', 'albums', 'artists', 'videos' ],
weight: 8
})
//获取UserAgent
//const userAgent = await access(APIPermissions.GET_USER_AGENT)
access(APIPermissions.ADD_REQUEST_HANDLER, {
id: QQ.CODE,
hosts: ['qq.com'],
defaultHeaders: {
Origin: 'https://y.qq.com/',
Referer: 'https://y.qq.com/',
},
includes: [{
pattern: 'moviets.tc.qq.com',
headers: {
Origin: 'https://v.qq.com/',
Referer: 'https://v.qq.com/'
}
}]
})
console.log('[ PLUGIN - Activated ] 音乐平台 - QQ音乐')
}
//插件停用
export const deactivate = () => {
//归还权限
access(APIPermissions.REMOVE_PLATFORM, QQ.CODE)
access(APIPermissions.REMOVE_REQUEST_HANDLER, QQ.CODE)
console.log('[ PLUGIN - Deactivated ] 音乐平台 - QQ音乐')
}
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
JavaScript
1
https://gitee.com/rive08/less-player-desktop-plugins.git
git@gitee.com:rive08/less-player-desktop-plugins.git
rive08
less-player-desktop-plugins
Less Player Desktop Plugins
master

搜索帮助

344bd9b3 5694891 D2dac590 5694891