added album and playlist support, cleaned up code
This commit is contained in:
parent
54dbc4e08d
commit
fdd1151cbe
|
@ -2,7 +2,7 @@
|
||||||
"name": "tdlworker",
|
"name": "tdlworker",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"wrangler": "2.0.12"
|
"wrangler": "^2.0.14"
|
||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
109
src/index.js
109
src/index.js
|
@ -7,6 +7,11 @@ const CLIENT_ID = 'dN2N95wCyEBTllu4' // mobile atmos
|
||||||
const AUDIO_QUALITIES = ['LOW', 'HIGH', 'LOSSLESS', 'HI_RES']
|
const AUDIO_QUALITIES = ['LOW', 'HIGH', 'LOSSLESS', 'HI_RES']
|
||||||
const VIDEO_QUALITIES = ['AUDIO_ONLY', 'LOW', 'MEDIUM', 'HIGH']
|
const VIDEO_QUALITIES = ['AUDIO_ONLY', 'LOW', 'MEDIUM', 'HIGH']
|
||||||
|
|
||||||
|
const TYPE_QUALITY_INFO = {
|
||||||
|
tracks: { default: 'LOSSLESS', list: AUDIO_QUALITIES, param: 'audioquality' },
|
||||||
|
videos: { default: 'HIGH', list: VIDEO_QUALITIES, param: 'videoquality' },
|
||||||
|
}
|
||||||
|
|
||||||
function to_form_data(obj) {
|
function to_form_data(obj) {
|
||||||
return Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('&')
|
return Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('&')
|
||||||
}
|
}
|
||||||
|
@ -62,25 +67,28 @@ async function api_call(method, path, params={}, body=null) {
|
||||||
return { status, json }
|
return { status, json }
|
||||||
}
|
}
|
||||||
|
|
||||||
router.get('/track/:id', async ({ params, query }) => {
|
async function playbackinfopostpaywall(type, id, q) {
|
||||||
let id = parseInt(params.id)
|
id = parseInt(id)
|
||||||
if (isNaN(id)) {
|
if (isNaN(id)) {
|
||||||
return new Response('Invalid track id', { status: 400 })
|
return new Response('Invalid id', { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
let quality = query.q || 'LOSSLESS'
|
const quality_info = TYPE_QUALITY_INFO[type]
|
||||||
|
let quality = q || quality_info.default
|
||||||
quality = quality.toUpperCase()
|
quality = quality.toUpperCase()
|
||||||
if (!AUDIO_QUALITIES.includes(quality)) {
|
if (!quality_info.list.includes(quality)) {
|
||||||
return new Response('Invalid quality', { status: 400 })
|
return new Response('Invalid quality', { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api_call('GET', `tracks/${id}/playbackinfopostpaywall`, {
|
let params = {
|
||||||
audioquality: quality,
|
|
||||||
assetpresentation: 'FULL',
|
assetpresentation: 'FULL',
|
||||||
playbackmode: 'OFFLINE'
|
playbackmode: 'OFFLINE'
|
||||||
})
|
}
|
||||||
|
params[quality_info.param] = quality
|
||||||
|
|
||||||
console.log(res.status, res.json)
|
const res = await api_call('GET', `${type}/${id}/playbackinfopostpaywall`, params)
|
||||||
|
|
||||||
|
console.log('playbackinfopostpaywall', res.status, res.json)
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
return new Response('Couldn\'t get manifest', { status: 403 })
|
return new Response('Couldn\'t get manifest', { status: 403 })
|
||||||
|
@ -92,38 +100,89 @@ router.get('/track/:id', async ({ params, query }) => {
|
||||||
|
|
||||||
const manifest = JSON.parse(atob(res.json.manifest))
|
const manifest = JSON.parse(atob(res.json.manifest))
|
||||||
return Response.redirect(manifest.urls[0], 302)
|
return Response.redirect(manifest.urls[0], 302)
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/track/:id', async ({ params, query }) => {
|
||||||
|
return await playbackinfopostpaywall('tracks', params.id, query.q)
|
||||||
})
|
})
|
||||||
|
|
||||||
router.get('/video/:id', async ({ params, query }) => {
|
router.get('/video/:id', async ({ params, query }) => {
|
||||||
let id = parseInt(params.id)
|
return await playbackinfopostpaywall('videos', params.id, query.q)
|
||||||
if (isNaN(id)) {
|
})
|
||||||
return new Response('Invalid video id', { status: 400 })
|
|
||||||
|
function m3u8(items, audio_quality, video_quality, host) {
|
||||||
|
let list = '#EXTM3U\n'
|
||||||
|
|
||||||
|
for (const item_info of items) {
|
||||||
|
const item = item_info.item
|
||||||
|
const type = item_info.type
|
||||||
|
|
||||||
|
let quality
|
||||||
|
if (type === 'track') {
|
||||||
|
quality = audio_quality
|
||||||
|
} else {
|
||||||
|
quality = video_quality
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `https://${host}/${type}/${item.id}?q=${quality}`
|
||||||
|
list += `#EXTINF:${item.duration},${item.title}\n${url}\n`
|
||||||
}
|
}
|
||||||
|
|
||||||
let quality = query.q || 'HIGH'
|
return list
|
||||||
quality = quality.toUpperCase()
|
}
|
||||||
if (!VIDEO_QUALITIES.includes(quality)) {
|
|
||||||
return new Response('Invalid quality', { status: 400 })
|
async function get_items(type, id, aq, vq, host) {
|
||||||
|
let audio_quality = aq || 'LOSSLESS'
|
||||||
|
audio_quality = audio_quality.toUpperCase()
|
||||||
|
if (!AUDIO_QUALITIES.includes(audio_quality)) {
|
||||||
|
return new Response('Invalid audio quality', { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await api_call('GET', `videos/${id}/playbackinfopostpaywall`, {
|
let video_quality = vq || 'HIGH'
|
||||||
videoquality: quality,
|
video_quality = video_quality.toUpperCase()
|
||||||
assetpresentation: 'FULL',
|
if (!VIDEO_QUALITIES.includes(video_quality)) {
|
||||||
playbackmode: 'OFFLINE'
|
return new Response('Invalid video quality', { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await api_call('GET', `${type}/${id}/items`, {
|
||||||
|
offset: 0,
|
||||||
|
limit: 4294967295,
|
||||||
|
countryCode: 'GB',
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log(res.status, res.json)
|
console.log(res.status, res.json)
|
||||||
|
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
return new Response('Couldn\'t get manifest', { status: 403 })
|
return new Response('Couldn\'t get items', { status: 403 })
|
||||||
}
|
}
|
||||||
|
|
||||||
if (res.json.manifestMimeType !== 'application/vnd.tidal.bts') {
|
const m3u = m3u8(res.json.items, audio_quality, video_quality, host)
|
||||||
return new Response('Invalid manifest mime type', { status: 500 })
|
|
||||||
|
return new Response(m3u, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/x-mpegurl'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
router.get('/album/:id', async ({ params, query, headers }) => {
|
||||||
|
let id = parseInt(params.id)
|
||||||
|
if (isNaN(id)) {
|
||||||
|
return new Response('Invalid album id', { status: 400 })
|
||||||
}
|
}
|
||||||
|
|
||||||
const manifest = JSON.parse(atob(res.json.manifest))
|
return await get_items('albums', id, query.aq, query.vq, headers.get('host'))
|
||||||
return Response.redirect(manifest.urls[0], 302)
|
})
|
||||||
|
|
||||||
|
router.get('/playlist/:id', async ({ params, query, headers }) => {
|
||||||
|
let id = params.id
|
||||||
|
|
||||||
|
const uuid_regex = /^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$/gi;
|
||||||
|
if (!uuid_regex.test(id)) {
|
||||||
|
return new Response('Invalid playlist id', { status: 400 })
|
||||||
|
}
|
||||||
|
|
||||||
|
return await get_items('playlists', id, query.aq, query.vq, headers.get('host'))
|
||||||
})
|
})
|
||||||
|
|
||||||
addEventListener('fetch', event =>
|
addEventListener('fetch', event =>
|
||||||
|
|
|
@ -503,10 +503,10 @@ urlpattern-polyfill@^4.0.3:
|
||||||
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-4.0.3.tgz#c1fa7a73eb4e6c6a1ffb41b24cf31974f7392d3b"
|
resolved "https://registry.yarnpkg.com/urlpattern-polyfill/-/urlpattern-polyfill-4.0.3.tgz#c1fa7a73eb4e6c6a1ffb41b24cf31974f7392d3b"
|
||||||
integrity sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==
|
integrity sha512-DOE84vZT2fEcl9gqCUTcnAw5ZY5Id55ikUcziSUntuEFL3pRvavg5kwDmTEUJkeCHInTlV/HexFomgYnzO5kdQ==
|
||||||
|
|
||||||
wrangler@2.0.12:
|
wrangler@^2.0.14:
|
||||||
version "2.0.12"
|
version "2.0.14"
|
||||||
resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-2.0.12.tgz#506af20c67aa2b8dbed480713f1103814a943d08"
|
resolved "https://registry.yarnpkg.com/wrangler/-/wrangler-2.0.14.tgz#3f89bde117ba5e459f29a9be3a4f51a2f2706813"
|
||||||
integrity sha512-Dnzqxl3vW5J16Lt3gEbl/teNnTuAqMwsuMexR7BFSHWTwfWjHT9Sc7B6bPIKw0O01zDU/hUscw8nkyhMMM3xDw==
|
integrity sha512-uPfz2yJ13nbhEdSqdXBoXBANB60cwZ77KkHcP8AIUWHj2GJovQFvvfbBZwO1Ok8JWPhfpkICnIiGU6IBrT7p/w==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@cloudflare/kv-asset-handler" "^0.2.0"
|
"@cloudflare/kv-asset-handler" "^0.2.0"
|
||||||
"@esbuild-plugins/node-globals-polyfill" "^0.1.1"
|
"@esbuild-plugins/node-globals-polyfill" "^0.1.1"
|
||||||
|
|
Loading…
Reference in New Issue