Skip to content

Development Guideline v2

Alex Ling edited this page Apr 3, 2022 · 1 revision

First of all, thank you for your interest in contributing! Please read this guideline carefully before you start working on your plugin.

Your plugin folder should have the following structure

.
├── index.js
├── info.json
└── README.md

Thanks to Duktape and duktape.cr, the plugins can be written in simple JavaScript. The index.js file defines the logic of the plugin. Unfortunately, due to some technical limitations, there's no ES6 syntax support (duktape#2261), and you cannot require other modules in your script (duktape.cr#55).

Types

A type is a JavaScript object with required and optional properties. Here we define some types that will be used throughout this documentation.

Manga

A manga series in your plugin.

Key Type Description Required
id string A unique ID of the manga true
title string true
description string
authors Array<string>
cover_url string
tags Array<string>

Chapter

A downloadable chapter in your plugin.

Key Type Description Required
id string A unique ID of the chapter true
title string true
pages number Number of pages in the chapter true
manga_title string Title of the manga containing this chapter true
volume string The volume number
chapter string The chapter number
groups Array<string> A list of group names
language string
tags Array<string>

NOTE: The chapter ID must be universally unique within your plugin. You can't have two chapters (even in different manga) with the same ID.

Page

A downloadable page in a chapter.

Key Type Description Required
url string true
filename string The unique filename of the page true
headers object<string, string> The headers to use when downloading the page

Helper Functions

Mango provides some helper functions for you to use in your plugin script. They can be accessed from the mango object.

  • mango.get(url, [headers])

This function sends a GET request to url, and optionally you can pass in a second parameter headers. It returns an object with the following fields: body, status_code, and headers.

Example:

mango.get('https://github.com');

// returns the following object
{
  status_code: 200,
  body: "<!DOCTYPE html><html lang=\"en\"> ... </html>",
  headers: {
    "content-type": "text/html; charset=utf-8",
    "server":"GitHub.com",
    // ...
  }
}
  • mango.post(url, body, [headers]) (Mango version > v0.18.0)

This function sends a POST request to url. The request body can be defined with the second parameter, and the third parameter headers is optional. It returns an object with the following fields: body, status_code, and headers.

Example:

mango.post('https://httpbin.org/anything', "Test Body", {"Header1": "Value1", "Header2": "Value2"});

// sends the following data
{ 
  'data': 'Test Body'
  'headers': { 
    'Accept-Encoding': 'gzip, deflate',  
    'Content-Length': '9',  
    'Header1': 'Value1',  
    'Header2': 'Value2',  
    'Host': 'httpbin.org',  
    'User-Agent': 'Crystal'
    // ...
  }
}

// receives the following data
{
  "status_code": 200,
  "body": { 
    'data': 'Test Body'
    'headers': { 
      'Accept-Encoding': 'gzip, deflate',  
      'Content-Length': '9',  
      'Header1': 'Value1',  
      'Header2': 'Value2',  
      'Host': 'httpbin.org',  
      'User-Agent': 'Crystal'
    },  
    'json': null,  
    'method': 'POST'
  },
  "headers": {
    "Content-Type": "application/json",
    "Server": "gunicorn/19.9.0",
    // ...
  }
}
  • mango.css(html, selector)

This function applies the CSS selector and returns an array of matched HTML elements. An empty array is returned when no match is found.

Example:

mango.css('<ul><li>A</li><li>B</li><li>C</li></ul>', 'li');

// returns the following array
["<li>A</li>", "<li>B</li>", "<li>C</li>"]
  • mango.text(html)

This function returns the inner text of html. An empty string is returned when the HTML contains no text.

Example:

mango.text('<a href="https://github.com">Click Me<a>');

// returns the following string
"Click Me"
  • mango.attribute(html, attr)

This function returns the attr attribute of the outmost node. If no matched attribute is found, it returns undefined.

Example:

mango.attribute('<a href="https://github.com">Click Me<a>', 'href');

// returns the following string
"https://github.com"
  • mango.storage(key, [val])

This function can be used to store and retrieve key/value pairs. The data is stored in storage.json in your plugin folder, and Mango will not modify its content.

Usage:

// This stores the login token
mango.storage('login-token', '9fa35e3588c15bf4cc13ee1f4d5043ab');

// This retrieves the login token
var token = mango.storage('login-token');

// If the key does not exist, the function returns `undefined`
mango.storage('heyo'); // undefined
  • mango.settings(key)

This function retrieves the value for key as defined in the settings property in info.json. It returns undefined when the key doesn't exist, or when the value is null. For example, in your plugin's README, you may ask the user to paste their credentials for a website into the settings property, and in your script you can load the credentials with this function and perform authentication.

Usage:

var username = mango.settings('username');
var password = mango.settings('password');

if (!username || !password) {
  // error handling
  // ...
}

// Use the values defined in settings
var token = myAwesomeLoginFunction(username, password);
  • mango.raise(msg)

This function raises an exception with error message msg in the upstream Crystal method.

index.js

This file defines the logic of your plugin. You must define the following functions in index.js:

  • searchManga(query: string): Array<Manga>
  • listChapters(manga_id: string): Array<Chapter>
  • selectChapter(id: string): Chapter
  • nextPage(): Page

And optionally you can define the following functions

  • newChapters(manga_id: string, after_timestamp: number): Array<Chapter>

NOTE: The returned values from these functions must be JSON stringified.

We will now go through the functions in detail here.

searchManga(query: string): Array<Manga>

This function is called by Mango when the user searches something on the download page of your plugin, and query is a string containing the user's input. It should return a JSON stringified array of Manga matching the query. If no matching manga is found, return a stringified empty array.

listChapters(manga_id: string): Array<Chapter>

This function is called by Mango when the user chooses a manga to view its chapters, and manga_id is the ID of the selected manga. It should return a JSON stringified array of Chapters within the manga.

selectChapter(id: string): Chapter

This function is called by Mango when it starts to download the chapter with id, and it should return a JSON stringified Chapter matching the ID.

nextPage(): Page

This function is called by Mango when it starts to download a page of the selected chapter, and it should return a JSON stringified Page. You can optionally include the headers field in the Page object to instruct Mango to use the headers when downloading the page.

newChapters(manga_id: string, after_timestamp: number): Array<Chapter>

This function is optional. When it is defined, the users will be able to subscribe to some manga in your plugin. Mango will call this method periodically to check for new chapter in the manga, and any chapter returned will be added to the download queue.

The function should return a JSON stringified array of Chapters. manga_id is the ID of the manga, and after_timestamp is a unix timestamp in milliseconds. Your implementation of this function should check for new chapters released AFTER the specified timestamp. If no new chapters are available, return a JSON stringified empty array.

info.json

This JSON file contains the metadata of the plugin. It must contain the following fields:

  • id: The ID of the plugin. It should contain only alphanumerical characters and underlines. In principle, it should be identical to the plugin folder name.
  • title: The human-readable title of the plugin.
  • placeholder: The placeholder text to be displayed on the input field on the plugin download page.
  • wait_seconds: The number of seconds to wait before downloading the next page.

Optionally you can include the following fields

  • api_version: The API version to use. Since you are reading the development guideline for v2, you probably want to put 2 in this field. If this field is undefined, Mango would treat it as a v1 plugin.
  • settings: A read-only object containing settings that can be modified by users. You can retrieve the content using the mango.settings() helper function. Each value in the object should be either a string or null.