The Rust Program
Aidoku sources are fundamentally no_std
Rust programs that compile to WebAssembly. Thus, any Rust crates that support no_std (or no_std
with an allocator) can be utilized as dependencies.
aidoku-rs expects sources to be written by implementing traits that it provides to a struct, then
passing that struct to the register_source! macro.
Traits
All sources must implement the one required Source trait, and then can optionally implement any other traits for more features. Check here for a complete list of traits that aidoku-rs provides.
The Source trait has four required functions:
- new: performs any setup required for the source to function (e.g. setting global rate limit) and initializes the source struct.
- get_search_manga_list:
returns a single page of results, with a query and list of filters if input by the user. Filter
values will correspond to any of the configured filters in
filters.json, and page numbers will start at1. If theMangaPageResultyou return hashas_next_pageset totrue, then the function will be called again with the page number incremented if the user scroll down to load another page of results.Mangareturned in the result entries only need to have theid,title, andcoverattributes populated. - get_manga_update:
if the
needs_detailsparameter istrue, then as many of the availablemangastruct attributes should be filled as possible to provide the UI with information to display. Ifneeds_chaptersistrue, thechaptersattribute should be set. This function may be called with either or both of the booleans set totrue, but not neither. - get_page_list: returns a list of pages for a given manga and chapter.
Most sources should implement the DeepLinkHandler trait, to allow users to replace the “http” or “https” in a url with “aidoku” to open the app directly to whatever series or chapter the web page was displaying. This may also be used in the future by the app to open urls in the app directly.
After implementing a basic source that supports searching and filtering, your next step would be considering if you want to implement listings with the ListingProvider trait and a home page with the Home trait.
Registering
The register_source! macro should be called with the source struct name as the first parameter,
and then all the other traits the struct implements apart from the Source trait. For example:
struct MySource;
impl Source for MySource { ... }
impl ImageRequestProvider for MySource { ... }
impl DeepLinkHandler for MySource { ... }
register_source!(MySource, ImageRequestProvider, DeepLinkHandler);
Under the hood, this macro generates the actual functions that are exposed for Aidoku to call directly, and converts the raw data sent from the app to the source into Rust structs for the source to manipulate, then converts the structs the source returns in its implemented functions back into raw data for the app to read. If you implement a trait for your source, the app won’t take it into account unless it is properly registered.
Error Handling
All aidoku-rs trait functions expect a Result<..., AidokuError> to be returned, and most library
functions return Results with an AidokuError, or errors that can be converted into an
AidokuError. This means that the ? operator can be used liberally, allowing the app to handle
any errors. In addition, there are two helper macros to assist with creating errors yourself:
error! and bail!. Both macros allow you to provide a message (that supports formatting like
format! and println!) that the app will display in the UI if encountered.
error! simply constructs an AidokuError, and is typically used for something like this:
let img_element = html.select("img").ok_or_else(|| error!("img element not found"))?;
Whereas bail! will return immediately. It can be used like this:
let Some(img_element) = html.select("img") else {
bail!("img element not found");
};
Network Requests
Nearly every Aidoku source will need to use network requests to fetch data from its target website, be it HTML content or API responses, such as JSON data. As a simple example:
let response: Response = Request::get("http://example.com")?
.header("User-Agent", "Aidoku")
.send()?;
assert_eq!(response.status_code(), 200);
The other commonly used functions that “send” the network request and return a result are
data to get
the raw byte data,
string to
get the text content, and
html to
automatically parse as HTML. If the “json” feature is enabled for the aidoku-rs dependency, the
json_owned function can be used to automatically parse the response data using
serde_json.
Note that all function calls that send the network request are blocking, and will pause your source execution until the data is available to return. When performing multiple network requests that don’t depend on each other, you can consider parallelizing them with Request::send_all.
HTML Parsing
Aidoku provides APIs for interacting with the SwiftSoup library, which allows you to extract data from HTML elements using CSS selectors. The main functions you should be aware of are Element::select, Element::select_first, Element::text, and Element::attr.
let html = Request::get("http://aidoku.app")?.html()?;
let text = html
.select_first("main.home .features > .feature h2")
.and_then(|element| element.text());
let image = html
.select_first(".image > img")
.and_then(|element| element.attr("abs:src"));