Skip to content

Commit

Permalink
Re-add ksk
Browse files Browse the repository at this point in the history
  • Loading branch information
WaltersAsh committed Oct 15, 2023
1 parent 54e1f7c commit 5d95728
Show file tree
Hide file tree
Showing 3 changed files with 340 additions and 0 deletions.
211 changes: 211 additions & 0 deletions src/Koushoku/Koushoku.ts
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()
}
});
}
}
129 changes: 129 additions & 0 deletions src/Koushoku/KoushokuParser.ts
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.`);
}
}
Binary file added src/Koushoku/includes/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 5d95728

Please sign in to comment.