diff --git a/plugins/english/animeAnyway.ts b/plugins/english/animeAnyway.ts new file mode 100644 index 000000000..70d2c449c --- /dev/null +++ b/plugins/english/animeAnyway.ts @@ -0,0 +1,299 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; + +type PortableSpan = { + _type: 'span'; + text: string; + marks?: string[]; +}; + +type PortableBlock = + | { + _type: 'block'; + style?: string; + children?: PortableSpan[]; + } + | { _type: 'horizontalLine' } + | { _type: 'image'; asset?: { _ref?: string } } + | { _type: string }; + +type SanityVolume = { + title: string; + volkeyword: string; + mainImage?: { asset?: { url?: string } }; + banner?: { asset?: { url?: string } }; + progress?: number; + releaseDate?: string; + synopsis?: PortableBlock[]; + chapters?: { + chkeyword: string; + title: string; + }[]; +}; + +type SanityAllVolumesEntry = { + title: string; + volkeyword: string; + mainImage?: { asset?: { url?: string } }; +}; + +type NextData = { props: { pageProps: T } }; + +type HomePageProps = { allVolumes?: SanityAllVolumesEntry[] }; +type VolumePageProps = { vol?: SanityVolume; statusCode?: number }; +type ChapterPageProps = { + chapter?: { content?: PortableBlock[] }; + statusCode?: number; +}; + +/** One novel per volume — each is published as its own distinct book. */ +type CatalogueEntry = { + volkeyword: string; + name: string; + cover: string; + releaseDate: string; +}; + +class AnimeAnyway implements Plugin.PluginBase { + id = 'animeanyway'; + name = 'Anime Anyway'; + icon = 'src/en/animeanyway/icon.png'; + site = 'https://animeanyway.com/'; + version = '1.0.0'; + + /** + * The site's own volume titles ("Year 3 Vol. 4") don't name the franchise, + * unlike "High School Syndrome" which is already self-explanatory — prefix + * just the former so both are recognisable in a novel list. + */ + private readonly yearVolumePattern = /^Year\s+\d+\s+Vol\.?\s*\d+/i; + + /** + * Sanity's image CDN URL for a Portable Text image block, which only carries + * an asset reference ("image--x-"), not a resolved URL — + * unlike mainImage/banner elsewhere on the site, which come pre-resolved. + * Project id and dataset are this site's own fixed identifiers. + */ + private readonly sanityImageBase = + 'https://cdn.sanity.io/images/m1xj6lbt/production/'; + + private cataloguePromise: Promise | null = null; + + /** Extracts the Next.js page data every server-rendered page embeds. */ + private async fetchPageData(path: string): Promise { + const response = await fetchApi(`${this.site}${path}`); + const html = await response.text(); + const match = html.match( + /