Writing Plugins

Currently plugins are written in Rust and compiled using the same build system as Spyglass. An example plugin can be found here

Folder Structure

The folder structure for the plugin in spyglass follows the outline below

<SPYGLASS_REPO>/plugins/<PLUGIN_NAME>/
                        -------------> .cargo
                        ---------------------> config.toml
                        -------------> src
                        ------------------> main.rs
                        ------------------> manifest.ron
                        -------------> Cargo.toml
Cargo.toml

The cargo file is a standard rust project cargo file (Manifest Format). Below is from the example plugin. Note that the spyglass-plugin dependency is required, but all other dependencies depend on the type of plugin being written.

[package]
name = "example-plugin"
version = "0.1.0"
edition = "2021"
license = "AGPL"

[[bin]]
name = "example-plugin"
path = "src/main.rs"

[dependencies]
serde_json = "1.0"
spyglass-plugin = { path = "../../crates/spyglass-plugin" }
url = "2.2"
config.toml

Required configuration file to define the build target

[build]
target = "wasm32-wasi"
manifest.ron

The manifest file defines the metadata associated with the plugin.

(
	name: "example-plugin",
	author: "spyglass-search",
	description: "Example plugin to provide an example on how to create a plugin.",
	version: "1",
	plugin_type: Lens,
	trigger: "example-plugin",
	// User settings w/ the default value, this will be added the plugin environment
	user_settings: {
	   "API_KEY": (
	        label: "Example Plugin API Key",
	        value: "",
	        form_type: Text,
	        restart_required: false,
	        help_text: Some("Example with custom string configuration")
	    ),
	    "ENABLE_API": (
	        label: "Example Plugin Enable API boolean",
			value: "",
			form_type: Bool,
			restart_required: false,
			help_text: Some("Example with custom boolean configuration")
		),
	}
)

Name: The name of the plugin
Author: The developer of the plugin
Description: Description shown in the GUI used to explain what the plugin does
Version: Plugin version
Plugin_type: Currently we only support the Lens plugin type
Trigger: The lens trigger automatically used in search GUI
User_settings: Optional user settings. This can be used to define configuration needed by the plugin. This configuration is used by the GUI to provide input fields in the User Settings menu. All configured values are passed to the plugin through environmental variables.

main.rs

The main.rs is where the plugin code begins. The full source of an example plugin can be found here . Below are snippets of code from the example plugin.


#![allow(unused)]
fn main() {
use spyglass_plugin::*;

#[derive(Default)]
struct Plugin;

register_plugin!(Plugin);
}

The most basic start of a plugin is defining a struct and registering it with the system using the register_plugin! macro. After the plugin has been defined and registered the Plugin struct needs to implement the SpyglassPlugin trait.

The SpyglassPlugin trait defines the set of required methods for a plugin


#![allow(unused)]
fn main() {
pub trait SpyglassPlugin {

/// Initial plugin load, setup any configuration you need here as well as
/// subscribe to specific events.
fn load(&mut self);

/// Asynchronous updates for plugin events
fn update(&mut self, event: PluginEvent);
}
}

Implement the SpyglassPlugin trait and define the plugins functionality


#![allow(unused)]
fn main() {
impl SpyglassPlugin for Plugin {

    fn load(&mut self) {
		let _ = subscribe_for_updates();
	}

	fn update(&mut self, event: PluginEvent) {

		match event {
			PluginEvent::IntervalUpdate => {
				//DO STUFF HERE
			}
			PluginEvent::HttpResponse { url: _, result } => {
			   // DO STUFF HERE
			}
			PluginEvent::DocumentResponse {
				request_id: _,
				page_count: _,
				page: _,
				documents,
				} => {
				// DO STUFF HERE
			}
		}
	}
}
}

The load method is called each time the plugin is loaded. Plugins are loaded when Spyglass starts and when a user toggles the plugin to the enabled state.

The update method is called when asynchronous events occur. This method will only be called in response to an action taken by the plugin. Example calling subscribe_for_updates() will trigger the update method to be called with the IntervalUpdate event every 10 minutes

API Helper Methods

Following is the list of API methods available for plugins. Note plugins are in active development and the API can change. Shims is a good place to check to see the current methods available.

Helper Functions

Note that all helper functions are asynchronous. All requested data will be provided through the update method asynchronously.

Log is used to write a string message into the server log. This is used for debugging. Note that due to the nature of the wasm environment println! cannot be used


#![allow(unused)]
fn main() {
pub fn log(msg: &str)
}

Enqueue all is used to add urls to the list of urls to crawl and index. After calling this method the Spyglass system will add the urls in question to the crawl queue and index the documents as part of normal processing


#![allow(unused)]
fn main() {
pub fn enqueue_all(urls: &[String])
}

Delete doc will delete a document from the index. This can be used to remove documents that were added by the plugin, but are no longer needed.


#![allow(unused)]
fn main() {
pub fn delete_doc(url: &str)
}

Modify tags is used to add or remove tags for all documents that match the query. This allows plugins to add custom tags to documents found in the index.


#![allow(unused)]
fn main() {
pub fn modify_tags(query: DocumentQuery, modification: TagModification) -> Result<(), ron::Error>
}

Query documents can be used to access documents found in the index based on the document query.


#![allow(unused)]
fn main() {
pub fn query_documents(query: DocumentQuery) -> Result<(), ron::Error>
}

Subscribe for documents is the same as query documents, but will run the query at a regular interval and send the results to the update method. This is useful if the plugin is designed to watch for new documents and update tags for those documents.


#![allow(unused)]
fn main() {
pub fn subscribe_for_documents(query: DocumentQuery) -> Result<(), ron::Error>
}

Subscribe for updates will tell spyglass to send an interval update to the plugin every 10 minutes. This can be used to allow the plugin to run updates at a regular interval.


#![allow(unused)]
fn main() {
pub fn subscribe_for_updates() -> Result<(), ron::Error>
}

Add document will add a document to the index. If a document with the same url already exists the document will be updated with the provided contents


#![allow(unused)]
fn main() {
pub fn add_document(documents: Vec<DocumentUpdate>, tags: Vec<Tag>) -> Result<(), ron::Error>
}

Helper Structs

Http is an available struct that can be used to make http requests. Due to the type of wasm module used popular http libraries like reqwest will not work, instead we provide a basic Http helper that can be used for requests. Just like the helper functions all requests return results asynchronously through the update method.


#![allow(unused)]
fn main() {
impl Http {
	pub fn get(url: &str, headers: Vec<(String, String)>)
	pub fn request(url: &str) -> HttpRequestBuilder 
}
}

Http::get("...", vec![])

Generates a get request for the specified url. The request is the same as calling Http::request(url).headers(headers).get().run()

For more options in building http requests the request method can be used. Http::request("...")

HttpRequestBuilder is a helper used to build an http request.


#![allow(unused)]
fn main() {
impl HttpRequestBuilder {
	// Builds a request that is a get request
	pub fn get(&self) -> Self 
	// Builds a request that is a put request
	pub fn put(&self) -> Self 
	// Builds a request that is a post request
	pub fn post(&self) -> Self 
	// Builds a request that is a patch request
	pub fn patch(&self) -> Self 
	// Builds a request that is a delete request
	pub fn delete(&self) -> Self 
	// Adds a body to the request
	pub fn body(&self, new_body: String) -> Self 
	// Adds headers to the request
	pub fn headers(&self, new_headers: Vec<(String, String)>) -> Self 
	// Add basic authentication to the request
	pub fn basic_auth(&self, key: &str, val: Option<String>) -> Self 
	// Adds bearer auth to the request
	pub fn bearer_auth(&self, key: &str) -> Self 
	// Runs the http request. Results will be sent to the update method
	pub fn run(&self) 
}
}