Skip to content

Commit 3cc3368

Browse files
author
GameFuzzy
committed
Add BatoTo, MangaPill and ComicExtra
1 parent eb8b479 commit 3cc3368

File tree

16 files changed

+2015
-3
lines changed

16 files changed

+2015
-3
lines changed

.eslintignore

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
node_modules/*
2-
*.test.ts
1+
node_modules/*

src/BatoTo/BatoTo.ts

+295
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,295 @@
1+
import {
2+
Chapter,
3+
ChapterDetails,
4+
ContentRating,
5+
HomeSection,
6+
Manga,
7+
MangaUpdates,
8+
PagedResults,
9+
SearchRequest,
10+
Source,
11+
SourceInfo,
12+
TagSection,
13+
TagType,
14+
} from 'paperback-extensions-common'
15+
16+
import { Parser } from './Parser'
17+
18+
const BATOTO_DOMAIN = 'https://bato.to'
19+
20+
export const BatoToInfo: SourceInfo = {
21+
version: '2.0.0',
22+
name: 'Bato.To',
23+
description: 'Extension that pulls western comics from bato.to',
24+
author: 'GameFuzzy',
25+
authorWebsite: 'http://github.com/gamefuzzy',
26+
icon: 'icon.png',
27+
contentRating: ContentRating.ADULT,
28+
websiteBaseURL: BATOTO_DOMAIN,
29+
sourceTags: [
30+
{
31+
text: 'Notifications',
32+
type: TagType.GREEN
33+
},
34+
{
35+
text: 'Cloudflare',
36+
type: TagType.RED
37+
}
38+
]
39+
}
40+
41+
export class BatoTo extends Source {
42+
parser = new Parser()
43+
44+
requestManager = createRequestManager({
45+
requestsPerSecond: 5,
46+
requestTimeout: 20000
47+
})
48+
49+
override getMangaShareUrl(mangaId: string): string {
50+
return `${BATOTO_DOMAIN}/series/${mangaId}`
51+
}
52+
53+
async getMangaDetails(mangaId: string): Promise<Manga> {
54+
55+
const request = createRequestObject({
56+
url: `${BATOTO_DOMAIN}/series/${mangaId}`,
57+
method: 'GET'
58+
})
59+
const data = await this.requestManager.schedule(request, 1)
60+
61+
const $ = this.cheerio.load(data.data)
62+
63+
return this.parser.parseMangaDetails($, mangaId)
64+
}
65+
66+
67+
async getChapters(mangaId: string): Promise<Chapter[]> {
68+
let chapters: Chapter[] = []
69+
const pageRequest = createRequestObject({
70+
url: `${BATOTO_DOMAIN}/series/${mangaId}`,
71+
method: 'GET'
72+
})
73+
const pageData = await this.requestManager.schedule(pageRequest, 1)
74+
const $ = this.cheerio.load(pageData.data)
75+
chapters = chapters.concat(this.parser.parseChapterList($, mangaId, this))
76+
77+
return (chapters)
78+
}
79+
80+
81+
async getChapterDetails(mangaId: string, chapterId: string): Promise<ChapterDetails> {
82+
83+
const request = createRequestObject({
84+
url: `${BATOTO_DOMAIN}/chapter/${chapterId}`,
85+
method: 'GET',
86+
})
87+
88+
const data = await this.requestManager.schedule(request, 1)
89+
90+
const $ = this.cheerio.load(data.data, {xmlMode: false})
91+
const pages: string[] = this.parser.parseChapterDetails($)
92+
93+
return createChapterDetails({
94+
id: chapterId,
95+
mangaId: mangaId,
96+
pages: pages,
97+
longStrip: false
98+
})
99+
}
100+
101+
override async filterUpdatedManga(mangaUpdatesFoundCallback: (updates: MangaUpdates) => void, time: Date, ids: string[]): Promise<void> {
102+
103+
let loadNextPage = true
104+
let currPageNum = 1
105+
106+
while (loadNextPage) {
107+
108+
const request = createRequestObject({
109+
url: `${BATOTO_DOMAIN}/browse/?sort=update&page=${String(currPageNum)}`,
110+
method: 'GET'
111+
})
112+
113+
const data = await this.requestManager.schedule(request, 1)
114+
const $ = this.cheerio.load(data.data)
115+
116+
const updatedManga = this.parser.filterUpdatedManga($, time, ids, this)
117+
loadNextPage = updatedManga.loadNextPage
118+
if (loadNextPage) {
119+
currPageNum++
120+
}
121+
if (updatedManga.updates.length > 0) {
122+
mangaUpdatesFoundCallback(createMangaUpdates({
123+
ids: updatedManga.updates
124+
}))
125+
}
126+
}
127+
}
128+
129+
override async getSearchResults(query: SearchRequest, metadata: any): Promise<PagedResults> {
130+
const page: number = metadata?.page ?? 1
131+
132+
const request = createRequestObject({
133+
url: `${BATOTO_DOMAIN}/search`,
134+
method: 'GET',
135+
param: `?word=${encodeURIComponent(query.title ?? '')}&page=${page}`
136+
})
137+
138+
const data = await this.requestManager.schedule(request, 1)
139+
const $ = this.cheerio.load(data.data)
140+
const manga = this.parser.parseSearchResults($, this)
141+
let mData = undefined
142+
if (!this.parser.isLastPage($)) {
143+
mData = {page: (page + 1)}
144+
}
145+
146+
return createPagedResults({
147+
results: manga,
148+
metadata: mData
149+
})
150+
151+
}
152+
153+
154+
override async getTags(): Promise<TagSection[]> {
155+
const request = createRequestObject({
156+
url: `${BATOTO_DOMAIN}/browse`,
157+
method: 'GET'
158+
})
159+
160+
const data = await this.requestManager.schedule(request, 1)
161+
const $ = this.cheerio.load(data.data)
162+
163+
return this.parser.parseTags($)
164+
}
165+
166+
167+
override async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise<void> {
168+
169+
const sections = [
170+
{
171+
request: createRequestObject({
172+
url: `${BATOTO_DOMAIN}/browse?sort=create`,
173+
method: 'GET'
174+
}),
175+
section: createHomeSection({
176+
id: '0',
177+
title: 'RECENTLY ADDED',
178+
view_more: true,
179+
}),
180+
},
181+
{
182+
request: createRequestObject({
183+
url: `${BATOTO_DOMAIN}/browse?sort=update`,
184+
method: 'GET'
185+
}),
186+
section: createHomeSection({
187+
id: '1',
188+
title: 'RECENTLY UPDATED',
189+
view_more: true,
190+
}),
191+
},
192+
{
193+
request: createRequestObject({
194+
url: `${BATOTO_DOMAIN}/browse?sort=views_a`,
195+
method: 'GET'
196+
}),
197+
section: createHomeSection({
198+
id: '2',
199+
title: 'POPULAR',
200+
view_more: true
201+
}),
202+
},
203+
]
204+
205+
const promises: Promise<void>[] = []
206+
207+
for (const section of sections) {
208+
// Let the app load empty sections
209+
sectionCallback(section.section)
210+
211+
// Get the section data
212+
promises.push(
213+
this.requestManager.schedule(section.request, 1).then(response => {
214+
const $ = this.cheerio.load(response.data)
215+
section.section.items = this.parser.parseHomePageSection($, this)
216+
sectionCallback(section.section)
217+
}),
218+
)
219+
}
220+
221+
// Make sure the function completes
222+
await Promise.all(promises)
223+
}
224+
225+
226+
override async getViewMoreItems(homepageSectionId: string, metadata: any): Promise<PagedResults> {
227+
let webPage = ''
228+
const page: number = metadata?.page ?? 1
229+
switch (homepageSectionId) {
230+
case '0': {
231+
webPage = `?sort=create&page=${page}`
232+
break
233+
}
234+
case '1': {
235+
webPage = `?sort=update&page=${page}`
236+
break
237+
}
238+
case '2': {
239+
webPage = `?sort=views_a&page=${page}`
240+
break
241+
}
242+
default:
243+
return Promise.resolve(createPagedResults({results: []}))
244+
}
245+
246+
const request = createRequestObject({
247+
url: `${BATOTO_DOMAIN}/browse${webPage}`,
248+
method: 'GET'
249+
})
250+
251+
const data = await this.requestManager.schedule(request, 1)
252+
const $ = this.cheerio.load(data.data)
253+
const manga = this.parser.parseHomePageSection($, this)
254+
let mData
255+
if (!this.parser.isLastPage($)) {
256+
mData = {page: (page + 1)}
257+
} else {
258+
mData = undefined // There are no more pages to continue on to, do not provide page metadata
259+
}
260+
261+
return createPagedResults({
262+
results: manga,
263+
metadata: mData
264+
})
265+
}
266+
267+
override getCloudflareBypassRequest() {
268+
return createRequestObject({
269+
url: `${BATOTO_DOMAIN}`,
270+
method: 'GET',
271+
})
272+
}
273+
274+
protected convertTime(timeAgo: string): Date {
275+
let time: Date
276+
let trimmed = Number((/\d*/.exec(timeAgo) ?? [])[0])
277+
trimmed = (trimmed == 0 && timeAgo.includes('a')) ? 1 : trimmed
278+
if (timeAgo.includes('sec') || timeAgo.includes('secs')) {
279+
time = new Date(Date.now() - trimmed * 1000)
280+
} else if (timeAgo.includes('min') || timeAgo.includes('mins')) {
281+
time = new Date(Date.now() - trimmed * 60000)
282+
} else if (timeAgo.includes('hour') || timeAgo.includes('hours')) {
283+
time = new Date(Date.now() - trimmed * 3600000)
284+
} else if (timeAgo.includes('day') || timeAgo.includes('days')) {
285+
time = new Date(Date.now() - trimmed * 86400000)
286+
} else if (timeAgo.includes('year') || timeAgo.includes('years')) {
287+
time = new Date(Date.now() - trimmed * 31556952000)
288+
} else {
289+
time = new Date(Date.now())
290+
}
291+
292+
return time
293+
}
294+
295+
}

src/BatoTo/Languages.ts

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import {LanguageCode} from 'paperback-extensions-common'
2+
3+
export const reverseLangCode: {[x: string]: LanguageCode} = {
4+
'_unknown': LanguageCode.UNKNOWN,
5+
'bd': LanguageCode.BENGALI,
6+
'bg': LanguageCode.BULGARIAN,
7+
'br': LanguageCode.BRAZILIAN,
8+
'cn': LanguageCode.CHINEESE,
9+
'cz': LanguageCode.CZECH,
10+
'de': LanguageCode.GERMAN,
11+
'dk': LanguageCode.DANISH,
12+
'gb': LanguageCode.ENGLISH,
13+
'es': LanguageCode.SPANISH,
14+
'fi': LanguageCode.FINNISH,
15+
'fr': LanguageCode.FRENCH,
16+
'gr': LanguageCode.GREEK,
17+
'hk': LanguageCode.CHINEESE_HONGKONG,
18+
'hu': LanguageCode.HUNGARIAN,
19+
'id': LanguageCode.INDONESIAN,
20+
'il': LanguageCode.ISRELI,
21+
'in': LanguageCode.INDIAN,
22+
'ir': LanguageCode.IRAN,
23+
'it': LanguageCode.ITALIAN,
24+
'jp': LanguageCode.JAPANESE,
25+
'kr': LanguageCode.KOREAN,
26+
'lt': LanguageCode.LITHUANIAN,
27+
'mn': LanguageCode.MONGOLIAN,
28+
'mx': LanguageCode.MEXIAN,
29+
'my': LanguageCode.MALAY,
30+
'nl': LanguageCode.DUTCH,
31+
'no': LanguageCode.NORWEGIAN,
32+
'ph': LanguageCode.PHILIPPINE,
33+
'pl': LanguageCode.POLISH,
34+
'pt': LanguageCode.PORTUGUESE,
35+
'ro': LanguageCode.ROMANIAN,
36+
'ru': LanguageCode.RUSSIAN,
37+
'sa': LanguageCode.SANSKRIT,
38+
'si': LanguageCode.SAMI,
39+
'th': LanguageCode.THAI,
40+
'tr': LanguageCode.TURKISH,
41+
'ua': LanguageCode.UKRAINIAN,
42+
'vn': LanguageCode.VIETNAMESE
43+
}

0 commit comments

Comments
 (0)