Skip to content

Extension API V2

appdevelpo edited this page Apr 14, 2026 · 2 revisions

Miru Extension API V2 (Recommended)

Miru API V2 uses a modern, global function-based approach for a cleaner syntax and native async/await support.

JS Environment Restrictions

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.

Filter Lifecycle

To provide a dynamic search experience, the filter system follows this flow:

  1. 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).
  2. Execution: When the user finally presses the Search button, your search(kw, page, filter) function is called. The filter parameter will contain all the selections currently active in the UI.

Watch & Mirror Lifecycle

For API V2, the process of playing a video or viewing content follows a two-step resolution:

  1. 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 of mirrors.
  2. 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 the url passed to it.
    • Custom Logic: If your watch() function returns mirrors, the mirror() function is your chance to resolve those mirrors into a final stream (e.g., executing a second scraping step or solving a challenge).

Core Interface

To implement a V2 extension, you define the following global async functions in your .js file.

1. load()

Called when the extension is initialized. Use this for setup and registering settings.

async function load() {
    // Register settings or init data
}

2. latest(page)

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"
    }
  }
]

3. search(kw, page, filter)

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 in createFilter.
  • Returns: [ExtensionListItem]

Example JSON Response:

[
  {
    "title": "Search Result",
    "url": "/anime/456",
    "cover": "https://example.com/cover2.jpg"
  }
]

4. createFilter(filter)

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"
    }
  }
}

5. detail(url)

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"
  }
}

6. watch(url)

Fetches the content for a specific episode or chapter.

  • url: string - The episode/chapter URL.
  • Returns: ExtensionBangumiWatch | ExtensionMangaWatch | ExtensionFikushonWatch

Bangumi (Video) Example:

{
  "type": "hls",
  "url": "https://example.com/master.m3u8",
  "subtitles": [
    { "title": "English", "url": "https://...", "language": "en" }
  ],
  "headers": { "User-Agent": "Miru" }
}

Manga (Images) Example:

{
  "urls": [
    "https://example.com/1.jpg",
    "https://example.com/2.jpg"
  ],
  "headers": { "Referer": "https://manga.site" }
}

Fikushon (Text/Novel) Example:

{
  "title": "Chapter 1",
  "subtitle": "The Start",
  "content": [
    "First paragraph text...",
    "Second paragraph text..."
  ]
}

Returning Mirrors (API V2 Exclusive)

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
}

7. mirror(url)

Resolves the final playable URL from a mirror link selected by the user.

  • url: string - The URL of the selected mirror.
  • Returns: string

Load & Settings

Use the load() function to initialize settings that will appear in the Miru UI.

registerSetting(setting)

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 with options (Key-Value map).
  • toggle (2): Checkbox.

getSetting(key)

Retrieves the current value of a setting.

const domain = await getSetting("domain");

Networking

Use the standard fetch() API for all network requests.

const res = await fetch("...");
const data = await res.json();

Utilities

Standard Node-like modules are available via require():

  • linkedom: DOM Parser
  • crypto-js: Cryptography
  • md5, jsencrypt, url