mirror of
https://codeberg.org/lucida/lucida.git
synced 2026-11-12 14:47:24 -05:00
add dispatcher support for all platforms
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { fetch } from 'undici'
|
||||
import { Dispatcher, fetch } from 'undici'
|
||||
import {
|
||||
Album,
|
||||
Artist,
|
||||
@@ -29,6 +29,7 @@ import { Blowfish } from 'blowfish-cbc'
|
||||
|
||||
interface DeezerOptions {
|
||||
arl?: string
|
||||
dispatcher?: Dispatcher | undefined
|
||||
}
|
||||
|
||||
interface APIMethod {
|
||||
@@ -86,11 +87,13 @@ export default class Deezer implements StreamerWithLogin {
|
||||
renewTimestamp?: number
|
||||
country?: string
|
||||
language?: string
|
||||
dispatcher?: Dispatcher | undefined
|
||||
|
||||
availableFormats: Set<DeezerFormat> = new Set()
|
||||
|
||||
constructor(options?: DeezerOptions) {
|
||||
if (options?.arl) this.arl = options.arl
|
||||
//if (options?.dispatcher) this.dispatcher = options.dispatcher
|
||||
}
|
||||
|
||||
async #apiCall<T extends keyof APIMethod>(
|
||||
@@ -114,7 +117,12 @@ export default class Deezer implements StreamerWithLogin {
|
||||
}
|
||||
|
||||
const body = JSON.stringify(data)
|
||||
const req = await fetch(url, { method: 'POST', body, headers: this.headers })
|
||||
const req = await fetch(url, {
|
||||
method: 'POST',
|
||||
body,
|
||||
headers: this.headers,
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
const { results, error } = <DeezerResponse>await req.json()
|
||||
|
||||
if (error.constructor.name == 'Object') {
|
||||
@@ -167,7 +175,10 @@ export default class Deezer implements StreamerWithLogin {
|
||||
async login(username: string, password: string): Promise<void> {
|
||||
if (this.arl) await this.#loginViaArl(this.arl)
|
||||
else {
|
||||
const resp = await fetch('https://www.deezer.com/', { headers: this.headers })
|
||||
const resp = await fetch('https://www.deezer.com/', {
|
||||
headers: this.headers,
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
const setCookie = resp.headers.get('Set-Cookie') ?? ''
|
||||
const sid = setCookie.match(/sid=(fr[0-9a-f]+)/)![1]
|
||||
this.headers['Cookie'] = `sid=${sid}`
|
||||
@@ -181,7 +192,7 @@ export default class Deezer implements StreamerWithLogin {
|
||||
password,
|
||||
hash: this.#md5(CLIENT_ID + username + password + CLIENT_SECRET)
|
||||
})}`,
|
||||
{ headers: this.headers }
|
||||
{ headers: this.headers, dispatcher: this.dispatcher }
|
||||
)
|
||||
const { error } = <DeezerLoginResponse>await loginReq.json()
|
||||
|
||||
@@ -242,7 +253,7 @@ export default class Deezer implements StreamerWithLogin {
|
||||
/* ---------- GET INFO FROM URL ---------- */
|
||||
|
||||
async #unshortenUrl(url: URL): Promise<URL> {
|
||||
const res = await fetch(url, { redirect: 'manual' })
|
||||
const res = await fetch(url, { redirect: 'manual', dispatcher: this.dispatcher })
|
||||
const location = res.headers.get('Location')
|
||||
|
||||
if (res.status != 302 || !location) throw new Error('URL not supported')
|
||||
@@ -351,7 +362,7 @@ export default class Deezer implements StreamerWithLogin {
|
||||
// download
|
||||
|
||||
const url = await this.#getTrackUrl(id, trackToken, trackTokenExpiry, format)
|
||||
const streamResp = await fetch(url)
|
||||
const streamResp = await fetch(url, { dispatcher: this.dispatcher })
|
||||
if (!streamResp.ok)
|
||||
throw new Error(`Failed to get track stream. Status code: ${streamResp.status}`)
|
||||
|
||||
@@ -441,7 +452,8 @@ export default class Deezer implements StreamerWithLogin {
|
||||
}
|
||||
],
|
||||
track_tokens: [trackToken]
|
||||
})
|
||||
}),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
const res = <DeezerMediaResponse>await req.json()
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import crypto from 'crypto'
|
||||
import { fetch } from 'undici'
|
||||
import { fetch, Dispatcher } from 'undici'
|
||||
import {
|
||||
ItemType,
|
||||
StreamerWithLogin,
|
||||
@@ -27,6 +27,7 @@ interface QobuzOptions {
|
||||
appSecret: string
|
||||
appId: string
|
||||
token?: string
|
||||
dispatcher?: Dispatcher
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
@@ -66,11 +67,13 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
token?: string
|
||||
appSecret: string
|
||||
appId: string
|
||||
dispatcher: Dispatcher | undefined
|
||||
fetch?(url: URL | RequestInfo, init?: RequestInit): Promise<Response>
|
||||
constructor(options: QobuzOptions) {
|
||||
this.appSecret = options.appSecret
|
||||
this.appId = options.appId
|
||||
if (options.token) this.token = options.token
|
||||
if (options.dispatcher) this.dispatcher = options.dispatcher
|
||||
}
|
||||
|
||||
async #get(url: string, params: { [key: string]: string | number }) {
|
||||
@@ -83,7 +86,8 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
)}`,
|
||||
{
|
||||
method: 'get',
|
||||
headers: headers(this.token)
|
||||
headers: headers(this.token),
|
||||
dispatcher: this.dispatcher
|
||||
}
|
||||
)
|
||||
if (!response.ok) {
|
||||
@@ -179,7 +183,7 @@ export default class Qobuz implements StreamerWithLogin {
|
||||
)
|
||||
if (trackFileResponse.sample == true)
|
||||
throw new Error(`Could not get non-sample file. Make sure the track isn't purchase-only.`)
|
||||
const streamResponse = await fetch(trackFileResponse.url)
|
||||
const streamResponse = await fetch(trackFileResponse.url, { dispatcher: this.dispatcher })
|
||||
return {
|
||||
mimeType: trackFileResponse.mime_type,
|
||||
sizeBytes: parseInt(<string>streamResponse.headers.get('Content-Length')),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetch, HeadersInit } from 'undici'
|
||||
import { Dispatcher, fetch, HeadersInit } from 'undici'
|
||||
import { DEFAULT_HEADERS } from './constants.js'
|
||||
import {
|
||||
ItemType,
|
||||
@@ -30,6 +30,7 @@ function headers(oauthToken?: string | undefined): HeadersInit {
|
||||
|
||||
interface SoundcloudOptions {
|
||||
oauthToken?: string
|
||||
dispatcher: Dispatcher | undefined
|
||||
}
|
||||
|
||||
interface SoundcloudTranscoding {
|
||||
@@ -69,8 +70,10 @@ export default class Soundcloud implements Streamer {
|
||||
} as const
|
||||
oauthToken?: string
|
||||
client?: ScClient
|
||||
dispatcher: Dispatcher | undefined
|
||||
constructor(options: SoundcloudOptions) {
|
||||
this.oauthToken = options?.oauthToken
|
||||
this.dispatcher = options?.dispatcher
|
||||
}
|
||||
async search(query: string, limit = 20): Promise<SearchResults> {
|
||||
const client = this.client || (await this.#getClient())
|
||||
@@ -81,7 +84,7 @@ export default class Soundcloud implements Streamer {
|
||||
)}&offset=0&linked_partitioning=1&app_locale=en&limit=${limit}`,
|
||||
client
|
||||
),
|
||||
{ method: 'get', headers: headers(this.oauthToken) }
|
||||
{ method: 'get', headers: headers(this.oauthToken), dispatcher: this.dispatcher }
|
||||
)
|
||||
if (!response.ok) {
|
||||
const errMsg = await response.text()
|
||||
@@ -116,7 +119,8 @@ export default class Soundcloud implements Streamer {
|
||||
const response = await (
|
||||
await fetch(`https://soundcloud.com/`, {
|
||||
method: 'get',
|
||||
headers: headers()
|
||||
headers: headers(this.oauthToken),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
).text()
|
||||
|
||||
@@ -163,7 +167,13 @@ export default class Soundcloud implements Streamer {
|
||||
url = url.replace('//m.', '//')
|
||||
|
||||
// getting the IDs and track authorization
|
||||
const html = await (await fetch(url, { method: 'get', headers: headers() })).text()
|
||||
const html = await (
|
||||
await fetch(url, {
|
||||
method: 'get',
|
||||
headers: headers(this.oauthToken),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
).text()
|
||||
|
||||
switch (type) {
|
||||
case 'track': {
|
||||
@@ -177,7 +187,8 @@ export default class Soundcloud implements Streamer {
|
||||
await (
|
||||
await fetch(this.#formatURL(naked, client), {
|
||||
method: 'get',
|
||||
headers: headers(this.oauthToken)
|
||||
headers: headers(this.oauthToken),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
).text()
|
||||
)
|
||||
@@ -248,7 +259,8 @@ export default class Soundcloud implements Streamer {
|
||||
await (
|
||||
await fetch(this.#formatURL(`https://api-v2.soundcloud.com/tracks/${id}`, client), {
|
||||
method: 'get',
|
||||
headers: headers(this.oauthToken)
|
||||
headers: headers(this.oauthToken),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
).text()
|
||||
)
|
||||
@@ -265,7 +277,8 @@ export default class Soundcloud implements Streamer {
|
||||
),
|
||||
{
|
||||
method: 'get',
|
||||
headers: headers(this.oauthToken)
|
||||
headers: headers(this.oauthToken),
|
||||
dispatcher: this.dispatcher
|
||||
}
|
||||
)
|
||||
).json()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { fetch } from 'undici'
|
||||
import { Dispatcher, fetch } from 'undici'
|
||||
import { spawn } from 'child_process'
|
||||
import os from 'node:os'
|
||||
import fs from 'node:fs'
|
||||
@@ -32,6 +32,7 @@ interface TidalOptions {
|
||||
refreshToken: string
|
||||
expires: number
|
||||
countryCode: string
|
||||
dispatcher?: Dispatcher
|
||||
}
|
||||
|
||||
interface LoginData {
|
||||
@@ -71,6 +72,7 @@ export default class Tidal implements Streamer {
|
||||
expires: number
|
||||
countryCode: string
|
||||
userId: number | undefined
|
||||
dispatcher: Dispatcher | undefined
|
||||
hostnames = ['tidal.com', 'www.tidal.com', 'listen.tidal.com']
|
||||
testData = {
|
||||
'https://tidal.com/browse/artist/3908662': {
|
||||
@@ -96,6 +98,7 @@ export default class Tidal implements Streamer {
|
||||
this.refreshToken = options.refreshToken
|
||||
this.expires = options.expires
|
||||
this.countryCode = options.countryCode
|
||||
if (options.dispatcher) this.dispatcher = options.dispatcher
|
||||
|
||||
const getReady = async () => {
|
||||
if (!this.refreshToken) return
|
||||
@@ -130,7 +133,8 @@ export default class Tidal implements Streamer {
|
||||
const response = await fetch(
|
||||
`${base}${url}?${new URLSearchParams(<{ [key: string]: string }>params)}`,
|
||||
{
|
||||
headers: this.headers()
|
||||
headers: this.headers(),
|
||||
dispatcher: this.dispatcher
|
||||
}
|
||||
)
|
||||
if (!response.ok) {
|
||||
@@ -156,13 +160,15 @@ export default class Tidal implements Streamer {
|
||||
}
|
||||
async sessionValid() {
|
||||
const resp = await fetch('https://api.tidal.com/v1/sessions', {
|
||||
headers: this.headers()
|
||||
headers: this.headers(),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
return resp.ok
|
||||
}
|
||||
async getCountryCode() {
|
||||
const sessionResponse = await fetch('https://api.tidal.com/v1/sessions', {
|
||||
headers: this.headers()
|
||||
headers: this.headers(),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
if (sessionResponse.status != 200) return false
|
||||
const sessionData = <SessionData>await sessionResponse.json()
|
||||
@@ -176,7 +182,8 @@ export default class Tidal implements Streamer {
|
||||
body: new URLSearchParams({
|
||||
client_id: this.tvToken,
|
||||
scope: 'r_usr w_usr'
|
||||
})
|
||||
}),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
if (deviceAuthResponse.status != 200) throw new Error(`Couldn't authorize Tidal`)
|
||||
interface DeviceAuth {
|
||||
@@ -199,7 +206,8 @@ export default class Tidal implements Streamer {
|
||||
await new Promise((r) => setTimeout(r, 1000))
|
||||
const loginResponse = await fetch(`${TIDAL_AUTH_BASE}oauth2/token`, {
|
||||
method: 'post',
|
||||
body: new URLSearchParams(params)
|
||||
body: new URLSearchParams(params),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
statusCode = loginResponse.status
|
||||
loginData = <LoginData>await loginResponse.json()
|
||||
@@ -235,7 +243,8 @@ export default class Tidal implements Streamer {
|
||||
client_id: this.tvToken,
|
||||
client_secret: this.tvSecret,
|
||||
grant_type: 'refresh_token'
|
||||
})
|
||||
}),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
|
||||
if (refreshResponse.status == 200) {
|
||||
@@ -350,7 +359,7 @@ export default class Tidal implements Streamer {
|
||||
|
||||
if (playbackInfoResponse.manifestMimeType != 'application/dash+xml') {
|
||||
const manifest = <Manifest>JSON.parse(manifestStr)
|
||||
const streamResponse = await fetch(manifest.urls[0])
|
||||
const streamResponse = await fetch(manifest.urls[0], { dispatcher: this.dispatcher })
|
||||
return {
|
||||
mimeType: manifest.mimeType,
|
||||
sizeBytes: parseInt(<string>streamResponse.headers.get('Content-Length')),
|
||||
@@ -500,7 +509,8 @@ export default class Tidal implements Streamer {
|
||||
if (this.userId == undefined || this.countryCode == undefined) {
|
||||
if (Date.now() > this.expires) await this.refresh()
|
||||
const sessionResponse = await fetch('https://api.tidal.com/v1/sessions', {
|
||||
headers: this.headers()
|
||||
headers: this.headers(),
|
||||
dispatcher: this.dispatcher
|
||||
})
|
||||
const sessionData = <SessionData>await sessionResponse.json()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user