diff --git a/src/app.css b/src/app.css index 54f0346..d1a23f3 100644 --- a/src/app.css +++ b/src/app.css @@ -6,6 +6,7 @@ img { max-width: 100%; + max-height: 100%; } /* Hide scrollbar for Chrome, Safari and Opera */ diff --git a/src/lib/components/media/mediaCard.svelte b/src/lib/components/media/mediaCard.svelte index 2134641..0d14fb4 100644 --- a/src/lib/components/media/mediaCard.svelte +++ b/src/lib/components/media/mediaCard.svelte @@ -4,106 +4,58 @@ import IconButton from '$lib/components/util/iconButton.svelte' import { goto } from '$app/navigation' - const iconClasses = { - song: 'fa-solid fa-music', - album: 'fa-solid fa-compact-disc', - artist: 'fa-solid fa-user', - playlist: 'fa-solid fa-forward-fast', - } + let image: HTMLImageElement, captionText: HTMLDivElement - let card: HTMLDivElement, cardGlare: HTMLDivElement - - const rotateCard = (event: MouseEvent): void => { - const cardRect = card.getBoundingClientRect() - const x = (2 * (event.x - cardRect.left)) / cardRect.width - 1 // These are simplified calculations to find the x-y coords relative to the center of the card - const y = (2 * (cardRect.top - event.y)) / cardRect.height + 1 - - const angle = Math.atan(x / y) // You'd think it should be y / x but it's actually the inverse - const distanceFromCorner = Math.sqrt((x - 1) ** 2 + (y - 1) ** 2) // This is a cool little trick, the -1 on the x an y coordinate is effective the same as saying "make the origin of the glare [1, 1]" - - cardGlare.style.backgroundImage = `linear-gradient(${angle}rad, transparent ${distanceFromCorner * 50 + 50}%, rgba(255, 255, 255, 0.1) ${distanceFromCorner * 50 + 60}%, transparent 100%)` - card.style.transform = `rotateX(${y * 10}deg) rotateY(${x * 10}deg)` - } - - const checkSong = (item: MediaItem): item is Song => { - return (item as Song).type === 'song' - } - const checkAlbum = (item: MediaItem): item is Album => { - return (item as Album).type === 'album' + const checkSongOrAlbum = (item: MediaItem): item is Song | Album => { + return item.type === 'song' || item.type === 'album' } -
-
rotateCard(event)} - on:mouseleave={() => (card.style.transform = '')} - role="menuitem" - tabindex="-1" - > - - +
+
-
-
{mediaItem.name}
-
- {#if checkSong(mediaItem) || checkAlbum(mediaItem)} + +
+
{mediaItem.name}
+
+ {#if checkSongOrAlbum(mediaItem) && 'artists' in mediaItem && mediaItem.artists} {#each mediaItem.artists as artist} {@const listIndex = mediaItem.artists.indexOf(artist)} {artist.name} - {#if listIndex === mediaItem.artists.length - 2} - & - {:else if listIndex < mediaItem.artists.length - 2} + {#if listIndex < mediaItem.artists.length - 1} , {/if} {/each} {/if} -
diff --git a/src/lib/components/util/iconButton.svelte b/src/lib/components/util/iconButton.svelte index 9847e6b..a726cc1 100644 --- a/src/lib/components/util/iconButton.svelte +++ b/src/lib/components/util/iconButton.svelte @@ -7,7 +7,13 @@ const dispatch = createEventDispatcher() - diff --git a/src/lib/server/users.db b/src/lib/server/users.db index 6e27b59..1eacbe3 100644 Binary files a/src/lib/server/users.db and b/src/lib/server/users.db differ diff --git a/src/lib/service-managers/youtube-music-types.d.ts b/src/lib/service-managers/youtube-music-types.d.ts new file mode 100644 index 0000000..b1007fc --- /dev/null +++ b/src/lib/service-managers/youtube-music-types.d.ts @@ -0,0 +1,192 @@ +export namespace InnerTube { + interface BrowseResponse { + responseContext: { + visitorData: string + serviceTrackingParams: object[] + maxAgeSeconds: number + } + contents: { + singleColumnBrowseResultsRenderer: { + tabs: [ + { + tabRenderer: { + endpoint: object + title: 'Home' + selected: boolean + content: { + sectionListRenderer: { + contents: { + musicCarouselShelfRenderer: musicCarouselShelfRenderer + }[] + continuations: [object] + trackingParams: string + header: { + chipCloudRenderer: object + } + } + } + icon: object + tabIdentifier: 'FEmusic_home' + trackingParams: string + } + }, + ] + } + } + trackingParams: string + maxAgeStoreSeconds: number + background: { + musicThumbnailRenderer: { + thumbnail: object + thumbnailCrop: string + thumbnailScale: string + trackingParams: string + } + } + } + + type musicCarouselShelfRenderer = { + header: { + musicCarouselShelfBasicHeaderRenderer: { + title: { + runs: [runs] + } + strapline: [runs] + accessibilityData: accessibilityData + headerStyle: string + moreContentButton?: { + buttonRenderer: { + style: string + text: { + runs: [runs] + } + navigationEndpoint: navigationEndpoint + trackingParams: string + accessibilityData: accessibilityData + } + } + thumbnail?: musicThumbnailRenderer + trackingParams: string + } + } + contents: + | { + musicTwoRowItemRenderer: musicTwoRowItemRenderer + }[] + | { + musicResponsiveListItemRenderer: musicResponsiveListItemRenderer + }[] + trackingParams: string + itemSize: string + } + + type musicDescriptionShelfRenderer = { + header: { + runs: [runs] + } + description: { + runs: [runs] + } + } + + type musicTwoRowItemRenderer = { + thumbnailRenderer: { + musicThumbnailRenderer: musicThumbnailRenderer + } + aspectRatio: string + title: { + runs: [runs] + } + subtitle: { + runs: runs[] + } + navigationEndpoint: navigationEndpoint + trackingParams: string + menu: unknown + thumbnailOverlay: unknown + } + + type musicResponsiveListItemRenderer = { + thumbnail: { + musicThumbnailRenderer: musicThumbnailRenderer + } + overlay: unknown + flexColumns: { + musicResponsiveListItemFlexColumnRenderer: { + text: { runs: [runs] } + } + }[] + menu: unknown + playlistItemData: { + videoId: string + } + } + + type musicThumbnailRenderer = { + thumbnail: { + thumbnails: { + url: string + width: number + height: number + }[] + } + thumbnailCrop: string + thumbnailScale: string + trackingParams: string + accessibilityData?: accessibilityData + onTap?: navigationEndpoint + targetId?: string + } + + type runs = { + text: string + navigationEndpoint?: navigationEndpoint + } + + type navigationEndpoint = { + clickTrackingParams: string + } & ( + | { + browseEndpoint: browseEndpoint + } + | { + watchEndpoint: watchEndpoint + } + | { + watchPlaylistEndpoint: watchPlaylistEndpoint + } + ) + + type browseEndpoint = { + browseId: string + params?: string + browseEndpointContextSupportedConfigs: { + browseEndpointContextMusicConfig: { + pageType: 'MUSIC_PAGE_TYPE_ALBUM' | 'MUSIC_PAGE_TYPE_ARTIST' | 'MUSIC_PAGE_TYPE_PLAYLIST' + } + } + } + + type watchEndpoint = { + videoId: string + playlistId: string + params?: string + loggingContext: { + vssLoggingContext: object + } + watchEndpointMusicSupportedConfigs: { + watchEndpointMusicConfig: object + } + } + + type watchPlaylistEndpoint = { + playlistId: string + params?: string + } + + type accessibilityData = { + accessibilityData: { + label: string + } + } +} diff --git a/src/lib/service-managers/youtube-music.ts b/src/lib/service-managers/youtube-music.ts index b918af4..23c1b33 100644 --- a/src/lib/service-managers/youtube-music.ts +++ b/src/lib/service-managers/youtube-music.ts @@ -1,197 +1,7 @@ import { google } from 'googleapis' +import type { InnerTube } from './youtube-music-types.d.ts' -declare namespace InnerTube { - interface BrowseResponse { - responseContext: { - visitorData: string - serviceTrackingParams: object[] - maxAgeSeconds: number - } - contents: { - singleColumnBrowseResultsRenderer: { - tabs: [ - { - tabRenderer: { - endpoint: object - title: 'Home' - selected: boolean - content: { - sectionListRenderer: { - contents: { - musicCarouselShelfRenderer: musicCarouselShelfRenderer - }[] - continuations: [object] - trackingParams: string - header: { - chipCloudRenderer: object - } - } - } - icon: object - tabIdentifier: 'FEmusic_home' - trackingParams: string - } - }, - ] - } - } - trackingParams: string - maxAgeStoreSeconds: number - background: { - musicThumbnailRenderer: { - thumbnail: object - thumbnailCrop: string - thumbnailScale: string - trackingParams: string - } - } - } - - type musicCarouselShelfRenderer = { - header: { - musicCarouselShelfBasicHeaderRenderer: { - title: { - runs: [runs] - } - strapline: [runs] - accessibilityData: accessibilityData - headerStyle: string - moreContentButton?: { - buttonRenderer: { - style: string - text: { - runs: [runs] - } - navigationEndpoint: navigationEndpoint - trackingParams: string - accessibilityData: accessibilityData - } - } - thumbnail?: musicThumbnailRenderer - trackingParams: string - } - } - contents: - | { - musicTwoRowItemRenderer: musicTwoRowItemRenderer - }[] - | { - musicResponsiveListItemRenderer: musicResponsiveListItemRenderer - }[] - trackingParams: string - itemSize: string - } - - type musicDescriptionShelfRenderer = { - header: { - runs: [runs] - } - description: { - runs: [runs] - } - } - - type musicTwoRowItemRenderer = { - thumbnailRenderer: { - musicThumbnailRenderer: musicThumbnailRenderer - } - aspectRatio: string - title: { - runs: [runs] - } - subtitle: { - runs: runs[] - } - navigationEndpoint: navigationEndpoint - trackingParams: string - menu: unknown - thumbnailOverlay: unknown - } - - type musicResponsiveListItemRenderer = { - thumbnail: { - musicThumbnailRenderer: musicThumbnailRenderer - } - overlay: unknown - flexColumns: { - musicResponsiveListItemFlexColumnRenderer: { - text: { runs: [runs] } - } - }[] - menu: unknown - playlistItemData: { - videoId: string - } - } - - type musicThumbnailRenderer = { - thumbnail: { - thumbnails: { - url: string - width: number - height: number - }[] - } - thumbnailCrop: string - thumbnailScale: string - trackingParams: string - accessibilityData?: accessibilityData - onTap?: navigationEndpoint - targetId?: string - } - - type runs = { - text: string - navigationEndpoint?: navigationEndpoint - } - - type navigationEndpoint = { - clickTrackingParams: string - } & ( - | { - browseEndpoint: browseEndpoint - } - | { - watchEndpoint: watchEndpoint - } - | { - watchPlaylistEndpoint: watchPlaylistEndpoint - } - ) - - type browseEndpoint = { - browseId: string - params?: string - browseEndpointContextSupportedConfigs: { - browseEndpointContextMusicConfig: { - pageType: 'MUSIC_PAGE_TYPE_ALBUM' | 'MUSIC_PAGE_TYPE_ARTIST' | 'MUSIC_PAGE_TYPE_PLAYLIST' - } - } - } - - type watchEndpoint = { - videoId: string - playlistId: string - params?: string - loggingContext: { - vssLoggingContext: object - } - watchEndpointMusicSupportedConfigs: { - watchEndpointMusicConfig: object - } - } - - type watchPlaylistEndpoint = { - playlistId: string - params?: string - } - - type accessibilityData = { - accessibilityData: { - label: string - } - } -} +// TODO: Change hook based token refresh to YouTubeMusic class middleware export class YouTubeMusic { connectionId: string @@ -253,7 +63,6 @@ export class YouTubeMusic { }), }) - console.log(response.status) const data: InnerTube.BrowseResponse = await response.json() const contents = data.contents.singleColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents @@ -281,7 +90,6 @@ export class YouTubeMusic { } } - console.log(JSON.stringify(homeItems)) return homeItems } @@ -367,7 +175,7 @@ export class YouTubeMusic { private refineThumbnailUrl = (urlString: string): string => { const url = new URL(urlString) if (url.origin === 'https://i.ytimg.com') { - return urlString.slice(0, urlString.indexOf('?')) + return urlString.slice(0, urlString.indexOf('?')).replace('sddefault', 'mqdefault') } else if (url.origin === 'https://lh3.googleusercontent.com' || url.origin === 'https://yt3.googleusercontent.com' || url.origin === 'https://yt3.ggpht.com') { return urlString.slice(0, urlString.indexOf('=')) } else { diff --git a/src/routes/(app)/+page.svelte b/src/routes/(app)/+page.svelte index 91bc96d..1279bb0 100644 --- a/src/routes/(app)/+page.svelte +++ b/src/routes/(app)/+page.svelte @@ -6,5 +6,5 @@
- +
diff --git a/src/routes/api/users/[userId]/recommendations/+server.ts b/src/routes/api/users/[userId]/recommendations/+server.ts index 788712d..d0b1166 100644 --- a/src/routes/api/users/[userId]/recommendations/+server.ts +++ b/src/routes/api/users/[userId]/recommendations/+server.ts @@ -36,7 +36,12 @@ export const GET: RequestHandler = async ({ params, fetch }) => { break case 'youtube-music': const youtubeMusic = new YouTubeMusic(connection) - youtubeMusic.getHome() + await youtubeMusic + .getHome() + .then(({ listenAgain, quickPicks, newReleases }) => { + for (const mediaItem of listenAgain) recommendations.push(mediaItem) + }) + .catch() break } }