This guide outlines the process for creating a new source extension for Midoku. Please read it carefully if you are a new contributor or do not have any experience on the required languages and tooling.
This guide is not definitive and may change over time. If you find any issues on it, feel free to open an issue or a pull request.
Before starting, please note that the ability to use the following technologies is required and that existing contributors will not actively teach them to you.
- cargo-component
- Basic image editing software
When using rust-analyzer, you may need to configure it to work with
cargo-component
. You will need to set the
check.overrideCommand
option to use
cargo-component
instead of cargo check
.
Here is a minimal configuration for VSCode:
{
"rust-analyzer.check.overrideCommand": [
"cargo",
"component",
"check",
"--workspace",
"--all-targets",
"--message-format=json"
],
}
Here is a minimal configuration for Zed:
{
"lsp": {
"rust-analyzer": {
"initialization_options": {
"check": {
"overrideCommand": [
"cargo",
"component",
"check",
"--workspace",
"--all-targets",
"--message-format=json"
]
}
}
}
}
}
The following steps can be used to skip unrelated extensions and branches, which will make it faster to pull and navigate. This will also reduce disk usage and network traffic.
These steps are optional and only needed when the repository is too large and contains a lot of extensions.
-
When forking a repository, only fork the
main
branch. You may also want to disable GitHub Actions on your fork. -
Do a partial clone:
git clone --filter=blob:none --sparse <fork-repo-url> cd midoku-community-extensions/
-
Configure spase checkout:
Enable it using the following command:
git sparse-checkout set
Edit
.git/info/sparse-checkout
and add the extensions you want to work on:/* !/src # Add the extensions you want to work on /src/<lang>/<extension-name>
The syntax is the same as
.gitignore
files. Here we first add everything to the sparse checkout, then exclude thesrc
directory and finally include the extensions we want to work on. -
Configure remotes:
# add upstream git remote add upstream https://github.com/sehnryr/midoku-community-extensions # optionally disable push to upstream git remote set-url --push upstream no_pushing # optionally fetch main only (ignore all other branches) git config remote.upstream.fetch "+refs/heads/main:refs/remotes/upstream/main" # update remotes git remote update # track main of upstream instead of fork git branch main -u upstream/main
-
Useful configurations (optional):
# prune obsolete remote branches on fetch git config remote.origin.prune true # fast-forward only when pulling main branch git config pull.ff only
Important
Later, if changes are made to the sparse checkout filter, you will need to
reapply it using git sparse-checkout reapply
.
Read more on Git's object model, partial clone, sparse checkout and negative refspecs.
Join the Midoku Discord server for online help and to ask questions while developing your extension. When doing so, please ask it in the #extension-dev channel.
Some features and tricks not covered in this guide can be found in the existing extension code. Please refer to it for examples.
The quickest way to get started is to copy an existing extension and renaming it as needed. We also recommend reading through the code of a few existing extensions before starting.
Each extension should reside in src/<lang>/<extension-name>
.
<lang>
should be an IETF BCP 47 compliant
language subtag (or multi
for extensions that support
multiple languages). For example, pt
for Portuguese or Brazilian (pt-BR
),
en
for English, etc.
<extension-name>
should be a unique name for the extension in kebab-case.
The following is the basic file structure for an extension:
$ tree src/<lang>/<extension-name>
src/<lang>/<extension-name>
├── build.rs
├── Cargo.toml
├── res
│ ├── filters.json
│ ├── icon.png
│ └── source.json
├── src
│ ├── bindings.rs
│ └── lib.rs
└── wit
├── deps
│ ├── midoku-bindings
│ │ └── bindings.wit
│ ├── midoku-http
│ │ └── http.wit
│ ├── midoku-limiter
│ │ └── limiter.wit
│ ├── midoku-settings
│ │ └── settings.wit
│ └── midoku-types
│ └── types.wit
└── world.wit
10 directories, 13 files
src/bindings.rs
is an automatically generated file by wit-bindgen
and should
not be edited manually. It contains the bindings to the Wit world.
The wit
directory contains the Wit world and its dependencies. The world.wit
file is the main file that defines the extension's behavior. Its content should
not be edited manually.
build.rs
is a build script that automatically puts in the version of the
package from Cargo.toml
into the res/source.json
file.
This file contains the manifest of the extension. It should follow this structure:
{
"name": "<source-name>",
"language": "<lang>",
"version": "<extension-version>",
"url": "<source-url>",
"nsfw": <true|false>,
}
Field | Description |
---|---|
name |
The displayed name of the source. |
language |
The language of the source. It should be an IETF BCP 47 compliant language tag. |
version |
The version of the extension. It should follow Semantic Versioning. |
url |
The URL of the source. |
nsfw |
Whether the source contains NSFW content. |
This file contains the search filters for the source. Read through the
filters.json
of other extensions to understand how to write it.
This file contains the settings for the extension. It contains a dictionary
where the key is the setting unique name and the value is the setting. Read
through the settings.json
of other extensions to understand how to write it.
If the extension supports multiple languages, this file should be present. It contains the languages supported by the source. It should follow this structure:
{
"en_US": {
"code": "en",
"default": true
},
"ja_JP": {
"code": "ja"
},
...
}
Keys are the IETF BCP 47 compliant language tags and values are objects containing the extension's internal language code and whether it is the default language (only one language should be marked as default).
This file contains the main logic of the extension. Here is a minimal template:
#[allow(warnings)]
mod bindings;
use bindings::exports::midoku::bindings::api::Guest;
use bindings::exports::midoku::types::chapter::Chapter;
use bindings::exports::midoku::types::filter::Filter;
use bindings::exports::midoku::types::manga::Manga;
use bindings::exports::midoku::types::page::Page;
struct Component;
impl Guest for Component {
fn initialize() -> Result<(), ()> {
...
}
fn get_manga_list(filter: Vec<Filter>, page: u32) -> Result<(Vec<Manga>, bool), ()> {
...
}
fn get_manga_details(manga_id: String) -> Result<Manga, ()> {
...
}
fn get_chapter_list(manga_id: String) -> Result<Vec<Chapter>, ()> {
...
}
fn get_page_list(manga_id: String, chapter_id: String) -> Result<Vec<Page>, ()> {
...
}
}
bindings::export!(Component with_types_in bindings);
This file contains the metadata of the extension package. Here is a minimal template:
[package]
name = "<extension-package-name>"
version = "0.1.0"
edition.workspace = true
publish.workspace = true
[dependencies]
wit-bindgen-rt = { version = "0.24.0", features = ["bitflags"] }
[build-dependencies]
serde = { version = "1.0.201", features = ["derive"] }
serde_json = "1.0.117"
[lib]
crate-type = ["cdylib"]
[package.metadata.component]
package = "midoku:midoku-extension"
[package.metadata.component.target.dependencies]
"midoku:bindings" = { path = "wit/deps/midoku-bindings" }
"midoku:http" = { path = "wit/deps/midoku-http" }
"midoku:limiter" = { path = "wit/deps/midoku-limiter" }
"midoku:settings" = { path = "wit/deps/midoku-settings" }
"midoku:types" = { path = "wit/deps/midoku-types" }
extension-package-name
should be the name of the extension package. It should
be unique and follow this format: midoku-<lang>-<source-name>
. For example,
midoku-multi-mangadex
.
The package.metadata.component
section is used to specify the extension's
metadata for cargo-component
. It should not be modified.
The lib.crate-type
should be set to ["cdylib"]
to build the extension as a
WebAssembly module.
wit-bindgen-rt
is a dependency that provides the runtime for the Wit bindings.
It is required for all extensions and should not be removed.
Read through the code of other extensions to understand how to write it.
The following functions should be implemented in the Guest
trait:
This function is called when the extension is initialized. It should be used to initialize the extension and set up any necessary configuration.
This function should return a list of manga based on the given filters and page number. It should return a tuple containing the list of manga and a boolean indicating whether there are more pages.
This function should return the details of a manga based on the given manga ID.
This function should return a list of chapters based on the given manga ID.
This function should return a list of pages based on the given manga ID and chapter ID.
To test the extension, run the following command:
cargo test --package <extension-package-name>
Replace <extension-package-name>
with the name of the extension package.
Unit tests should be written in the files under the src
directory. The tests
should be placed in the same file as the code they are testing.
Integration tests should be written in the tests
directory. Each test file
should be named <test-name>.rs
and should contain the tests for the
extension.
Read through the integration tests of MangaDex's extension to understand how to write them.
To build the extension, run the following command:
cargo component build --release --target wasm32-unknown-unknown --package <extension-package-name>
Replace <extension-package-name>
with the name of the extension package.
You can also build all extensions (which is not recommended when the number of extensions is large) by running the following command:
cargo component build --release --target wasm32-unknown-unknown --workspace
The built extension will be located in the
target/wasm32-unknown-unknown/release
directory.
When you feel confident about your changes, submit a new Pull Request so your
code can be reviewed and merged if approved. We encourage following a
GitHub Standard Fork & Pull Request Workflow
and following the good practices of the workflow, such as not commiting directly
to main
: always create a new branch for your changes.
Please do test your changes before submitting a Pull Request. Also make sure to follow the Pull Request checklist available in the Pull Request body field when creating a new Pull Request. As a reference, you can find it below.
- I updated extension's version for individual extension changes
- I set appropriate
nsfw
value - I did not change
id
even if an extension's name or language were changed - I tested the modifications locally