moosync_edk/
api.rs

1// Moosync
2// Copyright (C) 2024, 2025  Moosync <support@moosync.app>
3//
4// This program is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8//
9// This program is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13//
14// You should have received a copy of the GNU General Public License
15// along with this program.  If not, see <http://www.gnu.org/licenses/>.
16
17use extism_pdk::host_fn;
18use serde_json::Value;
19use types::entities::{QueryableAlbum, QueryableArtist, QueryablePlaylist, SearchResult};
20use types::errors::Result as MoosyncResult;
21use types::extensions::MainCommand;
22use types::songs::Song;
23use types::ui::extensions::{
24    AccountLoginArgs, ContextMenuReturnType, CustomRequestReturnType, ExtensionAccountDetail,
25    ExtensionProviderScope, PlaybackDetailsReturnType, PreferenceArgs,
26    SongsWithPageTokenReturnType,
27};
28
29#[allow(unused_variables)]
30/// Trait for handling account-related events.
31pub trait Accounts {
32    /// Called when the main app requests the list of accounts.
33    fn get_accounts(&self) -> MoosyncResult<Vec<ExtensionAccountDetail>> {
34        Err("Not implemented".into())
35    }
36
37    /// Called when the main app requests to perform an account login.
38    fn perform_account_login(&self, args: AccountLoginArgs) -> MoosyncResult<String> {
39        Err("Not implemented".into())
40    }
41
42    /// Called when the main app provides an OAuth callback code.
43    fn oauth_callback(&self, code: String) -> MoosyncResult<()> {
44        Err("Not implemented".into())
45    }
46}
47
48#[allow(unused_variables)]
49/// Trait for handling database-related events.
50pub trait DatabaseEvents {
51    /// Called when a song is added to the database.
52    fn on_song_added(&self, song: Song) -> MoosyncResult<()> {
53        Err("Not implemented".into())
54    }
55
56    /// Called when a song is removed from the database.
57    fn on_song_removed(&self, song: Song) -> MoosyncResult<()> {
58        Err("Not implemented".into())
59    }
60
61    /// Called when a playlist is added to the database.
62    fn on_playlist_added(&self, playlist: QueryablePlaylist) -> MoosyncResult<()> {
63        Err("Not implemented".into())
64    }
65
66    /// Called when a playlist is removed from the database.
67    fn on_playlist_removed(&self, playlist: QueryablePlaylist) -> MoosyncResult<()> {
68        Err("Not implemented".into())
69    }
70}
71
72#[allow(unused_variables)]
73/// Trait for handling preference-related events.
74pub trait PreferenceEvents {
75    /// Called when preferences are changed.
76    fn on_preferences_changed(&self, args: PreferenceArgs) -> MoosyncResult<()> {
77        Err("Not implemented".into())
78    }
79}
80
81#[allow(unused_variables)]
82/// Trait for handling player-related events.
83pub trait PlayerEvents {
84    /// Called when the queue is changed.
85    fn on_queue_changed(&self, queue: Value) -> MoosyncResult<()> {
86        Err("Not implemented".into())
87    }
88
89    /// Called when the volume is changed.
90    fn on_volume_changed(&self) -> MoosyncResult<()> {
91        Err("Not implemented".into())
92    }
93
94    /// Called when the player state is changed.
95    fn on_player_state_changed(&self) -> MoosyncResult<()> {
96        Err("Not implemented".into())
97    }
98
99    /// Called when the song is changed.
100    fn on_song_changed(&self) -> MoosyncResult<()> {
101        Err("Not implemented".into())
102    }
103
104    /// Called when the player is seeked to a specific time.
105    fn on_seeked(&self, time: f64) -> MoosyncResult<()> {
106        Err("Not implemented".into())
107    }
108}
109
110#[allow(unused_variables)]
111/// Trait for handling provider-related events.
112pub trait Provider {
113    /// Called when the main app requests the provider scopes.
114    fn get_provider_scopes(&self) -> MoosyncResult<Vec<ExtensionProviderScope>>;
115
116    /// Called when the main app requests the list of playlists.
117    fn get_playlists(&self) -> MoosyncResult<Vec<QueryablePlaylist>> {
118        Err("Not implemented".into())
119    }
120
121    /// Called when the main app requests the content of a specific playlist.
122    fn get_playlist_content(
123        &self,
124        id: String,
125        next_page_token: Option<String>,
126    ) -> MoosyncResult<SongsWithPageTokenReturnType> {
127        Err("Not implemented".into())
128    }
129
130    /// Called when the main app requests a playlist from a URL.
131    fn get_playlist_from_url(&self, url: String) -> MoosyncResult<Option<QueryablePlaylist>> {
132        Err("Not implemented".into())
133    }
134
135    /// Called when the main app requests playback details for a song.
136    fn get_playback_details(&self, song: Song) -> MoosyncResult<PlaybackDetailsReturnType> {
137        Err("Not implemented".into())
138    }
139
140    /// Called when the main app performs a search.
141    fn search(&self, term: String) -> MoosyncResult<SearchResult> {
142        Err("Not implemented".into())
143    }
144
145    /// Called when the main app requests recommendations.
146    fn get_recommendations(&self) -> MoosyncResult<Vec<Song>> {
147        Err("Not implemented".into())
148    }
149
150    /// Called when the main app requests a song from a URL.
151    fn get_song_from_url(&self, url: String) -> MoosyncResult<Option<Song>> {
152        Err("Not implemented".into())
153    }
154
155    /// Called when the main app handles a custom request.
156    fn handle_custom_request(&self, url: String) -> MoosyncResult<CustomRequestReturnType> {
157        Err("Not implemented".into())
158    }
159
160    /// Called when the main app requests songs of a specific artist.
161    fn get_artist_songs(
162        &self,
163        artist: QueryableArtist,
164        next_page_token: Option<String>,
165    ) -> MoosyncResult<SongsWithPageTokenReturnType> {
166        Err("Not implemented".into())
167    }
168
169    /// Called when the main app requests songs of a specific album.
170    fn get_album_songs(
171        &self,
172        album: QueryableAlbum,
173        next_page_token: Option<String>,
174    ) -> MoosyncResult<SongsWithPageTokenReturnType> {
175        Err("Not implemented".into())
176    }
177
178    /// Called when the main app requests a song from an ID.
179    fn get_song_from_id(&self, id: String) -> MoosyncResult<Option<Song>> {
180        Err("Not implemented".into())
181    }
182
183    /// Called when the main app requests to scrobble a song.
184    fn scrobble(&self, song: Song) -> MoosyncResult<()> {
185        Err("Not implemented".into())
186    }
187
188    /// Called when the main app requests lyrics for a song.
189    fn get_lyrics(&self, song: Song) -> MoosyncResult<String> {
190        Err("Not implemented".into())
191    }
192}
193
194#[allow(unused_variables)]
195/// Trait for handling context menu-related events.
196pub trait ContextMenu {
197    /// Called when the main app requests the context menu for songs.
198    fn get_song_context_menu(&self, songs: Vec<Song>) -> MoosyncResult<Vec<ContextMenuReturnType>> {
199        Err("Not implemented".into())
200    }
201
202    /// Called when the main app requests the context menu for a playlist.
203    fn get_playlist_context_menu(
204        &self,
205        playlist: QueryablePlaylist,
206    ) -> MoosyncResult<Vec<ContextMenuReturnType>> {
207        Err("Not implemented".into())
208    }
209
210    /// Called when the main app performs an action from the context menu.
211    fn on_context_menu_action(&self, action: String) -> MoosyncResult<()> {
212        Err("Not implemented".into())
213    }
214}
215
216/// Trait that combines all other traits for the extension.
217pub trait Extension:
218    Provider + PlayerEvents + PreferenceEvents + DatabaseEvents + Accounts + ContextMenu
219{
220}
221
222#[host_fn]
223extern "ExtismHost" {
224    fn send_main_command(command: MainCommand) -> Option<Value>;
225    fn system_time() -> u64;
226    fn open_clientfd(path: String) -> i64;
227    fn write_sock(sock_id: i64, buf: Vec<u8>) -> i64;
228    fn read_sock(sock_id: i64, read_len: u64) -> Vec<u8>;
229    fn hash(hash_type: String, data: Vec<u8>) -> Vec<u8>;
230}
231
232pub mod extension_api {
233    use serde_json::Value;
234    use types::entities::{GetEntityOptions, QueryablePlaylist};
235    use types::errors::{MoosyncError, Result as MoosyncResult};
236    use types::extensions::MainCommand;
237    use types::preferences::PreferenceUIData;
238    use types::songs::{GetSongOptions, Song};
239    use types::ui::extensions::{AddToPlaylistRequest, PreferenceData};
240    use types::ui::player_details::PlayerState;
241
242    use super::{
243        hash, open_clientfd, read_sock as read_sock_ext, send_main_command, system_time,
244        write_sock as write_sock_ext,
245    };
246
247    macro_rules! create_api_fn {
248        ($(
249            $(#[doc = $doc:literal])*
250            $fn_name:ident (
251                $variant:ident,
252                $( $arg_name:ident : $arg_type:ty ),*
253            ) -> $ret_type:ty
254        );* $(;)?) => {
255            $(
256                $(#[doc = $doc])*
257                pub fn $fn_name($( $arg_name: $arg_type ),*) -> MoosyncResult<$ret_type> {
258                    unsafe {
259                        match send_main_command(MainCommand::$variant($($arg_name),*)) {
260                            Ok(resp) => {
261                                if let Some(resp) = resp {
262                                    return Ok(serde_json::from_value(resp)?);
263                                }
264                                Err(MoosyncError::String("No response".into()))
265                            }
266                            Err(e) => Err(e.to_string().into()),
267                        }
268                    }
269                }
270            )*
271        };
272    }
273
274    macro_rules! create_api_fn_no_resp {
275        ($(
276            $(#[doc = $doc:literal])*
277            $fn_name:ident (
278                $variant:ident,
279                $( $arg_name:ident : $arg_type:ty ),*
280            ) -> $ret_type:ty
281        );* $(;)?) => {
282            $(
283                $(#[doc = $doc])*
284                pub fn $fn_name($( $arg_name: $arg_type ),*) -> MoosyncResult<$ret_type> {
285                    unsafe {
286                        match send_main_command(MainCommand::$variant($($arg_name),*)) {
287                            Ok(_) => {
288                                return Ok(())
289                            }
290                            Err(e) => Err(e.to_string().into()),
291                        }
292                    }
293                }
294            )*
295        };
296    }
297
298    create_api_fn! {
299        /// Retrieves a list of songs based on the provided options.
300        ///
301        /// # Arguments
302        ///
303        /// * `options` - The options to filter the songs.
304        ///
305        /// # Returns
306        ///
307        /// A vector of `Song` objects.
308        get_song(GetSong, options: GetSongOptions) -> Vec<Song>;
309
310        /// Retrieves the current song being played.
311        ///
312        /// # Returns
313        ///
314        /// An optional `Song` object representing the current song.
315        get_current_song(GetCurrentSong,) -> Option<Song>;
316
317        get_entity(GetEntity, options: GetEntityOptions) -> Vec<Value>;
318
319        /// Retrieves the current state of the player.
320        ///
321        /// # Returns
322        ///
323        /// A `PlayerState` object representing the current state of the player.
324        get_player_state(GetPlayerState,) -> PlayerState;
325
326        /// Retrieves the current volume level.
327        ///
328        /// # Returns
329        ///
330        /// A floating-point number representing the current volume level.
331        get_volume(GetVolume,) -> f64;
332
333        /// Retrieves the current playback time.
334        ///
335        /// # Returns
336        ///
337        /// A floating-point number representing the current playback time in seconds.
338        get_time(GetTime,) -> f64;
339
340        /// Retrieves the current playback queue.
341        ///
342        /// # Returns
343        ///
344        /// A vector of `Song` objects representing the current queue.
345        get_queue(GetQueue,) -> Vec<Song>;
346
347        /// Retrieves a preference value based on the provided data.
348        ///
349        /// # Arguments
350        ///
351        /// * `data` - The data to filter the preference.
352        ///
353        /// # Returns
354        ///
355        /// A `Value` object representing the preference.
356        get_preference(GetPreference, data: PreferenceData) -> PreferenceData;
357
358        /// Retrieves a secure preference value based on the provided data.
359        ///
360        /// # Arguments
361        ///
362        /// * `data` - The data to filter the secure preference.
363        ///
364        /// # Returns
365        ///
366        /// A `Value` object representing the secure preference.
367        get_secure(GetSecure, data: PreferenceData) -> PreferenceData;
368
369        /// Adds a new playlist to the main app.
370        ///
371        /// # Arguments
372        ///
373        /// * `playlist` - The playlist to be added.
374        ///
375        /// # Returns
376        ///
377        /// A string representing the ID of the added playlist.
378        add_playlist(AddPlaylist, playlist: QueryablePlaylist) -> String;
379    }
380
381    create_api_fn_no_resp! {
382        /// Sets a preference value based on the provided data.
383        ///
384        /// # Arguments
385        ///
386        /// * `data` - The data to set the preference.
387        set_preference(SetPreference, data: PreferenceData) -> ();
388
389        /// Sets a secure preference value based on the provided data.
390        ///
391        /// # Arguments
392        ///
393        /// * `data` - The data to set the secure preference.
394        set_secure(SetSecure, data: PreferenceData) -> ();
395
396        /// Adds a list of songs to the main app.
397        ///
398        /// # Arguments
399        ///
400        /// * `songs` - The list of songs to be added.
401        add_songs(AddSongs, songs: Vec<Song>) -> ();
402
403        /// Removes a song from the main app.
404        ///
405        /// # Arguments
406        ///
407        /// * `song` - The song to be removed.
408        remove_song(RemoveSong, song: Song) -> ();
409
410        /// Updates a song in the main app.
411        ///
412        /// # Arguments
413        ///
414        /// * `song` - The song to be updated.
415        update_song(UpdateSong, song: Song) -> ();
416
417        /// Adds a song to a playlist.
418        ///
419        /// # Arguments
420        ///
421        /// * `request` - The request containing the song and playlist details.
422        add_to_playlist(AddToPlaylist, request: AddToPlaylistRequest) -> ();
423
424        /// Registers an OAuth token with the main app.
425        ///
426        /// # Arguments
427        ///
428        /// * `token` - The OAuth token to be registered.
429        register_oauth(RegisterOAuth, token: String) -> ();
430
431        /// Opens an external URL.
432        ///
433        /// # Arguments
434        ///
435        /// * `url` - The URL to be opened.
436        open_external_url(OpenExternalUrl, url: String) -> ();
437
438        /// Updates the list of accounts in the main app.
439        ///
440        /// # Arguments
441        ///
442        /// * `package_name` - The optional package name to filter the accounts.
443        update_accounts(UpdateAccounts, package_name: Option<String>) -> ();
444
445        /// Registers user preferences with the main app.
446        ///
447        /// # Arguments
448        ///
449        /// * `prefs` - A vector of `PreferenceUIData` representing the user preferences to register.
450        register_user_preferences(RegisterUserPreference, prefs: Vec<PreferenceUIData>) -> ();
451
452        /// Unregisters user preferences from the main app.
453        ///
454        /// # Arguments
455        ///
456        /// * `pref_keys` - A vector of strings representing the keys of the preferences to unregister.
457        unregister_user_preferences(UnregisterUserPreference, pref_keys: Vec<String>) -> ();
458    }
459
460    pub fn get_system_time() -> u64 {
461        unsafe {
462            if let Ok(time) = system_time() {
463                return time;
464            }
465            0u64
466        }
467    }
468
469    pub fn open_sock(path: String) -> MoosyncResult<i64> {
470        let res = unsafe { open_clientfd(path) };
471        res.map_err(|e| MoosyncError::String(e.to_string()))
472    }
473
474    pub fn write_sock(sock_id: i64, buf: Vec<u8>) -> MoosyncResult<i64> {
475        let res = unsafe { write_sock_ext(sock_id, buf) };
476        res.map_err(|e| MoosyncError::String(e.to_string()))
477    }
478
479    pub fn read_sock(sock_id: i64, read_len: u64) -> MoosyncResult<Vec<u8>> {
480        let res = unsafe { read_sock_ext(sock_id, read_len) };
481        res.map_err(|e| MoosyncError::String(e.to_string()))
482    }
483
484    pub fn gen_hash(hash_type: String, data: Vec<u8>) -> MoosyncResult<Vec<u8>> {
485        let res = unsafe { hash(hash_type, data) };
486        res.map_err(|e| MoosyncError::String(e.to_string()))
487    }
488}