Files
lnreader-plugins/plugins/russian/authortoday.broken.ts
T
nyagami 20efcf404c refactor: Classes for Plugins (#631)
* set target to ES5

* refactor types

* use .gitkeep

* use dot to make dist/js dirs on top

* add hako

* add Hako

* add madara

* fix: url path js -> .js

* fix: branch

* freestyle for using UA, cookie

* add parseNovels for Hako

* update readme

* fix: conflict types

* add Kolnovel, remove node-fetch

* add bestlightnovel

* add plugins

* WQMangaStreamScraper
* PandaMTL
* NobleMTL
* LightNovelBrasil
* WPMangaStream

* add filters for popularNovels

* fix undefined json

* Linovelib (#26)

* linovelib CN

* icon for linovelib

* fix undefined chapter

* add check

* fix: add artist

* TuNovelaLigera ES + Madara Fix (#28)

* app repo test

* POST is now working

* update madara template + run generator

* template fix didnt get pushed commited idk why

* template update POST

* generated

* fix: useNewChapterEndpoint update (#29)

* useNewChapterEndpoint update

* Update Selectors

---------

Co-authored-by: K1ngfish3r <trulyihu@gmail.com>
2023-11-04 15:45:49 +05:30

547 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Chapter, Novel, Plugin } from "@typings/plugin";
import { FilterInputs } from "@libs/filterInputs";
import { defaultCover } from "@libs/defaultCover";
import { fetchApi, fetchFile } from "@libs/fetch";
import { NovelStatus } from "@libs/novelStatus";
import { load as parseHTML } from "cheerio";
import dayjs from "dayjs";
export const id = "AT";
export const name = "Автор Тудей";
export const site = "https://author.today";
export const version = "1.0.0";
export const icon = "src/ru/authortoday/icon.png";
const apiUrl = "https://api.author.today/";
const token = "Bearer guest";
export const popularNovels: Plugin.popularNovels = async function (
page,
{ showLatestNovels, filters }
) {
let url = apiUrl + "v1/catalog/search?page=" + page;
if (filters?.genre) {
url += "&genre=" + filters.genre;
}
url +=
"&sorting=" + (showLatestNovels ? "recent" : filters?.sort || "popular");
url += "&form=" + (filters?.form || "any");
url += "&state=" + (filters?.state || "any");
url += "&series=" + (filters?.series || "any");
url += "&access=" + (filters?.access || "any");
url += "&promo=" + (filters?.promo || "hide");
const result = await fetchApi(url, {
headers: {
Authorization: token,
},
});
const json = (await result.json()) as response;
if (json?.code === "NotFound") {
return [];
}
let novels: Novel.Item[] = [];
json?.searchResults?.forEach((novel) =>
novels.push({
name: novel.title,
cover: novel?.coverUrl
? "https://cm.author.today/content/" + novel.coverUrl
: defaultCover,
url: site + "/work/" + novel.id,
})
);
return novels;
};
export const parseNovelAndChapters: Plugin.parseNovelAndChapters =
async function (novelUrl) {
const workID = novelUrl.split("/")[4];
let result = await fetchApi(`${apiUrl}v1/work/${workID}/details`, {
headers: {
Authorization: token,
},
});
let json = (await result.json()) as responseBook;
let novel: Novel.instance = {
url: novelUrl,
name: json.title,
cover: json?.coverUrl ? json.coverUrl.split("?")[0] : defaultCover,
author:
json?.originalAuthor ||
json?.authorFIO ||
json?.coAuthorFIO ||
json?.secondCoAuthorFIO ||
json?.translator ||
"",
genres: json?.tags?.join(", "),
status: json?.isFinished ? NovelStatus.Completed : NovelStatus.Ongoing,
};
novel.summary = "";
novel.summary += json?.annotation ? json.annotation + "\n" : "";
novel.summary += json?.authorNotes
? "Примечания автора:\n" + json.authorNotes
: "";
// all chapters
let chaptersRaw = await fetchApi(`${apiUrl}v1/work/${workID}/content`, {
headers: {
Authorization: token,
},
});
let chaptersJSON = (await chaptersRaw.json()) as ChaptersEntity[];
let chapters: Chapter.Item[] = [];
chaptersJSON?.forEach((chapter, index) => {
if (chapter?.isAvailable && !chapter?.isDraft) {
chapters.push({
name: chapter?.title || `Глава ${index + 1}`,
releaseTime: dayjs(
chapter?.publishTime || chapter.lastModificationTime
).format("LLL"),
url: `${apiUrl}v1/work/${workID}/chapter/${chapter.id}/text`,
});
}
});
novel.chapters = chapters;
return novel;
};
export const parseChapter: Plugin.parseChapter = async function (chapterUrl) {
const result = await fetchApi(chapterUrl, {
headers: {
Authorization: token,
},
});
const json = (await result.json()) as encryptedСhapter;
if (json?.code) {
return json.code + "\n" + json?.message;
}
let key = json.key.split("").reverse().join("") + "@_@";
let text = "";
for (let i = 0; i < json.text.length; i++) {
text += String.fromCharCode(
json.text.charCodeAt(i) ^ key.charCodeAt(Math.floor(i % key.length))
);
}
const loadedCheerio = parseHTML(text);
loadedCheerio("img").each(function () {
if (!loadedCheerio(this).attr("src")?.startsWith("http")) {
const src = loadedCheerio(this).attr("src");
loadedCheerio(this).attr("src", site + src);
}
});
const chapterText = loadedCheerio.html();
return chapterText;
};
export const searchNovels: Plugin.searchNovels = async function (searchTerm) {
const url = `${site}/search?category=works&q=${searchTerm}`;
const result = await fetchApi(url);
const body = await result.text();
const loadedCheerio = parseHTML(body);
let novels: Novel.Item[] = [];
loadedCheerio("div.book-row").each(function () {
const name = loadedCheerio(this)
.find('div[class="book-title"] a')
.text()
.trim();
let cover = loadedCheerio(this).find("img").attr("src");
const url =
site +
"/work/" +
loadedCheerio(this)
.find('div[class="book-title"] a')
.attr("href")
?.split("/")[2];
if (cover) {
cover = cover.split("?")[0];
} else {
cover = defaultCover;
}
novels.push({ name, cover, url });
});
return novels;
};
export const fetchImage: Plugin.fetchImage = fetchFile;
export const filters = [
{
key: "sort",
label: "Сортировка",
values: [
{ label: "По популярности", value: "popular" },
{ label: "По количеству лайков", value: "likes" },
{ label: "По комментариям", value: "comments" },
{ label: "По новизне", value: "recent" },
{ label: "По просмотрам", value: "views" },
{ label: "Набирающие популярность", value: "trending" },
],
inputType: FilterInputs.Picker,
},
{
key: "genre",
label: "Жанры",
values: [
{ label: "Альтернативная история", value: "sf-history" },
{ label: "Антиутопия", value: "dystopia" },
{ label: "Бизнес-литература", value: "biznes-literatura" },
{ label: "Боевая фантастика", value: "sf-action" },
{ label: "Боевик", value: "action" },
{ label: "Боевое фэнтези", value: "fantasy-action" },
{ label: "Бояръ-Аниме", value: "boyar-anime" },
{ label: "Героическая фантастика", value: "sf-heroic" },
{ label: "Героическое фэнтези", value: "heroic-fantasy" },
{ label: "Городское фэнтези", value: "urban-fantasy" },
{ label: "Детектив", value: "detective" },
{ label: "Детская литература", value: "detskaya-literatura" },
{ label: "Документальная проза", value: "non-fiction" },
{ label: "Историческая проза", value: "historical-fiction" },
{
label: "Исторические приключения",
value: "historical-adventure",
},
{ label: "Исторический детектив", value: "historical-mystery" },
{
label: "Исторический любовный роман",
value: "historical-romance",
},
{ label: "Историческое фэнтези", value: "historical-fantasy" },
{ label: "Киберпанк", value: "cyberpunk" },
{ label: "Короткий любовный роман", value: "short-romance" },
{ label: "Космическая фантастика", value: "sf-space" },
{ label: "ЛитРПГ", value: "litrpg" },
{ label: "Любовное фэнтези", value: "love-fantasy" },
{ label: "Любовные романы", value: "romance" },
{ label: "Мистика", value: "paranormal" },
{ label: "Назад в СССР", value: "back-to-ussr" },
{ label: "Научная фантастика", value: "science-fiction" },
{ label: "Подростковая проза", value: "teen-prose" },
{ label: "Политический роман", value: "political-fiction" },
{ label: "Попаданцы", value: "popadantsy" },
{ label: "Попаданцы в космос", value: "popadantsy-v-kosmos" },
{
label: "Попаданцы в магические миры",
value: "popadantsy-v-magicheskie-miry",
},
{ label: "Попаданцы во времени", value: "popadantsy-vo-vremeni" },
{ label: "Постапокалипсис", value: "postapocalyptic" },
{ label: "Поэзия", value: "poetry" },
{ label: "Приключения", value: "adventure" },
{ label: "Публицистика", value: "publicism" },
{ label: "Развитие личности", value: "razvitie-lichnosti" },
{ label: "Разное", value: "other" },
{ label: "РеалРПГ", value: "realrpg" },
{ label: "Романтическая эротика", value: "romantic-erotika" },
{ label: "Сказка", value: "fairy-tale" },
{ label: "Современная проза", value: "modern-prose" },
{
label: "Современный любовный роман",
value: "contemporary-romance",
},
{ label: "Социальная фантастика", value: "sf-social" },
{ label: "Стимпанк", value: "steampunk" },
{ label: "Темное фэнтези", value: "dark-fantasy" },
{ label: "Триллер", value: "thriller" },
{ label: "Ужасы", value: "horror" },
{ label: "Фантастика", value: "sci-fi" },
{
label: "Фантастический детектив",
value: "detective-science-fiction",
},
{ label: "Фанфик", value: "fanfiction" },
{ label: "Фэнтези", value: "fantasy" },
{ label: "Шпионский детектив", value: "spy-mystery" },
{ label: "Эпическое фэнтези", value: "epic-fantasy" },
{ label: "Эротика", value: "erotica" },
{ label: "Эротическая фантастика", value: "sf-erotika" },
{ label: "Эротический фанфик", value: "fanfiction-erotika" },
{ label: "Эротическое фэнтези", value: "fantasy-erotika" },
{ label: "Юмор", value: "humor" },
{ label: "Юмористическая фантастика", value: "sf-humor" },
{ label: "Юмористическое фэнтези", value: "ironical-fantasy" },
],
inputType: FilterInputs.Picker,
},
{
key: "form",
label: "Форма произведения",
values: [
{ label: "Любой", value: "any" },
{ label: "Перевод", value: "translation" },
{ label: "Повесть", value: "tale" },
{ label: "Рассказ", value: "story" },
{ label: "Роман", value: "novel" },
{ label: "Сборник поэзии", value: "poetry" },
{ label: "Сборник рассказов", value: "story-book" },
],
inputType: FilterInputs.Picker,
},
{
key: "state",
label: "Статус произведения",
values: [
{ label: "Любой статус", value: "any" },
{ label: "В процессе", value: "in-progress" },
{ label: "Завершено", value: "finished" },
],
inputType: FilterInputs.Picker,
},
{
key: "series",
label: "Статус цикла",
values: [
{ label: "Не важно", value: "any" },
{ label: "Вне цикла", value: "out" },
{ label: "Цикл завершен", value: "finished" },
{ label: "Цикл не завершен", value: "unfinished" },
],
inputType: FilterInputs.Picker,
},
{
key: "access",
label: "Тип доступа",
values: [
{ label: "Любой", value: "any" },
{ label: "Платный", value: "paid" },
{ label: "Бесплатный", value: "free" },
],
inputType: FilterInputs.Picker,
},
{
key: "promo",
label: "Промо-фрагмент",
values: [
{ label: "Скрывать", value: "hide" },
{ label: "Показывать", value: "show" },
],
inputType: FilterInputs.Picker,
},
];
interface response {
searchResults?: SearchResultsEntity[] | null;
realTotalCount: number;
code?: string;
errorMessage?: null;
isLastPage: boolean;
format: Format;
duration: Duration;
isWorkMarkEnabled: boolean;
}
interface SearchResultsEntity {
id: number;
title: string;
annotation: string;
authorId: number;
authorFIO: string;
authorUserName: string;
originalAuthor?: null;
coAuthorId?: number | null;
coAuthorFIO?: string | null;
coAuthorUserName?: string | null;
coAuthorConfirmed: boolean;
seriesId: number;
seriesTitle?: string | null;
publishTime: string;
lastModificationTime: string;
finishTime?: string | null;
isDraft: boolean;
formEnum: string;
genreId: number;
firstSubGenreId: number;
secondSubGenreId?: number | null;
adultOnly: boolean;
tags?: (string | null)[] | null;
coverUrl: string;
viewCount: number;
commentCount: number;
reviewCount: number;
textLength: number;
likeCount: number;
showAuthor: boolean;
price?: number | null;
discount?: null;
discountStartDate?: null;
discountEndDate?: null;
status: string;
promoFragment: boolean;
isExclusive: boolean;
format: string;
marks?: null[] | null;
isLiked: boolean;
isWorkMarkEnabled: boolean;
workInLibraryState: string;
}
interface Format {
enumValue: string;
value: string;
title: string;
mobileTitle: string;
}
interface Duration {
value: string;
title: string;
mobileTitle: string;
}
interface responseBook {
chapters?: ChaptersEntity[] | null;
allowDownloads: boolean;
downloadErrorCode: string;
downloadErrorMessage: string;
privacyDownloads: string;
collectionCount: number;
annotation: string;
authorNotes: string;
atRecommendation: boolean;
seriesWorkIds?: number[] | null;
seriesWorkNumber: number;
reviewCount: number;
tags?: string[] | null;
orderId?: null;
orderStatus: string;
orderStatusMessage?: null;
contests?: null[] | null;
galleryImages?: GalleryImagesEntity[] | null;
booktrailerVideoUrl?: null;
isExclusive: boolean;
freeChapterCount: number;
promoFragment: boolean;
recommendations?: RecommendationsEntity[] | null;
linkedWork?: null;
id: number;
title: string;
coverUrl: string;
lastModificationTime: string;
lastUpdateTime: string;
finishTime?: null;
isFinished: boolean;
textLength: number;
textLengthLastRead: number;
price: number;
discount?: null;
workForm: string;
status: string;
authorId: number;
authorFIO: string;
authorUserName: string;
originalAuthor?: null;
translator?: null;
reciter?: null;
coAuthorId?: null;
coAuthorFIO?: null;
coAuthorUserName?: null;
coAuthorConfirmed: boolean;
secondCoAuthorId?: null;
secondCoAuthorFIO?: null;
secondCoAuthorUserName?: null;
secondCoAuthorConfirmed: boolean;
isPurchased: boolean;
userLikeId?: null;
lastReadTime?: null;
lastChapterId?: null;
lastChapterProgress: number;
likeCount: number;
commentCount: number;
rewardCount: number;
rewardsEnabled: boolean;
inLibraryState: string;
addedToLibraryTime: string;
updateInLibraryTime: string;
privacyDisplay: string;
state: string;
isDraft: boolean;
enableRedLine: boolean;
enableTTS: boolean;
adultOnly: boolean;
seriesId: number;
seriesOrder: number;
seriesTitle: string;
afterword: string;
seriesNextWorkId?: null;
genreId: number;
firstSubGenreId: number;
secondSubGenreId: number;
format: string;
marks?: null;
purchaseTime?: null;
likeTime?: null;
markTime?: null;
}
interface ChaptersEntity {
id: number;
workId: number;
title: string;
isDraft: boolean;
sortOrder: number;
publishTime: string;
lastModificationTime: string;
textLength: number;
isAvailable: boolean;
}
interface GalleryImagesEntity {
id: string;
caption?: null;
url: string;
height: number;
width: number;
}
interface RecommendationsEntity {
title: string;
coverUrl: string;
authorId: number;
authorFIO: string;
authorUserName: string;
originalAuthor?: null;
coAuthorId?: null;
coAuthorFIO?: null;
coAuthorUserName?: null;
coAuthorConfirmed: boolean;
secondCoAuthorId?: null;
secondCoAuthorFIO?: null;
secondCoAuthorUserName?: null;
secondCoAuthorConfirmed: boolean;
discount?: null;
price?: number | null;
lastModificationTime: string;
finishTime: string;
finished: boolean;
isExclusive: boolean;
format: string;
status: string;
state: string;
removalReason?: null;
removalReasonComment?: null;
id: number;
}
interface encryptedСhapter {
id: number;
title: string;
isDraft: boolean;
publishTime: string;
lastModificationTime: string;
text: string;
key: string;
code?: string;
message?: string;
}