Skip to content

The nari API

main.lua gets a global nari table with everything Nari offers plugins. Background threads get a smaller version of it.

Function Description
nari.plugin { id, path, folder, data }: the plugin’s id, its folder as a Godot path (user://plugins/<id>) and as an OS path, and the OS path of its data folder (user://plugin_data/<id>, for files saved with nari.http).
nari.on(event, handler) Calls handler on an event.
nari.is_ready() true once Nari finished starting.
nari.current() The current song, or nil.
nari.log(...), print(...), nari.warn(...) Writes to Nari’s log under the plugin’s name, as LOG or WARN.
nari.log_color(color) Colors the plugin’s name in the log, like "#ff0033" or a Godot color name.
nari.path(file) Godot path of a file in the plugin folder. Paths with :// are returned as they are.
nari.load(file) Loads a file of the plugin: images become textures, sounds (mp3, ogg, wav) become audio streams, anything else goes through Godot’s loader (.gdshader, .tres, .tscn…). res:// paths load Nari’s own files.
nari.after(seconds, handler) Calls handler once after seconds.
nari.await(value, handler) Calls handler(result) when an async GDScript function finishes. See Async functions.
nari.open_url(url) Opens a link in the browser or the app that handles it. false if nothing could open it.
nari.get(key), nari.set(key, value) Data that survives restarts. See Storing data.
nari.command(name, description, handler) Adds a console command.
Function Description
nari.lyrics_provider(spec) Adds a lyrics provider.
nari.music_provider(spec) Adds a music provider and returns it, to emit its signals.
nari.effect(spec) Adds a sound effect to Now Playing’s Effects tab.
nari.visualizer(file or spec) Adds a visualizer.
nari.analyze(song, options, callback) Measures tempo, key, loudness or the waveform of a song. See Audio analysis.
nari.home_entry(spec) Adds an entry to Home. See Building UI.
nari.now_playing_tab(spec) Adds a tab to Now Playing. See Building UI.
nari.discover_section(spec) Adds a section to Discover. See Building UI.
nari.item_action(spec) Adds an option to the menu of songs, albums, artists or playlists. See Building UI.
nari.webview(spec) Opens a web page as a Nari screen. See Building UI.
nari.setting(spec) Adds a setting to the plugin’s settings page. See Settings.
nari.settings(builder) Adds entries to the plugin’s settings page. See Settings.
nari.settings_entry(category, builder) Adds entries to a Settings page: general, library, playback, appearance, lyrics or integrations. See Settings.
nari.thread(file, options) Starts a background thread and returns its handle.
Function Description
nari.http(request, callback) An HTTP request, see below.
nari.serve(handler, options) A small web server on this device. See Web helpers.
nari.json_encode(value), nari.json_decode(text) JSON.
nari.base64_encode(data, url_safe), nari.base64_decode(text) Base64. url_safe uses the URL alphabet without padding (JWT, PKCE). Decoding accepts both.
nari.md5(text, raw), nari.sha1(text, raw), nari.sha256(text, raw) Digests as hex, or as raw bytes when raw is true.
nari.hmac(algorithm, key, text, raw), nari.hmac_sha256(key, text, raw) HMAC with "md5", "sha1" or "sha256".
nari.cipher(spec) AES or Blowfish. See Ciphers.
nari.modpow(base, exponent, modulus) base ^ exponent mod modulus on big numbers given as big-endian bytes, for Diffie-Hellman and RSA.
nari.random_bytes(count) Secure random bytes (up to 4096), for nonces, states and PKCE verifiers.

nari.http runs a request on a worker thread and calls callback(response, problem) on the main thread when it’s done:

nari.http({
method = "POST", -- GET by default
url = "https://example.com/api",
headers = { ["Content-Type"] = "application/json" },
body = nari.json_encode({ hello = "world" }),
}, function(response, problem)
if not response then
nari.log("request failed: " .. problem)
return
end
nari.log(response.status, response.body)
end)
  • response is { status, body, headers, attempts } for every answer, 4xx and 5xx included. Header names are lowercase (response.headers["content-type"]), and a header sent several times (like set-cookie) comes as one value with a line per copy.
  • response is nil, with a problem, when the request couldn’t be made: no network, a bad URL, or the 20 second timeout.
  • Bodies are bytes, so they can carry binary data (protobuf, images) both ways.
  • Add save = "covers/art.jpg" to write the body to a file in the plugin’s data folder instead. It’s only written for 2xx answers, and response.path is its OS path.

Busy servers are retried for you: 429, 502, 503, 504 and network errors are tried up to 2 more times, waiting what the server asks in Retry-After (up to 60 seconds) or a growing delay. attempts says how many tries it took. Set retries = 0 to turn it off, or a number up to 10 for more tries.