Multiple MangaMutiny fixes/additions (#4008)
- fixed Genre parsing (website renamed "genres" to "tags" in their json responses) - corrected chapter release year parsing - increased manga list fetch size from 20 to 21 to make requests that are almost indistinguishable from requests the real website makes (only URI parameter order differs) - split Genre filter into Genre and Format filter to be more in line with what the website offers - updated Genre/Format filter content - implemented new way to process URI query parameters (first collect them in a mutable list, then add them to the URI) - added author/artist search (and prepared for scanlator search, although that feature doesn't work yet even on the website)
This commit is contained in:
parent
663583ccc9
commit
bcb9520edd
@ -5,7 +5,7 @@ ext {
|
|||||||
extName = 'Manga Mutiny'
|
extName = 'Manga Mutiny'
|
||||||
pkgNameSuffix = "en.mangamutiny"
|
pkgNameSuffix = "en.mangamutiny"
|
||||||
extClass = '.MangaMutiny'
|
extClass = '.MangaMutiny'
|
||||||
extVersionCode = 2
|
extVersionCode = 3
|
||||||
libVersion = '1.2'
|
libVersion = '1.2'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,6 +51,8 @@ class MangaMutiny : HttpSource() {
|
|||||||
private val apiMangaUrlPath = "v1/public/manga"
|
private val apiMangaUrlPath = "v1/public/manga"
|
||||||
private val apiChapterUrlPath = "v1/public/chapter"
|
private val apiChapterUrlPath = "v1/public/chapter"
|
||||||
|
|
||||||
|
private val fetchAmount = 21
|
||||||
|
|
||||||
// Popular manga
|
// Popular manga
|
||||||
override fun popularMangaRequest(page: Int): Request = mangaRequest(page)
|
override fun popularMangaRequest(page: Int): Request = mangaRequest(page)
|
||||||
|
|
||||||
@ -103,7 +105,7 @@ class MangaMutiny : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun parseDate(dateAsString: String): Long {
|
private fun parseDate(dateAsString: String): Long {
|
||||||
val format = SimpleDateFormat("YYYY-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
val format = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
|
||||||
format.timeZone = TimeZone.getTimeZone("UTC")
|
format.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
|
|
||||||
return format.parse(dateAsString)?.time ?: 0
|
return format.parse(dateAsString)?.time ?: 0
|
||||||
@ -147,7 +149,7 @@ class MangaMutiny : HttpSource() {
|
|||||||
artist = rootNode.get("artists").asString
|
artist = rootNode.get("artists").asString
|
||||||
author = rootNode.get("authors").asString
|
author = rootNode.get("authors").asString
|
||||||
|
|
||||||
genre = rootNode.get("genres").asJsonArray
|
genre = rootNode.get("tags").asJsonArray
|
||||||
.joinToString { singleGenre -> singleGenre.asString }
|
.joinToString { singleGenre -> singleGenre.asString }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -220,7 +222,7 @@ class MangaMutiny : HttpSource() {
|
|||||||
responseBody.close()
|
responseBody.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
return MangasPage(mangasPage, mangasPage.size == 20)
|
return MangasPage(mangasPage, mangasPage.size == fetchAmount)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun mangaRequest(page: Int, filters: FilterList? = null, query: String? = null): Request {
|
private fun mangaRequest(page: Int, filters: FilterList? = null, query: String? = null): Request {
|
||||||
@ -229,19 +231,26 @@ class MangaMutiny : HttpSource() {
|
|||||||
if (query?.isNotBlank() == true) {
|
if (query?.isNotBlank() == true) {
|
||||||
uri.appendQueryParameter("text", query)
|
uri.appendQueryParameter("text", query)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters != null) {
|
if (filters != null) {
|
||||||
|
val uriParameterMap = mutableMapOf<String, String>()
|
||||||
|
|
||||||
for (singleFilter in filters) {
|
for (singleFilter in filters) {
|
||||||
if (singleFilter is UriFilter) {
|
if (singleFilter is UriFilter) {
|
||||||
singleFilter.addToUri(uri)
|
singleFilter.potentiallyAddToUriParameterMap(uriParameterMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (uriParameter in uriParameterMap) {
|
||||||
|
uri.appendQueryParameter(uriParameter.key, uriParameter.value)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
uri.appendQueryParameter("sort", "-rating -ratingCount")
|
uri.appendQueryParameter("sort", "-rating -ratingCount")
|
||||||
}
|
}
|
||||||
uri.appendQueryParameter("limit", "20")
|
uri.appendQueryParameter("limit", fetchAmount.toString())
|
||||||
|
|
||||||
if (page != 1) {
|
if (page != 1) {
|
||||||
uri.appendQueryParameter("skip", (page * 20).toString())
|
uri.appendQueryParameter("skip", (page * fetchAmount).toString())
|
||||||
}
|
}
|
||||||
return GET(uri.build().toString(), headers)
|
return GET(uri.build().toString(), headers)
|
||||||
}
|
}
|
||||||
@ -251,8 +260,11 @@ class MangaMutiny : HttpSource() {
|
|||||||
return FilterList(
|
return FilterList(
|
||||||
StatusFilter(),
|
StatusFilter(),
|
||||||
CategoryFilter(),
|
CategoryFilter(),
|
||||||
GenreGroup(),
|
GenresFilter(),
|
||||||
SortFilter()
|
FormatsFilter(),
|
||||||
|
SortFilter(),
|
||||||
|
AuthorFilter()
|
||||||
|
// ScanlatorFilter()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -261,7 +273,18 @@ class MangaMutiny : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private interface UriFilter {
|
private interface UriFilter {
|
||||||
fun addToUri(uri: Uri.Builder)
|
fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>)
|
||||||
|
fun appendValueToKeyInUriParameterMap(parameterMap: MutableMap<String, String>, parameterName: String, additionalValue: String) {
|
||||||
|
if (additionalValue.isNotEmpty()) {
|
||||||
|
val newParameterValueBuilder = StringBuilder()
|
||||||
|
if (parameterMap[parameterName] != null) {
|
||||||
|
newParameterValueBuilder.append(parameterMap[parameterName] + " ")
|
||||||
|
}
|
||||||
|
newParameterValueBuilder.append(additionalValue)
|
||||||
|
|
||||||
|
parameterMap[parameterName] = newParameterValueBuilder.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private open class UriSelectFilter(
|
private open class UriSelectFilter(
|
||||||
displayName: String,
|
displayName: String,
|
||||||
@ -271,9 +294,12 @@ class MangaMutiny : HttpSource() {
|
|||||||
defaultValue: Int = 0
|
defaultValue: Int = 0
|
||||||
) :
|
) :
|
||||||
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
|
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
|
||||||
override fun addToUri(uri: Uri.Builder) {
|
|
||||||
if (state != 0 || !firstIsUnspecified)
|
// If not otherwise specified, any new parameter will overwrite any existing parameter in the parameter map
|
||||||
uri.appendQueryParameter(uriParam, vals[state].first)
|
override fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>) {
|
||||||
|
if (state != 0 || !firstIsUnspecified) {
|
||||||
|
parameterMap[uriParam] = vals[state].first
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,104 +309,109 @@ class MangaMutiny : HttpSource() {
|
|||||||
Pair("ongoing", "Ongoing")
|
Pair("ongoing", "Ongoing")
|
||||||
))
|
))
|
||||||
|
|
||||||
private class CategoryFilter : UriSelectFilter("Category", "category", arrayOf(
|
private class CategoryFilter : UriSelectFilter("Category", "tags", arrayOf(
|
||||||
Pair("", "All"),
|
Pair("", "All"),
|
||||||
Pair("josei", "Josei"),
|
Pair("josei", "Josei"),
|
||||||
Pair("seinen", "Seinen"),
|
Pair("seinen", "Seinen"),
|
||||||
Pair("shoujo", "Shoujo"),
|
Pair("shoujo", "Shoujo"),
|
||||||
Pair("shounen", "Shounen")
|
Pair("shounen", "Shounen")
|
||||||
))
|
)) {
|
||||||
|
override fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>) =
|
||||||
|
appendValueToKeyInUriParameterMap(parameterMap, uriParam, vals[state].first)
|
||||||
|
}
|
||||||
|
|
||||||
|
// A single filter: either a genre or a format filter
|
||||||
private class GenreFilter(val uriParam: String, displayName: String) : Filter.CheckBox(displayName)
|
private class GenreFilter(val uriParam: String, displayName: String) : Filter.CheckBox(displayName)
|
||||||
|
|
||||||
private class GenreGroup : Filter.Group<GenreFilter>("Genres", listOf(
|
// A collection of genre or format filters
|
||||||
GenreFilter("4-koma", "4-koma"),
|
private abstract class GenreFilterList(name: String, elementList: List<GenreFilter>) : Filter.Group<GenreFilter>(name, elementList), UriFilter {
|
||||||
GenreFilter("action", "Action"),
|
override fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>) {
|
||||||
GenreFilter("adaptation", "Adaptation"),
|
val genresParameterValue = state.filter { it.state }.joinToString(" ") { it.uriParam }
|
||||||
GenreFilter("adult", "Adult"),
|
|
||||||
GenreFilter("adventure", "Adventure"),
|
|
||||||
GenreFilter("aliens", "Aliens"),
|
|
||||||
GenreFilter("animals", "Animals"),
|
|
||||||
GenreFilter("anthology", "Anthology"),
|
|
||||||
GenreFilter("award_winning", "Award winning"),
|
|
||||||
GenreFilter("comedy", "Comedy"),
|
|
||||||
GenreFilter("cooking", "Cooking"),
|
|
||||||
GenreFilter("crossdressing", "Crossdressing"),
|
|
||||||
GenreFilter("delinquents", "Delinquents"),
|
|
||||||
GenreFilter("demons", "Demons"),
|
|
||||||
GenreFilter("doujinshi", "Doujinshi"),
|
|
||||||
GenreFilter("drama", "Drama"),
|
|
||||||
GenreFilter("ecchi", "Ecchi"),
|
|
||||||
GenreFilter("fan_colored", "Fan colored"),
|
|
||||||
GenreFilter("fantasy", "Fantasy"),
|
|
||||||
GenreFilter("full_color", "Full color"),
|
|
||||||
GenreFilter("gender_bender", "Gender bender"),
|
|
||||||
GenreFilter("genderswap", "Genderswap"),
|
|
||||||
GenreFilter("ghosts", "Ghosts"),
|
|
||||||
GenreFilter("gore", "Gore"),
|
|
||||||
GenreFilter("gyaru", "Gyaru"),
|
|
||||||
GenreFilter("harem", "Harem"),
|
|
||||||
GenreFilter("historical", "Historical"),
|
|
||||||
GenreFilter("horror", "Horror"),
|
|
||||||
GenreFilter("incest", "Incest"),
|
|
||||||
GenreFilter("isekai", "Isekai"),
|
|
||||||
GenreFilter("josei", "Josei"),
|
|
||||||
GenreFilter("loli", "Loli"),
|
|
||||||
GenreFilter("long_strip", "Long strip"),
|
|
||||||
GenreFilter("magic", "Magic"),
|
|
||||||
GenreFilter("magical_girls", "Magical girls"),
|
|
||||||
GenreFilter("manga", "Manga"),
|
|
||||||
GenreFilter("mangamutiny", "Mangamutiny"),
|
|
||||||
GenreFilter("manhua", "Manhua"),
|
|
||||||
GenreFilter("manhwa", "Manhwa"),
|
|
||||||
GenreFilter("martial_arts", "Martial arts"),
|
|
||||||
GenreFilter("mature", "Mature"),
|
|
||||||
GenreFilter("mecha", "Mecha"),
|
|
||||||
GenreFilter("medical", "Medical"),
|
|
||||||
GenreFilter("military", "Military"),
|
|
||||||
GenreFilter("monster_girls", "Monster girls"),
|
|
||||||
GenreFilter("monsters", "Monsters"),
|
|
||||||
GenreFilter("mystery", "Mystery"),
|
|
||||||
GenreFilter("ninja", "Ninja"),
|
|
||||||
GenreFilter("office_workers", "Office workers"),
|
|
||||||
GenreFilter("official_colored", "Official colored"),
|
|
||||||
GenreFilter("oneshot", "Oneshot"),
|
|
||||||
GenreFilter("philosophical", "Philosophical"),
|
|
||||||
GenreFilter("psychological", "Psychological"),
|
|
||||||
GenreFilter("reincarnation", "Reincarnation"),
|
|
||||||
GenreFilter("reverse_harem", "Reverse harem"),
|
|
||||||
GenreFilter("romance", "Romance"),
|
|
||||||
GenreFilter("school_life", "School life"),
|
|
||||||
GenreFilter("sci_fi", "Sci fi"),
|
|
||||||
GenreFilter("sci-fi", "Sci-fi"),
|
|
||||||
GenreFilter("seinen", "Seinen"),
|
|
||||||
GenreFilter("sexual_violence", "Sexual violence"),
|
|
||||||
GenreFilter("shota", "Shota"),
|
|
||||||
GenreFilter("shoujo", "Shoujo"),
|
|
||||||
GenreFilter("shounen", "Shounen"),
|
|
||||||
GenreFilter("shounen_ai", "Shounen ai"),
|
|
||||||
GenreFilter("slice_of_life", "Slice of life"),
|
|
||||||
GenreFilter("smut", "Smut"),
|
|
||||||
GenreFilter("sports", "Sports"),
|
|
||||||
GenreFilter("superhero", "Superhero"),
|
|
||||||
GenreFilter("supernatural", "Supernatural"),
|
|
||||||
GenreFilter("survival", "Survival"),
|
|
||||||
GenreFilter("time_travel", "Time travel"),
|
|
||||||
GenreFilter("tragedy", "Tragedy"),
|
|
||||||
GenreFilter("video_games", "Video games"),
|
|
||||||
GenreFilter("virtual_reality", "Virtual reality"),
|
|
||||||
GenreFilter("web_comic", "Web comic"),
|
|
||||||
GenreFilter("webtoons", "Webtoons"),
|
|
||||||
GenreFilter("wuxia", "Wuxia"),
|
|
||||||
GenreFilter("zombies", "Zombies")
|
|
||||||
)), UriFilter {
|
|
||||||
override fun addToUri(uri: Uri.Builder) {
|
|
||||||
val genresParameterValue = state.filter { it.state }.joinToString("+") { it.uriParam }
|
|
||||||
if (genresParameterValue.isNotEmpty()) {
|
if (genresParameterValue.isNotEmpty()) {
|
||||||
uri.appendQueryParameter("genres", genresParameterValue)
|
appendValueToKeyInUriParameterMap(parameterMap, "tags", genresParameterValue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Actual genere filter list
|
||||||
|
private class GenresFilter : GenreFilterList("Genres", listOf(
|
||||||
|
GenreFilter("action", "action"),
|
||||||
|
GenreFilter("adult", "adult"),
|
||||||
|
GenreFilter("adventure", "adventure"),
|
||||||
|
GenreFilter("aliens", "aliens"),
|
||||||
|
GenreFilter("animals", "animals"),
|
||||||
|
GenreFilter("comedy", "comedy"),
|
||||||
|
GenreFilter("cooking", "cooking"),
|
||||||
|
GenreFilter("crossdressing", "crossdressing"),
|
||||||
|
GenreFilter("delinquents", "delinquents"),
|
||||||
|
GenreFilter("demons", "demons"),
|
||||||
|
GenreFilter("drama", "drama"),
|
||||||
|
GenreFilter("ecchi", "ecchi"),
|
||||||
|
GenreFilter("fantasy", "fantasy"),
|
||||||
|
GenreFilter("gender_bender", "gender bender"),
|
||||||
|
GenreFilter("genderswap", "genderswap"),
|
||||||
|
GenreFilter("ghosts", "ghosts"),
|
||||||
|
GenreFilter("gore", "gore"),
|
||||||
|
GenreFilter("gyaru", "gyaru"),
|
||||||
|
GenreFilter("harem", "harem"),
|
||||||
|
GenreFilter("historical", "historical"),
|
||||||
|
GenreFilter("horror", "horror"),
|
||||||
|
GenreFilter("incest", "incest"),
|
||||||
|
GenreFilter("isekai", "isekai"),
|
||||||
|
GenreFilter("loli", "loli"),
|
||||||
|
GenreFilter("magic", "magic"),
|
||||||
|
GenreFilter("magical_girls", "magical girls"),
|
||||||
|
GenreFilter("mangamutiny", "mangamutiny"),
|
||||||
|
GenreFilter("martial_arts", "martial arts"),
|
||||||
|
GenreFilter("mature", "mature"),
|
||||||
|
GenreFilter("mecha", "mecha"),
|
||||||
|
GenreFilter("medical", "medical"),
|
||||||
|
GenreFilter("military", "military"),
|
||||||
|
GenreFilter("monster_girls", "monster girls"),
|
||||||
|
GenreFilter("monsters", "monsters"),
|
||||||
|
GenreFilter("mystery", "mystery"),
|
||||||
|
GenreFilter("ninja", "ninja"),
|
||||||
|
GenreFilter("office_workers", "office workers"),
|
||||||
|
GenreFilter("philosophical", "philosophical"),
|
||||||
|
GenreFilter("psychological", "psychological"),
|
||||||
|
GenreFilter("reincarnation", "reincarnation"),
|
||||||
|
GenreFilter("reverse_harem", "reverse harem"),
|
||||||
|
GenreFilter("romance", "romance"),
|
||||||
|
GenreFilter("school_life", "school life"),
|
||||||
|
GenreFilter("sci_fi", "sci fi"),
|
||||||
|
GenreFilter("sci-fi", "sci-fi"),
|
||||||
|
GenreFilter("sexual_violence", "sexual violence"),
|
||||||
|
GenreFilter("shota", "shota"),
|
||||||
|
GenreFilter("shoujo_ai", "shoujo ai"),
|
||||||
|
GenreFilter("shounen_ai", "shounen ai"),
|
||||||
|
GenreFilter("slice_of_life", "slice of life"),
|
||||||
|
GenreFilter("smut", "smut"),
|
||||||
|
GenreFilter("sports", "sports"),
|
||||||
|
GenreFilter("superhero", "superhero"),
|
||||||
|
GenreFilter("supernatural", "supernatural"),
|
||||||
|
GenreFilter("survival", "survival"),
|
||||||
|
GenreFilter("time_travel", "time travel"),
|
||||||
|
GenreFilter("tragedy", "tragedy"),
|
||||||
|
GenreFilter("video_games", "video games"),
|
||||||
|
GenreFilter("virtual_reality", "virtual reality"),
|
||||||
|
GenreFilter("webtoons", "webtoons"),
|
||||||
|
GenreFilter("wuxia", "wuxia"),
|
||||||
|
GenreFilter("zombies", "zombies")
|
||||||
|
))
|
||||||
|
|
||||||
|
// Actual format filter List
|
||||||
|
private class FormatsFilter : GenreFilterList("Formats", listOf(
|
||||||
|
GenreFilter("4-koma", "4-koma"),
|
||||||
|
GenreFilter("adaptation", "adaptation"),
|
||||||
|
GenreFilter("anthology", "anthology"),
|
||||||
|
GenreFilter("award_winning", "award winning"),
|
||||||
|
GenreFilter("doujinshi", "doujinshi"),
|
||||||
|
GenreFilter("fan_colored", "fan colored"),
|
||||||
|
GenreFilter("full_color", "full color"),
|
||||||
|
GenreFilter("long_strip", "long strip"),
|
||||||
|
GenreFilter("official_colored", "official colored"),
|
||||||
|
GenreFilter("oneshot", "oneshot"),
|
||||||
|
GenreFilter("web_comic", "web comic")
|
||||||
|
))
|
||||||
|
|
||||||
private class SortFilter : UriSelectFilter("Sort", "sort", arrayOf(
|
private class SortFilter : UriSelectFilter("Sort", "sort", arrayOf(
|
||||||
Pair("-rating -ratingCount", "Popular"),
|
Pair("-rating -ratingCount", "Popular"),
|
||||||
@ -388,4 +419,25 @@ class MangaMutiny : HttpSource() {
|
|||||||
Pair("-createdAt", "Newest"),
|
Pair("-createdAt", "Newest"),
|
||||||
Pair("title", "Name")
|
Pair("title", "Name")
|
||||||
), firstIsUnspecified = false, defaultValue = 0)
|
), firstIsUnspecified = false, defaultValue = 0)
|
||||||
|
|
||||||
|
private class AuthorFilter : Filter.Text("Manga Author & Artist"), UriFilter {
|
||||||
|
override fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>) {
|
||||||
|
if (state.isNotEmpty()) {
|
||||||
|
parameterMap["creator"] = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**The scanlator filter exists on the mangamutiny website website, however it doesn't work.
|
||||||
|
This should stay disabled in the extension until it's properly implemented on the website,
|
||||||
|
otherwise users may be confused by searches that return no results.**/
|
||||||
|
/*
|
||||||
|
private class ScanlatorFilter : Filter.Text("Scanlator Name"), UriFilter {
|
||||||
|
override fun potentiallyAddToUriParameterMap(parameterMap: MutableMap<String, String>) {
|
||||||
|
if (state.isNotEmpty()) {
|
||||||
|
parameterMap["scanlator"] = state
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user