13 KiB
Contributing
This guide have some instructions and tips on how to create a new Mangayomi extension on Dart extension.
Prerequisites
Before starting please have installed the recent desktop version of the mangayomi application preferably or if you want with a tablet too.
Writing your extension
- Open the app.
- Go to extension tab :

- then click
+and you will see :
- Fill in the fields with your new source that you would like to create,
NB: only the ApiUrlfield is optional then click on save - you will see your new source in the extension list
click to open settings - After click on edit code

- Finally you can now write the extension

- This page contains three parts:
- Code editor: where you will write your code
- Fecth result: where you will test the different implemented methods by having a result in the expected format
- Console: which will show you the logs
Once extension is ready you can relocate your code into mangayomi-extension project in a src or multisrc package
Create the folder with the name of the source such as this example
after go either to the anime_source_list.dart file for anime or manga_source_list.dart for the manga and import the extension then
create a Pull Request.
Source
| Field | Description |
|---|---|
name |
Name displayed in the "Sources" tab in Mangayomi. |
baseUrl |
Base URL of the source without any trailing slashes. |
apiUrl |
(Optional, defaults is empty) Api URL of the source with trailing slashes. |
lang |
An ISO 639-1 compliant language code (two letters in lower case in most cases, but can also include the country/dialect part by using a simple dash character). |
id |
Identifier of your source, automatically set in Source. It should only be manually overriden if you need to copy an existing autogenerated ID. |
sourceCodeUrl |
contains the URL where the extension source code can be downloaded |
sourceCode |
contains the extension source code |
isManga |
(Optional, defaults to true) specify source type (false for anime and true for manga) |
dateFormat |
(Optional, defaults is empty) |
iconUrl |
The extension icon URL |
version |
The extension version code. This must be incremented with any change to the code. |
dateFormatLocale |
(Optional, defaults is empty) |
isNsfw |
(Optional, defaults to false) Flag to indicate that a source contains NSFW content. |
Extension call flow
Popular manga
a.k.a. the Browse source entry point in the app (invoked by tapping on the source name).
- The app calls
getPopularwhich should return aMPagescontaining the first batch of foundMMangaentries.- This method supports pagination. When user scrolls the manga list and more results must be fetched, the app calls it again with increasing
pagevalues(starting withpage=1). This continues whileMPages.hasNextPageis passed astrueandMPages.listis not empty.
- This method supports pagination. When user scrolls the manga list and more results must be fetched, the app calls it again with increasing
- To show the list properly, the app needs
url,titleandimageUrl. You must set them here. The rest of the fields could be filled later.(refer to Manga Details below).
Latest manga
a.k.a. the Latest source entry point in the app (invoked by tapping on the "Latest" button beside the source name).
- Similar to popular manga, but should be fetching the latest entries from a source.
Search manga
- When the user searches inside the app,
searchwill be called and the rest of the flow is similar to what happens withgetPopular.- If search functionality is not available, return
MPages([], false)
- If search functionality is not available, return
getFilterListwill be called to get all filters and filter types.
Filters
The search flow have support to filters that can be added to a FilterList inside the getFilterList method. When the user changes the filter's state, they will be passed to the search method, and they can be iterated to create the request (by getting the filter.state value, where the type varies depending on the Filter used). You can check the filter types available here and in the table below.
| Filter | Description |
|---|---|
HeaderFilter |
A simple header. Useful for separating sections in the list or showing any note or warning to the user. |
SeparatorFilter |
A line separator. Useful for visual distinction between sections. |
SelectFilter |
A select control, similar to HTML's <select>. Only one item can be selected, and the state is the index of the selected one. |
TextFilter |
A text control, similar to HTML's <input type="text">. |
CheckBoxFilter |
A checkbox control, similar to HTML's <input type="checkbox">. The state is true if it's checked. |
TriStateFilter |
A enhanced checkbox control that supports an excluding state |
GroupFilter |
A group of filters (preferentially of the same type). The state will be a List with all the states. |
SortFilter |
A control for sorting, with support for the ordering. The state indicates which item index is selected and if the sorting is ascending. |
All control filters can have a default state set. It's usually recommended if the source have filters to make the initial state match the popular manga list, so when the user open the filter sheet, the state is equal and represents the current manga showing.
Manga Details
- When user taps on an manga,
getDetailwill be called and the results will be cached.- A
MMangaentry is identified by itsurl.
- A
getDetailis called to update an manga's details from when it was initialized earlier.MManga.titleis a string containing title.MManga.descriptionis a string containing description.MManga.authoris a string containing author.MManga.genrecontain list of all genres.MManga.statusis an "enum" value.- To get the status in enum value from a status string, you can use a
parseStatusfunction like in the example below.StatusparseStatus(String status,List<dynamic> statusList)
Refer to the values in thefinal statusList = [ { "ongoing": 0, "complete": 1, "hiatus": 2, "canceled": 3, "publishingFinished": 4, } ]; final status = parseStatus('ongoing', statusList); print(status); // Status.ongoingMMangamodel.- To get the status in enum value from a status string, you can use a
- During a backup, only
urlandtitleare stored. To restore the rest of the manga data, the app callsgetDetail, so all fields should be (re)filled in if possible. - If a
MMangais cachedgetDetailwill be only called when the user does a manual update(Swipe-to-Refresh). MManga.chapterscontain list of all manga chapters.MChapter.nameis a string containing a chapter name.MChapter.urlis a string containing a chapter url.MChapter.scanlatoris a string containing a chapter scanlator.MChapter.dateUploadis a string containing date expressed in millisecondsSinceEpoch.- To get the time in millisecondsSinceEpoch from a date string, you can use a
parseDatesfunction like in the example below.List<dynamic>parseDates(List<dynamic> values,String dateFormat,String dateFormatLocale,)final dates = parseDates(["2023-12-10T11:49:02+000"], "yyyy-MM-dd'T'HH:mm:ss+SSS", "en_US"); - If you don't pass
MChapter.dateUploadand leave it null, the app will use the default date instead, but it's recommended to always fill it if it's available.
- To get the time in millisecondsSinceEpoch from a date string, you can use a
Chapter pages
- When user opens an chapter,
getPageListwill be called and it will return aList<String>orList<Map<String,dynamic>>with a list of{"url":string,"headers":Map<String,String>}that are used by the reader.
Episode Videos
- When user opens an episode,
getVideoListwill be called and it will return aList<MVideo>that are used by the player.
Example sources that can help you understand how to create your source
- Example of Json API usage.
- Example of HTML parsing using xpath selector.
- Example of HTML parsing using HTML DOM selector.
Some functions already available and usable
http client
Return Response
- Simple request
final Client client = Client();
final res = await client.get(Uri.parse("http://example.com"));
print(res.body);
- With headers
final Client client = Client();
final res = await client.get(Uri.parse("http://example.com"),headers:{"Referer": "http://example.com"});
print(res.body);
- With body
final Client client = Client();
final res = await client.post(Uri.parse("http://example.com"),headers:{"Referer": "http://example.com"},'body':{'name':'test'});
print(res.body);
xpath selector
Return result as List<String>
Example:
final String htmlString = '''
<html lang="en">
<body>
<div><a href='https://github.com/kodjodevf'>author</a></div>
<div class="head">div head</div>
<div class="container">
<table>
<tbody>
<tr>
<td id="td1" class="first1">1</td>
<td id="td2" class="first1">2</td>
<td id="td3" class="first2">3</td>
<td id="td4" class="first2 form">4</td>
<td id="td5" class="second1">one</td>
<td id="td6" class="second1">two</td>
<td id="td7" class="second2">three</td>
<td id="td8" class="second2">four</td>
</tr>
</tbody>
</table>
</div>
<div class="end">end</div>
</body>
</html>
''';
List<String> xpathRes = xpath(htmlString,'//div/a/@href');
print(xpathRes); // [https://github.com/kodjodevf]
print(xpathRes.first); // https://github.com/kodjodevf
HTML DOM selector
Example:
final String htmlString = '''
<html lang="en">
<body>
<div><a href='https://github.com/kodjodevf'>author</a></div>
<div class="head">div head</div>
<div class="container">
<table>
<tbody>
<tr>
<td id="td1" class="first1">1</td>
<td id="td2" class="first1">2</td>
<td id="td3" class="first2">3</td>
<td id="td4" class="first2 form">4</td>
<td id="td5" class="second1">one</td>
<td id="td6" class="second1">two</td>
<td id="td7" class="second2">three</td>
<td id="td8" class="second2">four</td>
</tr>
</tbody>
</table>
</div>
<div class="end">end</div>
</body>
</html>
''';
MDocument document = parseHtml(htmlString);
print(document.selectFirst("a").attr("href")); // https://github.com/kodjodevf
print(document.selectFirst("td").text); // 1
See MDocument model and MElement model to see available methods.
String utils
StringsubstringAfter(String text,String pattern)StringsubstringAfterLast(String text,String pattern)StringsubstringBefore(String text,String pattern)StringsubstringBeforeLast(String text,String pattern)StringgetUrlWithoutDomain(String url)
Crypto utils
StringunpackJs(String code);Future<String>evalJs(String code);StringdeobfuscateJsPassword(String inputString)StringencryptAESCryptoJS(String plainText,String passphrase)StringdecryptAESCryptoJS(String encrypted,String passphrase)StringcryptoHandler(String text,String iv,String secretKeyString,bool encrypt)
Help
If you need a help or have some questions, ask a community in our Discord server.