-
Notifications
You must be signed in to change notification settings - Fork 1
Extension API V2
Miru API V2 uses a modern, global function-based approach for a cleaner syntax and native async/await support.
Warning
Global Scope: Do not use const or let at the top-level global scope. The JS engine (Goja) may fail to evaluate the script correctly during re-loads. Use var for global variables or keep logic inside functions.
To provide a dynamic search experience, the filter system follows this flow:
-
Selection: When a user clicks an option in the filter UI, Miru calls
createFilter(filter)with the currently selected values. This allows you to dynamically change the next set of filter options based on previous choices (e.g., selecting a Category might fetch then update the Tags list). -
Execution: When the user finally presses the Search button, your
search(kw, page, filter)function is called. Thefilterparameter will contain all the selections currently active in the UI.
For API V2, the process of playing a video or viewing content follows a two-step resolution:
-
Initial Resolution: When a user clicks to watch an episode or chapter, Miru calls
watch(url). This function can return a direct stream/content URL or a structured list ofmirrors. -
Final Resolution: Miru then calls
mirror(url)to get the final playable stream link.-
Default Behavior: If you do not override the
mirror()function, the built-in runtime simply returns theurlpassed to it. -
Custom Logic: If your
watch()function returns mirrors, themirror()function is your chance to resolve those mirrors into a final stream (e.g., executing a second scraping step or solving a challenge).
-
Default Behavior: If you do not override the
To implement a V2 extension, you define the following global async functions in your .js file.
Called when the extension is initialized. Use this for setup and registering settings.
async function load() {
// Register settings or init data
}Fetch the latest updates or items.
-
page:
int- The requested page number. -
Returns:
[ExtensionListItem]
Example JSON Response:
[
{
"title": "Anime Title",
"url": "/anime/123",
"cover": "https://example.com/cover.jpg",
"update": "Ep 12",
"headers": {
"User-Agent": "Miru"
}
}
]Search for items based on a keyword and filters.
-
kw:
string- Search keyword. -
page:
int- Page number. -
filter:
object- The current filter values{ id: [values] }. This is the same object structure you define increateFilter. -
Returns:
[ExtensionListItem]
Example JSON Response:
[
{
"title": "Search Result",
"url": "/anime/456",
"cover": "https://example.com/cover2.jpg"
}
]Generate filter options for the search UI.
-
filter:
object- Currently active selections. Action: Every time a user interacts with a filter option in the UI, this function is re-called, allowing you to update available filters dynamically. -
Returns:
map<string, ExtensionFilter>
Example JSON Response:
{
"genre": {
"title": "Genres",
"min": 0,
"max": 1,
"default": "action",
"options": {
"action": "Action",
"romance": "Romance",
"horror": "Horror"
}
},
"year": {
"title": "Year",
"min": 0,
"max": 1,
"options": {
"2023": "2023",
"2022": "2022"
}
}
}Fetch item details and episodes/chapters.
-
url:
string- The item URL. -
Returns:
ExtensionDetail
Example JSON Response:
{
"title": "Steins;Gate",
"cover": "https://...",
"desc": "A mad scientist...",
"episodes": [
{
"title": "TV Series",
"urls": [
{
"name": "Episode 1",
"url": "/watch/1",
"update": "2023-10-01",
"description": "The first encounter."
},
{
"name": "Episode 2",
"url": "/watch/2",
"update": "2023-10-08"
}
]
}
],
"headers": {
"Referer": "https://mystream.site"
}
}Fetches the content for a specific episode or chapter.
-
url:
string- The episode/chapter URL. -
Returns:
ExtensionBangumiWatch|ExtensionMangaWatch|ExtensionFikushonWatch
{
"type": "hls",
"url": "https://example.com/master.m3u8",
"subtitles": [
{ "title": "English", "url": "https://...", "language": "en" }
],
"headers": { "User-Agent": "Miru" }
}{
"urls": [
"https://example.com/1.jpg",
"https://example.com/2.jpg"
],
"headers": { "Referer": "https://manga.site" }
}{
"title": "Chapter 1",
"subtitle": "The Start",
"content": [
"First paragraph text...",
"Second paragraph text..."
]
}Instead of a direct URL, you can return mirrors to let the user select the source.
{
"mirrors": [
{
"title": "Group A",
"mirrors": [
{ "name": "Mirror 1", "url": "https://site-a.com/link1" },
{ "name": "Mirror 2", "url": "https://site-b.com/link2" }
]
}
],
"default_group": "Group A",
"default_index": 0
}Resolves the final playable URL from a mirror link selected by the user.
-
url:
string- The URL of the selected mirror. -
Returns:
string
Use the load() function to initialize settings that will appear in the Miru UI.
async function load() {
// Input setting
await registerSetting({
title: "Site Domain",
key: "domain",
type: "input",
description: "Homepage URL used for scraping",
defaultValue: "https://example.com"
});
// Radio setting
await registerSetting({
title: "Quality Preference",
key: "quality",
type: "radio",
defaultValue: "1080p",
options: {
"1080p": "High (1080p)",
"720p": "Medium (720p)",
"480p": "Low (480p)"
}
});
}Setting Types:
-
input(0): Text field. -
radio(1): Selection withoptions(Key-Value map). -
toggle(2): Checkbox.
Retrieves the current value of a setting.
const domain = await getSetting("domain");Use the standard fetch() API for all network requests.
const res = await fetch("...");
const data = await res.json();Standard Node-like modules are available via require():
-
linkedom: DOM Parser -
crypto-js: Cryptography -
md5,jsencrypt,url