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 = / w e b t o o n s \. c o m \/ \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 = / i d ( \d + ) c h ( .+ ) n u m ( \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 ( / l i s t / , 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+ }
0 commit comments