diff --git a/LICENSE b/LICENSE index cdb0799..0ff5373 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2024 suzuki3jp +Copyright (c) 2025 suzuki3jp Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 773bd50..1e879cb 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ We are striving to support more endpoints, but currently, there are many unsuppo | [MembershipsLevels](https://developers.google.com/youtube/v3/docs/membershipsLevels) | × | - | - | - | | [PlaylistImages](https://developers.google.com/youtube/v3/docs/playlistImages) | × | × | × | × | | [PlaylistItems](https://developers.google.com/youtube/v3/docs/playlistItems) | × | × | × | × | -| [Playlists](https://developers.google.com/youtube/v3/docs/playlists) | ✅ | ✅ | × | ✅ | +| [Playlists](https://developers.google.com/youtube/v3/docs/playlists) | ✅ | ✅ | ✅ | ✅ | | [Search](https://developers.google.com/youtube/v3/docs/search) | × | - | - | - | | [Subscriptions](https://developers.google.com/youtube/v3/docs/subscriptions) | × | × | - | × | | [Thumbnails](https://developers.google.com/youtube/v3/docs/thumbnails) | - | × | - | - | diff --git a/docs/02-usecases.md b/docs/02-usecases.md index 5c8448f..d0c320c 100644 --- a/docs/02-usecases.md +++ b/docs/02-usecases.md @@ -1,2 +1,73 @@ -# Use cases -coming soon... \ No newline at end of file +# Use Cases + +## Note +All methods in `youtubes.js` return [`Result`](https://github.com/suzuki3jp/result4js) for safe error handling. +Learn more about error handling with `Result` [here](./01-introduction.md#handling-errors). + +## Pagination +The YouTube Data API v3 sometimes returns paginated responses. +For such endpoints, `youtubes.js` returns data wrapped in [`Pagination`](../src/Pagination.ts) to simplify requesting additional pages. + +```ts +import { ApiClient, StaticOAuthProvider } from "youtubes.js"; + +const oauth = new StaticOAuthProvider({ + accessToken: "ACCESS_TOKEN", +}); +const client = new ApiClient({ oauth }); + +const playlists = await client.playlists.getMine(); // Result, YouTubesJsErrors> +console.log(playlists.data); // The first page of playlists + +// Get the previous page +// Returns null if no previous page exists +const prevPage = await playlists.prev(); // Result, YouTubesJsErrors> | null + +// Get the next page +// Returns null if no next page exists +const nextPage = await playlists.next(); // Result, YouTubesJsErrors> | null + +// Get all pages +// **NOTE**: This method may consume unnecessary quotas, so be careful when using it in actual applications. +// We strongly recommend fetching the next page based on user actions (e.g., scrolling). +const allPages = await playlists.all() // Result, YouTubesJsErrors> +const playlists = await client.playlists.getByIds(["ID1", "ID2"]); // Result, YouTubesJsErrors> +const channelPlaylists = await client.playlists.getByChannelId("CHANNEL_ID"); // Result, YouTubesJsErrors> +``` \ No newline at end of file diff --git a/src/Pagination.ts b/src/Pagination.ts index 3659dac..7a14cab 100644 --- a/src/Pagination.ts +++ b/src/Pagination.ts @@ -89,9 +89,11 @@ export class Pagination { * }); * const client = new ApiClient({ oauth }); * - * const playlists = await client.playlists.getMine(); + * + * // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction. + * const playlists = (await client.playlists.getMine()).throw(); * console.log(playlists.data); // The first page of playlists - * const prevPage = await playlists.prev(); + * const prevPage = (await playlists.prev()).throw(); * console.log(prevPage?.data); // The previous page of playlists or null if there is no previous page * ``` */ @@ -120,9 +122,11 @@ export class Pagination { * }); * const client = new ApiClient({ oauth }); * - * const playlists = await client.playlists.getMine(); + * + * // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction. + * const playlists = (await client.playlists.getMine()).throw(); * console.log(playlists.data); // The first page of playlists - * const nextPage = await playlists.next(); + * const nextPage = (await playlists.next()).throw(); * console.log(nextPage?.data); // The second page of playlists or null if there is no next page * ``` */ @@ -149,8 +153,9 @@ export class Pagination { * }); * const client = new ApiClient({ oauth }); * - * const playlists = await client.playlists.getMine(); - * const allPlaylists = (await playlists.all()).flat(); + * // THIS IS UNSAFE ERROR HANDLING. See the safe error handling in the README.md Introduction. + * const playlists = (await client.playlists.getMine()).throw(); + * const allPlaylists = (await playlists.all()).throw().flat(); * ``` */ public async all(): Promise> { diff --git a/src/index.ts b/src/index.ts index 6178336..7bcf0b9 100644 --- a/src/index.ts +++ b/src/index.ts @@ -8,6 +8,7 @@ export type { } from "./managers/PlaylistManager"; export { ApiClient, ApiClientOptions } from "./ApiClient"; +export { YouTubesJsErrors, YouTubeApiError, LikelyBugError } from "./errors"; export { StaticCredentials, StaticOAuthProvider, diff --git a/src/managers/PlaylistManager.ts b/src/managers/PlaylistManager.ts index 6a35a6c..2b2f1fb 100644 --- a/src/managers/PlaylistManager.ts +++ b/src/managers/PlaylistManager.ts @@ -228,6 +228,53 @@ export class PlaylistManager { return Ok(playlist.data); } + /** + * Updates a playlist by its ID. + * + * - This operation uses 50 quota units. + * - [If you are submitting an update request, and your request does not specify a value for a property that already has a value, the property's existing value will be deleted.](https://developers.google.com/youtube/v3/docs/playlists/update#request-body) + * - For example, when updating a playlist that has a description set, if you don't specify the `description`, it will be set to an empty string. + * - However, for the `privacy` property, it seems to remain unchanged if not specified. + * + * [YouTube Data API Reference](https://developers.google.com/youtube/v3/docs/playlists/update) + * @param options - Options for updating a playlist. + * @returns - The updated playlist. + */ + public async updateById( + options: UpdatePlaylistOptions, + ): Promise> { + const { + id, + title, + description, + privacy, + defaultLanguage, + localizations, + } = options; + const rawData = await wrapGaxios( + this.client.playlists.update({ + part: this.ALL_PARTS, + requestBody: { + id, + snippet: { + title, + description, + defaultLanguage, + }, + status: { + privacyStatus: privacy, + }, + localizations, + }, + }), + ); + if (rawData.isErr()) return Err(rawData.data); + const playlist = Playlist.from(rawData.data, this.logger); + if (playlist.isErr()) return Err(playlist.data); + + return Ok(playlist.data); + } + /** * Deletes a playlist by its ID. * @@ -270,6 +317,20 @@ export interface CreatePlaylistOptions { localizations?: Record; } +export interface UpdatePlaylistOptions { + id: string; + + title: string; + + description?: string; + + privacy?: Privacy; + + defaultLanguage?: string; + + localizations?: Record; +} + export interface PlaylistManagerOptions { oauth: OAuthProviders; logger: Logger;