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

Add ClipStudioReader decorators and websites #724

Open
wants to merge 32 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
9c1dfd1
fist attempt
MikeZeDev Jul 30, 2024
4b662ff
Add ClipStudioDecorator and websites
MikeZeDev Aug 2, 2024
cf65ecc
simplified code
MikeZeDev Aug 2, 2024
ce96150
handle parameters in queryString & redirection
MikeZeDev Aug 2, 2024
0244592
add tests & minor fixes
MikeZeDev Aug 3, 2024
e3fbf13
Update ClipStudioReader.ts
MikeZeDev Aug 3, 2024
d8f20b2
Update ClipStudioReader.ts
MikeZeDev Aug 3, 2024
0413be6
Merge branch 'master' into clipstudioreader
MikeZeDev Aug 3, 2024
d9ba04d
reject promise on catched error
MikeZeDev Aug 3, 2024
2dd6d03
Merge branch 'master' into clipstudioreader
MikeZeDev Aug 3, 2024
201fc8c
typo
MikeZeDev Aug 3, 2024
ddbd500
use DOMParser
MikeZeDev Aug 4, 2024
6e074f7
Merge branch 'master' into clipstudioreader
MikeZeDev Aug 7, 2024
0265e2c
Update _index.ts
MikeZeDev Aug 7, 2024
43a6526
Merge branch 'master' into clipstudioreader
MikeZeDev Aug 14, 2024
a0b7140
Merge branch 'master' into clipstudioreader
MikeZeDev Aug 16, 2024
ca65f0f
Merge branch 'master' into clipstudioreader
MikeZeDev Sep 9, 2024
fa1db94
Merge branch 'master' into clipstudioreader
MikeZeDev Sep 28, 2024
86f9c00
use typed Pages
MikeZeDev Sep 28, 2024
ac91bc0
Merge branch 'master' into clipstudioreader
MikeZeDev Oct 7, 2024
39beec6
Merge branch 'master' into clipstudioreader
MikeZeDev Oct 13, 2024
2d6f529
Merge branch 'master' into clipstudioreader
MikeZeDev Oct 20, 2024
b253569
Add DigimonCard
MikeZeDev Oct 20, 2024
88f850f
Merge branch 'master' into clipstudioreader
MikeZeDev Oct 27, 2024
1d17057
update tests
MikeZeDev Oct 27, 2024
468dee0
Merge branch 'master' into clipstudioreader
MikeZeDev Nov 2, 2024
84ff08e
fix festa chapters & update tests
MikeZeDev Nov 2, 2024
6c1caef
Merge branch 'master' into clipstudioreader
MikeZeDev Nov 17, 2024
73bcb07
Update _index.ts
MikeZeDev Nov 17, 2024
5e2b6d9
Merge branch 'master' into clipstudioreader
MikeZeDev Nov 26, 2024
595c7e0
Merge branch 'master' into clipstudioreader
MikeZeDev Dec 15, 2024
e5a784a
reflect Common changes
MikeZeDev Dec 15, 2024
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
72 changes: 72 additions & 0 deletions web/src/engine/websites/ComicFesta.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import { Tags } from '../Tags';
import icon from './ComicFesta.webp';
import { Chapter, DecoratableMangaScraper, type Manga } from '../providers/MangaPlugin';
import * as Common from './decorators/Common';
import * as ClipStudioReader from './decorators/ClipStudioReader';
import { FetchCSS } from '../platform/FetchProvider';

type JSONChapters = {
packages: {
id: number,
number: number,
fairInfo : {
free: {
endAt: string,
startAt: string
},
trial: {
endAt: string,
startAt: string
}
}
}[]
}

@Common.MangaCSS(/^{origin}\/titles\/\d+/, 'section[class*="title-name-section_section__"] h2', Common.ElementLabelExtractor('span'))
@Common.MangasNotSupported()
@ClipStudioReader.PagesSinglePageAJAX()
@ClipStudioReader.ImageAjax()
export default class extends DecoratableMangaScraper {

public constructor() {
super('comicfesta', 'コミックフェスタ | ComicFesta', 'https://comic.iowl.jp', Tags.Media.Manga, Tags.Language.Japanese, Tags.Source.Official);
}

public override get Icon() {
return icon;
}

public override async FetchChapters(manga: Manga): Promise<Chapter[]> {
const scripts = await FetchCSS<HTMLScriptElement>(new Request(new URL(manga.Identifier, this.URI)), 'script:not([src])');
const { packages } = this.FindJSONObject<JSONChapters>(scripts, /isDisplayVolumeNumber/, 'isDisplayVolumeNumber');
return packages
.map(chapter => {
const suffix = chapter.fairInfo.free ? '/free_download' : chapter.fairInfo.trial ? '/trial_download' : '';
return new Chapter(this, manga, `/volumes/${chapter.id}${suffix}`, chapter.number.toString());
});
}

private FindJSONObject<T>(scripts: HTMLScriptElement[], scriptRegex: RegExp, keyName: string, currentElement = undefined): T {

if (scripts && scriptRegex) {
const script = scripts.find(script => scriptRegex.test(script.text))?.text;
if (!script) return undefined;
//script are like self.__next_f.push([1,"
const json = JSON.parse(script.substring(script.indexOf(',"') + 1, script.length - 2));// to remove trailing )]
currentElement = JSON.parse(json.substring(json.indexOf(':') + 1));
return this.FindJSONObject<T>(undefined, undefined, keyName, currentElement);
}

if (!currentElement) return undefined;
if (currentElement[keyName]) {
return currentElement;
}
let result = undefined;
for (let i in currentElement) {
if (result) break;
if (typeof currentElement[i] === 'object')
result = result ?? this.FindJSONObject<T>(undefined, undefined, keyName, currentElement[i]);
}
return result as T;
}
}
Binary file added web/src/engine/websites/ComicFesta.webp
Binary file not shown.
24 changes: 24 additions & 0 deletions web/src/engine/websites/ComicFesta_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: 'comicfesta',
title: 'コミックフェスタ | ComicFesta'
},
container: {
url: 'https://comic.iowl.jp/titles/171482',
id: '/titles/171482',
title: '悪役令嬢の発情期【タテヨミ】【フルカラー】',
},
child: {
id: '/volumes/605060/free_download',
title: '1'
},
entry: {
index: 0,
size: 315_948,
type: 'image/png'
}
};

new TestFixture(config).AssertWebsite();
34 changes: 34 additions & 0 deletions web/src/engine/websites/DigimonCard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Tags } from '../Tags';
import icon from './DigimonCard.webp';
import { DecoratableMangaScraper, Manga, type MangaPlugin } from '../providers/MangaPlugin';
import * as Common from './decorators/Common';
import * as ClipStudioReader from './decorators/ClipStudioReader';

function ChapterExtractor(anchor: HTMLAnchorElement) {
return {
id: anchor.pathname + anchor.search,
title: anchor.querySelector<HTMLDivElement>('div.chapterListTitle').textContent.trim()
};
}

@Common.MangaCSS(/^{origin}\/digimon_liberator\/(en|jp)\/comic\/$/, 'meta[name="Description"]')
@Common.ChaptersSinglePageCSS('section#chapters ul.chapterList li.chapterListBox > a:not(.closed)', ChapterExtractor)
@ClipStudioReader.PagesSinglePageAJAX()
@ClipStudioReader.ImageAjax()
export default class extends DecoratableMangaScraper {

public constructor() {
super('digimoncard', 'DigimonCard', 'https://digimoncard.com', Tags.Media.Manga, Tags.Language.Japanese, Tags.Language.English, Tags.Source.Official);
}

public override get Icon() {
return icon;
}

public override async FetchMangas(provider: MangaPlugin): Promise<Manga[]> {
return [
new Manga(this, provider, '/digimon_liberator/en/comic/', 'DIGIMON LIBERATOR', Tags.Language.English),
new Manga(this, provider, '/digimon_liberator/jp/comic/', 'DIGIMON LIBERATOR', Tags.Language.Japanese),
];
}
}
Binary file added web/src/engine/websites/DigimonCard.webp
Binary file not shown.
25 changes: 25 additions & 0 deletions web/src/engine/websites/DigimonCard_e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TestFixture, type Config } from '../../../test/WebsitesFixture';

const config: Config = {
plugin: {
id: 'digimoncard',
title: 'DigimonCard'
},
container: {
url: 'https://digimoncard.com/digimon_liberator/en/comic/',
id: '/digimon_liberator/en/comic/',
title: 'DIGIMON LIBERATOR'
},
/* url param "param" is random
child: {
id: '/digimon_liberator/viewer/index.php?cgi=/digimon_liberator/api/diazepam_hai.php&param=ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ=',
title: 'Episode 07 [Pt.1]'
},
entry: {
index: 2,
size: 550_521,
type: 'image/png'
}*/
};

new TestFixture(config).AssertWebsite();
21 changes: 21 additions & 0 deletions web/src/engine/websites/DreComics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Tags } from '../Tags';
import icon from './DreComics.webp';
import { DecoratableMangaScraper} from '../providers/MangaPlugin';
import * as Common from './decorators/Common';
import * as ClipStudioReader from './decorators/ClipStudioReader';

@Common.MangaCSS(/^{origin}\/drecomics\/series\/[^/]+$/, 'div.detailComics h1.detailComics_title span:first-of-type')
@Common.MangasSinglePagesCSS(['/drecomics/series'], 'div.seriesList li.seriesList__item a.seriesList__link')
@Common.ChaptersSinglePageCSS('div.ebookListItem a.ebookListItem_title')
@ClipStudioReader.PagesSinglePageAJAX()
@ClipStudioReader.ImageAjax()
export default class extends DecoratableMangaScraper {

public constructor() {
super('drecomics', 'DRE Comics', 'https://drecom-media.jp', Tags.Media.Manga, Tags.Language.Japanese, Tags.Source.Official);
}

public override get Icon() {
return icon;
}
}
Binary file added web/src/engine/websites/DreComics.webp
Binary file not shown.
24 changes: 24 additions & 0 deletions web/src/engine/websites/DreComics_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: 'drecomics',
title: 'DRE Comics'
},
container: {
url: 'https://drecom-media.jp/drecomics/series/kakuresaijo',
id: '/drecomics/series/kakuresaijo',
title: '隠れ才女は全然めげない'
},
child: {
id: '/viewer/e/123',
title: '第1話①'
},
entry: {
index: 4,
size: 1_796_783,
type: 'image/png'
}
};

new TestFixture(config).AssertWebsite();
63 changes: 63 additions & 0 deletions web/src/engine/websites/FireCross.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import { Tags } from '../Tags';
import icon from './FireCross.webp';
import { Chapter, DecoratableMangaScraper, Page, type Manga } from '../providers/MangaPlugin';
import * as Common from './decorators/Common';
import { FetchJSON } from '../platform/FetchProvider';
import * as ClipStudioReader from './decorators/ClipStudioReader';

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

type APIResponse = {
redirect: string
}
function ChapterExtractor(element: HTMLElement) {
const form = element.querySelector('form[data-Api="reader"]');
const result = {
id: JSON.stringify({
token: form.querySelector<HTMLInputElement>('input[name="_token"]').value,
id: form.querySelector<HTMLInputElement>('input[name="ebook_id"]').value,
}),
title: element.querySelector('span.shop-item-info-name').textContent.trim()
};
return result;
}

@Common.MangaCSS(/^{origin}\/ebook\/series\/\d+$/, 'div.ebook-series-grid-left h1.ebook-series-title')
@Common.MangasMultiPageCSS('/ebook/comics?page={page}', 'li.seriesList_item a.seriesList_itemTitle')
@Common.ChaptersSinglePageCSS('ul.shop-list li.shop-item--episode:has(form)', ChapterExtractor)
@ClipStudioReader.ImageAjax()

export default class extends DecoratableMangaScraper {
private readonly apiUrl = 'https://firecross.jp/api/';

public constructor() {
super('firecross', 'FireCross', 'https://firecross.jp', Tags.Media.Manga, Tags.Language.Japanese, Tags.Source.Official);
}

public override get Icon() {
return icon;
}

public override async FetchPages(chapter: Chapter): Promise<Page[]> {

const chapterID: ChapterID = JSON.parse(chapter.Identifier);
const { redirect } = await FetchJSON<APIResponse>(new Request(new URL('reader', this.apiUrl), {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest'
},
body: new URLSearchParams({
_token: chapterID.token,
ebook_id: chapterID.id,
}).toString()
}));

const fakechapter = new Chapter(this, chapter.Parent as Manga, redirect, chapter.Title);
return (await ClipStudioReader.FetchPagesSinglePageAJAX.call(this, fakechapter)).map(page => new Page(this, chapter, page.Link, page.Parameters));
}
}
Binary file added web/src/engine/websites/FireCross.webp
Binary file not shown.
25 changes: 25 additions & 0 deletions web/src/engine/websites/FireCross_e2e.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { TestFixture } from '../../../test/WebsitesFixture';

const config = {
plugin: {
id: 'firecross',
title: 'FireCross'
},
container: {
url: 'https://firecross.jp/ebook/series/235',
id: '/ebook/series/235',
title: '精霊幻想記'
},
/* Chapters are tokenified
child: {
id: JSON.stringify({ token: '', id: '235' }),
title: '第57話'
},
entry: {
index: 0,
size: -1,
type: 'image/png'
}*/
};

new TestFixture(config).AssertWebsite();
4 changes: 4 additions & 0 deletions web/src/engine/websites/_index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export { default as ComicDays } from './ComicDays';
export { default as ComicEarthStar } from './ComicEarthStar';
export { default as ComicExtra } from './ComicExtra';
export { default as ComicFans } from './ComicFans';
export { default as ComicFesta } from './ComicFesta';
export { default as ComicFuz } from './ComicFuz';
export { default as ComicGardo } from './ComicGardo';
export { default as ComicGrowl } from './ComicGrowl';
Expand Down Expand Up @@ -108,6 +109,7 @@ export { default as DEXHentai } from './DEXHentai';
export { default as DiamondFansub } from './DiamondFansub';
export { default as DianxiaTrads } from './DianxiaTrads';
export { default as Digimon } from './Digimon';
export { default as DigimonCard } from './DigimonCard';
export { default as DigitalTeam } from './DigitalTeam';
export { default as DingManhua } from './DingManhua';
export { default as DisasterScans } from './DisasterScans';
Expand All @@ -126,6 +128,7 @@ export { default as DoujinZa } from './DoujinZa';
export { default as DragonTea } from './DragonTea';
export { default as DragonTranslation } from './DragonTranslation';
export { default as DrakeScans } from './DrakeScans';
export { default as DreComics } from './DreComics';
export { default as DtupScan } from './DtupScan';
export { default as DuaLeoTruyen } from './DuaLeoTruyen';
export { default as DynastyScans } from './DynastyScans';
Expand All @@ -140,6 +143,7 @@ export { default as FayScans } from './FayScans';
export { default as FbSquads } from './FbSquads';
export { default as FeelWeb } from './FeelWeb';
export { default as FireComics } from './FireComics';
export { default as FireCross } from './FireCross';
export { default as FlameComics } from './FlameComics';
export { default as FMTeam } from './FMTeam';
export { default as FoyScan } from './FoyScan';
Expand Down
Loading
Loading