updated itty-router, changed client id

This commit is contained in:
uh wot 2023-11-04 03:17:21 +01:00
parent fdd1151cbe
commit e11199165b
Signed by: uhwot
GPG key ID: CB2454984587B781
5 changed files with 461 additions and 428 deletions

View file

@ -1,8 +1,10 @@
import { Router } from 'itty-router'
import { error, json as jsonResp, html, Router, createResponse } from 'itty-router'
const router = Router()
const CLIENT_ID = 'dN2N95wCyEBTllu4' // mobile atmos
// android automotive new2 atmos client
const CLIENT_ID = '4N3n6Q1x95LL5K7p'
const CLIENT_SECRET = 'oKOXfJW371cX6xaZ0PyhgGNBdNLlBZd4AKKYougMjik='
const AUDIO_QUALITIES = ['LOW', 'HIGH', 'LOSSLESS', 'HI_RES']
const VIDEO_QUALITIES = ['AUDIO_ONLY', 'LOW', 'MEDIUM', 'HIGH']
@ -16,7 +18,7 @@ function to_form_data(obj) {
return Object.entries(obj).map(([k, v]) => `${k}=${v}`).join('&')
}
async function renew_token() {
async function renew_token(env) {
const res = await fetch('https://auth.tidal.com/v1/oauth2/token', {
method: 'POST',
headers: {
@ -24,8 +26,9 @@ async function renew_token() {
},
body: to_form_data({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
grant_type: 'refresh_token',
refresh_token: REFRESH_TOKEN,
refresh_token: env.REFRESH_TOKEN,
scope: 'r_usr'
})
})
@ -34,19 +37,19 @@ async function renew_token() {
const access_token = json.access_token
console.log('access_token', access_token)
await KV.put('access_token', access_token, {expirationTtl: json.expires_in})
await env.KV.put('access_token', access_token, {expirationTtl: json.expires_in})
return access_token
}
async function api_call(method, path, params={}, body=null) {
async function api_call(env, method, path, params={}, body=null) {
let url = `https://api.tidal.com/v1/${path}`
if (params) {
url += '?' + to_form_data(params)
}
let access_token = await KV.get('access_token')
let access_token = await env.KV.get('access_token')
if (access_token === null) {
access_token = await renew_token()
access_token = await renew_token(env)
}
if (body) {
@ -67,47 +70,47 @@ async function api_call(method, path, params={}, body=null) {
return { status, json }
}
async function playbackinfopostpaywall(type, id, q) {
async function playbackinfopostpaywall(env, type, id, q) {
id = parseInt(id)
if (isNaN(id)) {
return new Response('Invalid id', { status: 400 })
return error(400, 'Invalid id')
}
const quality_info = TYPE_QUALITY_INFO[type]
let quality = q || quality_info.default
quality = quality.toUpperCase()
if (!quality_info.list.includes(quality)) {
return new Response('Invalid quality', { status: 400 })
return error(400, 'Invalid quality')
}
let params = {
assetpresentation: 'FULL',
playbackmode: 'OFFLINE'
playbackmode: 'STREAM'
}
params[quality_info.param] = quality
const res = await api_call('GET', `${type}/${id}/playbackinfopostpaywall`, params)
const res = await api_call(env, 'GET', `${type}/${id}/playbackinfopostpaywall/v4`, params)
console.log('playbackinfopostpaywall', res.status, res.json)
if (res.status !== 200) {
return new Response('Couldn\'t get manifest', { status: 403 })
return error(403, 'Couldn\'t get manifest')
}
if (res.json.manifestMimeType !== 'application/vnd.tidal.bts') {
return new Response('Invalid manifest mime type', { status: 500 })
if (!('application/vnd.tidal.bts', 'application/vnd.tidal.emu').includes(res.json.manifestMimeType)) {
return error(500, 'Invalid manifest mime type')
}
const manifest = JSON.parse(atob(res.json.manifest))
return Response.redirect(manifest.urls[0], 302)
}
router.get('/track/:id', async ({ params, query }) => {
return await playbackinfopostpaywall('tracks', params.id, query.q)
router.get('/track/:id', async ({ params, query }, env) => {
return await playbackinfopostpaywall(env, 'tracks', params.id, query.q)
})
router.get('/video/:id', async ({ params, query }) => {
return await playbackinfopostpaywall('videos', params.id, query.q)
router.get('/video/:id', async ({ params, query }, env) => {
return await playbackinfopostpaywall(env, 'videos', params.id, query.q)
})
function m3u8(items, audio_quality, video_quality, host) {
@ -131,60 +134,59 @@ function m3u8(items, audio_quality, video_quality, host) {
return list
}
async function get_items(type, id, aq, vq, host) {
async function get_items(type, id, aq, vq, host, env) {
let audio_quality = aq || 'LOSSLESS'
audio_quality = audio_quality.toUpperCase()
if (!AUDIO_QUALITIES.includes(audio_quality)) {
return new Response('Invalid audio quality', { status: 400 })
return error(400, 'Invalid audio quality')
}
let video_quality = vq || 'HIGH'
video_quality = video_quality.toUpperCase()
if (!VIDEO_QUALITIES.includes(video_quality)) {
return new Response('Invalid video quality', { status: 400 })
return error(400, 'Invalid video quality')
}
const res = await api_call('GET', `${type}/${id}/items`, {
const res = await api_call(env, 'GET', `${type}/${id}/items`, {
offset: 0,
limit: 4294967295,
countryCode: 'GB',
countryCode: 'US',
})
console.log(res.status, res.json)
if (res.status !== 200) {
return new Response('Couldn\'t get items', { status: 403 })
return error(403, 'Couldn\'t get items')
}
const m3u = m3u8(res.json.items, audio_quality, video_quality, host)
return new Response(m3u, {
headers: {
'Content-Type': 'application/x-mpegurl'
}
})
return createResponse('application/x-mpegurl')(m3u)
}
router.get('/album/:id', async ({ params, query, headers }) => {
router.get('/album/:id', async ({ params, query, headers }, env) => {
let id = parseInt(params.id)
if (isNaN(id)) {
return new Response('Invalid album id', { status: 400 })
return error(400, 'Invalid album id')
}
return await get_items('albums', id, query.aq, query.vq, headers.get('host'))
return await get_items('albums', id, query.aq, query.vq, headers.get('host'), env)
})
router.get('/playlist/:id', async ({ params, query, headers }) => {
router.get('/playlist/:id', async ({ params, query, headers }, env) => {
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 error(400, 'Invalid playlist id')
}
return await get_items('playlists', id, query.aq, query.vq, headers.get('host'))
return await get_items('playlists', id, query.aq, query.vq, headers.get('host'), env)
})
addEventListener('fetch', event =>
event.respondWith(router.handle(event.request))
)
export default {
fetch: (req, env, ctx) =>
router
.handle(req, env, ctx)
.catch(error), // catch errors
}