diff --git a/website/src/content/docs/developing-plugins/album-endpoint.mdx b/website/src/content/docs/developing-plugins/album-endpoint.mdx new file mode 100644 index 00000000..bf8317a5 --- /dev/null +++ b/website/src/content/docs/developing-plugins/album-endpoint.mdx @@ -0,0 +1,48 @@ +--- +layout: "layouts/DocLayout.astro" +title: The AlbumEndpoint +description: "" +order: 6 +--- + +The AlbumEndpoint is used to fetch album information and do album-related actions. In the `src/segments/album.ht` file you can find all the +required method definitions. + +```hetu_script +class AlbumEndpoint { + construct (this.client) + + fun getAlbum(id: string) { + // TODO: Implement method + } + + fun tracks(id: string, {offset: int, limit: int}) { + // TODO: Implement method + } + + fun releases({offset: int, limit: int}) { + // TODO: Implement method + } + + fun save(albumIds: List) { // List + // TODO: Implement method + } + + fun unsave(albumIds: List) { // List + // TODO: Implement method + } +} +``` + +| Method | Description | Returns | +| ------------ | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `getAlbum()` | Fetches album information by ID. | [`SpotubeFullAlbumObject`][SpotubeFullAlbumObject] | +| `tracks()` | Fetches tracks of the specified album. Accepts an ID and optional pagination parameters. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullTrackObject`][SpotubeFullTrackObject] | +| `releases()` | Fetches new album releases user followed artists or globally | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullAlbumObject`][SpotubeFullAlbumObject] | +| `save()` | Saves the specified albums. Accepts a list of album IDs. | `void` | +| `unsave()` | Removes the specified albums from saved albums. Accepts a list of album IDs. | `void` | + +{/* Urls */} +[SpotubePaginationResponseObject]: /models/spotube-pagination-response-object +[SpotubeFullAlbumObject]: /models/spotube-full-album-object +[SpotubeFullTrackObject]: /models/spotube-full-track-object diff --git a/website/src/content/docs/developing-plugins/artist-endpoint.mdx b/website/src/content/docs/developing-plugins/artist-endpoint.mdx new file mode 100644 index 00000000..fef8b44a --- /dev/null +++ b/website/src/content/docs/developing-plugins/artist-endpoint.mdx @@ -0,0 +1,56 @@ +--- +layout: "layouts/DocLayout.astro" +title: The ArtistEndpoint +description: "" +order: 7 +--- + +The ArtistEndpoint is used to fetch artist information and do artist-related actions. In the `src/segments/artist.ht` file you can find all the +required method definitions. + +```hetu_script +class ArtistEndpoint { + var client: HttpClient + + construct (this.client) + + fun getArtist(id: string) { + // TODO: Implement method + } + + fun related(id: string, {offset: int, limit: int}) { + // TODO: Implement method + } + + fun topTracks(id: string, {limit: int, offset: int}) { + // TODO: Implement method + } + + fun albums(id: string, {offset: int, limit: int}) { + // TODO: Implement method + } + + fun save(artistIds: List) { + // TODO: Implement method + } + + fun unsave(artistIds: List) { + // TODO: Implement method + } +} +``` + +| Method | Description | Returns | +| ------------- | -------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | +| `getArtist()` | Fetches artist information by ID. | [`SpotubeFullArtistObject`][SpotubeFullArtistObject] | +| `related()` | Fetches related artists based on the specified artist ID. Accepts optional pagination. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullArtistObject`][SpotubeFullArtistObject] | +| `topTracks()` | Fetches top tracks of the specified artist. Accepts optional pagination. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullTrackObject`][SpotubeFullTrackObject] | +| `albums()` | Fetches albums of the specified artist. Accepts optional pagination. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullAlbumObject`][SpotubeFullAlbumObject] | +| `save()` | Saves the specified artists. Accepts a list of artist IDs. | `void` | +| `unsave()` | Removes the specified artists from saved artists. Accepts a list of artist IDs. | `void` | + +{/* Urls */} +[SpotubePaginationResponseObject]: /models/spotube-pagination-response-object +[SpotubeFullAlbumObject]: /models/spotube-full-album-object +[SpotubeFullArtistObject]: /models/spotube-full-artist-object +[SpotubeFullTrackObject]: /models/spotube-full-track-object diff --git a/website/src/content/docs/developing-plugins/auth-endpoint.mdx b/website/src/content/docs/developing-plugins/auth-endpoint.mdx new file mode 100644 index 00000000..9a7e6ebb --- /dev/null +++ b/website/src/content/docs/developing-plugins/auth-endpoint.mdx @@ -0,0 +1,72 @@ +--- +layout: "layouts/DocLayout.astro" +title: The AuthEndpoint +description: "" +order: 3 +--- + +> If your plugin doesn't need authentication support, you can skip this section. + +In the `src/segments/auth.ht` file you can find all the required method definition. These are the necessary +methods Spotube calls in it's lifecycle. + +```hetu_script +class AuthEndpoint { + var client: HttpClient + final controller: StreamController + + get authStateStream -> Stream => controller.stream + + construct (this.client){ + controller = StreamController.broadcast() + } + + fun isAuthenticated() -> bool { + // TODO: Implement method + return false + } + + fun authenticate() -> Future { + // TODO: Implement method + } + + fun logout() -> Future { + // TODO: Implement method + } +} +``` + +For this specific endpoint, you may need `WebView` or `Forms` to get user inputs. The [`hetu_spotube_plugin`][hetu_spotube_plugin] provides +such APIs. + +> Learn more about it in the [Spotube Plugin API][spotube_plugin_api] section + +### The `.authStateStream` property + +The `AuthEndpoint.authStateStream` property is also necessary to notify Spotube about the authentication status. [`hetu_std`][hetu_std] is a built-in +module and it exports `StreamController` which basically 1:1 copy of the Dart's [StreamController][dart_stream_controller]. +If the status of authentication changes you need to add a new event using the `controller.add` +Following events are respected by Spotube: + +| Name | Description | +| ----------- | ------------------------------------------------------------ | +| `login` | When user successfully completes login | +| `logout` | When user logs out of the service | +| `recovered` | When user's cached/saved credentials are recovered from disk | +| `refreshed` | When user's session is refreshed | + +Example of adding a new authentication event: + +```hetu_script +controller.add({ type: "login" }.toJson()) +``` + +By the way, the event type is a `Map` in the Dart side, so make sure to always convert hetu_script's [structs into Maps][hetu_struct_into_map] + +{/* Urls */} +[hetu_script_import_export_docs]: https://hetu-script.github.io/docs/en-US/grammar/import/ +[hetu_spotube_plugin]: https://github.com/KRTirtho/hetu_spotube_plugin +[spotube_plugin_api]: / +[hetu_std]: https://github.com/hetu-community/hetu_std +[dart_stream_controller]: https://api.flutter.dev/flutter/dart-async/StreamController-class.html +[hetu_struct_into_map]: https://hetu-script.github.io/docs/en-US/api_reference/hetu/#struct diff --git a/website/src/content/docs/developing-plugins/create-your-first-plugin.mdx b/website/src/content/docs/developing-plugins/create-your-first-plugin.mdx index 8ea9d4d2..08a22e8f 100644 --- a/website/src/content/docs/developing-plugins/create-your-first-plugin.mdx +++ b/website/src/content/docs/developing-plugins/create-your-first-plugin.mdx @@ -13,12 +13,71 @@ This guide will help you initialize a plugin project and write your first plugin [spotube-plugin-template][spotube-plugin-template] is a template repository for Spotube plugins. It's a starting point with everything you need to get started with plugin development. You should use it to create your own plugin. -Simply clone or click "Use this template" button on the repository page to create a new repository. +Simply clone or click "Use this template" button on the GitHub repository page to create a new repository. ```bash $ git clone https://github.com/KRTirtho/spotube-plugin-template.git $ cd spotube-plugin-template ``` +## Understanding plugins.json + +After cloning the repository, you will find a file named `plugins.json` in the root directory. +This file is crucial for Spotube to recognize your plugin. It looks like this: + +```json +{ + "type": "metadata", + "version": "1.0.0", + "name": "Alphanumeric plugin name with hyphens or underscore", + "author": "Your Name", + "description": "A brief description of the plugin's functionality.", + "entryPoint": "plugin class name", + "apis": ["webview", "localstorage", "timezone"], + "abilities": ["authentication", "scrobbling"], + "repository": "https://github.com/KRTirtho/spotube-plugin-template", + "pluginApiVersion": "1.0.0" +} +``` + +| Property | Description | +| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `type` | The type of the plugin, which is always `metadata` for Spotube plugins. | +| `version` | The version of the plugin, following [semantic versioning][semantic-version] (e.g., `1.0.0`). | +| `name` | The name of the plugin | +| `author` | The name of the plugin author. | +| `description` | A brief description of the plugin's functionality. | +| `entryPoint` | The name of the class that serves as the entry point for the plugin. | +| `apis` | An array of APIs that the plugin uses. This is used to determine which APIs are available to the plugin. Following APIs are available "webview", "localstorage", "timezone" | +| `abilities` | An array of abilities that the plugin has. This is used to determine which abilities the plugin has. Following abilities can be listed: "authentication", "scrobbling" | +| `repository` | The URL of the plugin's repository. This is used to display the plugin's repository in the plugin manager. | +| `pluginApiVersion` | The version of the plugin API that the plugin uses. This is used to determine if the plugin is compatible with the current version of Spotube. | + +Change the values in the `plugins.json` file to match your plugin's information. + +## Running the `example` app + +There's an `example` folder that contains a simple Flutter app that utilizes all the methods +Spotube would call on your plugin. You can run this app to test your plugin's functionality. + +But first you need too compile the plugin to bytecode. You can simply do this using: + +```shell +$ make +``` + +Make sure you've `make` command installed on your system and also must have the [hetu_script_dev_tools][hetu_script_dev_tools] package globally installed. +After compiling the plugin, you can run the example app like any other Flutter app. + +```shell +$ cd example +$ flutter run +``` + +> Most of the buttons, will not work as they not yet implemented. You've to implement the methods in your plugin source code. +> We will cover how to implement the methods in the next section. + {/* Links */} [spotube-plugin-template]: https://github.com/KRTirtho/spotube-plugin-template +[semantic-version]: https://semver.org/ +[hetu_script_dev_tools]: https://pub.dev/packages/hetu_script_dev_tools diff --git a/website/src/content/docs/developing-plugins/implmenting-plugin-methods.mdx b/website/src/content/docs/developing-plugins/implmenting-plugin-methods.mdx new file mode 100644 index 00000000..012922dd --- /dev/null +++ b/website/src/content/docs/developing-plugins/implmenting-plugin-methods.mdx @@ -0,0 +1,95 @@ +--- +layout: "layouts/DocLayout.astro" +title: Implementing plugin methods +description: Tutorial on how to implement methods in your Spotube plugin. +order: 2 +--- + +In the previous section, you learned how to create a plugin project and run the example app. +In this section, you will learn how to implement methods in your Spotube plugin. + +## The `entryPoint` class + +The `entryPoint` (from the plugin.json) class is the main class of your plugin. You can find it in `src/plugin.ht`. It's the class +that Spotube will instantiate when it loads your plugin. You can pretty much keep this class same as the template, unless you +there's something specific you want to change. + +```hetu_script +// The name of the class should match the `entryPoint` in plugin.json +class TemplateMetadataProviderPlugin { + // These are required properties that Spotube will use to call the methods. + // ==== Start of required properties ==== + var auth: AuthEndpoint + var album: AlbumEndpoint + var artist: ArtistEndpoint + var browse: BrowseEndpoint + var playlist: PlaylistEndpoint + var search: SearchEndpoint + var track: TrackEndpoint + var user: UserEndpoint + var core: CorePlugin + // ==== End of required properties ==== + + var api: HttpClient + + construct (){ + api = HttpClient( + HttpBaseOptions( + baseUrl: "https://example.com" + ) + ) + + auth = AuthEndpoint(api) + + album = AlbumEndpoint(api) + artist = ArtistEndpoint(api) + browse = BrowseEndpoint(api) + playlist = PlaylistEndpoint(api) + search = SearchEndpoint(api) + track = TrackEndpoint(api) + user = UserEndpoint(api) + core = CorePlugin(api) + + auth.authStateStream.listen((event) { + // get authentication events + }) + } +} +``` + +If you read how the import/export works for [hetu_script][hetu_script_import_export_docs], you should realize it's pretty similar to ECMA Script modules or ES6+ Modules +from the JavaScript world. + +```hetu_script +import { AuthEndpoint } from './segments/auth.ht' +import { AlbumEndpoint } from "./segments/album.ht" +import { ArtistEndpoint } from "./segments/artist.ht" +import { BrowseEndpoint } from "./segments/browse.ht" +import { PlaylistEndpoint } from './segments/playlist.ht' +import { SearchEndpoint } from './segments/search.ht' +import { TrackEndpoint } from './segments/track.ht' +import { UserEndpoint } from './segments/user.ht' +import { CorePlugin } from './segments/core.ht' +``` + +## Implementing subclasses + +Now that we've seen `entryPoint` class, we can look into the properties of that classes which are the actual +classes that contains methods that Spotube calls. All of them are in `src/segments` folder + +> **IMPORTANT!:** hetu\*script claims it supports async/await. But unfortunately it still doesn't work yet. +> So for now, we have to bear with .then() +> +> Also, if you've read the hetu_script docs, you should know hetu_script doesn't support Error Handling. +> This is a design decision of the language and the errors should only be handled in the Dart code. +> So there's no try/catch/finally or .catch() method + +In the next section, we will cover how to implement the methods in these classes. + +{/* Urls */} +[hetu_script_import_export_docs]: https://hetu-script.github.io/docs/en-US/grammar/import/ +[hetu_spotube_plugin]: https://github.com/KRTirtho/hetu_spotube_plugin +[spotube_plugin_api]: / +[hetu_std]: https://github.com/hetu-community/hetu_std +[dart_stream_controller]: https://api.flutter.dev/flutter/dart-async/StreamController-class.html +[hetu_struct_into_map]: https://hetu-script.github.io/docs/en-US/api_reference/hetu/#struct diff --git a/website/src/content/docs/developing-plugins/playlist-endpoint.mdx b/website/src/content/docs/developing-plugins/playlist-endpoint.mdx new file mode 100644 index 00000000..70b0a92a --- /dev/null +++ b/website/src/content/docs/developing-plugins/playlist-endpoint.mdx @@ -0,0 +1,81 @@ +--- +layout: "layouts/DocLayout.astro" +title: The PlaylistEndpoint +description: "" +order: 8 +--- + +The PlaylistEndpoint is used to fetch playlist information and do track-related actions. In the `src/segments/playlist.ht` file you can find all the +required method definitions. + +```hetu_script +class PlaylistEndpoint { + var client: HttpClient + + construct (this.client) + + fun getPlaylist(id: string) { + // TODO: Implement method + } + + fun tracks(id: string, { offset: int, limit: int }) { + // TODO: Implement method + } + + fun create(userId: string, { + name: string, + description: string, + public: bool, + collaborative: bool + }) { + // TODO: Implement method + } + + fun update(playlistId: string, { + name: string, + description: string, + public: bool, + collaborative: bool + }) { + // TODO: Implement method + } + + fun deletePlaylist(playlistId: string) { + // TODO: Implement method + } + + fun addTracks(playlistId: string, { trackIds: List, position: int }) { + // TODO: Implement method + } + + + fun removeTracks(playlistId: string, { trackIds: List }) { + // TODO: Implement method + } + + fun save(playlistId: string) { + // TODO: Implement method + } + + fun unsave(playlistId: string) { + // TODO: Implement method + } +} +``` + +| Method | Description | Returns | +| ---------------- | ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | +| `getPlaylist` | Fetches a playlist by its ID. | [`SpotubeFullPlaylistObject`][SpotubeFullPlaylistObject] | +| `tracks` | Fetches tracks in a playlist. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullTrackObject`][SpotubeFullTrackObject] | +| `create` | Creates a new playlist and returns | [`SpotubeFullPlaylistObject`][SpotubeFullPlaylistObject] | +| `update` | Updates an existing playlist. | `void` | +| `deletePlaylist` | Deletes a playlist. | `void` | +| `addTracks` | Adds tracks to a playlist. | `void` | +| `removeTracks` | Removes tracks from a playlist. | `void` | +| `save` | Saves a playlist to the user's library. | `void` | +| `unsave` | Removes a playlist from the user's library. | `void` | + +{/* Urls */} +[SpotubePaginationResponseObject]: /models/spotube-pagination-response-object +[SpotubeFullPlaylistObject]: /models/spotube-full-playlist-object +[SpotubeFullTrackObject]: /models/spotube-full-track-object diff --git a/website/src/content/docs/developing-plugins/track-endpoint.mdx b/website/src/content/docs/developing-plugins/track-endpoint.mdx new file mode 100644 index 00000000..41b4c8ac --- /dev/null +++ b/website/src/content/docs/developing-plugins/track-endpoint.mdx @@ -0,0 +1,43 @@ +--- +layout: "layouts/DocLayout.astro" +title: The TrackEndpoint +description: "" +order: 5 +--- + +The TrackEndpoint is used to fetch track information and do track-related actions. In the `src/segments/track.ht` file you can find all the +required method definitions. + +```hetu_script +class TrackEndpoint { + var client: HttpClient + + construct (this.client) + + fun getTrack(id: string) { + // TODO: Implement method + } + + fun save(trackIds: List) { // List + // TODO: Implement method + } + + fun unsave(trackIds: List) { // List + // TODO: Implement method + } + + fun radio(id: string) { + // TODO: Implement method + } +} +``` + +| Method | Description | Returns | +| ------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------- | +| `getTrack()` | Fetches track information by ID. | [SpotubeFullTrackObject][SpotubeFullTrackObject] | +| `save()` | Saves the specified tracks. Accepts a list of track IDs. | void | +| `unsave()` | Removes the specified tracks from saved tracks. Accepts a list of track IDs. | void | +| `radio()` | Fetches related tracks based on specified tracks. Try to return a List of 50 tracks. | [List\][SpotubeFullTrackObject] | + +{/* Urls */} +[SpotubeFullTrackObject]: /models/spotube-full-track-object diff --git a/website/src/content/docs/developing-plugins/user-endpoint.mdx b/website/src/content/docs/developing-plugins/user-endpoint.mdx new file mode 100644 index 00000000..5ce8e7b5 --- /dev/null +++ b/website/src/content/docs/developing-plugins/user-endpoint.mdx @@ -0,0 +1,83 @@ +--- +layout: "layouts/DocLayout.astro" +title: The UserEndpoint +description: "" +order: 4 +--- + +The UserEndpoint is used to fetch user information and manage user-related actions. +In the `src/segments/user.ht` file you can find all the required method definitions. These are the necessary +methods Spotube calls in its lifecycle. + +> Most of these methods should be just a mapping to an API call with minimum latency. Avoid calling plugin APIs like WebView or Forms +> in these methods. User interactions should be avoided here generally. + +```hetu_script +class UserEndpoint { + var client: HttpClient + + construct (this.client) + + fun me() { + // TODO: Implement method + } + + fun savedTracks({ offset: int, limit: int }) { + // TODO: Implement method + } + + fun savedPlaylists({ offset: int, limit: int }) { + // TODO: Implement method + } + + fun savedAlbums({ offset: int, limit: int }) { + // TODO: Implement method + } + + fun savedArtists({ offset: int, limit: int }) { + // TODO: Implement method + } + + fun isSavedPlaylist(playlistId: string) { // Future + // TODO: Implement method + } + + fun isSavedTracks(trackIds: List) { // Future> + // TODO: Implement method + } + + fun isSavedAlbums(albumIds: List) { // Future> + // TODO: Implement method + } + + fun isSavedArtists(artistIds: List) { // Future> + // TODO: Implement method + } +} +``` + +These methods are pretty self-explanatory. You need to implement them to fetch user information from your service. + +| Method | Description | Returns | +| ------------------- | ------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- | +| `me()` | Fetches the current user's information. | [`SpotubeUserObject`][SpotubeUserObject] | +| `savedTracks()` | Fetches the user's saved tracks with pagination support. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullTrackObject`][SpotubeFullTrackObject] | +| `savedPlaylists()` | Fetches the user's saved playlists with pagination support. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullPlaylistObject`][SpotubeFullPlaylistObject] | +| `savedAlbums()` | Fetches the user's saved albums with pagination support. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullAlbumObject`][SpotubeFullAlbumObject] | +| `savedArtists()` | Fetches the user's saved artists with pagination support. | [`SpotubePaginationResponseObject`][SpotubePaginationResponseObject] of [`SpotubeFullArtistObject`][SpotubeFullArtistObject] | +| `isSavedPlaylist()` | Checks if a playlist is saved by the user. Returns a `Future`. | `bool` | +| `isSavedTracks()` | Checks if tracks are saved by the user. Returns a `Future>`. | `List` (each boolean corresponds to a track ID) | +| `isSavedAlbums()` | Checks if albums are saved by the user. Returns a `Future>`. | `List` (each boolean corresponds to an album ID) | +| `isSavedArtists()` | Checks if artists are saved by the user. Returns a `Future>`. | `List` (each boolean corresponds to an artist ID) | + +> Note: The `isSavedTracks`, `isSavedAlbums`, and `isSavedArtists` methods accept a list of IDs and return a list of booleans +> indicating whether each item is saved by the user. The order of the booleans in the list corresponds to the order of the IDs +> in the input list. + +{/* Links */} +[SpotubeUserObject]: /models/spotube-user-object +[SpotubePaginationResponseObject]: /models/spotube-pagination-response-object +[SpotubeFullTrackObject]: /models/spotube-full-track-object +[SpotubeFullPlaylistObject]: /models/spotube-full-playlist-object +[SpotubeFullAlbumObject]: /models/spotube-full-album-object +[SpotubeFullArtistObject]: /models/spotube-full-artist-object