import { Plugin } from "@typings/plugin"; import { fetchApi, fetchFile } from "@libs/fetch"; import { NovelStatus } from "@libs/novelStatus"; import dayjs from "dayjs"; class Smakolykytl implements Plugin.PluginBase { id = "smakolykytl"; name = "Смаколики"; site = "https://smakolykytl.site/"; version = "1.0.0"; icon = "src/ua/smakolykytl/icon.png"; userAgent = ""; async popularNovels( pageNo: number, { showLatestNovels, filters }: Plugin.PopularNovelsOptions, ): Promise { const url = showLatestNovels ? "https://api.smakolykytl.site/api/user/updates" : "https://api.smakolykytl.site/api/user/projects"; const result = await fetchApi(url); const json = (await result.json()) as response; const novels: Plugin.NovelItem[] = []; (json?.projects || json?.updates)?.forEach((novel) => novels.push({ name: novel.title, cover: novel.image.url, url: this.site + "titles/" + novel.id, }), ); return novels; } async parseNovelAndChapters(novelUrl: string): Promise { const id = novelUrl.split("/").pop(); const result = await fetchApi( "https://api.smakolykytl.site/api/user/projects/" + id, ); const book = (await result.json()) as response; const novel: Plugin.SourceNovel = { url: novelUrl, name: book?.project?.title, cover: book?.project?.image?.url, summary: book?.project?.description, author: book?.project?.author, status: book?.project?.status_translate.includes("Триває") ? NovelStatus.Ongoing : NovelStatus.Completed, }; const tags = [book?.project?.genres, book?.project?.tags] .flat() .map((tags) => tags?.title) .filter((tags) => tags); if (tags.length) { novel.genres = tags.join(", "); } const chapters: Plugin.ChapterItem[] = []; const res = await fetchApi( "https://api.smakolykytl.site/api/user/projects/" + id + "/books", ); const data = (await res.json()) as response; data?.books?.forEach( (volume) => volume?.chapters?.map((chapter) => chapters.push({ name: volume.title + " " + chapter.title, releaseTime: dayjs(chapter.modifiedAt).format("LLL"), url: this.site + "read/" + chapter.id, }), ), ); novel.chapters = chapters; return novel; } async parseChapter(chapterUrl: string): Promise { const id = chapterUrl.split("/").pop(); const result = await fetchApi( "https://api.smakolykytl.site/api/user/chapters/" + id, ); const json = (await result.json()) as response; const chapterRaw: HTML[] = JSON.parse(json?.chapter?.content || "[]"); const chapterText = jsonToHtml(chapterRaw); return chapterText; } async searchNovels( searchTerm: string, //pageNo: number | undefined = 1, ): Promise { const result = await fetchApi( "https://api.smakolykytl.site/api/user/projects", ); const json = (await result.json()) as response; const novels: Plugin.NovelItem[] = []; json?.projects ?.filter( (novel) => novel.title.includes(searchTerm) || String(novel.id) === searchTerm, ) ?.forEach((novel) => novels.push({ name: novel.title, cover: novel.image.url, url: this.site + "titles/" + novel.id, }), ); return novels; } fetchImage = fetchFile; } export default new Smakolykytl(); function jsonToHtml(json: HTML[], html: string = "") { json.forEach((element) => { switch (element.type) { case "hardBreak": html += "
"; break; case "horizontalRule": html += "
"; break; case "image": if (element.attrs) { const attrs = Object.entries(element.attrs) .filter((attr) => attr?.[1]) .map((attr) => `${attr[0]}="${attr[1]}"`); html += ""; } break; case "paragraph": html += "

" + (element.content ? jsonToHtml(element.content) : "
") + "

"; break; case "text": html += element.text; break; default: html += JSON.stringify(element, null, "\t"); //maybe I missed something. break; } }); return html; } interface response { projects?: TopLevelProject[]; updates?: Update[]; project?: TopLevelProject; books?: BookElement[]; chapter?: TopLevelChapter; } interface BookElement { id: number; rank: number; title: string; chapters: PurpleChapter[]; } interface PurpleChapter { id: number; title: string; rank: string; modifiedAt: Date; } interface TopLevelChapter { id: number; title: string; rank: string; content: string; modifiedAt: Date; book: ChapterBook; } interface ChapterBook { id: number; rank: number; title: string; chapters: FluffyChapter[]; project: BookProject; } interface FluffyChapter { id: number; rank: string; } interface BookProject { id: number; } interface TopLevelProject { id: number; title: string; description: string; author: string; translator: string; modifiedAt: Date; alternatives: string; release: string; nation: string; status: string; status_translate: string; image: Image; tags?: Genre[]; genres?: Genre[]; } interface Genre { id: number; title: string; } interface Image { id: number; url: string; name: string; } interface Update { id: number; title: string; author: string; translator: string; modifiedAt: Date; image: Image; } interface HTML { type: string; content?: HTML[]; attrs?: Attrs; text?: string; } interface Attrs { src: string; alt: string | null; title: string | null; }