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