Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Port AllManga.to (AllAnimesite) #842

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion web/src/engine/transformers/BookmarkConverter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ import type { BookmarkSerialized } from '../providers/Bookmark';
* @remarks Only exported for testing
*/
export const legacyWebsiteIdentifierMap = new Map([
[ 'allanimesite', 'allmangato' ],
[ 'apolltoons', 'mundomanhwa' ],
[ 'aresnov', 'scarmanga' ],
[ 'azoramanga', 'azoraworld' ],
[ 'apolltoons', 'mundomanhwa' ],
[ 'bacamangaorg', 'bacamanga' ],
[ 'bananascan', 'harmonyscan' ],
[ 'blogtruyen', 'blogtruyenmoi' ],
Expand Down
3 changes: 2 additions & 1 deletion web/src/engine/transformers/BookmarkConverter_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ import { Key } from '../SettingsGlobal';
import { GetLocale } from '../../i18n/Localization';

const legacyWebsiteIdentifierMapTestCases = [
{ sourceID: 'aresnov', targetID: 'scarmanga' },
{ sourceID: 'allanimesite', targetID: 'allmangato' },
{ sourceID: 'apolltoons', targetID: 'mundomanhwa' },
{ sourceID: 'aresnov', targetID: 'scarmanga' },
{ sourceID: 'azoramanga', targetID: 'azoraworld' },
{ sourceID: 'bacamangaorg', targetID: 'bacamanga' },
{ sourceID: 'bananascan', targetID: 'harmonyscan' },
Expand Down
148 changes: 148 additions & 0 deletions web/src/engine/websites/AllMangaTo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import { Tags } from '../Tags';
import icon from './AllMangaTo.webp';
import { Chapter, Page } from '../providers/MangaPlugin';
import { DecoratableMangaScraper, Manga, type MangaPlugin } from '../providers/MangaPlugin';
import * as Common from './decorators/Common';
import { FetchCSS, FetchJSON } from '../platform/FetchProvider';

type GraphQLResult<T> = {
data: T
};

type APIMangas = {
mangas: {
edges: APIManga[],
}
}

type APIManga = {
_id: string,
englishName: string | null,
name: string,
availableChaptersDetail: Record<string, string[]>
}

type APIChapters = {
manga: APIManga
}

type ChapterID = {
id: string,
translation: string
}

type APIPages = {
chapterPages: {
edges: {
pictureUrlHead: string,
pictureUrls: {
url: string
}[]
}[]
}
}

@Common.ImageAjax()
export default class extends DecoratableMangaScraper {

private readonly apiUrl = 'https://api.allanime.day/api';

public constructor() {
super('allmangato', `AllManga.to`, 'https://allmanga.to', Tags.Media.Manga, Tags.Media.Manhua, Tags.Media.Manhwa, Tags.Language.Multilingual, Tags.Source.Aggregator);
}

public override get Icon() {
return icon;
}

public override ValidateMangaURL(url: string): boolean {
return new RegExpSafe(`^${this.URI.origin}/manga/[^/]+/[^/]+$`).test(url);
}

public override async FetchManga(provider: MangaPlugin, url: string): Promise<Manga> {
const title = (await FetchCSS(new Request(new URL(url)), 'ol.breadcrumb li:last-of-type')).shift().textContent.trim();
return new Manga(this, provider, url.match(/\/manga\/([^/]+)\//)[1], title);
}

public override async FetchMangas(provider: MangaPlugin): Promise<Manga[]> {
const mangaList: Manga[] = [];
for (let page = 1, run = true; run; page++) {
await new Promise(resolve => setTimeout(resolve, 200));
const mangas = await this.GetMangasFromPage(page, provider);
mangas.length > 0 ? mangaList.push(...mangas) : run = false;
}
return mangaList;
}

private async GetMangasFromPage(page: number, provider: MangaPlugin): Promise<Manga[]> {
const jsonVariables = {
search: {
isManga: true,
allowAdult: true,
allowUnknown: true
},
limit: 26, //impossible to change
page: page,
translationType: 'sub',
countryOrigin: 'ALL'
};
const jsonExtensions = {
persistedQuery: {
version: 1,
sha256Hash: 'a27e57ef5de5bae714db701fb7b5cf57e13d57938fc6256f7d5c70a975d11f3d'
}
};

const data = await this.FetchGraphQL<APIMangas>(jsonVariables, jsonExtensions);
return data?.mangas?.edges ? data.mangas.edges.map(manga => new Manga(this, provider, manga._id, manga.englishName ?? manga.name)) : [];
}

public override async FetchChapters(manga: Manga): Promise<Chapter[]> {
const jsonVariables = {
_id: manga.Identifier,
};
const jsonExtensions = {
persistedQuery: {
version: 1,
sha256Hash: 'a42e1106694628f5e4eaecd8d7ce0c73895a22a3c905c29836e2c220cf26e55f'
}
};
const { manga: { availableChaptersDetail } } = await this.FetchGraphQL<APIChapters>(jsonVariables, jsonExtensions);
return Object.keys(availableChaptersDetail).reduce((accumulator: Chapter[], key) => {
const chapters = availableChaptersDetail[key].map(chapter => new Chapter(this, manga, JSON.stringify({ id: chapter, translation: key }), `Chapter ${chapter} [${key}]`));
accumulator.push(...chapters);
return accumulator;
}, []);
}

public override async FetchPages(chapter: Chapter): Promise<Page[]> {
const { id, translation }: ChapterID = JSON.parse(chapter.Identifier);
const jsonVariables = {
mangaId: chapter.Parent.Identifier,
translationType: translation,
chapterString: id,
limit: 10,
offset: 0
};
const jsonExtensions = {
persistedQuery: {
version: 1,
sha256Hash: '121996b57011b69386b65ca8fc9e202046fc20bf68b8c8128de0d0e92a681195'
}
};
const { chapterPages: { edges } } = await this.FetchGraphQL<APIPages>(jsonVariables, jsonExtensions);
const source = edges.find(source => source.pictureUrlHead);
return source.pictureUrls.map(picture => new Page(this, chapter, new URL(picture.url, source.pictureUrlHead)));
}

private async FetchGraphQL<T extends JSONElement>(variables: JSONObject, extensions: JSONObject): Promise<T> {
const url = new URL(`?variables=${JSON.stringify(variables)}&extensions=${JSON.stringify(extensions)}`, this.apiUrl);
const { data } = await FetchJSON<GraphQLResult<T>>(new Request(url, {
headers: {
Origin: this.URI.origin
}
}));
return data;
}

}
Binary file added web/src/engine/websites/AllMangaTo.webp
Binary file not shown.
24 changes: 24 additions & 0 deletions web/src/engine/websites/AllMangaTo_e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { TestFixture } from '../../../test/WebsitesFixture';

const config = {
plugin: {
id: 'allmangato',
title: 'AllManga.to'
},
container: {
url: 'https://allmanga.to/manga/kFvrdRcbubPjrhr63/yuan-zun',
id: 'kFvrdRcbubPjrhr63',
title: 'Yuan Zun'
},
child: {
id: JSON.stringify({id: '643.5', translation: 'sub'}),
title: 'Chapter 643.5 [sub]'
},
entry: {
index: 0,
size: 714_974,
type: 'image/jpeg'
}
};

new TestFixture(config).AssertWebsite();
1 change: 1 addition & 0 deletions web/src/engine/websites/_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export { default as AGS } from './AGS';
export { default as Ainzscans } from './Ainzscans';
export { default as Akuma } from './Akuma';
export { default as AllHentai } from './AllHentai';
export { default as AllMangaTo } from './AllMangaTo';
export { default as AllPornComic } from './AllPornComic';
export { default as Alphapolis } from './Alphapolis';
export { default as AmuyScan } from './AmuyScan';
Expand Down