import { Plugin } from '@typings/plugin'; import { FilterTypes, Filters } from '@libs/filterInputs'; import { defaultCover } from '@libs/defaultCover'; import { fetchApi, fetchFile } from '@libs/fetch'; import { NovelStatus } from '@libs/novelStatus'; const statusKey: { [key: string]: string } = { '0': NovelStatus.Unknown, '1': NovelStatus.Ongoing, '2': NovelStatus.Completed, '3': NovelStatus.OnHiatus, '4': NovelStatus.Cancelled, }; class Neobook implements Plugin.PluginBase { id = 'neobook'; name = 'Neobook'; site = 'https://neobook.org'; version = '1.0.1'; icon = 'src/ru/neobook/icon.png'; async fetchNovels( page: number, { filters, showLatestNovels, }: Plugin.PopularNovelsOptions, searchTerm?: string, ): Promise { const formData = new FormData(); formData.append('version', '4.0'); formData.append('uid', '0'); formData.append('utoken', ''); formData.append('resource', 'general'); formData.append('action', 'get_bundle'); formData.append('bundle', 'bundle_books'); formData.append('target', 'feed'); formData.append('page', page.toString()); formData.append('filter_category_id', filters?.category?.value || '0'); formData.append('filter_completed', '-1'); formData.append('filter_search', searchTerm || ''); formData.append('filter_tags', filters?.tags?.value || ''); formData.append( 'filter_sort', showLatestNovels ? 'new' : filters?.sort?.value || 'popular', ); formData.append('filter_timeread', filters?.timeread?.value || '0-999999'); const { bundle_books }: { bundle_books: BundleBooks } = await fetchApi( 'https://api.neobook.org/', { method: 'post', body: formData, }, ).then(res => res.json()); const novels: Plugin.NovelItem[] = []; if (bundle_books.feed?.length) { bundle_books.feed?.forEach(novel => novels.push({ name: novel.title, cover: novel?.attachment?.image?.m || defaultCover, path: novel.token + '/', }), ); } return novels; } popularNovels = this.fetchNovels; async searchNovels( searchTerm: string, page: number, ): Promise { const defaultOptions: any = { filters: undefined, showLatestNovels: false, }; return this.fetchNovels(page, defaultOptions, searchTerm); } async parseNovel(novelPath: string): Promise { const body = await fetchApi(this.resolveUrl(novelPath, true)).then(res => res.text(), ); const novel: Plugin.SourceNovel = { path: novelPath, name: '', cover: defaultCover, }; const bookRaw = body.match(/var postData = ({.*?});/); if (bookRaw instanceof Array && bookRaw[1]) { const book: Novels = JSON.parse(bookRaw[1]); novel.name = book.title; novel.summary = book.text?.replace?.(/
/g, '\n') || book.text_fix; novel.author = book.user && book.user.firstname && book.user.lastname ? book.user.firstname + ' ' + book.user.lastname : book.user?.initials || ''; novel.status = statusKey[book.status || '0'] || NovelStatus.Unknown; if (book.attachment?.image?.m) novel.cover = book.attachment.image.m; if (book.tags?.length) novel.genres = book.tags.join(','); const chapters: Plugin.ChapterItem[] = []; book.chapters?.forEach((chapter, chapterIndex) => { if (chapter.access == '1' && chapter.status == '1') { chapters.push({ name: chapter.title || 'Глава ' + (chapterIndex + 1), path: `?book=${book.token}&chapter=${chapter.token}`, releaseTime: null, chapterNumber: Number(chapter.sort) || chapterIndex + 1, }); } }); novel.chapters = chapters; } return novel; } async parseChapter(chapterPath: string): Promise { const body = await fetchApi(this.resolveUrl(chapterPath)).then(res => res.text(), ); const bookRaw = body.match(/var data = ({.*?});/); let chapterText = ''; if (bookRaw instanceof Array && bookRaw[1]) { const token = chapterPath.split('=')[2]; const book: Novels = JSON.parse(bookRaw[1]); const chapter = book.chapters?.find?.(chapter => chapter.token == token); chapterText = (chapter?.data?.html || '').replace(/
/g, ''); } return chapterText; } fetchImage = fetchFile; resolveUrl = (path: string, isNovel?: boolean) => this.site + (isNovel ? '/book/' : '/reader/') + path; filters = { sort: { label: 'Сортировка:', value: 'popular', options: [ { label: 'Сначала популярные', value: 'popular' }, { label: 'Сначала новые', value: 'new' }, { label: 'В случайном порядке', value: 'rand' }, ], type: FilterTypes.Picker, }, timeread: { label: 'Время прочтения:', value: '0-999999', options: [ { label: 'Все', value: '0-999999' }, { label: 'Менее 15 минут', value: '0-900' }, { label: '15-30 минут', value: '900-1800' }, { label: '30-60 минут', value: '1800-3600' }, { label: '1-2 часа', value: '3600-7200' }, { label: 'Более 2 часов', value: '7200-999999' }, ], type: FilterTypes.Picker, }, category: { label: 'Жанр:', value: '', options: [ { label: 'Все', value: '' }, { label: 'Антиутопия', value: '10' }, { label: 'Детектив', value: '13' }, { label: 'Детские книги', value: '14' }, { label: 'Драма', value: '15' }, { label: 'Другое', value: '16' }, { label: 'История', value: '18' }, { label: 'Мелодрама', value: '21' }, { label: 'Мистика', value: '22' }, { label: 'Научная фантастика', value: '23' }, { label: 'Нон-фикшн', value: '35' }, { label: 'Подростки и молодежь', value: '26' }, { label: 'Постапокалипсис', value: '27' }, { label: 'Поэзия', value: '28' }, { label: 'Приключения', value: '29' }, { label: 'Рассказ', value: '34' }, { label: 'Роман', value: '36' }, { label: 'Творчество', value: '40' }, { label: 'Триллер', value: '91' }, { label: 'Ужасы', value: '90' }, { label: 'Фантастика', value: '41' }, { label: 'Фанфик', value: '42' }, { label: 'Фэнтези', value: '44' }, { label: 'Эротика', value: '46' }, { label: 'Юмор', value: '47' }, ], type: FilterTypes.Picker, }, tags: { label: 'Тэги:', value: '', type: FilterTypes.TextInput, }, } satisfies Filters; } export default new Neobook(); interface BundleBooks { categories: any[]; topSection: any[]; feed: Novels[]; } interface BottomSection { id: string; boost: string; preferenceid: string; type_id: string; contentTypeid: string; name: string; title: string; more: string; part: string; items: Novels[]; } interface Novels { status?: string; error?: string; login?: Login; requestid?: string; bundleBooks?: BundleBooks; available?: string; boosted?: string; id?: string; token: string; type?: string; typeid?: string; verified?: string; audio?: string; labelid?: string; labelTitle?: string; languageid?: string; languageTitle?: string; categoryid?: string; categoryTitle?: string; price?: Price; priceSpecial?: Price; priceDiscount?: string; title: string; text: string; text_fix: string; copyright?: string; align?: string; completed?: string; timereadS?: string; views?: string; likes?: string; comments?: string; bookmarks?: string; isLike?: string; isBookmark?: string; isPurchase?: string; isSelf?: string; dateEdit?: string; datePublish?: string; link_path?: string; user?: NovelsUser; attachment?: Attachment; adult?: string; adultPlaceholder?: string; nft?: string; feed?: Feed; counters?: NovelsCounters; rating?: Rating; readTime?: ReadTime; readProgress?: ReadProgress; tags?: string[]; chapters?: Chapter[]; lastComments?: any[]; bottomSection?: BottomSection; carouselItems?: Novels[]; book?: Novels; activeChapterIndex?: number; } interface Attachment { has: string; id: string; typeid: string; userid: string; width: string; height: string; duration: string; image: Image; video: string; origin: string; ext: string; } interface Image { s: string; m: string; l: string; has?: string; } interface Chapter { id: string; token: string; status: string; sort: string; free: string; title: string; words: string; access: string; loaded: string; data?: Data; } interface Data { available: string; access: string; id: string; token: string; postid: string; free: string; title: string; html: string; dateEdit: string; datePublish: string; attachment: Attachment; lastComments: any[]; } interface NovelsCounters { impressions: Bookmarks; views: Bookmarks; likes: Bookmarks; comments: Bookmarks; bookmarks: Bookmarks; } interface Bookmarks { value: string; formatted: string; } interface Feed { name: string; canRemoved: string; } interface Login { uid: string; utoken: string; has: string; banned: string; verified: string; acc: string; arm: string; grm: string; counters: LoginCounters; user: LoginUser; pro: any[]; earn: any[]; boost: any[]; deposit: any[]; affiliate: any[]; preferences: any[]; } interface LoginCounters { notifications: string; messages: string; } interface LoginUser { id: string; username: string; firstname: string; lastname: string; photo: string; status: string; balance: Balance; connectedService: string; } interface Balance { neo: Price; } interface Price { has: string; value: string; currency: Currency; formatted: string; deadline: string; } type Currency = 'NEO'; interface Rating { value: string; count: string; my: string; } interface ReadProgress { chapter: string; progress: string; } interface ReadTime { hours: string; minutes: string; } interface NovelsUser { available: string; boosted: string; id: string; status: string; verified: string; metrica: string; cover: Image; photo: Image; username: string; firstname: string; lastname: string; initials: string; link: string; about: string; subscriptions: string; subscribers: string; publications: string; books: string; poems: string; posts: string; drafts: string; isPro: string; isAdult: string; isBanned: string; isSubscription: string; isBlock: string; gr: string; }