Dropped ytdl, YT audio now fetched with Android Client. Began work on YT Premium support

This commit is contained in:
Eclypsed
2024-05-28 00:46:34 -04:00
parent fec4bba61e
commit 11497f8b91
28 changed files with 932 additions and 393 deletions

View File

@@ -5,26 +5,20 @@ export const GET: RequestHandler = async ({ url, request }) => {
const connectionId = url.searchParams.get('connection')
const id = url.searchParams.get('id')
if (!(connectionId && id)) return new Response('Missing query parameter', { status: 400 })
// Might want to re-evaluate how specific I make these ^ v error response messages
const connection = Connections.getConnection(connectionId)
if (!connection) return new Response('Invalid connection id', { status: 400 })
const range = request.headers.get('range')
const connection = Connections.getConnections([connectionId])[0]
const audioRequestHeaders = new Headers({ range: request.headers.get('range') ?? 'bytes=0-' })
const fetchStream = async (): Promise<Response> => {
const MAX_TRIES = 5
let tries = 0
while (tries < MAX_TRIES) {
++tries
const stream = await connection.getAudioStream(id, range).catch((reason) => {
console.error(`Audio stream fetch failed: ${reason}`)
return null
})
if (!stream || !stream.ok) continue
const response = await connection
.getAudioStream(id, audioRequestHeaders)
// * Withing the .getAudioStream() method of connections, a TypeError should be thrown if the request was invalid (e.g. non-existent id)
// * A standard Error should be thrown if the fetch to the service's server failed or the request returned invalid data
.catch((error: TypeError | Error) => {
if (error instanceof TypeError) return new Response('Malformed Request', { status: 400 })
return new Response('Failed to fetch valid audio stream', { status: 502 })
})
return stream
}
throw new Error(`Audio stream fetch to connection: ${connection.id} of id ${id} failed`)
}
return await fetchStream()
return response
}

View File

@@ -2,16 +2,21 @@ import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ url }) => {
const ids = url.searchParams.get('ids')?.replace(/\s/g, '').split(',')
if (!ids) return new Response('Missing ids query parameter', { status: 400 })
const ids = url.searchParams.get('id')?.replace(/\s/g, '').split(',')
if (!ids) return new Response('Missing id query parameter', { status: 400 })
const connections: ConnectionInfo[] = []
for (const connection of Connections.getConnections(ids)) {
await connection
.getConnectionInfo()
.then((info) => connections.push(info))
.catch((reason) => console.log(`Failed to fetch connection info: ${reason}`))
}
const connections = (
await Promise.all(
ids.map((id) =>
Connections.getConnection(id)
?.getConnectionInfo()
.catch((reason) => {
console.error(`Failed to fetch connection info: ${reason}`)
return undefined
}),
),
)
).filter((connection): connection is ConnectionInfo => connection?.id !== undefined)
return Response.json({ connections })
}

View File

@@ -0,0 +1,16 @@
import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params, url }) => {
const connectionId = params.connectionId!
const connection = Connections.getConnection(connectionId)
if (!connection) return new Response('Invalid connection id', { status: 400 })
const albumId = url.searchParams.get('id')
if (!albumId) return new Response(`Missing id search parameter`, { status: 400 })
const album = await connection.getAlbum(albumId).catch(() => undefined)
if (!album) return new Response(`Failed to fetch album with id: ${albumId}`, { status: 400 })
return Response.json({ album })
}

View File

@@ -0,0 +1,13 @@
import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params }) => {
const { connectionId, albumId } = params
const connection = Connections.getConnection(connectionId!)
if (!connection) return new Response('Invalid connection id', { status: 400 })
const items = await connection.getAlbumItems(albumId!).catch(() => undefined)
if (!items) return new Response(`Failed to fetch album with id: ${albumId!}`, { status: 400 })
return Response.json({ items })
}

View File

@@ -0,0 +1,16 @@
import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params, url }) => {
const connectionId = params.connectionId!
const connection = Connections.getConnection(connectionId)
if (!connection) return new Response('Invalid connection id', { status: 400 })
const playlistId = url.searchParams.get('id')
if (!playlistId) return new Response(`Missing id search parameter`, { status: 400 })
const playlist = await connection.getPlaylist(playlistId).catch(() => undefined)
if (!playlist) return new Response(`Failed to fetch playlist with id: ${playlistId}`, { status: 400 })
return Response.json({ playlist })
}

View File

@@ -0,0 +1,13 @@
import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params }) => {
const { connectionId, playlistId } = params
const connection = Connections.getConnection(connectionId!)
if (!connection) return new Response('Invalid connection id', { status: 400 })
const items = await connection.getPlaylistItems(playlistId!).catch((reason) => console.error(reason))
if (!items) return new Response(`Failed to fetch playlist with id: ${playlistId!}`, { status: 400 })
return Response.json({ items })
}

View File

@@ -11,19 +11,16 @@ export const GET: RequestHandler = async ({ url }) => {
let tries = 0
while (tries < MAX_TRIES) {
++tries
const response = await fetch(imageUrl).catch((reason) => {
console.error(`Image fetch to ${imageUrl} failed: ${reason}`)
return null
})
const response = await fetch(imageUrl).catch(() => null)
if (!response || !response.ok) continue
const contentType = response.headers.get('content-type')
if (!contentType || !contentType.startsWith('image')) throw new Error(`Url ${imageUrl} does not link to an image`)
if (!contentType || !contentType.startsWith('image')) throw Error(`Url ${imageUrl} does not link to an image`)
return response
}
throw new Error('Exceed Max Retires')
throw Error(`Failed to fetch image at ${url} Exceed Max Retires`)
}
return await fetchImage()

View File

@@ -2,18 +2,27 @@ import type { RequestHandler } from '@sveltejs/kit'
import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ url }) => {
const query = url.searchParams.get('query')
if (!query) return new Response('Missing query parameter', { status: 400 })
const userId = url.searchParams.get('userId')
if (!userId) return new Response('Missing userId parameter', { status: 400 })
const { query, userId, filter } = Object.fromEntries(url.searchParams) as { [k: string]: string | undefined }
if (!(query && userId)) return new Response('Missing search parameter', { status: 400 })
const searchResults: (Song | Album | Artist | Playlist)[] = []
for (const connection of Connections.getUserConnections(userId)) {
await connection
.search(query)
.then((results) => searchResults.push(...results))
.catch((reason) => console.error(`Failed to search "${query}" from connection ${connection.id}: ${reason}`))
}
const userConnections = Connections.getUserConnections(userId)
if (!userConnections) return new Response('Invalid user id', { status: 400 })
let checkedFilter: 'song' | 'album' | 'artist' | 'playlist' | undefined
if (filter === 'song' || filter === 'album' || filter === 'artist' || filter === 'playlist') checkedFilter = filter
const searchResults = (
await Promise.all(
userConnections.map((connection) =>
connection.search(query, checkedFilter).catch((reason) => {
console.error(`Failed to search "${query}" from connection ${connection.id}: ${reason}`)
return undefined
}),
),
)
)
.flat()
.filter((result): result is Song | Album | Artist | Playlist => result?.id !== undefined)
return Response.json({ searchResults })
}

View File

@@ -4,13 +4,19 @@ import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params }) => {
const userId = params.userId!
const connections: ConnectionInfo[] = []
for (const connection of Connections.getUserConnections(userId)) {
await connection
.getConnectionInfo()
.then((info) => connections.push(info))
.catch((reason) => console.log(`Failed to fetch connection info: ${reason}`))
}
const userConnections = Connections.getUserConnections(userId)
if (!userConnections) return new Response('Invalid user id', { status: 400 })
const connections = (
await Promise.all(
userConnections.map((connection) =>
connection.getConnectionInfo().catch((reason) => {
console.log(`Failed to fetch connection info: ${reason}`)
return undefined
}),
),
)
).filter((info): info is ConnectionInfo => info !== undefined)
return Response.json({ connections })
}

View File

@@ -6,13 +6,21 @@ import { Connections } from '$lib/server/connections'
export const GET: RequestHandler = async ({ params }) => {
const userId = params.userId!
const recommendations: (Song | Album | Artist | Playlist)[] = []
for (const connection of Connections.getUserConnections(userId)) {
await connection
.getRecommendations()
.then((connectionRecommendations) => recommendations.push(...connectionRecommendations))
.catch((reason) => console.log(`Failed to fetch recommendations: ${reason}`))
}
const userConnections = Connections.getUserConnections(userId)
if (!userConnections) return new Response('Invalid user id', { status: 400 })
const recommendations = (
await Promise.all(
userConnections.map((connection) =>
connection.getRecommendations().catch((reason) => {
console.log(`Failed to fetch recommendations: ${reason}`)
return undefined
}),
),
)
)
.flat()
.filter((recommendation): recommendation is Song | Album | Artist | Playlist => recommendation?.id !== undefined)
return Response.json({ recommendations })
}