Implementing Endpoints
AuthEndpoint
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.
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 provides
such APIs.
Learn more about it in the Spotube Plugin API section
The .authStateStream property
The AuthEndpoint.authStateStream property is also necessary to notify Spotube about the authentication status. hetu_std is a built-in
module and it exports StreamController which basically 1:1 copy of the Dart's StreamController.
If the status of authentication changes you need to add a new event using the controller.add
Following events are respected by Spotube:
Example of adding a new authentication event:
controller.add({ type: "login" }.toJson())
By the way, the event type is a Map<String, dynamic> in the Dart side, so make sure to always convert hetu_script's structs into Maps
UserEndpoint
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.
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<bool>
// TODO: Implement method
}
fun isSavedTracks(trackIds: List) { // Future<List<bool>>
// TODO: Implement method
}
fun isSavedAlbums(albumIds: List) { // Future<List<bool>>
// TODO: Implement method
}
fun isSavedArtists(artistIds: List) { // Future<List<bool>>
// TODO: Implement method
}
}
These methods are pretty self-explanatory. You need to implement them to fetch user information from your service.
Note: The
isSavedTracks,isSavedAlbums, andisSavedArtistsmethods 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.
TrackEndpoint
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.
class TrackEndpoint {
var client: HttpClient
construct (this.client)
fun getTrack(id: string) {
// TODO: Implement method
}
fun save(trackIds: List) { // List<String>
// TODO: Implement method
}
fun unsave(trackIds: List) { // List<String>
// TODO: Implement method
}
fun radio(id: string) {
// TODO: Implement method
}
}
{/* Urls */}
AlbumEndpoint
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.
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<String>
// TODO: Implement method
}
fun unsave(albumIds: List) { // List<String>
// TODO: Implement method
}
}
ArtistEndpoint
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.
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
}
}
PlaylistEndpoint
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.
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
}
}
SearchEndpoint
The SearchEndpoint is used to fetch search playlist, tracks, album and artists. In the src/segments/search.ht file you can find all the
required method definitions.
class SearchEndpoint {
var client: HttpClient
construct (this.client)
get chips -> List { // Set<string>
// can be tracks, playlists, artists, albums and all
return ["all", "tracks", "albums", "artists", "playlists"]
}
fun all(query: string) {
// TODO: Implement method
}
fun albums(query: string, {offset: int, limit: int}) {
// TODO: Implement method
}
fun artists(query: string, {offset: int, limit: int}) {
// TODO: Implement method
}
fun tracks(query: string, {offset: int, limit: int}) {
// TODO: Implement method
}
fun playlists(query: string, {offset: int, limit: int}) {
// TODO: Implement method
}
}
BrowseEndpoint
The BrowseEndpoint is used to fetch recommendations and catalogs of playlists, albums and artists. In the src/segments/browse.ht file you can find all the
required method definitions.
class BrowseEndpoint {
var client: HttpClient
construct (this.client)
fun sections({offset: int, limit: int}) {
// TODO: Implement method
}
fun sectionItems(id: string, {offset: int, limit: int}) {
// TODO: Implement method
}
}
In
sectionItems()Theidit takes comes fromsections(). It is basically used in an expanded screen to show the browse section items with pagination.For sections returned by
sections()ifbrowseMoreistruethat's whensectionItems()is used to fetch the items of that section.
By the way, the Object can be any of the following types:
CoreEndpoint
The CoreEndpoint is a special subclass which is used to check update and scrobbling and to get support text. In the src/segments/core.ht file you can find all the
required method definitions.
class CorePlugin {
var client: HttpClient
construct (this.client)
/// Checks for updates to the plugin.
/// [currentConfig] is just plugin.json's file content.
///
/// If there's an update available, it will return a map of:
/// - [downloadUrl] -> direct download url to the new plugin.smplug file.
/// - [version] of the new plugin.
/// - [changelog] Optionally, a changelog for the update (markdown supported).
///
/// If no update is available, it will return null.
fun checkUpdate(currentConfig: Map) -> Future {
// TODO: Check for updates
}
/// Returns the support information for the plugin in Markdown or plain text.
/// Supports images and links.
get support -> string {
// TODO: Return support information
return ""
}
/// Scrobble the provided details to the scrobbling service supported by the plugin.
/// "scrobbling" must be set as an ability in the plugin.json
/// [details] is a map containing the scrobble information, such as:
/// - [id] -> The unique identifier of the track.
/// - [title] -> The title of the track.
/// - [artists] -> List of artists
/// - [id] -> The unique identifier of the artist.
/// - [name] -> The name of the artist.
/// - [album] -> The album of the track
/// - [id] -> The unique identifier of the album.
/// - [name] -> The name of the album.
/// - [timestamp] -> The timestamp of the scrobble (optional).
/// - [duration_ms] -> The duration of the track in milliseconds (optional).
/// - [isrc] -> The ISRC code of the track (optional).
fun scrobble(details: Map) {
// TODO: Implement scrobbling
}
}
In the
checkUpdate()method theplugin.json's content will be passed as map. You can use that to check updates using theversionfield.Also, the
downloadUrlit provides should be a direct binary download link (redirect is supported) for the.smplugfile