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

Commit ac64768

Browse files
committed
Add Webtoons plugin
1 parent 39b7590 commit ac64768

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed

plugins/webtoons/README.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Webtoons Plugin
2+
3+
This is the Mango plugin for [Webtoons](https://www.webtoons.com/en/).
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+
- For webtoons with a large number of chapters (200+), getting the chapter list takes marginally longer amount of time.
10+
11+
Maintained by [@browningluke](https://github.com/browningluke).

plugins/webtoons/index.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
var currentPage = 0;
2+
var digits = 0;
3+
var imgURLs;
4+
5+
const BASE_URL = "https://www.webtoons.com";
6+
const MOBILE_URL = "https://m.webtoons.com";
7+
const SEARCH_URL = "https://ac.webtoons.com/ac?q=en%5E";
8+
const SEARCH_PARAMS = "&q_enc=UTF-8&st=1&r_format=json&r_enc=UTF-8";
9+
10+
const LIST_ENDPOINT = "/episodeList?titleNo=";
11+
const PAGE_QUERY = "&page=";
12+
13+
function listChapters(query) {
14+
15+
try {
16+
var searchResp = mango.get(SEARCH_URL + encodeURI(query) + SEARCH_PARAMS).body;
17+
var search = JSON.parse(searchResp);
18+
var mangaID = search["items"][0][0][3];
19+
} catch (error) {
20+
mango.raise("Could not find a webtoon with that title.");
21+
}
22+
23+
if (!mangaID) mango.raise("Could not get webtoon ID.");
24+
25+
try {
26+
var resp = mango.get(BASE_URL + LIST_ENDPOINT + mangaID);
27+
var urlLocation = resp.headers.Location;
28+
} catch (error) {
29+
mango.raise("Could not get webtoon page.");
30+
}
31+
32+
chapters = [];
33+
var html = mango.get(MOBILE_URL + urlLocation, {
34+
'referer': MOBILE_URL
35+
}).body;
36+
37+
if (!html) mango.raise("Failed to get chapter list.");
38+
39+
var liChapters = mango.css(html, "ul#_episodeList li[id*=episode]")
40+
41+
if (!liChapters) mango.raise("Failed to find chapters.");
42+
43+
liChapters.forEach(function(chapter) {
44+
var url = mango.attribute(mango.css(chapter, "a")[0], 'href');
45+
46+
var chapterIDRegex = /webtoons\.com\/\w{2}\/\w+\/(\w-?)+\/(.+)\//;
47+
var chapterIDMatch = chapterIDRegex.exec(url);
48+
49+
var chapterID;
50+
try {
51+
chapterID = chapterIDMatch[2];
52+
} catch (error) {
53+
mango.raise("Failed to get a chapter ID.");
54+
}
55+
56+
var subjectNode = mango.css(chapter, ".ellipsis")[0]
57+
var subject = mango.text(subjectNode);
58+
59+
if (!subject) mango.raise("Failed to get a chapter name.")
60+
61+
var numNode = mango.css(chapter, ".col.num");
62+
var num = mango.text(numNode[0]).substring(1);
63+
64+
var dateNode = mango.css(chapter, ".date");
65+
var date = mango.text(dateNode[0]);
66+
date = date.replace("UP", ""); // Remove webtoons "UP" tag on latest chapter
67+
68+
// Encode chapter in following format: idMANGAIDchCHAPTERIDnumNUM_NUM
69+
var chapterFullID = "id" + mangaID + "ch" + chapterID + "num" + num;
70+
chapterFullID = chapterFullID.replace(/\-/g, "_");
71+
72+
if (!chapterFullID) mango.raise("Failed to generate chapter full ID.");
73+
74+
slimObj = {}
75+
slimObj['id'] = chapterFullID;
76+
slimObj['title'] = subject;
77+
slimObj['#'] = num;
78+
slimObj['Date'] = date;
79+
80+
chapters.push(slimObj);
81+
});
82+
83+
try {
84+
var chapterTitleNode = mango.css(html, 'meta[property="og:title"]');
85+
var chapterTitle = mango.attribute(chapterTitleNode[0], "content");
86+
} catch (error) {
87+
mango.raise("Could not get title.");
88+
}
89+
90+
return JSON.stringify({
91+
chapters: chapters,
92+
title: chapterTitle
93+
});
94+
}
95+
96+
function selectChapter(id) {
97+
var mangaIDMatch = /id(\d+)ch(.+)num(\d+)/.exec(id);
98+
var mangaID = mangaIDMatch[1];
99+
var mangaChapterSlug = mangaIDMatch[2].replace(/\_/g, "-");
100+
var mangaChapterNum = mangaIDMatch[3].replace(/\_/g, ".");
101+
102+
try {
103+
var resp = mango.get(BASE_URL + LIST_ENDPOINT + mangaID);
104+
var urlLocation = resp.headers.Location;
105+
} catch (error) {
106+
mango.raise("Could not get webtoon chapter list.");
107+
}
108+
109+
var viewerURL = BASE_URL + urlLocation.replace(/list/, mangaChapterSlug + "/viewer")
110+
+ "&episode_no=" + mangaChapterNum;
111+
112+
var html = mango.get(viewerURL).body;
113+
114+
if(!html) mango.raise("Failed to load chapter images.");
115+
116+
var titleNode = mango.css(html, ".subj_info .subj_episode");
117+
118+
// Chapters get saved as NUM - CHAPTERNAME.cbz
119+
// This is done since some webtoons have names like:
120+
// `Episode 10` and `Season 2 Episode 10`, which
121+
// throws off the sorting.
122+
var chapterTitle = mangaChapterNum + " - " + mango.text(titleNode[0]);
123+
124+
var imgList = mango.css(html, "#_imageList img");
125+
126+
imgURLs = [];
127+
imgList.forEach(function(element) {
128+
imgURLs.push(
129+
mango.attribute(element, "data-url")
130+
);
131+
})
132+
133+
currentPage = 0;
134+
digits = Math.floor(Math.log10(imgURLs.length)) + 1;
135+
136+
return JSON.stringify({
137+
title: chapterTitle,
138+
pages: imgURLs.length
139+
});
140+
}
141+
142+
function nextPage() {
143+
if (currentPage >= imgURLs.length) {
144+
return JSON.stringify({});
145+
}
146+
147+
var url = imgURLs[currentPage];
148+
var filename = pad(currentPage, digits) + '.' + /\.(\w{3})($|\?\w+)/.exec(url)[1];
149+
150+
currentPage += 1;
151+
return JSON.stringify({
152+
url: url,
153+
filename: filename,
154+
headers: {
155+
'referer': BASE_URL + "/"
156+
}
157+
});
158+
}
159+
160+
// https://stackoverflow.com/a/10073788
161+
function pad(n, width, z) {
162+
z = z || '0';
163+
n = n + '';
164+
return n.length >= width ? n : new Array(width - n.length + 1).join(z) + n;
165+
}

plugins/webtoons/info.json

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

0 commit comments

Comments
 (0)