fix RoyalRoad & better filters (#1104)

* RoyalRoad: fix title getting cut of on html encoded char (é, ", ')

* RoyalRoad: better filters & version increase

* Add filter scrapper for RoyalRoad

* Remove filter scrapper for RoyalRoad
This commit is contained in:
Justin COLLON
2024-08-06 05:54:55 +02:00
committed by GitHub
parent 8c16e548e3
commit 50c40fdeb3
+472 -44
View File
@@ -7,7 +7,7 @@ import { NovelStatus } from '@libs/novelStatus';
class RoyalRoad implements Plugin.PluginBase { class RoyalRoad implements Plugin.PluginBase {
id = 'royalroad'; id = 'royalroad';
name = 'Royal Road'; name = 'Royal Road';
version = '2.0.0'; version = '2.0.1';
icon = 'src/en/royalroad/icon.png'; icon = 'src/en/royalroad/icon.png';
site = 'https://www.royalroad.com/'; site = 'https://www.royalroad.com/';
@@ -56,14 +56,29 @@ class RoyalRoad implements Plugin.PluginBase {
async popularNovels( async popularNovels(
page: number, page: number,
{ filters }: Plugin.PopularNovelsOptions<typeof this.filters>, {
filters,
showLatestNovels,
}: Plugin.PopularNovelsOptions<typeof this.filters>,
): Promise<Plugin.NovelItem[]> { ): Promise<Plugin.NovelItem[]> {
let link = `${this.site}fictions/`; let link = `${this.site}fictions/search`;
link += filters.order.value;
link += `?page=${page}`; link += `?page=${page}`;
if (!filters) filters = this.filters || {};
if (filters.genre.value !== '') link += `&genre=${filters.genre.value}`; if (showLatestNovels) link += '&orderBy=last_update';
for (const key in filters) {
if (filters[key as keyof typeof filters].value === '') continue;
if (key === 'genres' || key === 'tags' || key === 'content_warnings') {
if (filters[key].value.include)
for (const include of filters[key].value.include) {
link += `&tagsAdd=${include}`;
}
if (filters[key].value.exclude)
for (const exclude of filters[key].value.exclude) {
link += `&tagsRemove=${exclude}`;
}
} else if (typeof filters[key as keyof typeof filters] === 'object')
link += `&${key}=${filters[key as keyof typeof filters].value}`;
}
const body = await fetchApi(link).then(r => r.text()); const body = await fetchApi(link).then(r => r.text());
@@ -131,7 +146,7 @@ class RoyalRoad implements Plugin.PluginBase {
}, },
ontext(data) { ontext(data) {
if (isNovelName) { if (isNovelName) {
novel.name = data; novel.name = novel.name + data;
} }
if (isAuthorName) { if (isAuthorName) {
novel.author = data; novel.author = data;
@@ -340,44 +355,457 @@ class RoyalRoad implements Plugin.PluginBase {
} }
filters = { filters = {
order: { 'keyword': {
value: 'weekly-popular', 'type': FilterTypes.TextInput,
label: 'Order By', 'label': 'Keyword (title or description)',
options: [ 'value': '',
{ label: 'Best Rated', value: 'best-rated' },
{ label: 'Trending', value: 'trending' },
{ label: 'Ongoing Fictions', value: 'active-popular' },
{ label: 'Complete', value: 'complete' },
{ label: 'Popular this week', value: 'weekly-popular' },
{ label: 'Latest Updates', value: 'latest-updates' },
{ label: 'Newest Fictions', value: 'new' },
{ label: 'Rising Stars', value: 'rising-stars' },
{ label: 'Writathon', value: 'writathon' },
],
type: FilterTypes.Picker,
}, },
genre: { 'author': {
value: '', 'type': FilterTypes.TextInput,
label: 'Genres', 'label': 'Author',
options: [ 'value': '',
{ label: 'ALL', value: '' }, },
{ label: 'Action', value: 'action' }, 'genres': {
{ label: 'Adventure', value: 'adventure' }, 'type': FilterTypes.ExcludableCheckboxGroup,
{ label: 'Comedy', value: 'comedy' }, 'label': 'Genres',
{ label: 'Contemporary', value: 'contemporary' }, 'value': {
{ label: 'Drama', value: 'drama' }, 'include': [],
{ label: 'Fantasy', value: 'fantasy' }, 'exclude': [],
{ label: 'Historical', value: 'historical' }, },
{ label: 'Horror', value: 'horror' }, 'options': [
{ label: 'Mystery', value: 'mystery' }, {
{ label: 'Psychological', value: 'psychological' }, 'label': 'Action',
{ label: 'Romance', value: 'romance' }, 'value': 'action',
{ label: 'Satire', value: 'satire' }, },
{ label: 'Sci-fi', value: 'sci_fi' }, {
{ label: 'Short Story', value: 'one_shot' }, 'label': 'Adventure',
{ label: 'Tragedy', value: 'tragedy' }, 'value': 'adventure',
},
{
'label': 'Comedy',
'value': 'comedy',
},
{
'label': 'Contemporary',
'value': 'contemporary',
},
{
'label': 'Drama',
'value': 'drama',
},
{
'label': 'Fantasy',
'value': 'fantasy',
},
{
'label': 'Historical',
'value': 'historical',
},
{
'label': 'Horror',
'value': 'horror',
},
{
'label': 'Mystery',
'value': 'mystery',
},
{
'label': 'Psychological',
'value': 'psychological',
},
{
'label': 'Romance',
'value': 'romance',
},
{
'label': 'Satire',
'value': 'satire',
},
{
'label': 'Sci-fi',
'value': 'sci_fi',
},
{
'label': 'Short Story',
'value': 'one_shot',
},
{
'label': 'Tragedy',
'value': 'tragedy',
},
],
},
'tags': {
'type': FilterTypes.ExcludableCheckboxGroup,
'label': 'Tags',
'value': {
'include': [],
'exclude': [],
},
'options': [
{
'label': 'Anti-Hero Lead',
'value': 'anti-hero_lead',
},
{
'label': 'Artificial Intelligence',
'value': 'artificial_intelligence',
},
{
'label': 'Attractive Lead',
'value': 'attractive_lead',
},
{
'label': 'Cyberpunk',
'value': 'cyberpunk',
},
{
'label': 'Dungeon',
'value': 'dungeon',
},
{
'label': 'Dystopia',
'value': 'dystopia',
},
{
'label': 'Female Lead',
'value': 'female_lead',
},
{
'label': 'First Contact',
'value': 'first_contact',
},
{
'label': 'GameLit',
'value': 'gamelit',
},
{
'label': 'Gender Bender',
'value': 'gender_bender',
},
{
'label': 'Genetically Engineered',
'value': 'genetically_engineered ',
},
{
'label': 'Grimdark',
'value': 'grimdark',
},
{
'label': 'Hard Sci-fi',
'value': 'hard_sci-fi',
},
{
'label': 'Harem',
'value': 'harem',
},
{
'label': 'High Fantasy',
'value': 'high_fantasy',
},
{
'label': 'LitRPG',
'value': 'litrpg',
},
{
'label': 'Low Fantasy',
'value': 'low_fantasy',
},
{
'label': 'Magic',
'value': 'magic',
},
{
'label': 'Male Lead',
'value': 'male_lead',
},
{
'label': 'Martial Arts',
'value': 'martial_arts',
},
{
'label': 'Multiple Lead Characters',
'value': 'multiple_lead',
},
{
'label': 'Mythos',
'value': 'mythos',
},
{
'label': 'Non-Human Lead',
'value': 'non-human_lead',
},
{
'label': 'Portal Fantasy / Isekai',
'value': 'summoned_hero',
},
{
'label': 'Post Apocalyptic',
'value': 'post_apocalyptic',
},
{
'label': 'Progression',
'value': 'progression',
},
{
'label': 'Reader Interactive',
'value': 'reader_interactive',
},
{
'label': 'Reincarnation',
'value': 'reincarnation',
},
{
'label': 'Ruling Class',
'value': 'ruling_class',
},
{
'label': 'School Life',
'value': 'school_life',
},
{
'label': 'Secret Identity',
'value': 'secret_identity',
},
{
'label': 'Slice of Life',
'value': 'slice_of_life',
},
{
'label': 'Soft Sci-fi',
'value': 'soft_sci-fi',
},
{
'label': 'Space Opera',
'value': 'space_opera',
},
{
'label': 'Sports',
'value': 'sports',
},
{
'label': 'Steampunk',
'value': 'steampunk',
},
{
'label': 'Strategy',
'value': 'strategy',
},
{
'label': 'Strong Lead',
'value': 'strong_lead',
},
{
'label': 'Super Heroes',
'value': 'super_heroes',
},
{
'label': 'Supernatural',
'value': 'supernatural',
},
{
'label': 'Technologically Engineered',
'value': 'technologically_engineered',
},
{
'label': 'Time Loop',
'value': 'loop',
},
{
'label': 'Time Travel',
'value': 'time_travel',
},
{
'label': 'Urban Fantasy',
'value': 'urban_fantasy',
},
{
'label': 'Villainous Lead',
'value': 'villainous_lead',
},
{
'label': 'Virtual Reality',
'value': 'virtual_reality',
},
{
'label': 'War and Military',
'value': 'war_and_military',
},
{
'label': 'Wuxia',
'value': 'wuxia',
},
{
'label': 'Xianxia',
'value': 'xianxia',
},
],
},
'content_warnings': {
'type': FilterTypes.ExcludableCheckboxGroup,
'label': 'Content Warnings',
'value': {
'include': [],
'exclude': [],
},
'options': [
{
'label': 'Profanity',
'value': 'profanity',
},
{
'label': 'Sexual Content',
'value': 'sexuality',
},
{
'label': 'Graphic Violence',
'value': 'graphic_violence',
},
{
'label': 'Sensitive Content',
'value': 'sensitive',
},
{
'label': 'AI-Assisted Content',
'value': 'ai_assisted',
},
{
'label': 'AI-Generated Content',
'value': 'ai_generated',
},
],
},
'minPages': {
'type': FilterTypes.TextInput,
'label': 'Min Pages',
'value': '0',
},
'maxPages': {
'type': FilterTypes.TextInput,
'label': 'Max Pages',
'value': '20000',
},
'minRating': {
'type': FilterTypes.TextInput,
'label': 'Min Rating (0.0 - 5.0)',
'value': '0.0',
},
'maxRating': {
'type': FilterTypes.TextInput,
'label': 'Max Rating (0.0 - 5.0)',
'value': '5.0',
},
'status': {
'type': FilterTypes.Picker,
'label': 'Status',
'value': 'ALL',
'options': [
{
'label': 'All',
'value': 'ALL',
},
{
'label': 'Completed',
'value': 'COMPLETED',
},
{
'label': 'Dropped',
'value': 'DROPPED',
},
{
'label': 'Ongoing',
'value': 'ONGOING',
},
{
'label': 'Hiatus',
'value': 'HIATUS',
},
{
'label': 'Stub',
'value': 'STUB',
},
],
},
'orderBy': {
'type': FilterTypes.Picker,
'label': 'Order by',
'value': 'relevance',
'options': [
{
'label': 'Relevance',
'value': 'relevance',
},
{
'label': 'Popularity',
'value': 'popularity',
},
{
'label': 'Average Rating',
'value': 'rating',
},
{
'label': 'Last Update',
'value': 'last_update',
},
{
'label': 'Release Date',
'value': 'release_date',
},
{
'label': 'Followers',
'value': 'followers',
},
{
'label': 'Number of Pages',
'value': 'length',
},
{
'label': 'Views',
'value': 'views',
},
{
'label': 'Title',
'value': 'title',
},
{
'label': 'Author',
'value': 'author',
},
],
},
'dir': {
'type': FilterTypes.Picker,
'label': 'Direction',
'value': 'desc',
'options': [
{
'label': 'Ascending',
'value': 'asc',
},
{
'label': 'Descending',
'value': 'desc',
},
],
},
'type': {
'type': FilterTypes.Picker,
'label': 'Type',
'value': 'ALL',
'options': [
{
'label': 'All',
'value': 'ALL',
},
{
'label': 'Fan Fiction',
'value': 'fanfiction',
},
{
'label': 'Original',
'value': 'original',
},
], ],
type: FilterTypes.Picker,
}, },
} satisfies Filters; } satisfies Filters;
} }