1use extensions_proto::struct_proto::google::protobuf::Struct as ProtoStruct;
18use extism_pdk::{Prost, host_fn};
19
20pub use extensions_proto::moosync::types::{
21 AddPlaylistRequest, AddSongsRequest, AddToPlaylistRequest, ContextMenuActionRequest,
22 ContextMenuReturnType, CustomRequest, ExtensionAccountDetail, ExtensionProviderScope,
23 GetCurrentSongRequest, GetEntityRequest, GetPlayerStateRequest, GetPreferenceRequest,
24 GetQueueRequest, GetSecureRequest, GetSongRequest, GetTimeRequest, GetVolumeRequest,
25 MainCommand, MainCommandResponse, OauthCallbackRequest, OpenExternalUrlRequest,
26 PerformAccountLoginRequest, PlaybackDetailsRequestedRequest, PlayerState,
27 PlayerStateChangedRequest, PlaylistAddedRequest, PlaylistRemovedRequest,
28 PreferenceChangedRequest, PreferenceData, RegisterOauthRequest, RegisterUserPreferenceRequest,
29 RemoveSongRequest, RequestedAlbumSongsRequest, RequestedArtistSongsRequest,
30 RequestedLyricsRequest, RequestedPlaylistContextMenuRequest, RequestedPlaylistFromUrlRequest,
31 RequestedPlaylistSongsRequest, RequestedPlaylistsRequest, RequestedRecommendationsRequest,
32 RequestedSearchResultRequest, RequestedSongContextMenuRequest, RequestedSongFromIdRequest,
33 RequestedSongFromUrlRequest, ScrobbleRequest, SeekedRequest, SetAccountRequest,
34 SetPreferenceRequest, SetSecureRequest, SongAddedRequest, SongChangedRequest,
35 SongQueueChangedRequest, SongRemovedRequest, UnregisterUserPreferenceRequest, UpdateSongRequest,
36 VolumeChangedRequest,
37};
38use preferences_proto::moosync::types::PreferenceItem;
39use songs_proto::moosync::types::{EntityResult, Lyrics, Playlist, SearchResult, Song};
40
41pub type MoosyncResult<T> = Result<T, crate::handler::MoosyncError>;
42pub type AccountLoginArgs = PerformAccountLoginRequest;
43
44#[allow(unused_variables)]
45pub trait Accounts {
47 fn perform_account_login(&self, req: PerformAccountLoginRequest) -> MoosyncResult<String> {
49 Err("Not implemented".into())
50 }
51
52 fn oauth_callback(&self, req: OauthCallbackRequest) -> MoosyncResult<()> {
54 Err("Not implemented".into())
55 }
56}
57
58#[allow(unused_variables)]
59pub trait DatabaseEvents {
61 fn on_song_added(&self, req: SongAddedRequest) -> MoosyncResult<()> {
63 Err("Not implemented".into())
64 }
65
66 fn on_song_removed(&self, req: SongRemovedRequest) -> MoosyncResult<()> {
68 Err("Not implemented".into())
69 }
70
71 fn on_playlist_added(&self, req: PlaylistAddedRequest) -> MoosyncResult<()> {
73 Err("Not implemented".into())
74 }
75
76 fn on_playlist_removed(&self, req: PlaylistRemovedRequest) -> MoosyncResult<()> {
78 Err("Not implemented".into())
79 }
80}
81
82#[allow(unused_variables)]
83pub trait PreferenceEvents {
85 fn on_preferences_changed(&self, req: PreferenceChangedRequest) -> MoosyncResult<()> {
87 Err("Not implemented".into())
88 }
89}
90
91#[allow(unused_variables)]
92pub trait PlayerEvents {
94 fn on_queue_changed(&self, req: SongQueueChangedRequest) -> MoosyncResult<()> {
96 Err("Not implemented".into())
97 }
98
99 fn on_volume_changed(&self, req: VolumeChangedRequest) -> MoosyncResult<()> {
101 Err("Not implemented".into())
102 }
103
104 fn on_player_state_changed(&self, req: PlayerStateChangedRequest) -> MoosyncResult<()> {
106 Err("Not implemented".into())
107 }
108
109 fn on_song_changed(&self, req: SongChangedRequest) -> MoosyncResult<()> {
111 Err("Not implemented".into())
112 }
113
114 fn on_seeked(&self, req: SeekedRequest) -> MoosyncResult<()> {
116 Err("Not implemented".into())
117 }
118}
119
120#[allow(unused_variables)]
121pub trait Provider {
123 fn get_provider_scopes(&self) -> MoosyncResult<Vec<ExtensionProviderScope>>;
125
126 fn get_playlists(&self, req: RequestedPlaylistsRequest) -> MoosyncResult<Vec<Playlist>> {
128 Err("Not implemented".into())
129 }
130
131 fn get_playlist_content(
133 &self,
134 req: RequestedPlaylistSongsRequest,
135 ) -> MoosyncResult<SongsWithPageTokenReturnType> {
136 Err("Not implemented".into())
137 }
138
139 fn get_playlist_from_url(
141 &self,
142 req: RequestedPlaylistFromUrlRequest,
143 ) -> MoosyncResult<Option<Playlist>> {
144 Err("Not implemented".into())
145 }
146
147 fn get_playback_details(
149 &self,
150 req: PlaybackDetailsRequestedRequest,
151 ) -> MoosyncResult<PlaybackDetailsReturnType> {
152 Err("Not implemented".into())
153 }
154
155 fn search(&self, req: RequestedSearchResultRequest) -> MoosyncResult<SearchResult> {
157 Err("Not implemented".into())
158 }
159
160 fn get_recommendations(
162 &self,
163 req: RequestedRecommendationsRequest,
164 ) -> MoosyncResult<Vec<Song>> {
165 Err("Not implemented".into())
166 }
167
168 fn get_song_from_url(&self, req: RequestedSongFromUrlRequest) -> MoosyncResult<Option<Song>> {
170 Err("Not implemented".into())
171 }
172
173 fn handle_custom_request(&self, req: CustomRequest) -> MoosyncResult<CustomRequestReturnType> {
175 Err("Not implemented".into())
176 }
177
178 fn get_artist_songs(
180 &self,
181 req: RequestedArtistSongsRequest,
182 ) -> MoosyncResult<SongsWithPageTokenReturnType> {
183 Err("Not implemented".into())
184 }
185
186 fn get_album_songs(
188 &self,
189 req: RequestedAlbumSongsRequest,
190 ) -> MoosyncResult<SongsWithPageTokenReturnType> {
191 Err("Not implemented".into())
192 }
193
194 fn get_song_from_id(&self, req: RequestedSongFromIdRequest) -> MoosyncResult<Option<Song>> {
196 Err("Not implemented".into())
197 }
198
199 fn scrobble(&self, req: ScrobbleRequest) -> MoosyncResult<()> {
201 Err("Not implemented".into())
202 }
203
204 fn get_lyrics(&self, req: RequestedLyricsRequest) -> MoosyncResult<Option<Lyrics>> {
206 Err("Not implemented".into())
207 }
208}
209
210#[allow(unused_variables)]
211pub trait ContextMenu {
213 fn get_song_context_menu(
215 &self,
216 req: RequestedSongContextMenuRequest,
217 ) -> MoosyncResult<Vec<ContextMenuReturnType>> {
218 Err("Not implemented".into())
219 }
220
221 fn get_playlist_context_menu(
223 &self,
224 req: RequestedPlaylistContextMenuRequest,
225 ) -> MoosyncResult<Vec<ContextMenuReturnType>> {
226 Err("Not implemented".into())
227 }
228
229 fn on_context_menu_action(&self, req: ContextMenuActionRequest) -> MoosyncResult<()> {
231 Err("Not implemented".into())
232 }
233}
234
235pub trait Extension:
237 Provider + PlayerEvents + PreferenceEvents + DatabaseEvents + Accounts + ContextMenu
238{
239}
240
241#[derive(Debug)]
242pub struct PlaybackDetailsReturnType {
243 pub duration: u32,
244 pub url: String,
245}
246
247#[derive(Debug)]
248pub struct SongsWithPageTokenReturnType {
249 pub songs: Vec<Song>,
250 pub next_page_token: Option<String>,
251}
252
253#[derive(Debug)]
254pub struct ContextMenuReturnTypeWrapper(pub ContextMenuReturnType);
255
256#[derive(Debug)]
257pub struct CustomRequestReturnType {
258 pub mime_type: Option<String>,
259 pub data: Option<Vec<u8>>,
260 pub redirect_url: Option<String>,
261}
262
263#[derive(Debug)]
264pub struct EntityInfo {} #[host_fn]
267extern "ExtismHost" {
268 fn send_main_command(command: Prost<MainCommand>) -> Prost<MainCommandResponse>;
269 fn system_time() -> u64;
270 fn open_clientfd(path: String) -> i64;
271 fn write_sock(sock_id: i64, buf: Vec<u8>) -> i64;
272 fn read_sock(sock_id: i64, read_len: u64) -> Vec<u8>;
273 fn hash(hash_type: String, data: Vec<u8>) -> Vec<u8>;
274}
275
276pub mod extension_api {
277 use super::*;
278 use crate::handler::MoosyncError;
279 use crate::response_utils::Extract;
280 use extensions_proto::moosync::types::main_command::Command as MainCommandEnum;
281 use extensions_proto::moosync::types::main_command_response::Response as MainCommandResponseEnum;
282 use songs_proto::moosync::types::{GetEntityOptions, GetSongOptions}; use super::{
285 hash, open_clientfd, read_sock as read_sock_ext, send_main_command, system_time,
286 write_sock as write_sock_ext,
287 };
288
289 macro_rules! create_api_fn {
290 ($(
291 $(#[doc = $doc:literal])*
292 $fn_name:ident (
293 $Variant:ident,
294 $ReqType:ident,
295 $RespType:ident
296 $(, $arg_name:ident : $arg_type:ty )*
297 ) -> $ret_type:ty
298 );* $(;)?) => {
299 $(
300 $(#[doc = $doc])*
301 pub fn $fn_name($( $arg_name: $arg_type ),*) -> MoosyncResult<$ret_type> {
302 unsafe {
303 let request = $ReqType {
304 $( $arg_name: Some($arg_name.into()) ),*
305 };
306 let cmd_enum = MainCommandEnum::$Variant(request);
307 let cmd = MainCommand { command: Some(cmd_enum) };
308
309 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
310
311 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
312 return Err(MoosyncError::String(e.message.clone()));
313 }
314
315 if let Some(MainCommandResponseEnum::$Variant(data)) = res.response {
316 return Ok(data.extract());
317 }
318
319 Err(MoosyncError::String("Host returned invalid response".into()))
320 }
321 }
322 )*
323 };
324 }
325
326 macro_rules! create_api_fn_no_resp {
327 ($(
328 $(#[doc = $doc:literal])*
329 $fn_name:ident (
330 $Variant:ident,
331 $ReqType:ident
332 $(, $arg_name:ident : $arg_type:ty )*
333 ) -> $ret_type:ty
334 );* $(;)?) => {
335 $(
336 $(#[doc = $doc])*
337 pub fn $fn_name($( $arg_name: $arg_type ),*) -> MoosyncResult<$ret_type> {
338 unsafe {
339 let request = $ReqType {
340 $( $arg_name: Some($arg_name.into()) ),*
341 };
342 let cmd_enum = MainCommandEnum::$Variant(request);
343 let cmd = MainCommand { command: Some(cmd_enum) };
344
345 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
346
347 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
348 return Err(MoosyncError::String(e.message.clone()));
349 }
350
351 if let Some(MainCommandResponseEnum::$Variant(_)) = res.response {
352 return Ok(());
353 }
354
355 Err(MoosyncError::String("Host returned invalid response".into()))
356 }
357 }
358 )*
359 };
360 }
361
362 macro_rules! create_api_fn_repeated {
364 ($(
365 $(#[doc = $doc:literal])*
366 $fn_name:ident (
367 $Variant:ident,
368 $ReqType:ident,
369 $field:ident,
370 $arg_name:ident : $arg_type:ty
371 ) -> $ret_type:ty
372 );* $(;)?) => {
373 $(
374 $(#[doc = $doc])*
375 pub fn $fn_name( $arg_name: $arg_type ) -> MoosyncResult<$ret_type> {
376 unsafe {
377 let request = $ReqType {
378 $field: $arg_name, };
380 let cmd_enum = MainCommandEnum::$Variant(request);
381 let cmd = MainCommand { command: Some(cmd_enum) };
382
383 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
384
385 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
386 return Err(MoosyncError::String(e.message.clone()));
387 }
388
389 if let Some(MainCommandResponseEnum::$Variant(_)) = res.response {
390 return Ok(());
391 }
392
393 Err(MoosyncError::String("Host returned invalid response".into()))
394 }
395 }
396 )*
397 };
398 }
399
400 create_api_fn! {
401 get_song(GetSong, GetSongRequest, GetSongResponse, options: GetSongOptions) -> Vec<Song>;
403
404 get_current_song(GetCurrentSong, GetCurrentSongRequest, GetCurrentSongResponse) -> Option<Song>;
406
407 get_entity(GetEntity, GetEntityRequest, GetEntityResponse, options: GetEntityOptions) -> Option<EntityResult>;
408
409 get_player_state(GetPlayerState, GetPlayerStateRequest, GetPlayerStateResponse) -> PlayerState;
411
412 get_volume(GetVolume, GetVolumeRequest, GetVolumeResponse) -> f64;
414
415 get_time(GetTime, GetTimeRequest, GetTimeResponse) -> f64;
417
418 get_queue(GetQueue, GetQueueRequest, GetQueueResponse) -> Option<ProtoStruct>;
420
421 get_preference(GetPreference, GetPreferenceRequest, GetPreferenceResponse, data: PreferenceData) -> PreferenceData;
423
424 get_secure(GetSecure, GetSecureRequest, GetSecureResponse, data: PreferenceData) -> PreferenceData;
426
427 add_playlist(AddPlaylist, AddPlaylistRequest, AddPlaylistResponse, playlist: Playlist) -> String;
429 }
430
431 create_api_fn_no_resp! {
432 set_preference(SetPreference, SetPreferenceRequest, data: PreferenceData) -> ();
434
435 set_secure(SetSecure, SetSecureRequest, data: PreferenceData) -> ();
437
438 remove_song(RemoveSong, RemoveSongRequest, song: Song) -> ();
440
441 update_song(UpdateSong, UpdateSongRequest, song: Song) -> ();
443 }
444
445 pub fn set_account(account: ExtensionAccountDetail) -> MoosyncResult<()> {
447 unsafe {
448 let request = SetAccountRequest {
449 account: Some(account),
450 };
451 let cmd_enum = MainCommandEnum::SetAccount(request);
452 let cmd = MainCommand {
453 command: Some(cmd_enum),
454 };
455
456 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
457
458 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
459 return Err(MoosyncError::String(e.message.clone()));
460 }
461
462 if let Some(MainCommandResponseEnum::SetAccount(_)) = res.response {
463 return Ok(());
464 }
465
466 Err(MoosyncError::String(
467 "Host returned invalid response".into(),
468 ))
469 }
470 }
471 macro_rules! create_api_fn_pass_through {
476 ($(
477 $(#[doc = $doc:literal])*
478 $fn_name:ident (
479 $Variant:ident,
480 $ReqType:ident,
481 $arg_name:ident : $arg_type:ty
482 ) -> $ret_type:ty
483 );* $(;)?) => {
484 $(
485 $(#[doc = $doc])*
486 pub fn $fn_name( $arg_name: $arg_type ) -> MoosyncResult<$ret_type> {
487 unsafe {
488 let cmd_enum = MainCommandEnum::$Variant($arg_name);
490 let cmd = MainCommand { command: Some(cmd_enum) };
491
492 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
493
494 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
495 return Err(MoosyncError::String(e.message.clone()));
496 }
497
498 if let Some(MainCommandResponseEnum::$Variant(_)) = res.response {
499 return Ok(());
500 }
501
502 Err(MoosyncError::String("Host returned invalid response".into()))
503 }
504 }
505 )*
506 };
507 }
508
509 create_api_fn_pass_through! {
510 add_to_playlist(AddToPlaylist, AddToPlaylistRequest, request: AddToPlaylistRequest) -> ();
512 }
513
514 create_api_fn_repeated! {
515 add_songs(AddSongs, AddSongsRequest, songs, songs: Vec<Song>) -> ();
517
518 register_user_preferences(RegisterUserPreference, RegisterUserPreferenceRequest, prefs, prefs: Vec<PreferenceItem>) -> ();
521
522 unregister_user_preferences(UnregisterUserPreference, UnregisterUserPreferenceRequest, keys, keys: Vec<String>) -> ();
525 }
526
527 pub fn register_oauth(url: String) -> MoosyncResult<()> {
531 unsafe {
532 let request = RegisterOauthRequest { url };
533 let cmd_enum = MainCommandEnum::RegisterOauth(request);
534 let cmd = MainCommand {
535 command: Some(cmd_enum),
536 };
537 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
538
539 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
540 return Err(MoosyncError::String(e.message.clone()));
541 }
542 if let Some(MainCommandResponseEnum::RegisterOauth(_)) = res.response {
543 return Ok(());
544 }
545 Ok(())
548 }
549 }
550
551 pub fn open_external_url(url: String) -> MoosyncResult<()> {
552 unsafe {
553 let request = OpenExternalUrlRequest { url };
554 let cmd_enum = MainCommandEnum::OpenExternalUrl(request);
555 let cmd = MainCommand {
556 command: Some(cmd_enum),
557 };
558 let extism_pdk::Prost(res) = send_main_command(extism_pdk::Prost(cmd)).unwrap();
559
560 if let Some(MainCommandResponseEnum::Error(e)) = res.response.as_ref() {
561 return Err(MoosyncError::String(e.message.clone()));
562 }
563 Ok(())
565 }
566 }
567
568 pub fn get_system_time() -> u64 {
573 unsafe {
574 if let Ok(time) = system_time() {
575 return time;
576 }
577 0u64
578 }
579 }
580
581 pub fn open_sock(path: String) -> MoosyncResult<i64> {
582 let res = unsafe { open_clientfd(path) };
583 res.map_err(|e| MoosyncError::String(e.to_string()))
584 }
585
586 pub fn write_sock(sock_id: i64, buf: Vec<u8>) -> MoosyncResult<i64> {
587 let res = unsafe { write_sock_ext(sock_id, buf) };
588 res.map_err(|e| MoosyncError::String(e.to_string()))
589 }
590
591 pub fn read_sock(sock_id: i64, read_len: u64) -> MoosyncResult<Vec<u8>> {
592 let res = unsafe { read_sock_ext(sock_id, read_len) };
593 res.map_err(|e| MoosyncError::String(e.to_string()))
594 }
595
596 pub fn gen_hash(hash_type: String, data: Vec<u8>) -> MoosyncResult<Vec<u8>> {
597 let res = unsafe { hash(hash_type, data) };
598 res.map_err(|e| MoosyncError::String(e.to_string()))
599 }
600}