@timmo001/effect-youtube
v2.1.1
Published
Effect client for YouTube: channel feeds, live and upcoming streams, oEmbed and upnext media items
Readme
@timmo001/effect-youtube
An Effect client for YouTube: channel RSS feeds, live and upcoming streams from the Data API, subscriptions through a Google sign-in, oEmbed lookups, and mapping videos to Up Next media items.
It works on its own and knows nothing about Up Next's config files or socket.
Install
bun add @timmo001/effect-youtube effect
npm install @timmo001/effect-youtube effect
npx jsr add @timmo001/effect-youtubeeffect is a peer dependency, so install the same Effect v4 version your app uses.
Client
YouTubeClient.layer takes an optional Data API key and an optional access token from a Google sign-in. Feeds and oEmbed work without either; videos needs one of them, and subscriptions needs the access token.
import { YouTubeClient, toMediaItem } from "@timmo001/effect-youtube";
import { Effect, Option } from "effect";
import { FetchHttpClient } from "effect/http";
const uploads = Effect.gen(function* () {
const youtube = yield* YouTubeClient;
const entries = yield* youtube.channelFeed("UCxxxxxxxxxxxxxxxxxxxxxx");
return entries.map((entry) => toMediaItem(entry, Option.none()));
}).pipe(
Effect.provide(YouTubeClient.layer({ apiKey: Option.none() })),
Effect.provide(FetchHttpClient.layer),
);channelFeed(channelId): the latest 15 uploads from a channel's RSS feed. It takes the channel ID that starts withUC, not a handle.channelUploads(channelId): the same 15 uploads, read from the channel's uploads playlist through the API, for when the RSS feeds are down. Costs 1 unit of quota and needs the API key or access token.videos(videoIds): titles, live state, stream times and viewer counts, 50 videos per request. The key or token is sent as a header, so it never appears in a URL.subscriptions: every channel the signed-in account subscribes to, 50 per request.playlistTitle(playlistId): a playlist's title, or none when there's no such playlist. Needs the access token for a private playlist.playlistItems(playlistId): every video in a playlist, 50 per request, leaving out deleted and private ones. Needs the access token.addToPlaylist(playlistId, videoId)andremoveFromPlaylist(itemId): change a playlist. They need a sign-in withmanageScope.removeFromPlaylisttakes the entry'sitemId, not the video ID.oembed(url): the title, channel and thumbnail for any video URL, without a key.
toMediaItem(entry, details) turns a feed entry into a MediaItem. With details from videos, live and upcoming streams get the live or upcoming kind and their start time. toSavedItem(entry) turns a playlist entry into a saved item whose URL plays the video in its playlist. videoIdFromUrl reads the video ID from watch, youtu.be, shorts, live and embed URLs, playlistIdFrom reads a playlist ID from an ID or a URL with a list parameter, and playlistUrl builds a playlist's URL.
Google sign-in
authorizeUrl, exchangeCode and refreshTokens handle a Google sign-in. authorizeUrl asks for the read-only YouTube scope, requiredScope, unless you pass scope, such as manageScope to change playlists. They take the ID and secret of a Google OAuth client of the Desktop app type, which can redirect to any loopback port. authorizeUrl asks for offline access, so exchangeCode returns a refresh token. The returned tokens include the scopes Google granted, when it says.
Failures are a YouTubeError, with the HTTP status and the API's reason when there are them, or a YouTubeAuthError when Google rejects the sign-in or refresh token, or the sign-in doesn't allow changing playlists.
Licence
Apache 2.0. See LICENSE.
