1use crate::api::Extension;
18use extensions_proto::moosync::types::extension_command::Event;
19use extensions_proto::moosync::types::extension_command_response::Response;
20use extensions_proto::moosync::types::*;
21use extism_pdk::FnResult;
22use std::{cell::RefCell, rc::Rc};
23
24thread_local!(
25 static EXTENSION: RefCell<Option<Rc<Box<dyn Extension>>>> = RefCell::new(None);
26);
27
28#[tracing::instrument(level = "debug", skip(extension))]
29pub fn register_extension(extension: Box<dyn Extension>) -> FnResult<()> {
30 EXTENSION.with(|ext| {
31 ext.borrow_mut().replace(Rc::new(extension));
32 });
33 Ok(())
34}
35
36#[derive(Debug)]
37pub enum MoosyncError {
38 String(String),
39}
40
41impl From<String> for MoosyncError {
42 fn from(e: String) -> Self {
43 MoosyncError::String(e)
44 }
45}
46impl From<&str> for MoosyncError {
47 fn from(s: &str) -> Self {
48 MoosyncError::String(s.to_string())
49 }
50}
51
52impl std::fmt::Display for MoosyncError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 match self {
55 MoosyncError::String(s) => write!(f, "{}", s),
56 }
57 }
58}
59
60impl std::error::Error for MoosyncError {}
61
62macro_rules! dispatch_command {
64 ($ext:expr, $event:expr, {
65 $($Variant:ident $(($($arg:pat),*))? => $method:ident $(($($param:expr),*))? => $res:ident in $RespVariant:ident $Body:tt),* $(,)?
66 }) => {
67 match $event {
68 $(
69 Event::$Variant($($($arg),*)?) => {
70 let $res = $ext.$method($($($param),*)?)
71 .map_err(|e| extism_pdk::Error::msg(format!("Error: {:?}", e)))?;
72
73 Response::$Variant($RespVariant $Body)
74 }
75 )*
76 Event::GetRemoteUrl(_) => {
77 return Err(extism_pdk::Error::msg("Not implemented"));
78 }
79 }
80 };
81}
82
83pub fn handle_command(
84 cmd: ExtensionCommand,
85) -> Result<ExtensionCommandResponse, extism_pdk::Error> {
86 EXTENSION.with(|ext| {
87 if let Some(ext) = ext.borrow().as_ref() {
88 let mut response = ExtensionCommandResponse { response: None };
89
90 if let Some(event) = cmd.event {
91 let resp = dispatch_command!(ext, event, {
92 RequestedPlaylists(req) => get_playlists(req) => res in RequestedPlaylistsResponse { playlists: res },
93 RequestedPlaylistSongs(req) => get_playlist_content(req) => res in RequestedPlaylistSongsResponse {
94 songs: res.songs,
95 next_page_token: res.next_page_token,
96 },
97 OauthCallback(req) => oauth_callback(req) => _res in OauthCallbackResponse {},
98 SongQueueChanged(req) => on_queue_changed(req) => _res in SongQueueChangedResponse {},
99 Seeked(req) => on_seeked(req) => _res in SeekedResponse {},
100 VolumeChanged(req) => on_volume_changed(req) => _res in VolumeChangedResponse {},
101 PlayerStateChanged(req) => on_player_state_changed(req) => _res in PlayerStateChangedResponse {},
102 SongChanged(req) => on_song_changed(req) => _res in SongChangedResponse {},
103 PreferenceChanged(req) => on_preferences_changed(req) => _res in PreferenceChangedResponse {},
104 PlaybackDetailsRequested(req) => get_playback_details(req) => res in PlaybackDetailsRequestedResponse {
105 duration: res.duration,
106 url: res.url,
107 },
108 CustomRequest(req) => handle_custom_request(req) => res in CustomRequestResponse {
109 mime_type: res.mime_type,
110 data: res.data,
111 redirect_url: res.redirect_url,
112 },
113 RequestedSongFromUrl(req) => get_song_from_url(req) => res in RequestedSongFromUrlResponse { song: res },
114 RequestedPlaylistFromUrl(req) => get_playlist_from_url(req) => res in RequestedPlaylistFromUrlResponse {
115 playlist: res,
116 songs: vec![],
117 },
118 RequestedSearchResult(req) => search(req) => res in RequestedSearchResultResponse {
119 songs: res.songs,
120 playlists: res.playlists,
121 artists: res.artists,
122 albums: res.albums,
123 },
124 RequestedRecommendations(req) => get_recommendations(req) => res in RequestedRecommendationsResponse { songs: res },
125 RequestedLyrics(req) => get_lyrics(req) => res in RequestedLyricsResponse { lyrics: res },
126 RequestedArtistSongs(req) => get_artist_songs(req) => res in RequestedArtistSongsResponse {
127 songs: res.songs,
128 next_page_token: res.next_page_token,
129 },
130 RequestedAlbumSongs(req) => get_album_songs(req) => res in RequestedAlbumSongsResponse {
131 songs: res.songs,
132 next_page_token: res.next_page_token,
133 },
134 SongAdded(req) => on_song_added(req) => _res in SongAddedResponse {},
135 SongRemoved(req) => on_song_removed(req) => _res in SongRemovedResponse {},
136 PlaylistAdded(req) => on_playlist_added(req) => _res in PlaylistAddedResponse {},
137 PlaylistRemoved(req) => on_playlist_removed(req) => _res in PlaylistRemovedResponse {},
138 RequestedSongFromId(req) => get_song_from_id(req) => res in RequestedSongFromIdResponse { song: res },
139 Scrobble(req) => scrobble(req) => _res in ScrobbleResponse {},
140 RequestedSongContextMenu(req) => get_song_context_menu(req) => res in RequestedSongContextMenuResponse {
141 menu: res.into_iter().next(),
142 },
143 RequestedPlaylistContextMenu(req) => get_playlist_context_menu(req) => res in RequestedPlaylistContextMenuResponse {
144 menu: res.into_iter().next(),
145 },
146 ContextMenuAction(req) => on_context_menu_action(req) => _res in ContextMenuActionResponse {},
147 GetProviderScopes(_) => get_provider_scopes() => res in GetProviderScopesResponse {
148 scopes: res.into_iter().map(|s| s as i32).collect(),
149 },
150 GetAccounts(_) => get_accounts() => res in GetAccountsResponse { accounts: res },
151 PerformAccountLogin(req) => perform_account_login(req) => res in PerformAccountLoginResponse { status: res },
152 });
153
154 response.response = Some(resp);
155 }
156
157 Ok(response)
158 } else {
159 Err(extism_pdk::Error::msg("No extension registered"))
160 }
161 })
162}