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+ } catch ( error ) {
19+ mango . raise ( "An error occured while searching." ) ;
20+ }
21+
22+ if ( search [ "items" ] . length == 0 ) mango . raise ( "Could not find a webtoon with that title." ) ;
23+
24+ var mangaID ;
25+ for ( var i = 0 ; i < search [ "items" ] [ 0 ] . length ; i ++ ) {
26+ var item = search [ "items" ] [ 0 ] [ i ] ;
27+
28+ // Get first webtoon, ignore authors
29+ if ( item [ 1 ] [ 0 ] == "TITLE" ) {
30+ mangaID = item [ 3 ] [ 0 ] ;
31+ break ;
32+ }
33+ }
34+
35+ if ( ! mangaID ) mango . raise ( "Could not find a webtoon with that title." ) ;
36+
37+ try {
38+ var resp = mango . get ( BASE_URL + LIST_ENDPOINT + mangaID ) ;
39+ var urlLocation = resp . headers . Location ;
40+ } catch ( error ) {
41+ mango . raise ( "Could not get webtoon page." ) ;
42+ }
43+
44+ if ( ! urlLocation ) mango . raise ( "Could not get webtoon page." ) ;
45+
46+ chapters = [ ] ;
47+ var html = mango . get ( MOBILE_URL + urlLocation , {
48+ 'referer' : MOBILE_URL
49+ } ) . body ;
50+
51+ if ( ! html ) mango . raise ( "Failed to get chapter list." ) ;
52+
53+ var liChapters = mango . css ( html , "ul#_episodeList li[id*=episode]" )
54+
55+ if ( ! liChapters ) mango . raise ( "Failed to find chapters." ) ;
56+
57+ liChapters . forEach ( function ( chapter ) {
58+ var url = mango . attribute ( mango . css ( chapter , "a" ) [ 0 ] , 'href' ) ;
59+
60+ var chapterIDRegex = / w e b t o o n s \. c o m \/ \w { 2 } \/ .+ \/ ( \w - ? ) + \/ ( .+ ) \/ / ;
61+ var chapterIDMatch = chapterIDRegex . exec ( url ) ;
62+
63+ var chapterID ;
64+ try {
65+ chapterID = chapterIDMatch [ 2 ] ;
66+ } catch ( error ) {
67+ mango . raise ( "Failed to get a chapter ID." ) ;
68+ }
69+
70+ var subjectNode = mango . css ( chapter , ".ellipsis" ) [ 0 ]
71+ var subject = mango . text ( subjectNode ) ;
72+
73+ if ( ! subject ) mango . raise ( "Failed to get a chapter name." )
74+
75+ var numNode = mango . css ( chapter , ".col.num" ) ;
76+ var num = mango . text ( numNode [ 0 ] ) . substring ( 1 ) ;
77+
78+ var dateNode = mango . css ( chapter , ".date" ) ;
79+ var date = mango . text ( dateNode [ 0 ] ) ;
80+ date = date . replace ( "UP" , "" ) ; // Remove webtoons "UP" tag on latest chapter
81+
82+ // Encode chapter in following format: idMANGAIDchCHAPTERIDnumNUM_NUM
83+ var chapterFullID = "id" + mangaID + "ch" + chapterID + "num" + num ;
84+ chapterFullID = chapterFullID . replace ( / \- / g, "_" ) ;
85+
86+ if ( ! chapterFullID ) mango . raise ( "Failed to generate chapter full ID." ) ;
87+
88+ slimObj = { }
89+ slimObj [ 'id' ] = chapterFullID ;
90+ slimObj [ 'title' ] = subject ;
91+ slimObj [ '#' ] = num ;
92+ slimObj [ 'Date' ] = date ;
93+
94+ chapters . push ( slimObj ) ;
95+ } ) ;
96+
97+ try {
98+ var chapterTitleNode = mango . css ( html , 'meta[property="og:title"]' ) ;
99+ var chapterTitle = mango . attribute ( chapterTitleNode [ 0 ] , "content" ) ;
100+ } catch ( error ) {
101+ mango . raise ( "Could not get title." ) ;
102+ }
103+
104+ return JSON . stringify ( {
105+ chapters : chapters ,
106+ title : chapterTitle
107+ } ) ;
108+ }
109+
110+ function selectChapter ( id ) {
111+ var mangaIDMatch = / i d ( \d + ) c h ( .+ ) n u m ( \d + ) / . exec ( id ) ;
112+ var mangaID = mangaIDMatch [ 1 ] ;
113+ var mangaChapterSlug = mangaIDMatch [ 2 ] . replace ( / \_ / g, "-" ) ;
114+ var mangaChapterNum = mangaIDMatch [ 3 ] . replace ( / \_ / g, "." ) ;
115+
116+ try {
117+ var resp = mango . get ( BASE_URL + LIST_ENDPOINT + mangaID ) ;
118+ var urlLocation = resp . headers . Location ;
119+ } catch ( error ) {
120+ mango . raise ( "Could not get webtoon chapter list." ) ;
121+ }
122+
123+ var viewerURL = BASE_URL + urlLocation . replace ( / l i s t / , mangaChapterSlug + "/viewer" )
124+ + "&episode_no=" + mangaChapterNum ;
125+
126+ var html = mango . get ( viewerURL ) . body ;
127+
128+ if ( ! html ) mango . raise ( "Failed to load chapter images." ) ;
129+
130+ var titleNode = mango . css ( html , ".subj_info .subj_episode" ) ;
131+
132+ // Chapters get saved as NUM - CHAPTERNAME.cbz
133+ // This is done since some webtoons have names like:
134+ // `Episode 10` and `Season 2 Episode 10`, which
135+ // throws off the sorting.
136+ var chapterTitle = mangaChapterNum + " - " + mango . text ( titleNode [ 0 ] ) ;
137+
138+ var imgList = mango . css ( html , "#_imageList img" ) ;
139+
140+ imgURLs = [ ] ;
141+ imgList . forEach ( function ( element ) {
142+ imgURLs . push (
143+ mango . attribute ( element , "data-url" )
144+ ) ;
145+ } )
146+
147+ currentPage = 0 ;
148+ digits = Math . floor ( Math . log10 ( imgURLs . length ) ) + 1 ;
149+
150+ return JSON . stringify ( {
151+ title : chapterTitle ,
152+ pages : imgURLs . length
153+ } ) ;
154+ }
155+
156+ function nextPage ( ) {
157+ if ( currentPage >= imgURLs . length ) {
158+ return JSON . stringify ( { } ) ;
159+ }
160+
161+ var url = imgURLs [ currentPage ] ;
162+ var filename = pad ( currentPage , digits ) + '.' + / \. ( \w { 3 } ) ( $ | \? \w + ) / . exec ( url ) [ 1 ] ;
163+
164+ currentPage += 1 ;
165+ return JSON . stringify ( {
166+ url : url ,
167+ filename : filename ,
168+ headers : {
169+ 'referer' : BASE_URL + "/"
170+ }
171+ } ) ;
172+ }
173+
174+ // https://stackoverflow.com/a/10073788
175+ function pad ( n , width , z ) {
176+ z = z || '0' ;
177+ n = n + '' ;
178+ return n . length >= width ? n : new Array ( width - n . length + 1 ) . join ( z ) + n ;
179+ }
0 commit comments