diff --git a/src/Koushoku/Koushoku.ts b/src/Koushoku/Koushoku.ts deleted file mode 100644 index 6fa9c81..0000000 --- a/src/Koushoku/Koushoku.ts +++ /dev/null @@ -1,271 +0,0 @@ -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, -} from './KoushokuParser'; - -export const KoushokuInfo: SourceInfo = { - version: '1.0.4', - 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 -}; - -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.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 => { - return response; - } - } - }); - - getMangaShareUrl(mangaId: string): string { - return `${DOMAIN}/${mangaId}`; - } - - async getSearchTags(): Promise { - const tags = await getTags(this.requestManager, this.cheerio); - - return [ - App.createTagSection({ - id: 'tags', label: 'Tags', tags: tags ?? [] - }) - ]; - } - - async getHomePageSections(sectionCallback: (section: HomeSection) => void): Promise { - const requestForRecentlyAdded = App.createRequest({ - url: `${DOMAIN}/browse`, - method: 'GET' - }); - const responseForRecentlyAdded = await this.requestManager.schedule(requestForRecentlyAdded, 1); - 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); - - const requestForPopularWeekly = App.createRequest({ - url: `${DOMAIN}/popular/weekly`, - method: 'GET' - }); - const responseForPopularWeekly = await this.requestManager.schedule(requestForPopularWeekly, 1); - const $popularWeekly = this.cheerio.load(responseForPopularWeekly.data as string); - const popularWeeklySection = App.createHomeSection({id: 'popular weekly', title: 'Popular This Week', - containsMoreItems: true, type: HomeSectionType.singleRowNormal}); - const popularWeeklyAlbums = getAlbums($popularWeekly); - popularWeeklySection.items = popularWeeklyAlbums; - sectionCallback(popularWeeklySection); - - const requestForPopularMonthly = App.createRequest({ - url: `${DOMAIN}/popular/monthly`, - method: 'GET' - }); - const responseForPopularMonthly = await this.requestManager.schedule(requestForPopularMonthly, 1); - const $popularMonthly = this.cheerio.load(responseForPopularMonthly.data as string); - const popularMonthlySection = App.createHomeSection({id: 'popular monthly', title: 'Popular This Month', - containsMoreItems: true, type: HomeSectionType.singleRowNormal}); - const popularMonthlyAlbums = getAlbums($popularMonthly); - popularMonthlySection.items = popularMonthlyAlbums; - sectionCallback(popularMonthlySection); - - const requestForRecentDoujin = App.createRequest({ - url: `${DOMAIN}/browse?cat=2&sort=16`, - method: 'GET' - }); - const responseForRecentDoujin = await this.requestManager.schedule(requestForRecentDoujin, 1); - const $recentDoujin = this.cheerio.load(responseForRecentDoujin.data as string); - const recentDoujinSection = App.createHomeSection({id: 'recent doujins', title: 'Recent Doujins', - containsMoreItems: true, type: HomeSectionType.singleRowNormal}); - const recentDoujinAlbums = getAlbums($recentDoujin); - recentDoujinSection.items = recentDoujinAlbums; - sectionCallback(recentDoujinSection); - - const requestForRecentManga = App.createRequest({ - url: `${DOMAIN}/browse?cat=1`, - method: 'GET' - }); - const responseForRecentManga = await this.requestManager.schedule(requestForRecentManga, 1); - const $recentManga = this.cheerio.load(responseForRecentManga.data as string); - const recentMangaSection = App.createHomeSection({id: 'recent manga', title: 'Recent Manga', - containsMoreItems: true, type: HomeSectionType.singleRowNormal}); - const recentMangaAlbums = getAlbums($recentManga); - recentMangaSection.items = recentMangaAlbums; - sectionCallback(recentMangaSection); - - const requestForRecentIllustrations = App.createRequest({ - url: `${DOMAIN}/browse?cat=4`, - method: 'GET' - }); - const responseForRecentIllustrations = await this.requestManager.schedule(requestForRecentIllustrations, 1); - const $recentIllustrations = this.cheerio.load(responseForRecentIllustrations.data as string); - const recentIllustrationsSection = App.createHomeSection({id: 'recent illustrations', title: 'Recent Illustrations', - containsMoreItems: true, type: HomeSectionType.singleRowNormal}); - const recentIllustrationsAlbums = getAlbums($recentIllustrations); - recentIllustrationsSection.items = recentIllustrationsAlbums; - sectionCallback(recentIllustrationsSection); - } - - // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types, @typescript-eslint/no-explicit-any - async getViewMoreItems(homepageSectionId: string, metadata: any): Promise { - const albumNum: number = metadata?.page ?? 1; - - let param = ''; - switch (homepageSectionId) { - case 'recent': - param = `/browse/page/${albumNum}`; - break; - case 'popular weekly': - param = `/popular/weekly/page/${albumNum}`; - break; - case 'popular monthly': - param = `/popular/monthly/page/${albumNum}`; - break; - case 'recent doujins': - param = `/browse/page/${albumNum}?cat=2&sort=16`; - break; - case 'recent manga': - param = `/browse/page/${albumNum}?cat=1`; - break; - case 'recent illustrations': - param = `/browse/page/${albumNum}?cat=4`; - 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); - 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 { - 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: '' - }) - }); - } - - async getChapters(mangaId: string): Promise { - 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 { - 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 { - const searchPage: number = metadata?.page ?? 1; - - let request; - if (query.title) { - request = App.createRequest({ - url: `${DOMAIN}/browse/page/${searchPage}?s=${encodeURIComponent(query.title)}`, - method: 'GET' - }); - } else { - request = App.createRequest({ - url: `${DOMAIN}${query.includedTags?.map(x => x.id)}/page/${searchPage}`, - method: 'GET' - }); - } - const response = await this.requestManager.schedule(request, 1); - 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 - }); - } -} \ No newline at end of file diff --git a/src/Koushoku/KoushokuParser.ts b/src/Koushoku/KoushokuParser.ts deleted file mode 100644 index 208e243..0000000 --- a/src/Koushoku/KoushokuParser.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { - PartialSourceManga, - RequestManager, - Tag, - TagSection -} from '@paperback/types'; - -import entities = require('entities'); - -export const DOMAIN = 'https://ksk.moe'; -const REGEX_PAGE = /[0-9]+(.(jpg|png))/g; - -export async function getTags(requestManager: RequestManager, cheerio: CheerioAPI): Promise { - const request = App.createRequest({ - url: `${DOMAIN}/tags/page/1`, - method: 'GET' - }); - const data = await requestManager.schedule(request, 1); - const $ = cheerio.load(data.data as string); - const tagElements = $('main', 'main').children().toArray(); - - const request2 = App.createRequest({ - url: `${DOMAIN}/tags/page/2`, - method: 'GET' - }); - const data2 = await requestManager.schedule(request2, 1); - const $$ = cheerio.load(data2.data as string); - const tagElements2 = $$('main', 'main').children().toArray(); - - const tags: Tag[] = []; - - for (const element of tagElements) { - const id = $('a', element).attr('href') ?? ''; - const label = $('span', element).first().text() ?? ''; - tags.push(App.createTag({ id, label })); - } - - for (const element of tagElements2) { - const id = $$('a', element).attr('href') ?? ''; - const label = $$('span', element).first().text() ?? ''; - tags.push(App.createTag({ id, label })); - } - - return tags; -} - -export function getAlbums ($: CheerioStatic): PartialSourceManga[] { - const albums: PartialSourceManga[] = []; - const albumGroups = $('article', 'main').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).first().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 { - const request = App.createRequest({ - url: `${DOMAIN}/${id}`, - method: 'GET' - }); - const data = await requestManager.schedule(request, 1); - const $ = cheerio.load(data.data as string); - - const title = $('h2', 'section#metadata').first().text(); - const image = $('img').first().attr('src') ?? 'https://i.imgur.com/GYUxEX8.png'; - const artistSection = $('strong:contains("Artist")').parent(); - const artist = $('span', artistSection).first().text(); - - const tagsElement1 = $('strong:contains("Tag")').first().parent(); - const tagsElement2 = $('span', tagsElement1).toArray(); - - const tagsToRender: Tag[] = []; - for (const tag of tagsElement2) { - const label = $(tag).text(); - if (label.match(/^\d/)) continue; - - if (!label) { - continue; - } - tagsToRender.push({ id: `/tags/${encodeURIComponent(label)}`, label: label }); - } - - const tagSections: TagSection[] = [App.createTagSection({ - id: '0', - label: 'Tags', - tags: tagsToRender.map(x => App.createTag(x)) - })]; - - return { - id: id, - titles: [entities.decodeHTML(title as string)], - image: image, - artist: artist, - tags: tagSections - }; -} - -export async function getPages(id: string, requestManager: RequestManager, cheerio: CheerioAPI): Promise { - const imageId = id.slice(10); - const pages: string[] = []; - - // Determine page length - const request = App.createRequest({ - url: `${DOMAIN}/${id}`, - method: 'GET' - }); - const data = await requestManager.schedule(request, 1); - const $ = cheerio.load(data.data as string); - const length = parseInt($('span:contains("Pages")').text().split(' ')[0] ?? ''); - const urlPieces = $('img').first().attr('src')?.split('/') ?? ''; - const suffix = urlPieces[urlPieces?.length - 1] ?? ''; - - const pagesRequest = App.createRequest({ - url: `${DOMAIN}/read${id.slice(7)}/1`, - method: 'GET' - }); - const pagesData = await requestManager.schedule(pagesRequest, 1); - const $$ = cheerio.load(pagesData.data as string, {xmlMode: true}); - - // Image format is not guranteed - // const imageFormat = suffix.slice(suffix.length, 3) ? 'jpg' : 'png'; - const pageNumFormat = suffix.split('.')[0]?.length; - - // Extract page nums from script - const pageNums = $$('script[type$=javascript]').last().toString(); - const unsortedPageNumsList = pageNums.matchAll(REGEX_PAGE); - const pageNumsList = [...new Set(Array.from(unsortedPageNumsList, x => x[0]))]; - - if (pageNumFormat === 2 || pageNumFormat === 3) { - for (let i = 0; i < length; i++) { - const imageLink = `${DOMAIN}/resampled/${imageId}/${pageNumsList[i]}`; - pages.push(imageLink); - } - } else { - const urlFormat = urlPieces[urlPieces.length - 1]; - const firstHalf = urlFormat?.split('001')[0] ?? ''; - const lastHalf = urlFormat?.split('001')[1] ?? ''; - - for (let i = 1; i < length + 1; i++) { - // Pages start from 001, 002..010, 011..100, 101... - let pageNumFormat = i < 10 ? `00${i}` : `0${i}`; - if (i > 99) { - pageNumFormat = `${i}`; - } - const finalFormat = firstHalf?.concat(pageNumFormat, lastHalf); - const imageLink = `${DOMAIN}/resampled/${imageId}/${finalFormat}`; - pages.push(imageLink); - } - } - - return pages; -} - -export const isLastPage = (albums: PartialSourceManga[]): boolean => { - // max albums displayed per page, need to find better way - last page will have 35 albums for popular sections - return albums.length != 35; -}; - -export async function testImageLink(imageLink: string, requestManager: RequestManager): Promise { - const jpgImageLink = imageLink.replace(/.{0,3}$/, '') + 'jpg'; - - const request = App.createRequest({ - url: imageLink, - method: 'GET' - }); - - const status = (await requestManager.schedule(request, 1)).status; - if (status !== 200) { - return jpgImageLink; - } - - return imageLink; -} \ No newline at end of file diff --git a/src/Koushoku/includes/icon.png b/src/Koushoku/includes/icon.png deleted file mode 100644 index 91f4cc9..0000000 Binary files a/src/Koushoku/includes/icon.png and /dev/null differ