feat(multisrc): Add novelfire and novelphoenix (#2367)
Signed-off-by: K1ngfish3r <26593485+K1ngfish3r@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,454 @@
|
||||
{
|
||||
"filters": {
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"value": [],
|
||||
"options": [
|
||||
{
|
||||
"label": "Chinese Novel",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "Japanese Novel",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "English Novel",
|
||||
"value": "4"
|
||||
}
|
||||
],
|
||||
"type": "Checkbox"
|
||||
},
|
||||
"genre_operator": {
|
||||
"label": "Genres (And/Or/Exclude)",
|
||||
"value": "and",
|
||||
"options": [
|
||||
{
|
||||
"label": "AND",
|
||||
"value": "and"
|
||||
},
|
||||
{
|
||||
"label": "OR",
|
||||
"value": "or"
|
||||
},
|
||||
{
|
||||
"label": "EXCLUDE",
|
||||
"value": "exclude"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"genres": {
|
||||
"label": "Genres",
|
||||
"value": [],
|
||||
"options": [
|
||||
{
|
||||
"label": "Action",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Adult",
|
||||
"value": "28"
|
||||
},
|
||||
{
|
||||
"label": "Adventure",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "Anime",
|
||||
"value": "46"
|
||||
},
|
||||
{
|
||||
"label": "Arts",
|
||||
"value": "47"
|
||||
},
|
||||
{
|
||||
"label": "Comedy",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Drama",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"label": "Eastern",
|
||||
"value": "44"
|
||||
},
|
||||
{
|
||||
"label": "Ecchi",
|
||||
"value": "26"
|
||||
},
|
||||
{
|
||||
"label": "Fan-fiction",
|
||||
"value": "48"
|
||||
},
|
||||
{
|
||||
"label": "Fantasy",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Game",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Gender Bender",
|
||||
"value": "25"
|
||||
},
|
||||
{
|
||||
"label": "Harem",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Historical",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Horror",
|
||||
"value": "37"
|
||||
},
|
||||
{
|
||||
"label": "Isekai",
|
||||
"value": "49"
|
||||
},
|
||||
{
|
||||
"label": "Josei",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Lgbt+",
|
||||
"value": "45"
|
||||
},
|
||||
{
|
||||
"label": "Magic",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"label": "Magical realism",
|
||||
"value": "51"
|
||||
},
|
||||
{
|
||||
"label": "Manhua",
|
||||
"value": "52"
|
||||
},
|
||||
{
|
||||
"label": "Martial Arts",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Mature",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Mecha",
|
||||
"value": "34"
|
||||
},
|
||||
{
|
||||
"label": "Military",
|
||||
"value": "53"
|
||||
},
|
||||
{
|
||||
"label": "Modern life",
|
||||
"value": "54"
|
||||
},
|
||||
{
|
||||
"label": "Movies",
|
||||
"value": "55"
|
||||
},
|
||||
{
|
||||
"label": "Mystery",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Other",
|
||||
"value": "64"
|
||||
},
|
||||
{
|
||||
"label": "Psychological",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Realistic fiction",
|
||||
"value": "56"
|
||||
},
|
||||
{
|
||||
"label": "Reincarnation",
|
||||
"value": "43"
|
||||
},
|
||||
{
|
||||
"label": "Romance",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "School Life",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"label": "Sci-fi",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"label": "Seinen",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Shoujo",
|
||||
"value": "38"
|
||||
},
|
||||
{
|
||||
"label": "Shoujo ai",
|
||||
"value": "57"
|
||||
},
|
||||
{
|
||||
"label": "Shounen",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Shounen Ai",
|
||||
"value": "39"
|
||||
},
|
||||
{
|
||||
"label": "Slice of Life",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Smut",
|
||||
"value": "29"
|
||||
},
|
||||
{
|
||||
"label": "Sports",
|
||||
"value": "42"
|
||||
},
|
||||
{
|
||||
"label": "Supernatural",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "System",
|
||||
"value": "58"
|
||||
},
|
||||
{
|
||||
"label": "Tragedy",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"label": "Urban",
|
||||
"value": "63"
|
||||
},
|
||||
{
|
||||
"label": "Urban life",
|
||||
"value": "59"
|
||||
},
|
||||
{
|
||||
"label": "Video games",
|
||||
"value": "60"
|
||||
},
|
||||
{
|
||||
"label": "War",
|
||||
"value": "61"
|
||||
},
|
||||
{
|
||||
"label": "Wuxia",
|
||||
"value": "31"
|
||||
},
|
||||
{
|
||||
"label": "Xianxia",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"label": "Xuanhuan",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"label": "Yaoi",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Yuri",
|
||||
"value": "62"
|
||||
}
|
||||
],
|
||||
"type": "Checkbox"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Chapters",
|
||||
"value": "0",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"label": "<50",
|
||||
"value": "1,49"
|
||||
},
|
||||
{
|
||||
"label": "50-100",
|
||||
"value": "50,100"
|
||||
},
|
||||
{
|
||||
"label": "100-200",
|
||||
"value": "100,200"
|
||||
},
|
||||
{
|
||||
"label": "200-500",
|
||||
"value": "200,500"
|
||||
},
|
||||
{
|
||||
"label": "500-1000",
|
||||
"value": "500,1000"
|
||||
},
|
||||
{
|
||||
"label": ">1000",
|
||||
"value": "1001,1000000"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"rating_operator": {
|
||||
"label": "Rating (Min/Max)",
|
||||
"value": "min",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "min",
|
||||
"value": "min"
|
||||
},
|
||||
{
|
||||
"label": "max",
|
||||
"value": "max"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"rating": {
|
||||
"label": "Rating",
|
||||
"value": "0",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "none",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"label": "1",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "2",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "3",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "4",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "5",
|
||||
"value": "5"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"status": {
|
||||
"label": "Translation Status",
|
||||
"value": "-1",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "-1"
|
||||
},
|
||||
{
|
||||
"label": "Completed",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "Ongoing",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"sort": {
|
||||
"label": "Sort Results By",
|
||||
"value": "date",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "Last Updated (Newest)",
|
||||
"value": "date"
|
||||
},
|
||||
{
|
||||
"label": "Rank (Top)",
|
||||
"value": "rank-top"
|
||||
},
|
||||
{
|
||||
"label": "Rating Score (Top)",
|
||||
"value": "rating-score-top"
|
||||
},
|
||||
{
|
||||
"label": "Review Count (Most)",
|
||||
"value": "review"
|
||||
},
|
||||
{
|
||||
"label": "Comment Count (Most)",
|
||||
"value": "comment"
|
||||
},
|
||||
{
|
||||
"label": "Bookmark Count (Most)",
|
||||
"value": "bookmark"
|
||||
},
|
||||
{
|
||||
"label": "Today Views (Most)",
|
||||
"value": "today-view"
|
||||
},
|
||||
{
|
||||
"label": "Monthly Views (Most)",
|
||||
"value": "monthly-view"
|
||||
},
|
||||
{
|
||||
"label": "Total Views (Most)",
|
||||
"value": "total-view"
|
||||
},
|
||||
{
|
||||
"label": "Chapter Count (Most)",
|
||||
"value": "chapter-count-most"
|
||||
},
|
||||
{
|
||||
"label": "Title (A>Z)",
|
||||
"value": "abc"
|
||||
},
|
||||
{
|
||||
"label": "Title (Z>A)",
|
||||
"value": "cba"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"tagcon": {
|
||||
"label": "Tags (And/Or)",
|
||||
"value": "and",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "AND",
|
||||
"value": "and"
|
||||
},
|
||||
{
|
||||
"label": "OR",
|
||||
"value": "or"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"author": {
|
||||
"label": "Author",
|
||||
"value": "",
|
||||
"type": "Text"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,456 @@
|
||||
{
|
||||
"filters": {
|
||||
"language": {
|
||||
"label": "Language",
|
||||
"value": [],
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "Chinese Novel",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "Japanese Novel",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "English Novel",
|
||||
"value": "4"
|
||||
}
|
||||
],
|
||||
"type": "Checkbox"
|
||||
},
|
||||
"genre_operator": {
|
||||
"label": "Genres (And/Or/Exclude)",
|
||||
"value": "and",
|
||||
"options": [
|
||||
{
|
||||
"label": "AND",
|
||||
"value": "and"
|
||||
},
|
||||
{
|
||||
"label": "OR",
|
||||
"value": "or"
|
||||
},
|
||||
{
|
||||
"label": "EXCLUDE",
|
||||
"value": "exclude"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"genres": {
|
||||
"label": "Genres",
|
||||
"value": [],
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "Action",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "Adult",
|
||||
"value": "28"
|
||||
},
|
||||
{
|
||||
"label": "Adventure",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "Anime",
|
||||
"value": "46"
|
||||
},
|
||||
{
|
||||
"label": "Arts",
|
||||
"value": "47"
|
||||
},
|
||||
{
|
||||
"label": "Comedy",
|
||||
"value": "5"
|
||||
},
|
||||
{
|
||||
"label": "Drama",
|
||||
"value": "24"
|
||||
},
|
||||
{
|
||||
"label": "Eastern",
|
||||
"value": "44"
|
||||
},
|
||||
{
|
||||
"label": "Ecchi",
|
||||
"value": "26"
|
||||
},
|
||||
{
|
||||
"label": "Fan-fiction",
|
||||
"value": "48"
|
||||
},
|
||||
{
|
||||
"label": "Fantasy",
|
||||
"value": "6"
|
||||
},
|
||||
{
|
||||
"label": "Game",
|
||||
"value": "19"
|
||||
},
|
||||
{
|
||||
"label": "Gender Bender",
|
||||
"value": "25"
|
||||
},
|
||||
{
|
||||
"label": "Harem",
|
||||
"value": "7"
|
||||
},
|
||||
{
|
||||
"label": "Historical",
|
||||
"value": "12"
|
||||
},
|
||||
{
|
||||
"label": "Horror",
|
||||
"value": "37"
|
||||
},
|
||||
{
|
||||
"label": "Isekai",
|
||||
"value": "49"
|
||||
},
|
||||
{
|
||||
"label": "Josei",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "Lgbt+",
|
||||
"value": "45"
|
||||
},
|
||||
{
|
||||
"label": "Magic",
|
||||
"value": "50"
|
||||
},
|
||||
{
|
||||
"label": "Magical realism",
|
||||
"value": "51"
|
||||
},
|
||||
{
|
||||
"label": "Manhua",
|
||||
"value": "52"
|
||||
},
|
||||
{
|
||||
"label": "Martial Arts",
|
||||
"value": "15"
|
||||
},
|
||||
{
|
||||
"label": "Mature",
|
||||
"value": "8"
|
||||
},
|
||||
{
|
||||
"label": "Mecha",
|
||||
"value": "34"
|
||||
},
|
||||
{
|
||||
"label": "Military",
|
||||
"value": "53"
|
||||
},
|
||||
{
|
||||
"label": "Modern life",
|
||||
"value": "54"
|
||||
},
|
||||
{
|
||||
"label": "Movies",
|
||||
"value": "55"
|
||||
},
|
||||
{
|
||||
"label": "Mystery",
|
||||
"value": "16"
|
||||
},
|
||||
{
|
||||
"label": "Other",
|
||||
"value": "64"
|
||||
},
|
||||
{
|
||||
"label": "Psychological",
|
||||
"value": "9"
|
||||
},
|
||||
{
|
||||
"label": "Realistic fiction",
|
||||
"value": "56"
|
||||
},
|
||||
{
|
||||
"label": "Reincarnation",
|
||||
"value": "43"
|
||||
},
|
||||
{
|
||||
"label": "Romance",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "School Life",
|
||||
"value": "21"
|
||||
},
|
||||
{
|
||||
"label": "Sci-fi",
|
||||
"value": "20"
|
||||
},
|
||||
{
|
||||
"label": "Seinen",
|
||||
"value": "10"
|
||||
},
|
||||
{
|
||||
"label": "Shoujo",
|
||||
"value": "38"
|
||||
},
|
||||
{
|
||||
"label": "Shoujo ai",
|
||||
"value": "57"
|
||||
},
|
||||
{
|
||||
"label": "Shounen",
|
||||
"value": "17"
|
||||
},
|
||||
{
|
||||
"label": "Shounen Ai",
|
||||
"value": "39"
|
||||
},
|
||||
{
|
||||
"label": "Slice of Life",
|
||||
"value": "13"
|
||||
},
|
||||
{
|
||||
"label": "Smut",
|
||||
"value": "29"
|
||||
},
|
||||
{
|
||||
"label": "Sports",
|
||||
"value": "42"
|
||||
},
|
||||
{
|
||||
"label": "Supernatural",
|
||||
"value": "18"
|
||||
},
|
||||
{
|
||||
"label": "System",
|
||||
"value": "58"
|
||||
},
|
||||
{
|
||||
"label": "Tragedy",
|
||||
"value": "32"
|
||||
},
|
||||
{
|
||||
"label": "Urban",
|
||||
"value": "63"
|
||||
},
|
||||
{
|
||||
"label": "Urban life",
|
||||
"value": "59"
|
||||
},
|
||||
{
|
||||
"label": "Video games",
|
||||
"value": "60"
|
||||
},
|
||||
{
|
||||
"label": "War",
|
||||
"value": "61"
|
||||
},
|
||||
{
|
||||
"label": "Wuxia",
|
||||
"value": "31"
|
||||
},
|
||||
{
|
||||
"label": "Xianxia",
|
||||
"value": "23"
|
||||
},
|
||||
{
|
||||
"label": "Xuanhuan",
|
||||
"value": "22"
|
||||
},
|
||||
{
|
||||
"label": "Yaoi",
|
||||
"value": "14"
|
||||
},
|
||||
{
|
||||
"label": "Yuri",
|
||||
"value": "62"
|
||||
}
|
||||
],
|
||||
"type": "Checkbox"
|
||||
},
|
||||
"chapters": {
|
||||
"label": "Chapters",
|
||||
"value": "0",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"label": "<50",
|
||||
"value": "1,49"
|
||||
},
|
||||
{
|
||||
"label": "50-100",
|
||||
"value": "50,100"
|
||||
},
|
||||
{
|
||||
"label": "100-200",
|
||||
"value": "100,200"
|
||||
},
|
||||
{
|
||||
"label": "200-500",
|
||||
"value": "200,500"
|
||||
},
|
||||
{
|
||||
"label": "500-1000",
|
||||
"value": "500,1000"
|
||||
},
|
||||
{
|
||||
"label": ">1000",
|
||||
"value": "1001,1000000"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"rating_operator": {
|
||||
"label": "Rating (Min/Max)",
|
||||
"value": "min",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "min",
|
||||
"value": "min"
|
||||
},
|
||||
{
|
||||
"label": "max",
|
||||
"value": "max"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"rating": {
|
||||
"label": "Rating",
|
||||
"value": "0",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "none",
|
||||
"value": "0"
|
||||
},
|
||||
{
|
||||
"label": "1",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "2",
|
||||
"value": "2"
|
||||
},
|
||||
{
|
||||
"label": "3",
|
||||
"value": "3"
|
||||
},
|
||||
{
|
||||
"label": "4",
|
||||
"value": "4"
|
||||
},
|
||||
{
|
||||
"label": "5",
|
||||
"value": "5"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"status": {
|
||||
"label": "Translation Status",
|
||||
"value": "-1",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "All",
|
||||
"value": "-1"
|
||||
},
|
||||
{
|
||||
"label": "Completed",
|
||||
"value": "1"
|
||||
},
|
||||
{
|
||||
"label": "Ongoing",
|
||||
"value": "0"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"sort": {
|
||||
"label": "Sort Results By",
|
||||
"value": "date",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "Last Updated (Newest)",
|
||||
"value": "date"
|
||||
},
|
||||
{
|
||||
"label": "Rank (Top)",
|
||||
"value": "rank-top"
|
||||
},
|
||||
{
|
||||
"label": "Rating Score (Top)",
|
||||
"value": "rating-score-top"
|
||||
},
|
||||
{
|
||||
"label": "Review Count (Most)",
|
||||
"value": "review"
|
||||
},
|
||||
{
|
||||
"label": "Comment Count (Most)",
|
||||
"value": "comment"
|
||||
},
|
||||
{
|
||||
"label": "Bookmark Count (Most)",
|
||||
"value": "bookmark"
|
||||
},
|
||||
{
|
||||
"label": "Today Views (Most)",
|
||||
"value": "today-view"
|
||||
},
|
||||
{
|
||||
"label": "Monthly Views (Most)",
|
||||
"value": "monthly-view"
|
||||
},
|
||||
{
|
||||
"label": "Total Views (Most)",
|
||||
"value": "total-view"
|
||||
},
|
||||
{
|
||||
"label": "Chapter Count (Most)",
|
||||
"value": "chapter-count-most"
|
||||
},
|
||||
{
|
||||
"label": "Title (A>Z)",
|
||||
"value": "abc"
|
||||
},
|
||||
{
|
||||
"label": "Title (Z>A)",
|
||||
"value": "cba"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"tagcon": {
|
||||
"label": "Tags (And/Or)",
|
||||
"value": "and",
|
||||
|
||||
"options": [
|
||||
{
|
||||
"label": "AND",
|
||||
"value": "and"
|
||||
},
|
||||
{
|
||||
"label": "OR",
|
||||
"value": "or"
|
||||
}
|
||||
],
|
||||
"type": "Picker"
|
||||
},
|
||||
"author": {
|
||||
"label": "Author",
|
||||
"value": "",
|
||||
"type": "Text"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import list from './sources.json' with { type: 'json' };
|
||||
import { existsSync, readFileSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
|
||||
const folder = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
export const generateAll = function () {
|
||||
return list.map(source => {
|
||||
const exist = existsSync(join(folder, 'filters', source.id + '.json'));
|
||||
if (exist) {
|
||||
const filters = readFileSync(
|
||||
join(folder, 'filters', source.id + '.json'),
|
||||
'utf-8',
|
||||
);
|
||||
// strip `// prettier-ignore` comments (JSONC) since JSON.parse doesn't allow them
|
||||
source.filters = JSON.parse(
|
||||
filters.replace(/^\s*\/\/[^\n]*$/gm, ''),
|
||||
).filters;
|
||||
}
|
||||
console.log(
|
||||
`[novelfire] Generating: ${source.id}${' '.repeat(20 - source.id.length)} ${source.filters ? '🔎with filters🔍' : '🚫no filters🚫'}`,
|
||||
);
|
||||
return generator(source);
|
||||
});
|
||||
};
|
||||
|
||||
const generator = function generator(source) {
|
||||
const NovelFireTemplate = readFileSync(join(folder, 'template.ts'), {
|
||||
encoding: 'utf-8',
|
||||
});
|
||||
|
||||
const pluginScript = `
|
||||
${NovelFireTemplate}
|
||||
const plugin = new NovelFirePlugin(${JSON.stringify(source)});
|
||||
export default plugin;
|
||||
`.trim();
|
||||
|
||||
return {
|
||||
lang: source.options?.lang || 'English',
|
||||
filename: source.sourceName.replace(/[^\w]/g, ''),
|
||||
pluginScript,
|
||||
};
|
||||
};
|
||||
@@ -0,0 +1,290 @@
|
||||
import * as fs from 'fs';
|
||||
import * as cheerio from 'cheerio';
|
||||
import * as path from 'path';
|
||||
import * as readline from 'readline';
|
||||
import process from 'process';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
function getFilters(name, html) {
|
||||
const $ = cheerio.load(html);
|
||||
|
||||
const filters = {
|
||||
filters: {
|
||||
language: {
|
||||
label: 'Language',
|
||||
value: [],
|
||||
options: [],
|
||||
type: 'Checkbox',
|
||||
},
|
||||
genre_operator: {
|
||||
label: 'Genres (And/Or/Exclude)',
|
||||
value: 'and',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
genres: {
|
||||
label: 'Genres',
|
||||
value: [],
|
||||
options: [],
|
||||
type: 'Checkbox',
|
||||
},
|
||||
chapters: {
|
||||
label: 'Chapters',
|
||||
value: '0',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
rating_operator: {
|
||||
label: 'Rating (Min/Max)',
|
||||
value: 'min',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
rating: {
|
||||
label: 'Rating',
|
||||
value: '0',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
status: {
|
||||
label: 'Translation Status',
|
||||
value: '-1',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
sort: {
|
||||
label: 'Sort Results By',
|
||||
value: 'rank-top',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
tagcon: {
|
||||
label: 'Tags (And/Or)',
|
||||
value: 'and',
|
||||
options: [],
|
||||
type: 'Picker',
|
||||
},
|
||||
author: {
|
||||
label: 'Author',
|
||||
value: '',
|
||||
type: 'Text',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// ==================== Language ====================
|
||||
filters.filters.language.label =
|
||||
$('.search-adv-form > label').text().trim() || 'Language';
|
||||
$('input[name="country_id[]"]').each((i, el) => {
|
||||
filters.filters.language.options.push({
|
||||
label: $(el).parent().text().trim() || $(el).next('label').text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== Genre operator ====================
|
||||
$('select[name="ctgcon"] option').each((i, el) => {
|
||||
filters.filters.genre_operator.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const genreOpSelected =
|
||||
$('select[name="ctgcon"] option:selected').attr('value') || 'and';
|
||||
filters.filters.genre_operator.value = genreOpSelected;
|
||||
|
||||
// ==================== Genres ====================
|
||||
$('input[name="categories[]"]').each((i, el) => {
|
||||
filters.filters.genres.options.push({
|
||||
label: $(el).parent().text().trim() || $(el).next('label').text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
|
||||
// ==================== Chapters ====================
|
||||
$('select[name="totalchapter"] option').each((i, el) => {
|
||||
filters.filters.chapters.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const chaptersSelected =
|
||||
$('select[name="totalchapter"] option:selected').attr('value') || '0';
|
||||
filters.filters.chapters.value = chaptersSelected;
|
||||
|
||||
// ==================== Rating operator ====================
|
||||
$('select[name="ratcon"] option').each((i, el) => {
|
||||
filters.filters.rating_operator.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const ratconSelected =
|
||||
$('select[name="ratcon"] option:selected').attr('value') || 'min';
|
||||
filters.filters.rating_operator.value = ratconSelected;
|
||||
|
||||
// ==================== Rating ====================
|
||||
$('select[name="rating"] option').each((i, el) => {
|
||||
filters.filters.rating.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const ratingSelected =
|
||||
$('select[name="rating"] option:selected').attr('value') || '0';
|
||||
filters.filters.rating.value = ratingSelected;
|
||||
|
||||
// ==================== Status ====================
|
||||
$('select[name="status"] option').each((i, el) => {
|
||||
filters.filters.status.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const statusSelected =
|
||||
$('select[name="status"] option:selected').attr('value') || '-1';
|
||||
filters.filters.status.value = statusSelected;
|
||||
|
||||
// ==================== Sort ====================
|
||||
$('select[name="sort"] option').each((i, el) => {
|
||||
filters.filters.sort.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const sortSelected =
|
||||
$('select[name="sort"] option:selected').attr('value') || 'rank-top';
|
||||
filters.filters.sort.value = sortSelected;
|
||||
|
||||
// ==================== Tag operator ====================
|
||||
$('select[name="tagcon"] option').each((i, el) => {
|
||||
filters.filters.tagcon.options.push({
|
||||
label: $(el).text().trim(),
|
||||
value: $(el).attr('value'),
|
||||
});
|
||||
});
|
||||
const tagconSelected =
|
||||
$('select[name="tagcon"] option:selected').attr('value') || 'and';
|
||||
filters.filters.tagcon.value = tagconSelected;
|
||||
|
||||
// ==================== Author ====================
|
||||
filters.filters.author.label =
|
||||
$('input[name="author"]').prev('label').text().trim() ||
|
||||
$('input[name="author"]').parent().prev().text().trim() ||
|
||||
'Author';
|
||||
|
||||
// ==================== checks ====================
|
||||
if (
|
||||
filters.filters.genres.options.length == 0 ||
|
||||
filters.filters.language.options.length == 0 ||
|
||||
filters.filters.sort.options.length == 0
|
||||
) {
|
||||
console.error(
|
||||
`🚨Error in filters for ${name} please fix manually (${path.join(__dirname, 'filters', name + '.json')})🚨`,
|
||||
);
|
||||
}
|
||||
|
||||
// add `// prettier-ignore` above each `options` array so prettier does not
|
||||
// reformat them (the generator strips these comments before parsing)
|
||||
const json = JSON.stringify(filters, null, 2).replace(
|
||||
/\n(\s*)"options": \[/g,
|
||||
'\n$1// prettier-ignore\n$1"options": [',
|
||||
);
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, 'filters', name + '.json'),
|
||||
json + '\n',
|
||||
);
|
||||
console.log(`✅Filters created successfully for ${name}✅`);
|
||||
}
|
||||
|
||||
async function getFiltersFromURL(name, url) {
|
||||
const response = await fetch(url + '/search-adv');
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`HTTP error! status: ${response.status}, while fetching ${response.url}`,
|
||||
);
|
||||
}
|
||||
const html = await response.text();
|
||||
try {
|
||||
getFilters(name, html);
|
||||
} catch (e) {
|
||||
console.error('Error while getting filters from', url);
|
||||
console.error('(' + e + ')');
|
||||
}
|
||||
}
|
||||
|
||||
async function askGetFilter() {
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const EREASE_PREV_LINE = '\x1b[1A\r\x1b[2K';
|
||||
await rl.question(
|
||||
'Enter the id of the site (same one as in sources.json): ',
|
||||
async name => {
|
||||
await rl.question(
|
||||
EREASE_PREV_LINE +
|
||||
"Do you want to get the filters from a URL or the html text? (if url dosen't work try html) (url/html): ",
|
||||
async method => {
|
||||
if (method.toLowerCase() === 'url') {
|
||||
const sources = JSON.parse(
|
||||
fs.readFileSync(path.join(__dirname, 'sources.json'), 'utf-8'),
|
||||
);
|
||||
const source = sources.find(s => s.id === name);
|
||||
if (source && source.sourceSite) {
|
||||
console.log('Getting filters from', source.sourceSite);
|
||||
try {
|
||||
await getFiltersFromURL(name, source.sourceSite);
|
||||
} catch (e) {
|
||||
console.error(
|
||||
'Error while getting filters from',
|
||||
source.sourceSite,
|
||||
);
|
||||
console.log(e.message || e);
|
||||
}
|
||||
rl.close();
|
||||
} else {
|
||||
await rl.question(
|
||||
EREASE_PREV_LINE +
|
||||
'Enter the URL (same one as in sources.json): ',
|
||||
async url => {
|
||||
rl.close();
|
||||
try {
|
||||
await getFiltersFromURL(name, url);
|
||||
} catch (e) {
|
||||
console.error('Error while getting filters from', url);
|
||||
console.log(e.message || e);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
} else {
|
||||
process.stdout.write(
|
||||
EREASE_PREV_LINE +
|
||||
`Enter the html text from the page at {sourceSite}/search-adv (at the end press ENTER then press CTRL+C)
|
||||
(to make it faster you can run \`$(".search-adv-form").parent().html()\` in the console to get only the important html part): `,
|
||||
);
|
||||
let html = '';
|
||||
rl.on('SIGINT', () => {
|
||||
console.log('Stopeed reading input, creating filters file');
|
||||
getFilters(name, html);
|
||||
rl.close();
|
||||
});
|
||||
rl.on('line', line => {
|
||||
html += line + '\n';
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
askGetFilter();
|
||||
|
||||
export { getFiltersFromURL };
|
||||
@@ -0,0 +1,20 @@
|
||||
[
|
||||
{
|
||||
"id": "novelfire",
|
||||
"sourceSite": "https://novelfire.net/",
|
||||
"sourceName": "Novel Fire",
|
||||
"options": {
|
||||
"lang": "English",
|
||||
"minorVer": 4,
|
||||
"versionIncrement": 3
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "novelphoenix",
|
||||
"sourceSite": "https://novelphoenix.com/",
|
||||
"sourceName": "Novel Phoenix",
|
||||
"options": {
|
||||
"lang": "English"
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -2,16 +2,33 @@ import { CheerioAPI, load } from 'cheerio';
|
||||
import { fetchApi } from '@libs/fetch';
|
||||
import { Plugin } from '@/types/plugin';
|
||||
import { NovelStatus } from '@libs/novelStatus';
|
||||
import { Filters, FilterTypes } from '@libs/filterInputs';
|
||||
import { Filters } from '@libs/filterInputs';
|
||||
import { defaultCover } from '@/types/constants';
|
||||
import { storage } from '@libs/storage';
|
||||
|
||||
class NovelFire implements Plugin.PluginBase {
|
||||
id = 'novelfire';
|
||||
name = 'Novel Fire';
|
||||
version = '1.4.3';
|
||||
icon = 'src/en/novelfire/icon.png';
|
||||
site = 'https://novelfire.net/';
|
||||
type NovelFireOptions = {
|
||||
lang?: string;
|
||||
majorVer?: number;
|
||||
minorVer?: number;
|
||||
versionIncrement?: number;
|
||||
};
|
||||
|
||||
export type NovelFireMetadata = {
|
||||
id: string;
|
||||
sourceSite: string;
|
||||
sourceName: string;
|
||||
options?: NovelFireOptions;
|
||||
filters?: Filters;
|
||||
};
|
||||
|
||||
export class NovelFirePlugin implements Plugin.PagePlugin {
|
||||
id: string;
|
||||
name: string;
|
||||
icon: string;
|
||||
site: string;
|
||||
version: string;
|
||||
options?: NovelFireOptions;
|
||||
filters?: Filters;
|
||||
webStorageUtilized = true;
|
||||
novelList = new Set<string>();
|
||||
draw = 0;
|
||||
@@ -32,6 +49,19 @@ class NovelFire implements Plugin.PluginBase {
|
||||
singlePage = storage.get('singlePage');
|
||||
pageLength = storage.get('pageLength');
|
||||
|
||||
constructor(metadata: NovelFireMetadata) {
|
||||
this.id = metadata.id;
|
||||
this.name = metadata.sourceName;
|
||||
this.icon = `multisrc/novelfire/${metadata.id.toLowerCase()}/icon.png`;
|
||||
this.site = metadata.sourceSite;
|
||||
const majorVer = metadata.options?.majorVer || 1;
|
||||
const minorVer = metadata.options?.minorVer || 0;
|
||||
const versionIncrement = metadata.options?.versionIncrement || 0;
|
||||
this.version = `${majorVer}.${minorVer}.${versionIncrement}`;
|
||||
this.options = metadata.options;
|
||||
this.filters = metadata.filters satisfies Filters;
|
||||
}
|
||||
|
||||
async getCheerio(url: string, search: boolean): Promise<CheerioAPI> {
|
||||
const r = await fetchApi(url);
|
||||
if (!r.ok && search != true)
|
||||
@@ -105,19 +135,31 @@ class NovelFire implements Plugin.PluginBase {
|
||||
const url = this.site + 'search-adv';
|
||||
const params = new URLSearchParams();
|
||||
|
||||
for (const language of filters.language.value) {
|
||||
for (const language of filters?.language?.value || []) {
|
||||
params.append('country_id[]', language);
|
||||
}
|
||||
params.append('ctgcon', filters.genre_operator.value);
|
||||
for (const genre of filters.genres.value) {
|
||||
params.append('ctgcon', filters?.genre_operator?.value || 'and');
|
||||
for (const genre of filters?.genres?.value || []) {
|
||||
params.append('categories[]', genre);
|
||||
}
|
||||
params.append('totalchapter', filters.chapters.value);
|
||||
params.append('ratcon', filters.rating_operator.value);
|
||||
params.append('rating', filters.rating.value);
|
||||
params.append('status', filters.status.value);
|
||||
params.append('sort', showLatestNovels ? 'date' : filters.sort.value);
|
||||
params.append('tagcon', 'and');
|
||||
params.append('totalchapter', filters?.chapters?.value || '0');
|
||||
params.append('ratcon', filters?.rating_operator?.value || 'min');
|
||||
params.append('rating', filters?.rating?.value || '0');
|
||||
params.append('status', filters?.status?.value || '-1');
|
||||
params.append(
|
||||
'sort',
|
||||
showLatestNovels ? 'date' : filters?.sort?.value || 'rank-top',
|
||||
);
|
||||
params.append('tagcon', filters?.tagcon?.value || 'and');
|
||||
for (const tag of filters?.tags?.value || []) {
|
||||
params.append('tags[]', tag);
|
||||
}
|
||||
for (const tag of filters?.tags_excluded?.value || []) {
|
||||
params.append('tags_excluded[]', tag);
|
||||
}
|
||||
if (filters?.author?.value) {
|
||||
params.append('author', filters.author.value);
|
||||
}
|
||||
params.append('page', pageNo.toString());
|
||||
|
||||
const loadedCheerio = await this.getCheerio(
|
||||
@@ -443,162 +485,8 @@ class NovelFire implements Plugin.PluginBase {
|
||||
page === 1,
|
||||
);
|
||||
}
|
||||
|
||||
filters = {
|
||||
sort: {
|
||||
label: 'Sort Results By',
|
||||
value: 'rank-top',
|
||||
options: [
|
||||
{ label: 'Rank (Top)', value: 'rank-top' },
|
||||
{ label: 'Rating Score (Top)', value: 'rating-score-top' },
|
||||
{ label: 'Review Count (Most)', value: 'review' },
|
||||
{ label: 'Comment Count (Most)', value: 'comment' },
|
||||
{ label: 'Bookmark Count (Most)', value: 'bookmark' },
|
||||
{ label: 'Today Views (Most)', value: 'today-view' },
|
||||
{ label: 'Monthly Views (Most)', value: 'monthly-view' },
|
||||
{ label: 'Total Views (Most)', value: 'total-view' },
|
||||
{ label: 'Title (A>Z)', value: 'abc' },
|
||||
{ label: 'Title (Z>A)', value: 'cba' },
|
||||
{ label: 'Last Updated (Newest)', value: 'date' },
|
||||
{ label: 'Chapter Count (Most)', value: 'chapter-count-most' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
status: {
|
||||
label: 'Translation Status',
|
||||
value: '-1',
|
||||
options: [
|
||||
{ label: 'All', value: '-1' },
|
||||
{ label: 'Completed', value: '1' },
|
||||
{ label: 'Ongoing', value: '0' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genre_operator: {
|
||||
label: 'Genres (And/Or/Exclude)',
|
||||
value: 'and',
|
||||
options: [
|
||||
{ label: 'AND', value: 'and' },
|
||||
{ label: 'OR', value: 'or' },
|
||||
{ label: 'EXCLUDE', value: 'exclude' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
genres: {
|
||||
label: 'Genres',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: 'Action', value: '3' },
|
||||
{ label: 'Adult', value: '28' },
|
||||
{ label: 'Adventure', value: '4' },
|
||||
{ label: 'Anime', value: '46' },
|
||||
{ label: 'Arts', value: '47' },
|
||||
{ label: 'Comedy', value: '5' },
|
||||
{ label: 'Drama', value: '24' },
|
||||
{ label: 'Eastern', value: '44' },
|
||||
{ label: 'Ecchi', value: '26' },
|
||||
{ label: 'Fan-fiction', value: '48' },
|
||||
{ label: 'Fantasy', value: '6' },
|
||||
{ label: 'Game', value: '19' },
|
||||
{ label: 'Gender Bender', value: '25' },
|
||||
{ label: 'Harem', value: '7' },
|
||||
{ label: 'Historical', value: '12' },
|
||||
{ label: 'Horror', value: '37' },
|
||||
{ label: 'Isekai', value: '49' },
|
||||
{ label: 'Josei', value: '2' },
|
||||
{ label: 'Lgbt+', value: '45' },
|
||||
{ label: 'Magic', value: '50' },
|
||||
{ label: 'Magical Realism', value: '51' },
|
||||
{ label: 'Manhua', value: '52' },
|
||||
{ label: 'Martial Arts', value: '15' },
|
||||
{ label: 'Mature', value: '8' },
|
||||
{ label: 'Mecha', value: '34' },
|
||||
{ label: 'Military', value: '53' },
|
||||
{ label: 'Modern Life', value: '54' },
|
||||
{ label: 'Movies', value: '55' },
|
||||
{ label: 'Mystery', value: '16' },
|
||||
{ label: 'Other', value: '64' },
|
||||
{ label: 'Psychological', value: '9' },
|
||||
{ label: 'Realistic Fiction', value: '56' },
|
||||
{ label: 'Reincarnation', value: '43' },
|
||||
{ label: 'Romance', value: '1' },
|
||||
{ label: 'School Life', value: '21' },
|
||||
{ label: 'Sci-fi', value: '20' },
|
||||
{ label: 'Seinen', value: '10' },
|
||||
{ label: 'Shoujo', value: '38' },
|
||||
{ label: 'Shoujo Ai', value: '57' },
|
||||
{ label: 'Shounen', value: '17' },
|
||||
{ label: 'Shounen Ai', value: '39' },
|
||||
{ label: 'Slice of Life', value: '13' },
|
||||
{ label: 'Smut', value: '29' },
|
||||
{ label: 'Sports', value: '42' },
|
||||
{ label: 'Supernatural', value: '18' },
|
||||
{ label: 'System', value: '58' },
|
||||
{ label: 'Tragedy', value: '32' },
|
||||
{ label: 'Urban', value: '63' },
|
||||
{ label: 'Urban Life', value: '59' },
|
||||
{ label: 'Video Games', value: '60' },
|
||||
{ label: 'War', value: '61' },
|
||||
{ label: 'Wuxia', value: '31' },
|
||||
{ label: 'Xianxia', value: '23' },
|
||||
{ label: 'Xuanhuan', value: '22' },
|
||||
{ label: 'Yaoi', value: '14' },
|
||||
{ label: 'Yuri', value: '62' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
language: {
|
||||
label: 'Language',
|
||||
value: [],
|
||||
options: [
|
||||
{ label: 'Chinese', value: '1' },
|
||||
{ label: 'Korean', value: '2' },
|
||||
{ label: 'Japanese', value: '3' },
|
||||
{ label: 'English', value: '4' },
|
||||
],
|
||||
type: FilterTypes.CheckboxGroup,
|
||||
},
|
||||
rating_operator: {
|
||||
label: 'Rating (Min/Max)',
|
||||
value: 'min',
|
||||
options: [
|
||||
{ label: 'Min', value: 'min' },
|
||||
{ label: 'Max', value: 'max' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
rating: {
|
||||
label: 'Rating',
|
||||
value: '0',
|
||||
options: [
|
||||
{ label: 'All', value: '0' },
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
{ label: '5', value: '5' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
chapters: {
|
||||
label: 'Chapters',
|
||||
value: '0',
|
||||
options: [
|
||||
{ label: 'All', value: '0' },
|
||||
{ label: '<50', value: '1,49' },
|
||||
{ label: '50-100', value: '50,100' },
|
||||
{ label: '100-200', value: '100,200' },
|
||||
{ label: '200-500', value: '200,500' },
|
||||
{ label: '500-1000', value: '500,1000' },
|
||||
{ label: '>1000', value: '1001,1000000' },
|
||||
],
|
||||
type: FilterTypes.Picker,
|
||||
},
|
||||
} satisfies Filters;
|
||||
}
|
||||
|
||||
export default new NovelFire();
|
||||
|
||||
// Custom error for when Novel Fire is rate limiting requests
|
||||
class NovelFireThrottlingError extends Error {
|
||||
constructor(message = 'Novel Fire is rate limiting requests') {
|
||||
|
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
Reference in New Issue
Block a user