Full extension example
Here is a complete extension example combining registration, provider capabilities, and API usage:
{{#tabs }} {{#tab name="Rust" }}
#![allow(unused)] #![no_main] fn main() { use moosync_edk::{ ExtensionProviderScope, RequestedSearchResultRequest, ScrobbleRequest, SearchResult, api::{ self, Accounts, ContextMenu, DatabaseEvents, Extension, PlayerEvents, PreferenceEvents, Provider, extension_api::get_current_song, }, handler::register_extension, info, }; // ANCHOR: first_extension struct SampleExtension; impl Extension for SampleExtension {} impl Accounts for SampleExtension {} impl DatabaseEvents for SampleExtension {} impl PlayerEvents for SampleExtension {} impl PreferenceEvents for SampleExtension {} impl ContextMenu for SampleExtension {} #[unsafe(no_mangle)] pub extern "C" fn init() { info!("Initializing SampleExtension"); register_extension(Box::new(SampleExtension)).unwrap(); info!("Initialized SampleExtension"); } // ANCHOR_END: first_extension impl Provider for SampleExtension { // ANCHOR: provider fn get_provider_scopes(&self) -> api::MoosyncResult<Vec<ExtensionProviderScope>> { Ok(vec![ExtensionProviderScope::Search]) } fn search(&self, req: RequestedSearchResultRequest) -> api::MoosyncResult<SearchResult> { info!("Search requested for query: {}", req.query); let url = format!("https://api.spotify.com/v1/search?q={}&type=track", req.query); let resp = moosync_edk::http::get(&url, None)?; if resp.is_success() { info!("Search API response: {}", resp.text().unwrap_or_default()); } Ok(SearchResult { songs: vec![], artists: vec![], playlists: vec![], albums: vec![], genres: vec![], }) } // ANCHOR_END: provider // ANCHOR: api_usage fn scrobble(&self, _req: ScrobbleRequest) -> api::MoosyncResult<()> { let song = get_current_song()?; if let Some(song) = song { if let Some(inner) = song.song { if let Some(title) = inner.title { info!("Currently playing song: {}", title); } } } Ok(()) } // ANCHOR_END: api_usage } // ANCHOR: http_usage pub fn search_and_fetch_details(query: &str) -> api::MoosyncResult<()> { use moosync_edk::http::{self, HttpRequest}; let url = format!("https://api.spotify.com/v1/search?q={}&type=track", query); let resp = http::get(&url, None)?; if resp.is_success() { info!("Search response: {}", resp.text().unwrap_or_default()); } let post_req = HttpRequest::post("https://api.spotify.com/v1/playlists") .header("Authorization", "Bearer token123") .body(r#"{"name":"My Playlist"}"#.as_bytes().to_vec()) .timeout_ms(5000); let post_resp = http::request(&post_req)?; info!("Create playlist status: {}", post_resp.status_code); let track_urls = vec![ "https://api.spotify.com/v1/tracks/1", "https://api.spotify.com/v1/tracks/2", ]; let responses = http::batch_get(&track_urls, None)?; for r in responses { info!("Track status: {}", r.status_code); } Ok(()) } // ANCHOR_END: http_usage }
{{#endtab }} {{#tab name="Golang" }}
package main
import (
extensions "github.com/moosync/moosync/types/extensions"
songs "github.com/moosync/moosync/types/songs"
"github.com/Moosync/extensions-sdk/wasm-extension-go/pkg/api"
)
// ANCHOR: first_extension
type SampleExtension struct {
api.DefaultExtension
}
//go:wasmexport entry
func entry() {
extension := &SampleExtension{}
api.RegisterExtension(extension)
}
func main() {}
// ANCHOR_END: first_extension
// ANCHOR: provider
func (s *SampleExtension) GetProviderScopes() ([]extensions.ExtensionProviderScope, error) {
return []extensions.ExtensionProviderScope{
extensions.ExtensionProviderScope_SEARCH,
}, nil
}
func (s *SampleExtension) Search(req *extensions.RequestedSearchResultRequest) (*songs.SearchResult, error) {
api.LogInfo("Search called with query: %s", req.GetQuery())
url := "https://api.spotify.com/v1/search?q=" + req.GetQuery() + "&type=track"
resp, err := api.HttpGet(url, nil)
if err != nil {
return nil, err
}
if resp.OK() {
api.LogInfo("Search response: %s", resp.Text())
}
return &songs.SearchResult{
Songs: []*songs.Song{},
Playlists: []*songs.Playlist{},
Artists: []*songs.Artist{},
Albums: []*songs.Album{},
Genres: []*songs.Genre{},
}, nil
}
// ANCHOR_END: provider
// ANCHOR: api_usage
func (s *SampleExtension) Scrobble(req *extensions.ScrobbleRequest) error {
song, err := api.GetCurrentSong()
if err != nil {
return err
}
if song != nil && song.GetSong() != nil {
api.LogInfo("Currently playing song: %s", song.GetSong().GetTitle())
}
return nil
}
// ANCHOR_END: api_usage
// ANCHOR: http_usage
func searchAndFetchDetails(query string) error {
url := "https://api.spotify.com/v1/search?q=" + query + "&type=track"
resp, err := api.HttpGet(url, nil)
if err != nil {
return err
}
if resp.OK() {
api.LogInfo("Search response: %s", resp.Text())
}
postReq := api.HttpRequest{
URL: "https://api.spotify.com/v1/playlists",
Method: "POST",
Headers: map[string]string{"Authorization": "Bearer token123"},
Body: []byte(`{"name":"My Playlist"}`),
TimeoutMs: 5000,
}
postResp, err := api.SendHttpRequest(postReq)
if err != nil {
return err
}
api.LogInfo("Create playlist status: %d", postResp.StatusCode)
trackURLs := []string{
"https://api.spotify.com/v1/tracks/1",
"https://api.spotify.com/v1/tracks/2",
}
resps, errs := api.BatchHttpGet(trackURLs, nil)
if len(errs) > 0 {
return errs[0]
}
for _, r := range resps {
api.LogInfo("Track status: %d", r.StatusCode)
}
return nil
}
// ANCHOR_END: http_usage
{{#endtab }} {{#tab name="Python" }}
from moosync_edk import Extension, register_extension
from core.types.protos import extensions_pb2, songs_pb2
# ANCHOR: first_extension
class SampleExtension(Extension):
def __init__(self):
super().__init__()
def entry():
print("Initializing SampleExtension")
register_extension(SampleExtension())
# ANCHOR_END: first_extension
# ANCHOR: provider
class SearchProviderExtension(Extension):
def get_provider_scopes(self, req):
return extensions_pb2.GetProviderScopesResponse(
scopes=[extensions_pb2.ExtensionProviderScope.SEARCH]
)
def search(self, req):
print(f"Search query: {req.query}")
url = f"https://api.spotify.com/v1/search?q={req.query}&type=track"
resp = http_get(url)
if resp.ok:
print(f"Search response: {resp.text}")
return songs_pb2.SearchResult(
songs=[],
artists=[],
playlists=[],
albums=[],
genres=[]
)
# ANCHOR_END: provider
# ANCHOR: api_usage
class ScrobbleExtension(Extension):
def scrobble(self, req):
song = self.api.get_current_song()
if song and song.song:
print(f"Currently playing: {song.song.title}")
# ANCHOR_END: api_usage
# ANCHOR: http_usage
from moosync_edk import http_get, http_request, http_batch_get
def search_and_fetch_details(query):
url = f"https://api.spotify.com/v1/search?q={query}&type=track"
resp = http_get(url)
if resp.ok:
print(f"Search response: {resp.text}")
post_resp = http_request(
url="https://api.spotify.com/v1/playlists",
method="POST",
headers={"Authorization": "Bearer token123"},
body='{"name": "My Playlist"}',
timeout_ms=5000,
)
print(f"Create playlist status: {post_resp.status_code}")
track_urls = [
"https://api.spotify.com/v1/tracks/1",
"https://api.spotify.com/v1/tracks/2",
]
resps = http_batch_get(track_urls)
for r in resps:
print(f"Track status: {r.status_code}")
# ANCHOR_END: http_usage
{{#endtab }} {{#tab name="Javascript" }}
import {
GetProviderScopesResponse,
RequestedSearchResultResponse,
ScrobbleResponse,
getApi,
} from "wasm-extension-js";
export { handle_extension_command } from "wasm-extension-js";
// ANCHOR: first_extension
export function entry(): number {
const api = getApi();
console.log("Initialized sample extension");
return 0;
}
// ANCHOR_END: first_extension
// ANCHOR: provider
export function registerProviderHandlers() {
const api = getApi();
api.on("getProviderScopes", () => {
return new GetProviderScopesResponse({
scopes: [
13 as any // ExtensionProviderScope.SEARCH
]
});
});
api.on("requestedSearchResult", async (req) => {
console.log("Search query:", req.query);
const url = `https://api.spotify.com/v1/search?q=${encodeURIComponent(req.query)}&type=track`;
const resp = await api.fetch(url);
if (resp.ok) {
const text = await resp.text();
console.log("Search response:", text);
}
return new RequestedSearchResultResponse({
songs: []
});
});
}
// ANCHOR_END: provider
// ANCHOR: api_usage
export function registerApiUsage() {
const api = getApi();
api.on("scrobble", async (req) => {
const song = await api.getCurrentSong();
if (song && song.song && song.song.title) {
console.log("Currently playing song:", song.song.title);
}
return new ScrobbleResponse();
});
}
// ANCHOR_END: api_usage
// ANCHOR: http_usage
export async function searchAndFetchDetails(query: string) {
const api = getApi();
const url = `https://api.spotify.com/v1/search?q=${encodeURIComponent(query)}&type=track`;
const resp = await api.fetch(url);
if (resp.ok) {
const text = await resp.text();
console.log("Search response:", text);
}
const postResp = await api.fetch({
url: "https://api.spotify.com/v1/playlists",
method: "POST",
headers: { Authorization: "Bearer token123" },
body: JSON.stringify({ name: "My Playlist" }),
timeoutMs: 5000,
});
console.log("Create playlist status:", postResp.status);
const responses = await api.batchFetch([
"https://api.spotify.com/v1/tracks/1",
"https://api.spotify.com/v1/tracks/2",
]);
for (const r of responses) {
console.log("Track status:", r.status);
}
}
// ANCHOR_END: http_usage
{{#endtab }} {{#endtabs }}