Skip to content
This repository was archived by the owner on Mar 23, 2025. It is now read-only.

Commit 3ea8415

Browse files
authored
Merge pull request #5 from TheBritishAccent/master
2 parents b6cf0d9 + faebe5b commit 3ea8415

File tree

6 files changed

+341
-0
lines changed

6 files changed

+341
-0
lines changed

plugins/catmanga/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Catmanga Plugin
2+
3+
This is the Mango plugin for [Catmanga](https://catmanga.org).
4+
5+
Note that
6+
7+
- All downloaded chapters will be placed in their respective series folder in your library.
8+
- Cloudflare rate limits extensive downloads. Adjust `wait_seconds` in `info.json` if you plan to download many chapters in one go (or chapters with many pages).
9+
10+
Maintained by [@TheBritishAccent](https://github.com/TheBritishAccent).

plugins/catmanga/index.js

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
var currentPage = 0;
2+
var digits = 0;
3+
var imgURLs;
4+
5+
const ROOT_URL = "https://catmanga.org/";
6+
7+
// Helper function. Builds the API URL since there are no ES6 template strings.
8+
function buildApiEndpoint(buildID, mangaID, chapterNum) {
9+
return ROOT_URL + "_next/data/" + buildID
10+
+ "/series/" + mangaID + "/" + chapterNum + ".json";
11+
}
12+
13+
function listChapters(query) {
14+
// Get API data (stored in a <script> tag in the index html)
15+
// We can't use the API directly, since that means already
16+
// knowing the correct buildID.
17+
//
18+
// URL in format https://catmanga.org/_next/data/{BUILD-ID}/{ENDPOINT}.json
19+
20+
var indexHTML = mango.get(ROOT_URL).body
21+
if (!indexHTML) {
22+
mango.raise("Failed to get index.")
23+
}
24+
25+
var APINode = mango.css(indexHTML, "script#__NEXT_DATA__")[0];
26+
27+
if (!APINode) {
28+
mango.raise("Failed to get series data.")
29+
}
30+
31+
var indexJSONString = mango.text(APINode);
32+
33+
if (!indexJSONString) {
34+
mango.raise("An error occured when searching.");
35+
}
36+
37+
var indexJSON = JSON.parse(indexJSONString);
38+
var allSeries = indexJSON["props"]["pageProps"]["series"];
39+
40+
// Search all manga on site to find something that matches query.
41+
// We have to do it manually since Catmanga has no searching.
42+
var manga;
43+
allSeries.forEach(function(element) {
44+
if (query.toLowerCase() == element["title"].toLowerCase() ||
45+
query.toLowerCase() == element["series_id"].toLowerCase()) {
46+
manga = element;
47+
} else {
48+
element["alt_titles"].forEach(function(title) {
49+
if (title.toLowerCase() == query.toLowerCase()) {
50+
manga = element;
51+
}
52+
});
53+
}
54+
});
55+
56+
if (!manga) {
57+
mango.raise("Failed to find manga with title: " + query);
58+
}
59+
60+
var mangaTitle = manga["title"];
61+
62+
if (!mangaTitle) {
63+
mango.raise("Failed to get title of manga.");
64+
}
65+
66+
var chapters = [];
67+
manga["chapters"].forEach(function(chapter) {
68+
var seriesID = manga["series_id"];
69+
var chapterNum = chapter["number"].toString().replace(/\./, "_");
70+
var chapterGroups = chapter["groups"].join(", ")
71+
72+
var slimObj = {};
73+
slimObj["id"] = seriesID + "ch" + chapterNum
74+
slimObj["title"] = "Chapter " + chapter["number"];
75+
slimObj["groups"] = chapterGroups;
76+
77+
chapters.push(slimObj);
78+
});
79+
80+
return JSON.stringify({
81+
chapters: chapters.reverse(), // Catmanga sorts chapters oldest -> newest
82+
title: mangaTitle
83+
});
84+
}
85+
86+
function selectChapter(id) {
87+
// Get buildID for chapter
88+
var mangaIDMatch = /(.*?)ch((\d_?)*)$/.exec(id);
89+
var mangaID = mangaIDMatch[1];
90+
var mangaChapterNumber = mangaIDMatch[2].replace(/\_/, "."); // Convert '_' back to '.
91+
92+
var chapterReaderURL = ROOT_URL + "series/" + mangaID + "/" + mangaChapterNumber;
93+
var chapterHTML = mango.get(chapterReaderURL).body;
94+
95+
if (!chapterHTML) {
96+
mango.raise("Failed to load chapter HTML.");
97+
}
98+
99+
var APINode = mango.css(chapterHTML, "script#__NEXT_DATA__")[0];
100+
101+
if (!APINode) {
102+
mango.raise("Failed to get chapter buildID.")
103+
}
104+
105+
var buildIDJSONString = mango.text(APINode);
106+
var buildIDJSON = JSON.parse(buildIDJSONString);
107+
108+
var buildID = buildIDJSON["buildId"];
109+
110+
// Get manga images from API
111+
var mangaURL = buildApiEndpoint(buildID, mangaID, mangaChapterNumber);
112+
var mangaJSONString = mango.get(mangaURL).body;
113+
114+
if (!mangaJSONString) {
115+
mango.raise("Failed to load chapter.");
116+
}
117+
118+
var mangaJSON = JSON.parse(mangaJSONString);
119+
120+
var chapterTitle = "Chapter " + mangaJSON["pageProps"]["chapter"]["number"];
121+
122+
if (!chapterTitle) {
123+
mango.raise("Failed to get chapter title.");
124+
}
125+
126+
imgURLs = mangaJSON["pageProps"]["pages"];
127+
currentPage = 0;
128+
digits = Math.floor(Math.log10(imgURLs.length)) + 1;
129+
130+
return JSON.stringify({
131+
title: chapterTitle,
132+
pages: imgURLs.length
133+
});
134+
}
135+
136+
function nextPage() {
137+
if (currentPage >= imgURLs.length) {
138+
return JSON.stringify({});
139+
}
140+
141+
var url = imgURLs[currentPage]
142+
var filename = pad(currentPage, digits) + '.' + /\.(\w+)$/.exec(url)[0];
143+
144+
currentPage += 1;
145+
return JSON.stringify({
146+
url: url,
147+
filename: filename,
148+
headers: {
149+
'referer': "https://catmanga.org"
150+
}
151+
});
152+
}
153+
154+
// https://stackoverflow.com/a/10073788
155+
function pad(n, width, z) {
156+
z = z || '0';
157+
n = n + '';
158+
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
159+
}

plugins/catmanga/info.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "Catmanga",
3+
"title": "Catmanga",
4+
"author": "TheBritishAccent - [email protected]",
5+
"version": "v1.0",
6+
"placeholder": "Enter a full manga title to search Catmanga",
7+
"wait_seconds": 1
8+
}

plugins/cubari/README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Cubari Proxy Plugin
2+
3+
This is the Mango plugin for [Cubari](https://cubari.moe/).
4+
5+
Note that
6+
7+
- Accepted Cubari chapter URLs look like `https://cubari.moe/read/gist/xxxxx/x/x/` or `https://cubari.moe/read/imgur/xxxxxx/x/x/`.
8+
- Gist chapters will be placed in their respective series folder in your library.
9+
- Imgur chapters will be placed in the `cubari` series folder in your library, named `imgur-{ID}`.
10+
- nhentai chapter URLs are not supported. Use [this](https://github.com/hkalexling/mango-plugins/tree/master/plugins/nhentai) plugin instead.
11+
12+
Maintained by [@TheBritishAccent](https://github.com/TheBritishAccent).

plugins/cubari/index.js

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
var currentPage = 0;
2+
var digits = 0;
3+
var imgURLs;
4+
5+
const API_URL = "https://cubari.moe/read/api/";
6+
7+
function listChapters(query) {
8+
var cubariURLMatch = /\/read\/(\w+)\/(.+?)\//.exec(query);
9+
10+
if (!cubariURLMatch) {
11+
manga.raise("Invalid Cubari URL.");
12+
}
13+
14+
if (cubariURLMatch[1] != "gist" && cubariURLMatch[1] != "imgur") {
15+
manga.raise("Invalid Cubari URL.");
16+
}
17+
18+
var cubariType = cubariURLMatch[1]; // "imgur" or "gist"
19+
var mangaSlug = cubariURLMatch[2];
20+
var mangaURL = API_URL + cubariType + "/series/" + mangaSlug + "/";
21+
22+
var mangaJSONString = mango.get(mangaURL).body;
23+
24+
if (!mangaJSONString) {
25+
mango.raise("Failed to get JSON data.");
26+
}
27+
28+
var manga = JSON.parse(mangaJSONString);
29+
30+
if (!manga) {
31+
mango.raise("Failed to get manga");
32+
}
33+
34+
var mangaTitle = manga["title"];
35+
36+
if (!mangaTitle) {
37+
mango.raise("Failed to get title of manga.");
38+
}
39+
40+
var chapters = [];
41+
var chapterIndexAsArray = Object.keys(manga["chapters"]); // Since manga.chapters is an obj.
42+
chapterIndexAsArray.forEach(function(index) {
43+
const chapter = manga["chapters"][index];
44+
45+
var chapterID = cubariType + "___" + mangaSlug.replace(/\-/, "_")
46+
+ "___" + index.toString().replace(/\./, "_");
47+
var chapterTitle = chapter["title"];
48+
49+
var slimObj = {};
50+
slimObj["id"] = chapterID;
51+
slimObj["title"] = chapterTitle;
52+
53+
chapters.push(slimObj);
54+
});
55+
56+
return JSON.stringify({
57+
chapters: chapters.reverse(), // Cubari sorts chapters oldest -> newest
58+
title: cubariType == "imgur" ? "cubari" : mangaTitle
59+
});
60+
}
61+
62+
function selectChapter(id) {
63+
var mangaIDMatch = /(gist|imgur)_{3}(.+?)_{3}(.+)$/.exec(id);
64+
var cubariType = mangaIDMatch[1];
65+
var mangaSlug = mangaIDMatch[2].replace(/\_/, "-"); // Convert '_' back to '-'
66+
var chapterNum = mangaIDMatch[3].replace(/\_/, "."); // Convert '_' back to '.'
67+
68+
var chapterTitle = cubariType == "imgur" ? "imgur-" + mangaSlug : "Chapter " + chapterNum;
69+
70+
var proxySlug = mangaSlug;
71+
72+
if (cubariType == "gist") {
73+
var mangaURL = API_URL + cubariType + "/series/" + mangaSlug + "/";
74+
var mangaJSONString = mango.get(mangaURL).body;
75+
76+
if (!mangaJSONString) {
77+
mango.raise("Failed to get JSON data.");
78+
}
79+
80+
var manga = JSON.parse(mangaJSONString);
81+
82+
if (!manga) {
83+
mango.raise("Failed to get manga");
84+
}
85+
86+
var groups = manga["chapters"][chapterNum]["groups"];
87+
var groupFirstKey = Object.keys(groups)[0]; // Get first (and most likely only) group url.
88+
var proxyURL = groups[groupFirstKey];
89+
90+
var mangaURLMatch = /chapter\/(.+?)\//.exec(proxyURL);
91+
92+
if (!mangaURLMatch) {
93+
mango.raise("Failed to get chapter image URL.");
94+
}
95+
96+
proxySlug = mangaURLMatch[1];
97+
}
98+
99+
chapterURL = API_URL + "imgur/chapter/" + proxySlug + "/";
100+
101+
var proxyJSONString = mango.get(chapterURL).body;
102+
103+
if (!proxyJSONString) {
104+
mango.raise("Failed to get proxy JSON data.");
105+
}
106+
107+
var proxyJSON = JSON.parse(proxyJSONString);
108+
109+
imgURLs = [];
110+
var chapterIndexAsArray = Object.keys(proxyJSON);
111+
chapterIndexAsArray.forEach(function(index) {
112+
imgURLs.push(proxyJSON[index]["src"]);
113+
});
114+
115+
currentPage = 0;
116+
digits = Math.floor(Math.log10(imgURLs.length)) + 1;
117+
118+
return JSON.stringify({
119+
title: chapterTitle,
120+
pages: imgURLs.length
121+
});
122+
}
123+
124+
function nextPage() {
125+
if (currentPage >= imgURLs.length) {
126+
return JSON.stringify({});
127+
}
128+
129+
var url = imgURLs[currentPage]
130+
var filename = pad(currentPage, digits) + '.' + /\.(\w+)(\?.*)?$/.exec(url)[0];
131+
132+
currentPage += 1;
133+
return JSON.stringify({
134+
url: url,
135+
filename: filename
136+
});
137+
}
138+
139+
// https://stackoverflow.com/a/10073788
140+
function pad(n, width, z) {
141+
z = z || '0';
142+
n = n + '';
143+
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
144+
}

plugins/cubari/info.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "Cubari",
3+
"title": "Cubari",
4+
"author": "TheBritishAccent - [email protected]",
5+
"version": "v1.0",
6+
"placeholder": "Cubari Chapter URL",
7+
"wait_seconds": 1
8+
}

0 commit comments

Comments
 (0)