diff --git a/plugins/english/wetriedtls.ts b/plugins/english/wetriedtls.ts new file mode 100644 index 0000000..8b3f492 --- /dev/null +++ b/plugins/english/wetriedtls.ts @@ -0,0 +1,131 @@ +import { fetchApi } from '@libs/fetch'; +import { Plugin } from '@/types/plugin'; +import { defaultCover } from '@libs/defaultCover'; +import { NovelStatus } from '@libs/novelStatus'; + +// WeTriedTLS runs HeanCMS; everything comes from its JSON API. +const API = 'https://api.wetriedtls.com'; + +type Series = { + id: number; + title: string; + series_slug: string; + thumbnail?: string; + description?: string; + author?: string; + studio?: string; + status?: string; + alternative_names?: string; + tags?: { name: string }[]; +}; + +type Chapter = { + chapter_name: string; + chapter_title?: string | null; + chapter_slug: string; + index?: string; + price: number; + created_at?: string; +}; + +type Page = { data: T[]; meta: { last_page: number } }; + +const STATUS: Record = { + Ongoing: NovelStatus.Ongoing, + Completed: NovelStatus.Completed, + Hiatus: NovelStatus.OnHiatus, + Dropped: NovelStatus.Cancelled, +}; + +class WeTriedTLS implements Plugin.PluginBase { + id = 'wetriedtls'; + name = 'WeTriedTLS'; + icon = 'src/en/wetriedtls/icon.png'; + site = 'https://wetriedtls.com'; + version = '1.0.0'; + + async api(path: string): Promise { + const res = await fetchApi(API + path, { headers: { Referer: this.site } }); + if (!res.ok) throw new Error(`WeTriedTLS API ${res.status} for ${path}`); + return res.json(); + } + + toItem = (s: Series): Plugin.NovelItem => ({ + name: s.title, + path: s.series_slug, + cover: s.thumbnail || defaultCover, + }); + + async query(params: string, pageNo: number): Promise { + const res = await this.api>( + `/query?series_type=Novel&perPage=20&page=${pageNo}&${params}`, + ); + return res.data.map(this.toItem); + } + + popularNovels( + pageNo: number, + { showLatestNovels }: Plugin.PopularNovelsOptions, + ): Promise { + const order = showLatestNovels ? 'latest' : 'total_views'; + return this.query(`orderBy=${order}&order=desc&query_string=`, pageNo); + } + + searchNovels(searchTerm: string, pageNo: number) { + return this.query(`query_string=${encodeURIComponent(searchTerm)}`, pageNo); + } + + async parseNovel(novelPath: string): Promise { + const s = await this.api(`/series/${novelPath}`); + + const chapters: Plugin.ChapterItem[] = []; + for (let page = 1, last = 1; page <= last; page++) { + const res = await this.api>( + `/chapters/${s.id}?page=${page}&perPage=1000`, + ); + last = res.meta.last_page; + for (const c of res.data) { + const title = c.chapter_title + ? `${c.chapter_name} - ${c.chapter_title}` + : c.chapter_name; + chapters.push({ + // Paid chapters stay paid: they are labelled and parseChapter shows a notice. + name: c.price > 0 ? `🔒 ${title}` : title, + path: `${novelPath}/${c.chapter_slug}`, + releaseTime: c.created_at, + chapterNumber: c.index ? Number(c.index) : undefined, + }); + } + } + chapters.reverse(); // API lists newest first + + return { + path: novelPath, + name: s.title, + cover: s.thumbnail || defaultCover, + author: s.author, + artist: s.studio || undefined, + summary: s.description?.replace(/<[^>]+>/g, '').trim(), + genres: s.tags?.map(t => t.name).join(', '), + status: (s.status && STATUS[s.status]) || NovelStatus.Unknown, + chapters, + }; + } + + async parseChapter(chapterPath: string): Promise { + const res = await this.api<{ + chapter: { chapter_content?: string; price: number }; + }>(`/chapter/${chapterPath}`); + const content = res.chapter.chapter_content; + if (!content) { + return res.chapter.price > 0 + ? `

This chapter is paid on WeTriedTLS. Unlock it at ${this.site} to read it.

` + : '

Chapter content is unavailable.

'; + } + return content; + } + + resolveUrl = (path: string) => `${this.site}/series/${path}`; +} + +export default new WeTriedTLS(); diff --git a/public/static/src/en/wetriedtls/icon.png b/public/static/src/en/wetriedtls/icon.png new file mode 100644 index 0000000..027df50 Binary files /dev/null and b/public/static/src/en/wetriedtls/icon.png differ