-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
54e1f7c
commit 5d95728
Showing
3 changed files
with
340 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,211 @@ | ||
import { | ||
SourceManga, | ||
Chapter, | ||
ChapterDetails, | ||
HomeSection, | ||
HomeSectionType, | ||
TagSection, | ||
SearchRequest, | ||
PagedResults, | ||
SourceInfo, | ||
ContentRating, | ||
BadgeColor, | ||
Request, | ||
Response, | ||
SourceIntents, | ||
ChapterProviding, | ||
MangaProviding, | ||
SearchResultsProviding, | ||
HomePageSectionsProviding | ||
} from '@paperback/types'; | ||
|
||
import { | ||
DOMAIN, | ||
getAlbums, | ||
getGalleryData, | ||
getPages, | ||
getTags, | ||
isLastPage, | ||
CloudFlareError | ||
} from './KoushokuParser'; | ||
|
||
export const KoushokuInfo: SourceInfo = { | ||
version: '2.0.0', | ||
name: 'Koushoku', | ||
icon: 'icon.png', | ||
author: 'WaltersAsh', | ||
authorWebsite: 'https://github.com/WaltersAsh', | ||
description: 'Extension to grab albums from Koushoku', | ||
contentRating: ContentRating.ADULT, | ||
websiteBaseURL: DOMAIN, | ||
sourceTags: [ | ||
{ | ||
text: '18+', | ||
type: BadgeColor.RED | ||
} | ||
], | ||
intents: SourceIntents.MANGA_CHAPTERS | SourceIntents.HOMEPAGE_SECTIONS | SourceIntents.CLOUDFLARE_BYPASS_REQUIRED | ||
}; | ||
|
||
export class Koushoku implements SearchResultsProviding, MangaProviding, ChapterProviding, HomePageSectionsProviding { | ||
|
||
constructor(private cheerio: CheerioAPI) { } | ||
|
||
readonly requestManager = App.createRequestManager({ | ||
requestsPerSecond: 4, | ||
requestTimeout: 15000, | ||
interceptor: { | ||
interceptRequest: async (request: Request): Promise<Request> => { | ||
request.headers = { | ||
...(request.headers ?? {}), | ||
...{ | ||
'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 12_4) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15', | ||
'referer': DOMAIN | ||
} | ||
}; | ||
return request; | ||
}, | ||
|
||
interceptResponse: async (response: Response): Promise<Response> => { | ||
return response; | ||
} | ||
} | ||
}); | ||
|
||
getMangaShareUrl(mangaId: string): string { | ||
return `${DOMAIN}/${mangaId}`; | ||
} | ||
|
||
async getSearchTags(): Promise<TagSection[]> { | ||
const tags = await getTags(this.requestManager, this.cheerio); | ||
|
||
return [ | ||
App.createTagSection({ | ||
id: 'tags', label: 'Tags', tags: tags ?? [] | ||
}) | ||
]; | ||
} | ||
|
||
async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise<void> { | ||
const requestForRecentlyAdded = App.createRequest({ | ||
url: `${DOMAIN}`, | ||
method: 'GET' | ||
}); | ||
const responseForRecentlyAdded = await this.requestManager.schedule(requestForRecentlyAdded, 1); | ||
CloudFlareError(responseForRecentlyAdded.status); | ||
const $recentlyAdded = this.cheerio.load(responseForRecentlyAdded.data as string); | ||
const recentlyAddedAlbumsSection = App.createHomeSection({id: 'recent', title: 'Recent Updates', | ||
containsMoreItems: true, type: HomeSectionType.singleRowNormal}); | ||
const recentlyAddedAlbums = getAlbums($recentlyAdded); | ||
recentlyAddedAlbumsSection.items = recentlyAddedAlbums; | ||
sectionCallback(recentlyAddedAlbumsSection); | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any | ||
async getViewMoreItems(homepageSectionId: string, metadata: any): Promise<PagedResults> { | ||
const albumNum: number = metadata?.page ?? 1; | ||
|
||
let param = ''; | ||
switch (homepageSectionId) { | ||
case 'recent': | ||
param = `/?page=${albumNum}`; | ||
break; | ||
default: | ||
throw new Error('Requested to getViewMoreItems for a section ID which doesn\'t exist'); | ||
} | ||
|
||
const request = App.createRequest({ | ||
url: `${DOMAIN}`, | ||
method: 'GET', | ||
param | ||
}); | ||
const response = await this.requestManager.schedule(request, 1); | ||
CloudFlareError(response.status); | ||
const $ = this.cheerio.load(response.data as string); | ||
|
||
const albums = getAlbums($); | ||
metadata = !isLastPage(albums) ? {page: albumNum + 1} : undefined; | ||
return App.createPagedResults({ | ||
results: albums, | ||
metadata | ||
}); | ||
} | ||
|
||
async getMangaDetails(mangaId: string): Promise<SourceManga> { | ||
const data = await getGalleryData(mangaId, this.requestManager, this.cheerio); | ||
|
||
return App.createSourceManga({ | ||
id: mangaId, | ||
mangaInfo: App.createMangaInfo({ | ||
titles: data.titles, | ||
image: data.image, | ||
status: 'Complete', | ||
hentai: true, | ||
tags: data.tags, | ||
author: data.artist, | ||
artist: data.artist, | ||
desc: data.desc | ||
}) | ||
}); | ||
} | ||
|
||
async getChapters(mangaId: string): Promise<Chapter[]> { | ||
const data = await getGalleryData(mangaId, this.requestManager, this.cheerio); | ||
const chapters: Chapter[] = []; | ||
chapters.push(App.createChapter({ | ||
id: data.id, | ||
name: 'Album', | ||
chapNum: 1 | ||
})); | ||
|
||
return chapters; | ||
} | ||
|
||
async getChapterDetails(mangaId: string, chapterId: string): Promise<ChapterDetails> { | ||
return App.createChapterDetails({ | ||
id: chapterId, | ||
mangaId: mangaId, | ||
pages: await getPages(mangaId, this.requestManager, this.cheerio) | ||
}); | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any | ||
async getSearchResults(query: SearchRequest, metadata: any): Promise<PagedResults> { | ||
const searchPage: number = metadata?.page ?? 1; | ||
|
||
let request; | ||
if (query.title) { | ||
request = App.createRequest({ | ||
url: `${DOMAIN}/search?page=${searchPage}&q=${encodeURIComponent(query.title)}`, | ||
method: 'GET' | ||
}); | ||
} else { | ||
request = App.createRequest({ | ||
url: `${DOMAIN}/tags/${query.includedTags?.map(x => x.id)}?page=${searchPage}`, | ||
method: 'GET' | ||
}); | ||
} | ||
const response = await this.requestManager.schedule(request, 1); | ||
CloudFlareError(response.status); | ||
const $ = this.cheerio.load(response.data as string); | ||
|
||
const albums = getAlbums($); | ||
metadata = !isLastPage(albums) ? {page: searchPage + albums.length} : undefined; | ||
|
||
return App.createPagedResults({ | ||
results: albums, | ||
metadata | ||
}); | ||
} | ||
|
||
async getCloudflareBypassRequestAsync(): Promise<Request> { | ||
return App.createRequest({ | ||
url: DOMAIN, | ||
method: 'GET', | ||
headers: { | ||
'referer': `${DOMAIN}/`, | ||
'user-agent': await this.requestManager.getDefaultUserAgent() | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
import { | ||
PartialSourceManga, | ||
RequestManager, | ||
Tag, | ||
TagSection | ||
} from '@paperback/types'; | ||
|
||
import entities = require('entities'); | ||
|
||
export const DOMAIN = 'https://fakku.cc'; | ||
|
||
export async function getTags(requestManager: RequestManager, cheerio: CheerioAPI): Promise<Tag[]> { | ||
const request = App.createRequest({ | ||
url: `${DOMAIN}/tags`, | ||
method: 'GET' | ||
}); | ||
const data = await requestManager.schedule(request, 1); | ||
CloudFlareError(data.status); | ||
const $ = cheerio.load(data.data as string); | ||
const tagElements = $('div.entry').toArray(); | ||
const tags: Tag[] = []; | ||
|
||
for (const element of tagElements) { | ||
const id = $('a', element).attr('href')?.slice(6) ?? ''; | ||
const label = $('strong', element).first().text() ?? ''; | ||
console.log(id); | ||
console.log(label); | ||
tags.push(App.createTag({ id, label })); | ||
} | ||
|
||
return tags; | ||
} | ||
|
||
export function getAlbums ($: CheerioStatic): PartialSourceManga[] { | ||
const albums: PartialSourceManga[] = []; | ||
const albumGroups = $('article.entry').toArray(); | ||
|
||
for (const album of albumGroups) { | ||
const image = $('img', album).attr('src') ?? ''; | ||
const id = $('a', album).attr('href') ?? ''; | ||
const title = $('a', album).attr('title') ?? ''; | ||
const artist = $('span', album).last().text() ?? ''; | ||
|
||
if (!id || !title) { | ||
continue; | ||
} | ||
|
||
albums.push(App.createPartialSourceManga({ | ||
mangaId: encodeURIComponent(id), | ||
image: image ? image : 'https://i.imgur.com/GYUxEX8.png', | ||
subtitle: artist, | ||
title: entities.decodeHTML(title) | ||
})); | ||
} | ||
|
||
return albums; | ||
} | ||
|
||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
export async function getGalleryData(id: string, requestManager: RequestManager, cheerio: CheerioAPI): Promise<any> { | ||
const request = App.createRequest({ | ||
url: `${DOMAIN}/${id}`, | ||
method: 'GET' | ||
}); | ||
const data = await requestManager.schedule(request, 1); | ||
CloudFlareError(data.status); | ||
const $ = cheerio.load(data.data as string); | ||
|
||
const title = $('h1.title').first().text(); | ||
const image = $('img').first().attr('src') ?? 'https://i.imgur.com/GYUxEX8.png'; | ||
const artist = $('a', 'tr.artists').text(); | ||
const magazine = $('a', 'tr.magazines').text(); | ||
const pages = $('td', 'tr.pages').last().text(); | ||
const created = $('td', 'tr.created').last().text(); | ||
const published = $('td', 'tr.published').last().text(); | ||
const desc = 'Magazine: ' + magazine + '\n' | ||
+ 'Pages: ' + pages + '\n' | ||
+ 'Created: ' + created + '\n' | ||
+ 'Published: ' + published + '\n'; | ||
|
||
|
||
return { | ||
id: id, | ||
titles: [entities.decodeHTML(title as string)], | ||
image: image, | ||
artist: artist, | ||
desc: desc | ||
}; | ||
} | ||
|
||
export async function getPages(id: string, requestManager: RequestManager, cheerio: CheerioAPI): Promise<string[]> { | ||
const pages: string[] = []; | ||
|
||
let request = App.createRequest({ | ||
url: `${DOMAIN}/${id}/1`, | ||
method: 'GET' | ||
}); | ||
let data = await requestManager.schedule(request, 1); | ||
CloudFlareError(data.status); | ||
let $ = cheerio.load(data.data as string); | ||
const lengthText = $('span.total').text(); | ||
const length = parseInt(lengthText.substring(0, lengthText.length / 2)); | ||
let imageLink = $('img', 'main.page').attr('src') ?? ''; | ||
pages.push(imageLink); | ||
|
||
for (let i = 2; i < length + 1; i++) { | ||
request = App.createRequest({ | ||
url: `${DOMAIN}/${id}/${i}`, | ||
method: 'GET' | ||
}); | ||
data = await requestManager.schedule(request, 1); | ||
CloudFlareError(data.status); | ||
$ = cheerio.load(data.data as string); | ||
imageLink = $('img', 'main.page').attr('src') ?? ''; | ||
pages.push(imageLink); | ||
} | ||
|
||
return pages; | ||
} | ||
|
||
export const isLastPage = (albums: PartialSourceManga[]): boolean => { | ||
return albums.length != 25; | ||
}; | ||
|
||
export function CloudFlareError(status: number): void { | ||
if (status == 503 || status == 403) { | ||
throw new Error(`CLOUDFLARE BYPASS ERROR:\nPlease go to the homepage of <${DOMAIN}> and press the cloud icon.`); | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.