feat: Add New Sources (#583)

* fix

* Update ranobelib.js

* fix

* test

* fix

* fix path

* test

* added sources

* added ALL sources on novel.tl

* add tags

* Update NovelTlScraper.js

* fix
This commit is contained in:
Rider21
2023-03-30 10:49:00 +03:00
committed by GitHub
parent 2d986f263d
commit be2dab5b5d
14 changed files with 8135 additions and 1507 deletions
+3 -1
View File
@@ -1,3 +1,4 @@
import CookieManager from '@react-native-cookies/cookies';
import { useMMKVObject } from 'react-native-mmkv';
import { MMKVStorage } from '@utils/mmkv/mmkv';
import { showToast } from './showToast';
@@ -38,7 +39,8 @@ const useSourceStorage = ({ sourceId = -1 }: { sourceId?: number }) => {
// }),
// );
// }
CookieManager.clearAll();
CookieManager.clearAll(true); //clears cookies in webkit
setValues({});
showToast('Cookies cleared');
};
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,230 @@
import dayjs from 'dayjs';
import * as cheerio from 'cheerio';
import { htmlToText } from '../../helpers/htmlToText';
import { Status, defaultCoverUri } from '../../helpers/constants';
class NovelTlScraper {
constructor(sourceId, baseUrl, sourceName, filter) {
this.sourceId = sourceId;
this.baseUrl = baseUrl;
this.domain = baseUrl.split('//')[1];
this.sourceName = sourceName;
this.filters = filter || null;
}
async popularNovels(page, { filters }) {
const result = await fetch('https://api.novel.tl/api/site/v2/graphql', {
method: 'post',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/json',
'Alt-Used': 'api.novel.tl',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
},
referrer: this.baseUrl,
body: JSON.stringify({
operationName: 'Projects',
query:
'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}',
variables: {
filter: filters?.tags?.length > 0 ? { tags: filters.tags } : {},
hostname: this.domain,
limit: 40,
page,
},
}),
});
const json = await result.json();
let novels = json?.data?.projects?.content?.map(novel => ({
novelName: novel.title,
novelUrl: novel.url,
sourceId: this.sourceId,
novelCover: novel.covers[0]?.url
? this.baseUrl + novel.covers[0].url
: defaultCoverUri,
}));
return { novels };
}
async parseNovelAndChapters(novelUrl) {
const result = await fetch('https://api.novel.tl/api/site/v2/graphql', {
method: 'post',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/json',
'Alt-Used': 'api.novel.tl',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
},
referrer: this.baseUrl,
body: JSON.stringify({
operationName: 'Book',
query:
'query Book($url:String){project(project:{fullUrl:$url}){title translationStatus url covers{url}persons(langs:["ru","en","*"]roles:["author","illustrator","original_story","original_design"]){role name{firstName lastName}}genres{nameRu nameEng}tags{nameRu nameEng}annotation{text}subprojects{content{title volumes{content{url shortName chapters{title publishDate url published}}}}}}}',
variables: {
hostname: this.domain,
project: novelUrl,
url: this.domain + '/r/' + novelUrl,
},
}),
});
const json = await result.json();
let novel = {
sourceId: this.sourceId,
sourceName: this.sourceName,
url: this.baseUrl + '/r/' + novelUrl,
novelUrl,
novelName: json.data.project.title,
novelCover: json.data.project.covers[0]?.url
? this.baseUrl + json.data.project.covers[0].url
: defaultCoverUri,
summary: htmlToText(json.data.project.annotation?.text),
author:
json.data.project.persons[0].name.firstName +
' ' +
json.data.project.persons[0].name.lastName,
status:
json.data.project.translationStatus === 'active'
? Status.ONGOING
: Status.COMPLETED,
};
let genres = []
.concat(json.data.project.tags, json.data.project.genres)
.map(item => item?.nameRu || item?.nameEng)
.filter(item => item);
if (genres.length > 0) {
novel.genre = genres.join(',');
}
let novelChapters = [];
json.data.project.subprojects.content.forEach(work =>
work.volumes.content.forEach(volume =>
volume.chapters.forEach(
chapter =>
chapter?.published &&
novelChapters.push({
chapterName: volume.shortName + ' ' + chapter.title,
chapterUrl: volume.url + '/' + chapter.url,
releaseDate: dayjs(chapter.publishDate).format('LLL'),
}),
),
),
);
novel.chapters = novelChapters;
return novel;
}
async parseChapter(novelUrl, chapterUrl) {
const baseUrl = this.baseUrl;
const sourceId = this.sourceId;
const result = await fetch('https://api.novel.tl/api/site/v2/graphql', {
method: 'post',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/json',
'Alt-Used': 'api.novel.tl',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
},
referrer: baseUrl,
body: JSON.stringify({
operationName: 'EReaderData',
query:
'query EReaderData($url:String!,$chapterSelector:Selector!){project(project:{fullUrl:$url}){title url}chapter(chapter:$chapterSelector){title text{text}}}',
variables: {
chapterSelector: {
fullUrl: this.domain + '/r/' + novelUrl + '/' + chapterUrl,
},
url: this.domain + '/r/' + novelUrl + '/' + chapterUrl,
},
}),
});
const json = await result.json();
const loadedCheerio = cheerio.load(json.data.chapter.text.text);
loadedCheerio('img').each(function () {
if (!loadedCheerio(this).attr('src')?.startsWith('http')) {
let src = loadedCheerio(this).attr('src');
loadedCheerio(this).attr('src', baseUrl + src);
}
});
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName: json.data.chapter.title,
chapterText: loadedCheerio.html(),
};
return chapter;
}
async searchNovels(searchTerm) {
const result = await fetch('https://api.novel.tl/api/site/v2/graphql', {
method: 'post',
headers: {
'User-Agent':
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:109.0) Gecko/20100101 Firefox/111.0',
'Accept': 'application/json, text/plain, */*',
'Accept-Language': 'ru-RU,ru;q=0.8,en-US;q=0.5,en;q=0.3',
'Content-Type': 'application/json',
'Alt-Used': 'api.novel.tl',
'Sec-Fetch-Dest': 'empty',
'Sec-Fetch-Mode': 'cors',
'Sec-Fetch-Site': 'cross-site',
},
referrer: this.baseUrl,
body: JSON.stringify({
operationName: 'Projects',
query:
'query Projects($hostname:String! $filter:SearchFilter $page:Int $limit:Int){projects(section:{fullUrl:$hostname}filter:$filter page:{pageSize:$limit,pageNumber:$page}){content{title url covers{url}}}}',
variables: {
filter: {
query: searchTerm,
},
hostname: this.domain,
limit: 40,
page: 1,
},
}),
});
const json = await result.json();
let novels = [];
json?.data?.projects?.content?.forEach(novel =>
novels.push({
novelName: novel.title,
novelUrl: novel.url,
sourceId: this.sourceId,
novelCover: novel.covers[0]?.url
? this.baseUrl + novel.covers[0].url
: defaultCoverUri,
}),
);
return novels;
}
}
export default NovelTlScraper;
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,282 @@
import { FilterInputs } from '../../types/filterTypes';
import { htmlToText } from '../../helpers/htmlToText';
import { Status } from '../../helpers/constants';
import * as cheerio from 'cheerio';
import { defaultTo } from 'lodash-es';
class RulateScraper {
constructor(sourceId, baseUrl, sourceName, filter) {
this.sourceId = sourceId;
this.baseUrl = baseUrl;
this.sourceName = sourceName;
this.filters = [
{
key: 'sort',
label: 'Сортировка',
values: [
{ label: 'По рейтингу', value: '6' },
{ label: 'По степени готовности', value: '0' },
{ label: 'По названию на языке оригинала', value: '1' },
{ label: 'По названию на языке перевода', value: '2' },
{ label: 'По дате создания', value: '3' },
{ label: 'По дате последней активности', value: '4' },
{ label: 'По просмотрам', value: '5' },
{ label: 'По кол-ву переведённых глав', value: '7' },
{ label: 'По кол-ву лайков', value: '8' },
{ label: 'По кол-ву страниц', value: '10' },
{ label: 'По кол-ву бесплатных глав', value: '11' },
{ label: 'По кол-ву рецензий', value: '12' },
{ label: 'По кол-ву в закладках', value: '13' },
{ label: 'По кол-ву в избранном', value: '14' },
],
inputType: FilterInputs.Picker,
},
{
key: 'type',
label: 'Тип',
values: [
{ label: 'Неважно', value: '0' },
{ label: 'Только переводы', value: '1' },
{ label: 'Только авторские', value: '2' },
],
inputType: FilterInputs.Picker,
},
{
key: 'atmosphere',
label: 'Атмосфера',
values: [
{ label: 'Неважно', value: '0' },
{ label: 'Позитивная', value: '1' },
{ label: 'Dark', value: '2' },
],
inputType: FilterInputs.Picker,
},
{
key: 'trash',
label: 'Другое',
values: [
{ label: 'Готовые на 100%', value: 'ready' },
{ label: 'Доступные для перевода', value: 'tr' },
{ label: 'Доступные для скачивания', value: 'gen' },
{ label: 'Завершённые проекты', value: 'wealth' },
{ label: 'Распродажа', value: 'discount' },
{ label: 'Только онгоинги', value: 'ongoings' },
{ label: 'Убрать машинный', value: 'remove_machinelate' },
],
inputType: FilterInputs.Checkbox,
},
...filter,
{
key: 'adult',
label: 'Возрастные ограничения',
values: [
{ label: 'Все', value: '0' },
{ label: 'Убрать 18+', value: '1' },
{ label: 'Только 18+', value: '2' },
],
inputType: FilterInputs.Picker,
},
];
}
async popularNovels(page, { showLatestNovels, filters }) {
const baseUrl = this.baseUrl;
const sourceId = this.sourceId;
let url = baseUrl + '/search?t=&cat=2';
url += '&sort=' + defaultTo(filters?.sort, showLatestNovels ? '4' : '6');
url += '&type=' + defaultTo(filters?.type, '0');
url += '&atmosphere=' + defaultTo(filters?.atmosphere, '0');
url += '&adult=' + defaultTo(filters?.adult, '0');
if (filters?.genres?.length) {
url += filters.genres.map(i => `&genres[]=${i}`).join('');
}
if (filters?.tags?.length) {
url += filters.tags.map(i => `&tags[]=${i}`).join('');
}
if (filters?.trash?.length) {
url += filters.trash.map(i => `&${i}=1`);
}
url += `&Book_page=${page}`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novels = [];
loadedCheerio(
'ul[class="search-results"] > li:not([class="ad_type_catalog"])',
).each(function () {
novels.push({
sourceId,
novelName: loadedCheerio(this).find('p > a').text(),
novelCover: baseUrl + loadedCheerio(this).find('img').attr('src'),
novelUrl: loadedCheerio(this).find('p > a').attr('href'),
});
});
return { novels };
}
async parseNovelAndChapters(novelUrl) {
let result = await fetch(this.baseUrl + novelUrl);
if (result.url.includes('mature?path=')) {
const formData = new FormData();
formData.append('path', novelUrl);
formData.append('ok', 'Да');
await fetch(result.url, {
method: 'POST',
body: formData,
});
result = await fetch(this.baseUrl + novelUrl);
}
const body = await result.text();
const loadedCheerio = cheerio.load(body);
let novel = {
sourceId: this.sourceId,
sourceName: this.sourceName,
url: this.baseUrl + novelUrl,
novelUrl,
};
novel.novelName = loadedCheerio('div[class="container"] > div > div > h1')
.text()
.trim();
novel.novelCover =
this.baseUrl + loadedCheerio('div[class="images"] > div img').attr('src');
novel.summary = loadedCheerio('#Info > div:nth-child(3)').html();
let genre = [];
if (novel?.summary) {
novel.summary = htmlToText(novel.summary);
}
loadedCheerio('div[class="span5"] > p').each(function () {
switch (loadedCheerio(this).find('strong').text()) {
case 'Автор:':
novel.author = loadedCheerio(this).find('em > a').text().trim();
break;
case 'Выпуск:':
novel.status =
loadedCheerio(this).find('em').text().trim() === 'продолжается'
? Status.ONGOING
: Status.COMPLETED;
break;
case 'Тэги:':
loadedCheerio(this)
.find('em > a')
.each(function () {
genre.push(loadedCheerio(this).text());
});
break;
case 'Жанры:':
loadedCheerio(this)
.find('em > a')
.each(function () {
genre.push(loadedCheerio(this).text());
});
break;
}
});
if (genre.length > 0) {
novel.genre = genre.reverse().join(',');
}
let chapters = [];
loadedCheerio('table > tbody > tr.chapter_row').each(function () {
const chapterName = loadedCheerio(this)
.find('td[class="t"] > a')
.text()
.trim();
const releaseDate = loadedCheerio(this)
.find('td > span')
.attr('title')
?.trim();
const chapterUrl = loadedCheerio(this)
.find('td[class="t"] > a')
.attr('href');
if (
loadedCheerio(this).find('td > span[class="disabled"]').length < 1 &&
releaseDate
) {
chapters.push({ chapterName, releaseDate, chapterUrl });
}
});
novel.chapters = chapters;
return novel;
}
async parseChapter(novelUrl, chapterUrl) {
const baseUrl = this.baseUrl;
const sourceId = this.sourceId;
let result = await fetch(baseUrl + chapterUrl);
if (result.url.includes('mature?path=')) {
const formData = new FormData();
formData.append('path', novelUrl);
formData.append('ok', 'Да');
await fetch(result.url, {
method: 'POST',
body: formData,
});
result = await fetch(baseUrl + chapterUrl);
}
const body = await result.text();
const loadedCheerio = cheerio.load(body);
loadedCheerio('.content-text img').each(function () {
if (!loadedCheerio(this).attr('src')?.startsWith('http')) {
let src = loadedCheerio(this).attr('src');
loadedCheerio(this).attr('src', baseUrl + src);
}
});
const chapterName = loadedCheerio(
'.chapter_select > select > option[selected]',
)
.text()
.trim();
const chapterText = loadedCheerio('.content-text').html();
const chapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
}
async searchNovels(searchTerm) {
const result = await fetch(
this.baseUrl + '/search/autocomplete?query=' + searchTerm,
);
let json = await result.json();
let novels = [];
json.forEach(item => {
const novelName = item.title_one + ' / ' + item.title_two;
const novelCover = this.baseUrl + item.img;
const novelUrl = item.url;
novels.push({ sourceId: this.sourceId, novelName, novelCover, novelUrl });
});
return novels;
}
}
export default RulateScraper;
+2 -2
View File
@@ -9,7 +9,7 @@ const sourceName = 'Jaomix';
const baseUrl = 'https://jaomix.ru';
const popularNovels = async (page, { showLatestNovels, filters }) => {
let url = baseUrl + '/?search=&sortby=';
let url = baseUrl + '/?searchrn&sortby=';
url += defaultTo(filters?.sort, showLatestNovels ? 'upd' : 'count');
if (filters?.type?.length) {
@@ -110,7 +110,7 @@ const parseChapter = async (novelUrl, chapterUrl) => {
};
const searchNovels = async searchTerm => {
const url = `${baseUrl}/?search=${searchTerm}&but=%D0%9F%D0%BE%D0%B8%D1%81%D0%BA+%D0%BF%D0%BE+%D0%BD%D0%B0%D0%B7%D0%B2%D0%B0%D0%BD%D0%B8%D1%8E&sortby=new`;
const url = `${baseUrl}/?searchrn=${searchTerm}&but=Поиск по названию&sortby=upd`;
const result = await fetch(url);
let body = await result.text();
const loadedCheerio = cheerio.load(body);
+3 -3
View File
@@ -85,8 +85,8 @@ const parseNovelAndChapters = async novelUrl => {
const chaptersRaw = await fetch(fetchChaptersUrl);
const chaptersJSON = await chaptersRaw.json();
chaptersJSON.volumes.map(volume =>
volume.chapters.map(chapter =>
chaptersJSON.volumes.forEach(volume =>
volume.chapters.forEach(chapter =>
novelChapters.push({
chapterName: chapter.name,
chapterUrl: chapter.url,
@@ -153,7 +153,7 @@ const searchNovels = async searchTerm => {
data
.find(item => item.meta.key === 'ranobe')
?.data.map(novel =>
?.data.forEach(novel =>
novels.push({
sourceId,
novelName: novel.names.rus,
+16 -12
View File
@@ -8,6 +8,7 @@ const sourceId = 93;
const sourceName = 'RanobeLib';
const baseUrl = 'https://ranobelib.me';
var ui = null;
const popularNovels = async (page, { showLatestNovels, filters }) => {
let url = `${baseUrl}/manga-list?sort=`;
@@ -46,6 +47,9 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
const body = await result.text();
const loadedCheerio = cheerio.load(body);
ui = loadedCheerio('a.header-right-menu__item')
.attr('href')
?.replace?.(/[^0-9]/g, '');
let novels = [];
@@ -122,25 +126,25 @@ const parseNovelAndChapters = async novelUrl => {
?.slice(0, -1);
chaptersJson = JSON.parse(chaptersJson);
ui = chaptersJson?.user?.id;
const novelSlug = chaptersJson.manga.slug;
chaptersJson.chapters.list.map(item => {
const chapterName =
`Том ${item.chapter_volume} Глава ${item.chapter_number} ${item.chapter_name}`?.trim();
const releaseDate = dayjs(item.chapter_created_at).format('LLL');
const chapterUrl = `${baseUrl}/${novelSlug}/v${item.chapter_volume}/c${item.chapter_number}`;
chapters.push({ chapterName, releaseDate, chapterUrl });
chaptersJson.chapters.list.forEach(chapter => {
chapters.push({
chapterName:
`Том ${chapter.chapter_volume} Глава ${chapter.chapter_number} ${chapter.chapter_name}`?.trim(),
releaseDate: dayjs(chapter.chapter_created_at).format('LLL'),
chapterUrl:
`${baseUrl}/${chaptersJson.manga.slug}/v${chapter.chapter_volume}/c${chapter.chapter_number}?bid=` +
(chapter?.branch_id || ''),
});
});
novel.chapters = chapters.reverse();
return novel;
};
const parseChapter = async (novelUrl, chapterUrl) => {
const result = await fetch(chapterUrl);
const result = await fetch(chapterUrl + (ui ? `&ui=${ui}` : ''));
const body = await result.text();
const loadedCheerio = cheerio.load(body);
@@ -179,7 +183,7 @@ const searchNovels = async searchTerm => {
const body = await result.json();
let novels = [];
body.map(novel => {
body.forEach(novel => {
const novelName = novel?.rus_name || novel.name;
const novelUrl = novel?.href || baseUrl + '/' + novel.slug;
const novelCover = novel.coverImage;
+3 -3
View File
@@ -26,7 +26,7 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
json = JSON.parse(json);
let novels = [];
json.props.pageProps.totalData.items.map(item => {
json.props.pageProps.totalData.items.forEach(item => {
const novelName = item.title;
const novelUrl = baseUrl + '/' + item.slug;
const novelCover = item?.verticalImage?.url
@@ -65,7 +65,7 @@ const parseNovelAndChapters = async novelUrl => {
let chapters = [];
json.props.pageProps.book.chapters.map(item => {
json.props.pageProps.book.chapters.forEach(item => {
const chapterName = item.title;
const releaseDate = dayjs(item.publishedAt).format('LLL');
const chapterUrl = baseUrl + item.url;
@@ -115,7 +115,7 @@ const searchNovels = async searchTerm => {
const body = await result.json();
let novels = [];
body.items.map(item => {
body.items.forEach(item => {
const novelName = item.title;
const novelUrl = baseUrl + '/' + item.slug;
const novelCover = item?.verticalImage?.url
+3 -3
View File
@@ -37,7 +37,7 @@ const popularNovels = async (page, { showLatestNovels, filters }) => {
let novels = [];
body.content.map(item => {
body.content.forEach(item => {
const novelName = item.rus_name;
const novelUrl = item.dir;
const novelCover =
@@ -89,7 +89,7 @@ const parseNovelAndChapters = async novelUrl => {
);
let volumes = await chapterResult.json();
volumes.content.map(item => {
volumes.content.forEach(item => {
const chapterName =
`Том ${item.tome} Глава ${item.chapter} ${item.name}`?.trim();
const releaseDate = dayjs(item.upload_date).format('LLL');
@@ -126,7 +126,7 @@ const searchNovels = async searchTerm => {
let body = await result.json();
let novels = [];
body.content.map(item => {
body.content.forEach(item => {
const novelName = item.rus_name;
const novelUrl = item.dir;
const novelCover =
File diff suppressed because it is too large Load Diff
+35 -1
View File
@@ -91,7 +91,6 @@ import RanobeHubScraper from './ru/ranobehub';
import RanobeLibScraper from './ru/ranobelib';
import RanobeRFScraper from './ru/ranoberf';
import RenovelsScraper from './ru/renovels';
import RulateScraper from './ru/rulate';
import FicbookScraper from './ru/ficbook';
import DivineDaoLibraryScraper from './en/divinedaolibrary';
import NovelOnlineFullScraper from './en/novelonlinefull';
@@ -135,6 +134,26 @@ import {
} from './multisrc/wqmangastream/WQMangaStreamGenerator';
import AgitoonScraper from './kr/Agitoon';
import PawReadScraper from './en/pawread';
import {
RulateScraper,
ErolateScraper,
} from './multisrc/rulate/RulateGenerator';
import {
RuRanobeScraper,
UkrRanobeScraper,
NightnovelScraper,
KgRanobeScraper,
AxelScraper,
SferdrakonScraper,
OriginalScraper,
SnailulitkaScraper,
TsundokuScraper,
JapitScraper,
BelScraper,
RedScraper,
SiScraper,
KodScraper,
} from './multisrc/noveltl/NovelTlGenerator';
interface PopularNovelsResponse {
novels: SourceNovelItem[];
@@ -288,6 +307,21 @@ export const sourceManager = (sourceId: number): Scraper => {
145: AgitoonScraper, // @ts-ignore
146: PandaMtlScraper, // @ts-ignore
147: PawReadScraper, // @ts-ignore
148: ErolateScraper, // @ts-ignore
149: RuRanobeScraper, // @ts-ignore
150: UkrRanobeScraper, // @ts-ignore
151: NightnovelScraper, // @ts-ignore
152: KgRanobeScraper, // @ts-ignore
153: AxelScraper, // @ts-ignore
154: SferdrakonScraper, // @ts-ignore
155: OriginalScraper, // @ts-ignore
156: SnailulitkaScraper, // @ts-ignore
157: TsundokuScraper, // @ts-ignore
158: JapitScraper, // @ts-ignore
159: BelScraper, // @ts-ignore
160: RedScraper, // @ts-ignore
161: SiScraper, // @ts-ignore
162: KodScraper, // @ts-ignore
};
return scrapers[sourceId];
+105
View File
@@ -880,5 +880,110 @@
"icon": "https://www.pawread.com/favicon.ico",
"url": "https://pawread.com/",
"lang": "English"
},
{
"sourceId": 148,
"sourceName": "Erolate",
"url": "https://erolate.com",
"lang": "Russian",
"icon": "https://erolate.com/i/logo/erolate.png"
},
{
"sourceId": 149,
"sourceName": "РуРанобэ",
"url": "https://ruranobe.ru",
"lang": "Russian",
"icon": "https://ruranobe.ru/logo.png"
},
{
"sourceId": 150,
"sourceName": "Переклади українською мовою",
"url": "https://ukr.novel.tl",
"lang": "Ukraine",
"icon": "https://ukr.novel.tl/logo.png"
},
{
"sourceId": 151,
"sourceName": "Nightnovel",
"url": "https://nightnovel.online",
"lang": "Russian",
"icon": "https://nightnovel.online/logo.png"
},
{
"sourceId": 152,
"sourceName": "Карманная галактика",
"url": "https://kg.novel.tl",
"lang": "Russian",
"icon": "https://kg.novel.tl/logo.png"
},
{
"sourceId": 153,
"sourceName": "Аксел",
"url": "https://axel.znovel.space",
"lang": "Russian",
"icon": "https://axel.znovel.space/logo.png"
},
{
"sourceId": 154,
"sourceName": "Уголок дракона",
"url": "https://sferdrakon.novel.tl",
"lang": "Russian",
"icon": "https://sferdrakon.novel.tl/logo.png"
},
{
"sourceId": 155,
"sourceName": "Авторские работы",
"url": "https://original.novel.tl",
"lang": "Russian",
"icon": "https://original.novel.tl/logo.png"
},
{
"sourceId": 156,
"sourceName": "Snailulitka",
"url": "https://snailulitka.novel.tl",
"lang": "Russian",
"icon": "https://snailulitka.novel.tl/logo.png"
},
{
"sourceId": 157,
"sourceName": "TsundokuTrans",
"url": "https://tsundoku.novel.tl",
"lang": "Russian",
"icon": "https://tsundoku.novel.tl/logo.png"
},
{
"sourceId": 158,
"sourceName": "Japit Comics",
"url": "https://japit.novel.tl",
"lang": "Russian",
"icon": "https://japit.novel.tl/logo.png"
},
{
"sourceId": 159,
"sourceName": "Белманга",
"url": "https://bel.novel.tl",
"lang": "Belarus",
"icon": "https://bel.novel.tl/logo.png"
},
{
"sourceId": 160,
"sourceName": "Север и Юг",
"url": "https://red.novel.tl",
"lang": "Russian",
"icon": "https://red.novel.tl/logo.png"
},
{
"sourceId": 161,
"sourceName": "Сказочные истории",
"url": "https://si.novel.tl",
"lang": "Russian",
"icon": "https://si.novel.tl/logo.png"
},
{
"sourceId": 162,
"sourceName": "K.O.D.",
"url": "https://kod.novel.tl",
"lang": "Russian",
"icon": "https://kod.novel.tl/logo.png"
}
]
+2
View File
@@ -1,5 +1,6 @@
export const languages = [
'Arabic',
'Belarus',
'Chinese',
'English',
'French',
@@ -10,5 +11,6 @@ export const languages = [
'Russian',
'Spanish',
'Turkish',
'Ukraine',
'Vietnamese',
];