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

Commit 7c4ab71

Browse files
author
TheBritishAccent
committed
Add catmanga plugin
1 parent 07ab179 commit 7c4ab71

File tree

3 files changed

+177
-0
lines changed

3 files changed

+177
-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+
}

0 commit comments

Comments
 (0)