moosync_edk/
lib.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
17pub use extism_pdk::{config, error, http, info, log, warn, HttpRequest, HttpResponse};
18use extism_pdk::{plugin_fn, FnResult, Json};
19use handler::{
20    get_accounts, get_album_songs, get_artist_songs, get_lyrics, get_playback_details,
21    get_playlist_content, get_playlist_context_menu, get_playlist_from_url, get_playlists,
22    get_provider_scopes, get_recommendations, get_song_context_menu, get_song_from_id,
23    get_song_from_url, handle_custom_request, oauth_callback, on_context_menu_action,
24    on_player_state_changed, on_playlist_added, on_playlist_removed, on_preferences_changed,
25    on_queue_changed, on_seeked, on_song_added, on_song_changed, on_song_removed,
26    on_volume_changed, perform_account_login, scrobble, search,
27};
28use serde_json::Value;
29
30pub use types::{
31    entities::*, errors::*, extensions::*, preferences::*, songs::*, ui::extensions::*,
32    ui::player_details::PlayerState,
33};
34
35pub mod api;
36pub mod handler;
37
38unsafe extern "C" {
39    fn init();
40}
41
42#[tracing::instrument(level = "debug", skip())]
43#[plugin_fn]
44pub fn entry() -> FnResult<()> {
45    unsafe {
46        init();
47    }
48    Ok(())
49}
50
51#[tracing::instrument(level = "debug", skip())]
52#[plugin_fn]
53pub fn get_provider_scopes_wrapper() -> FnResult<Json<Vec<ExtensionProviderScope>>> {
54    let ret = get_provider_scopes()?;
55    Ok(Json(ret))
56}
57
58#[tracing::instrument(level = "debug", skip())]
59#[plugin_fn]
60pub fn get_playlists_wrapper() -> FnResult<Json<PlaylistReturnType>> {
61    let ret = get_playlists()?;
62    Ok(Json(PlaylistReturnType { playlists: ret }))
63}
64
65#[tracing::instrument(level = "debug", skip(id))]
66#[plugin_fn]
67pub fn get_playlist_content_wrapper(
68    Json((id, token)): Json<(String, Option<String>)>,
69) -> FnResult<Json<SongsWithPageTokenReturnType>> {
70    let ret = get_playlist_content(id, token)?;
71    Ok(Json(ret))
72}
73
74#[tracing::instrument(level = "debug", skip())]
75#[plugin_fn]
76pub fn get_playlist_from_url_wrapper(
77    Json(url): Json<String>,
78) -> FnResult<Json<PlaylistAndSongsReturnType>> {
79    let ret = get_playlist_from_url(url)?;
80    Ok(Json(PlaylistAndSongsReturnType {
81        playlist: ret,
82        songs: None,
83    }))
84}
85
86#[tracing::instrument(level = "debug", skip())]
87#[plugin_fn]
88pub fn get_playback_details_wrapper(
89    Json(song): Json<Song>,
90) -> FnResult<Json<PlaybackDetailsReturnType>> {
91    let ret = get_playback_details(song)?;
92    Ok(Json(ret))
93}
94
95#[tracing::instrument(level = "debug", skip(term))]
96#[plugin_fn]
97pub fn search_wrapper(Json(term): Json<String>) -> FnResult<Json<SearchReturnType>> {
98    let ret = search(term)?;
99    Ok(Json(SearchReturnType {
100        songs: ret.songs,
101        playlists: ret.playlists,
102        artists: ret.artists,
103        albums: ret.albums,
104    }))
105}
106
107#[tracing::instrument(level = "debug", skip())]
108#[plugin_fn]
109pub fn get_recommendations_wrapper() -> FnResult<Json<RecommendationsReturnType>> {
110    let ret = get_recommendations()?;
111    Ok(Json(RecommendationsReturnType { songs: ret }))
112}
113
114#[tracing::instrument(level = "debug", skip(url))]
115#[plugin_fn]
116pub fn get_song_from_url_wrapper(Json(url): Json<String>) -> FnResult<Json<SongReturnType>> {
117    let ret = get_song_from_url(url)?;
118    Ok(Json(SongReturnType { song: ret }))
119}
120
121#[tracing::instrument(level = "debug", skip(url))]
122#[plugin_fn]
123pub fn handle_custom_request_wrapper(
124    Json(url): Json<String>,
125) -> FnResult<Json<CustomRequestReturnType>> {
126    let ret = handle_custom_request(url)?;
127    Ok(Json(ret))
128}
129
130#[tracing::instrument(level = "debug", skip())]
131#[plugin_fn]
132pub fn get_artist_songs_wrapper(
133    Json((artist, token)): Json<(Artist, Option<String>)>,
134) -> FnResult<Json<SongsWithPageTokenReturnType>> {
135    let ret = get_artist_songs(artist, token)?;
136    Ok(Json(ret))
137}
138
139#[tracing::instrument(level = "debug", skip())]
140#[plugin_fn]
141pub fn get_album_songs_wrapper(
142    Json((album, token)): Json<(Album, Option<String>)>,
143) -> FnResult<Json<SongsWithPageTokenReturnType>> {
144    let ret = get_album_songs(album, token)?;
145    Ok(Json(ret))
146}
147
148#[tracing::instrument(level = "debug", skip(id))]
149#[plugin_fn]
150pub fn get_song_from_id_wrapper(Json(id): Json<String>) -> FnResult<Json<SongReturnType>> {
151    let ret = get_song_from_id(id)?;
152    Ok(Json(SongReturnType { song: ret }))
153}
154
155// PlayerEvents trait wrappers
156#[tracing::instrument(level = "debug", skip())]
157#[plugin_fn]
158pub fn on_queue_changed_wrapper(Json(queue): Json<Value>) -> FnResult<Json<()>> {
159    on_queue_changed(queue)?;
160    Ok(Json(()))
161}
162
163#[tracing::instrument(level = "debug", skip())]
164#[plugin_fn]
165pub fn on_volume_changed_wrapper() -> FnResult<Json<()>> {
166    on_volume_changed()?;
167    Ok(Json(()))
168}
169
170#[tracing::instrument(level = "debug", skip())]
171#[plugin_fn]
172pub fn on_player_state_changed_wrapper() -> FnResult<Json<()>> {
173    on_player_state_changed()?;
174    Ok(Json(()))
175}
176
177#[tracing::instrument(level = "debug", skip())]
178#[plugin_fn]
179pub fn on_song_changed_wrapper() -> FnResult<Json<()>> {
180    on_song_changed()?;
181    Ok(Json(()))
182}
183
184#[tracing::instrument(level = "debug", skip(time))]
185#[plugin_fn]
186pub fn on_seeked_wrapper(Json(time): Json<f64>) -> FnResult<Json<()>> {
187    on_seeked(time)?;
188    Ok(Json(()))
189}
190
191// PreferenceEvents trait wrapper
192#[tracing::instrument(level = "debug", skip())]
193#[plugin_fn]
194pub fn on_preferences_changed_wrapper(Json(args): Json<PreferenceArgs>) -> FnResult<Json<()>> {
195    on_preferences_changed(args)?;
196    Ok(Json(()))
197}
198
199// DatabaseEvents trait wrappers
200#[tracing::instrument(level = "debug", skip())]
201#[plugin_fn]
202pub fn on_song_added_wrapper(Json(song): Json<Song>) -> FnResult<Json<()>> {
203    on_song_added(song)?;
204    Ok(Json(()))
205}
206
207#[tracing::instrument(level = "debug", skip())]
208#[plugin_fn]
209pub fn on_song_removed_wrapper(Json(song): Json<Song>) -> FnResult<Json<()>> {
210    on_song_removed(song)?;
211    Ok(Json(()))
212}
213
214#[tracing::instrument(level = "debug", skip())]
215#[plugin_fn]
216pub fn on_playlist_added_wrapper(Json(playlist): Json<Playlist>) -> FnResult<Json<()>> {
217    on_playlist_added(playlist)?;
218    Ok(Json(()))
219}
220
221#[tracing::instrument(level = "debug", skip())]
222#[plugin_fn]
223pub fn on_playlist_removed_wrapper(Json(playlist): Json<Playlist>) -> FnResult<Json<()>> {
224    on_playlist_removed(playlist)?;
225    Ok(Json(()))
226}
227
228#[tracing::instrument(level = "debug", skip())]
229#[plugin_fn]
230pub fn get_accounts_wrapper() -> FnResult<Json<Vec<ExtensionAccountDetail>>> {
231    let ret = get_accounts()?;
232    Ok(Json(ret))
233}
234
235#[tracing::instrument(level = "debug", skip())]
236#[plugin_fn]
237pub fn perform_account_login_wrapper(Json(args): Json<AccountLoginArgs>) -> FnResult<Json<String>> {
238    let ret = perform_account_login(args)?;
239    Ok(Json(ret))
240}
241
242#[tracing::instrument(level = "debug", skip())]
243#[plugin_fn]
244pub fn scrobble_wrapper(Json(args): Json<Song>) -> FnResult<Json<()>> {
245    scrobble(args)?;
246    Ok(Json(()))
247}
248
249#[tracing::instrument(level = "debug", skip())]
250#[plugin_fn]
251pub fn oauth_callback_wrapper(Json(args): Json<String>) -> FnResult<Json<()>> {
252    oauth_callback(args)?;
253    Ok(Json(()))
254}
255
256#[tracing::instrument(level = "debug", skip())]
257#[plugin_fn]
258pub fn get_song_context_menu_wrapper(
259    Json(songs): Json<Vec<Song>>,
260) -> FnResult<Json<Vec<ContextMenuReturnType>>> {
261    Ok(Json(get_song_context_menu(songs)?))
262}
263
264#[tracing::instrument(level = "debug", skip())]
265#[plugin_fn]
266pub fn get_playlist_context_menu_wrapper(
267    Json(playlist): Json<Playlist>,
268) -> FnResult<Json<Vec<ContextMenuReturnType>>> {
269    Ok(Json(get_playlist_context_menu(playlist)?))
270}
271
272#[tracing::instrument(level = "debug", skip())]
273#[plugin_fn]
274pub fn on_context_menu_action_wrapper(Json(action): Json<String>) -> FnResult<Json<()>> {
275    Ok(Json(on_context_menu_action(action)?))
276}
277
278#[tracing::instrument(level = "debug", skip())]
279#[plugin_fn]
280pub fn get_lyrics_wrapper(Json(song): Json<Song>) -> FnResult<Json<String>> {
281    Ok(Json(get_lyrics(song)?))
282}