[RU]Newbie search fix (#11942)

* begin fix search

* filters

* filters+

* require_chapters

* search as match

* 18+

* structure Dto
This commit is contained in:
Ejan 2022-05-24 05:10:14 +05:00 committed by GitHub
parent 2236605211
commit b227fd2e39
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 328 additions and 118 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'NewManga(Newbie)' extName = 'NewManga(Newbie)'
pkgNameSuffix = 'ru.newbie' pkgNameSuffix = 'ru.newbie'
extClass = '.Newbie' extClass = '.Newbie'
extVersionCode = 8 extVersionCode = 9
} }
dependencies { dependencies {

View File

@ -6,11 +6,15 @@ import LibraryDto
import MangaDetDto import MangaDetDto
import PageDto import PageDto
import PageWrapperDto import PageWrapperDto
import SearchLibraryDto
import SearchWrapperDto
import SeriesWrapperDto import SeriesWrapperDto
import SubSearchDto
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.annotation.TargetApi import android.annotation.TargetApi
import android.os.Build import android.os.Build
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -22,11 +26,12 @@ import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody import okhttp3.ResponseBody.Companion.toResponseBody
import org.jsoup.Jsoup import org.jsoup.Jsoup
@ -73,13 +78,7 @@ class Newbie : HttpSource() {
override fun popularMangaRequest(page: Int) = GET("$API_URL/projects/popular?scale=month&size=$count&page=$page", headers) override fun popularMangaRequest(page: Int) = GET("$API_URL/projects/popular?scale=month&size=$count&page=$page", headers)
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response) override fun popularMangaParse(response: Response): MangasPage {
override fun latestUpdatesRequest(page: Int): Request = GET("$API_URL/projects/updates?only_bookmarks=false&size=$count&page=$page", headers)
override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
override fun searchMangaParse(response: Response): MangasPage {
val page = json.decodeFromString<PageWrapperDto<LibraryDto>>(response.body!!.string()) val page = json.decodeFromString<PageWrapperDto<LibraryDto>>(response.body!!.string())
val mangas = page.items.map { val mangas = page.items.map {
it.toSManga() it.toSManga()
@ -95,8 +94,30 @@ class Newbie : HttpSource() {
url = "$id" url = "$id"
thumbnail_url = if (image.srcset.large.isNotEmpty()) { thumbnail_url = if (image.srcset.large.isNotEmpty()) {
"$IMAGE_URL/${image.srcset.large}" "$IMAGE_URL/${image.srcset.large}"
} else "" + } else "$IMAGE_URL/${image.srcset.small}"
"$IMAGE_URL/${image.srcset.small}" }
}
override fun latestUpdatesRequest(page: Int): Request = GET("$API_URL/projects/updates?only_bookmarks=false&size=$count&page=$page", headers)
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaParse(response: Response): MangasPage {
val page = json.decodeFromString<SearchWrapperDto<SubSearchDto<SearchLibraryDto>>>(response.body!!.string())
val mangas = page.result.hits.map {
it.toSearchManga()
}
return MangasPage(mangas, mangas.isNotEmpty())
}
private fun SearchLibraryDto.toSearchManga(): SManga {
return SManga.create().apply {
// Do not change the title name to ensure work with a multilingual catalog!
title = document.title_en
url = document.id
thumbnail_url = if (document.image_large.isNotEmpty()) {
"$IMAGE_URL/${document.image_large}"
} else "$IMAGE_URL/${document.image_small}"
} }
} }
@ -112,35 +133,62 @@ class Newbie : HttpSource() {
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
var url = "$API_URL/projects/catalog?size=$count&page=$page".toHttpUrlOrNull()!!.newBuilder() val mutableGenre = mutableListOf<String>()
if (query.isNotEmpty()) { val mutableExGenre = mutableListOf<String>()
url = "$API_URL/projects/search?size=$count&page=$page".toHttpUrlOrNull()!!.newBuilder() val mutableTag = mutableListOf<String>()
url.addQueryParameter("query", query) val mutableExTag = mutableListOf<String>()
} val mutableType = mutableListOf<String>()
val mutableStatus = mutableListOf<String>()
val mutableAge = mutableListOf<String>()
var orderBy = "MATCH"
var ascEnd = "DESC"
var requireChapters = true
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is OrderBy -> { is OrderBy -> {
val ord = arrayOf("rating", "fresh")[filter.state!!.index] if (query.isEmpty()) {
url.addQueryParameter("sorting", ord) orderBy = arrayOf("RATING", "VIEWS", "HEARTS", "COUNT_CHAPTERS", "CREATED_AT", "UPDATED_AT")[filter.state!!.index]
ascEnd = if (filter.state!!.ascending) "ASC" else "DESC"
}
}
is GenreList -> filter.state.forEach { genre ->
if (genre.state != Filter.TriState.STATE_IGNORE) {
if (genre.isIncluded()) mutableGenre += '"' + genre.name + '"' else mutableExGenre += '"' + genre.name + '"'
}
}
is TagsList -> filter.state.forEach { tag ->
if (tag.state != Filter.TriState.STATE_IGNORE) {
if (tag.isIncluded()) mutableTag += '"' + tag.name + '"' else mutableExTag += '"' + tag.name + '"'
}
} }
is TypeList -> filter.state.forEach { type -> is TypeList -> filter.state.forEach { type ->
if (type.state) { if (type.state) {
url.addQueryParameter("types", type.id) mutableType += '"' + type.id + '"'
} }
} }
is StatusList -> filter.state.forEach { status -> is StatusList -> filter.state.forEach { status ->
if (status.state) { if (status.state) {
url.addQueryParameter("statuses", status.id) mutableStatus += '"' + status.id + '"'
} }
} }
is GenreList -> filter.state.forEach { genre -> is AgeList -> filter.state.forEach { age ->
if (genre.state) { if (age.state) {
url.addQueryParameter("genres", genre.id) mutableAge += '"' + age.id + '"'
}
}
is RequireChapters -> {
if (filter.state == 1) {
requireChapters = false
} }
} }
} }
} }
return GET(url.toString(), headers)
return POST(
"https://neo.newmanga.org/catalogue",
body = """{"query":"$query","sort":{"kind":"$orderBy","dir":"$ascEnd"},"filter":{"hidden_projects":[],"genres":{"excluded":$mutableExGenre,"included":$mutableGenre},"tags":{"excluded":$mutableExTag,"included":$mutableTag},"type":{"allowed":$mutableType},"translation_status":{"allowed":[]},"released_year":{"min":null,"max":null},"require_chapters":$requireChapters,"original_status":{"allowed":$mutableStatus},"adult":{"allowed":$mutableAge}},"pagination":{"page":$page,"size":$count}}""".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
headers = headers
)
} }
private fun parseStatus(status: String): Int { private fun parseStatus(status: String): Int {
@ -324,90 +372,225 @@ class Newbie : HttpSource() {
} }
private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name) private class CheckFilter(name: String, val id: String) : Filter.CheckBox(name)
private class SearchFilter(name: String) : Filter.TriState(name)
private class TypeList(types: List<CheckFilter>) : Filter.Group<CheckFilter>("Типы", types) private class TypeList(types: List<CheckFilter>) : Filter.Group<CheckFilter>("Типы", types)
private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус", statuses) private class StatusList(statuses: List<CheckFilter>) : Filter.Group<CheckFilter>("Статус", statuses)
private class GenreList(genres: List<CheckFilter>) : Filter.Group<CheckFilter>("Жанры", genres) private class GenreList(genres: List<SearchFilter>) : Filter.Group<SearchFilter>("Жанры", genres)
private class TagsList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags)
private class AgeList(ages: List<CheckFilter>) : Filter.Group<CheckFilter>("Возрастное ограничение", ages)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
OrderBy(), OrderBy(),
GenreList(getGenreList()), GenreList(getGenreList()),
TagsList(getTagsList()),
TypeList(getTypeList()), TypeList(getTypeList()),
StatusList(getStatusList()) StatusList(getStatusList()),
AgeList(getAgeList()),
RequireChapters()
) )
private class OrderBy : Filter.Sort( private class OrderBy : Filter.Sort(
"Сортировка", "Сортировка",
arrayOf("По рейтенгу", "По новизне"), arrayOf("По рейтингу", "По просмотрам", "По лайкам", "По кол-ву глав", "По дате создания", "По дате обновления"),
Selection(0, false) Selection(0, false)
) )
private class RequireChapters : Filter.Select<String>(
"Только проекты с главами",
arrayOf("Да", "Все")
)
private fun getTypeList() = listOf( private fun getTypeList() = listOf(
CheckFilter("Манга", "manga"), CheckFilter("Манга", "MANGA"),
CheckFilter("Манхва", "manhwa"), CheckFilter("Манхва", "MANHWA"),
CheckFilter("Маньхуа", "manhya"), CheckFilter("Маньхуа", "MANHYA"),
CheckFilter("Сингл", "single"), CheckFilter("Сингл", "SINGLE"),
CheckFilter("OEL-манга", "oel"), CheckFilter("OEL-манга", "OEL"),
CheckFilter("Комикс", "comics"), CheckFilter("Комикс", "COMICS"),
CheckFilter("Руманга", "russian") CheckFilter("Руманга", "RUSSIAN")
) )
private fun getStatusList() = listOf( private fun getStatusList() = listOf(
CheckFilter("Выпускается", "on_going"), CheckFilter("Выпускается", "ON_GOING"),
CheckFilter("Заброшен", "abandoned"), CheckFilter("Приостановлен", "SUSPENDED"),
CheckFilter("Завершён", "completed"), CheckFilter("Завершён", "COMPLETED"),
CheckFilter("Приостановлен", "suspended") CheckFilter("Анонс", "ANNOUNCEMENT"),
) )
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
CheckFilter("cёнэн-ай", "28"), SearchFilter("cёнэн-ай"),
CheckFilter("боевик", "17"), SearchFilter("боевик"),
CheckFilter("боевые искусства", "33"), SearchFilter("боевые искусства"),
CheckFilter("гарем", "34"), SearchFilter("гарем"),
CheckFilter("гендерная интрига", "3"), SearchFilter("гендерная интрига"),
CheckFilter("героическое фэнтези", "19"), SearchFilter("героическое фэнтези"),
CheckFilter("детектив", "35"), SearchFilter("детектив"),
CheckFilter("дзёсэй", "4"), SearchFilter("дзёсэй"),
CheckFilter("додзинси", "20"), SearchFilter("додзинси"),
CheckFilter("драма", "36"), SearchFilter("драма"),
CheckFilter("ёнкома", "5"), SearchFilter("ёнкома"),
CheckFilter("игра", "21"), SearchFilter("игра"),
CheckFilter("драма", "36"), SearchFilter("драма"),
CheckFilter("ёнкома", "5"), SearchFilter("ёнкома"),
CheckFilter("игра", "21"), SearchFilter("игра"),
CheckFilter("исекай", "37"), SearchFilter("исекай"),
CheckFilter("история", "6"), SearchFilter("история"),
CheckFilter("киберпанк", "22"), SearchFilter("киберпанк"),
CheckFilter("кодомо", "38"), SearchFilter("кодомо"),
CheckFilter("комедия", "7"), SearchFilter("комедия"),
CheckFilter("махо-сёдзё", "23"), SearchFilter("махо-сёдзё"),
CheckFilter("меха", "39"), SearchFilter("меха"),
CheckFilter("мистика", "8"), SearchFilter("мистика"),
CheckFilter("научная фантастика", "24"), SearchFilter("научная фантастика"),
CheckFilter("омегаверс", "40"), SearchFilter("омегаверс"),
CheckFilter("повседневность", "9"), SearchFilter("повседневность"),
CheckFilter("постапокалиптика", "25"), SearchFilter("постапокалиптика"),
CheckFilter("приключения", "41"), SearchFilter("приключения"),
CheckFilter("психология", "10"), SearchFilter("психология"),
CheckFilter("романтика", "26"), SearchFilter("романтика"),
CheckFilter("самурайский боевик", "42"), SearchFilter("самурайский боевик"),
CheckFilter("сверхъестественное", "11"), SearchFilter("сверхъестественное"),
CheckFilter("сёдзё", "27"), SearchFilter("сёдзё"),
CheckFilter("сёдзё-ай", "43"), SearchFilter("сёдзё-ай"),
CheckFilter("сёнэн", "13"), SearchFilter("сёнэн"),
CheckFilter("спорт", "44"), SearchFilter("спорт"),
CheckFilter("сэйнэн", "12"), SearchFilter("сэйнэн"),
CheckFilter("трагедия", "29"), SearchFilter("трагедия"),
CheckFilter("триллер", "45"), SearchFilter("триллер"),
CheckFilter("ужасы", "14"), SearchFilter("ужасы"),
CheckFilter("фантастика", "30"), SearchFilter("фантастика"),
CheckFilter("фэнтези", "46"), SearchFilter("фэнтези"),
CheckFilter("школа", "15"), SearchFilter("школа"),
CheckFilter("элементы юмора", "1"), SearchFilter("элементы юмора"),
CheckFilter("эротика", "31"), SearchFilter("эротика"),
CheckFilter("этти", "47"), SearchFilter("этти"),
CheckFilter("юри", "16"), SearchFilter("юри"),
CheckFilter("яой", "32"), SearchFilter("яой"),
)
private fun getTagsList() = listOf(
SearchFilter("веб"),
SearchFilter("в цвете"),
SearchFilter("сборник"),
SearchFilter("хентай"),
SearchFilter("азартные игры"),
SearchFilter("алхимия"),
SearchFilter("амнезия"),
SearchFilter("ангелы"),
SearchFilter("антигерой"),
SearchFilter("антиутопия"),
SearchFilter("апокалипсис"),
SearchFilter("аристократия"),
SearchFilter("армия"),
SearchFilter("артефакты"),
SearchFilter("боги"),
SearchFilter("бои на мечах"),
SearchFilter("борьба за власть"),
SearchFilter("брат и сестра"),
SearchFilter("будущее"),
SearchFilter("вампиры"),
SearchFilter("ведьма"),
SearchFilter("вестерн"),
SearchFilter("видеоигры"),
SearchFilter("виртуальная реальность"),
SearchFilter("военные"),
SearchFilter("война"),
SearchFilter("волшебники"),
SearchFilter("волшебные существа"),
SearchFilter("воспоминания из другого мира"),
SearchFilter("врачи / доктора"),
SearchFilter("выживание"),
SearchFilter("гг женщина"),
SearchFilter("гг имба"),
SearchFilter("гг мужчина"),
SearchFilter("гг не человек"),
SearchFilter("геймеры"),
SearchFilter("гильдии"),
SearchFilter("глупый гг"),
SearchFilter("гоблины"),
SearchFilter("горничные"),
SearchFilter("грузовик-сан"),
SearchFilter("гяру"),
SearchFilter("демоны"),
SearchFilter("драконы"),
SearchFilter("дружба"),
SearchFilter("ёнкома"),
SearchFilter("жестокий мир"),
SearchFilter("животные компаньоны"),
SearchFilter("завоевание мира"),
SearchFilter("зверолюди"),
SearchFilter("злые духи"),
SearchFilter("зомби"),
SearchFilter("игровые элементы"),
SearchFilter("империи"),
SearchFilter("исекай"),
SearchFilter("квесты"),
SearchFilter("космос"),
SearchFilter("кулинария"),
SearchFilter("культивация"),
SearchFilter("лгбт"),
SearchFilter("легендарное оружие"),
SearchFilter("лоли"),
SearchFilter("магическая академия"),
SearchFilter("магия"),
SearchFilter("мафия"),
SearchFilter("медицина"),
SearchFilter("месть"),
SearchFilter("монстродевушки"),
SearchFilter("монстры"),
SearchFilter("музыка"),
SearchFilter("навыки / способности"),
SearchFilter("наёмники"),
SearchFilter("насилие / жестокость"),
SearchFilter("нежить"),
SearchFilter("ниндзя"),
SearchFilter("обмен телами"),
SearchFilter("оборотни"),
SearchFilter("обратный гарем"),
SearchFilter("огнестрельное оружие"),
SearchFilter("офисные работники"),
SearchFilter("пародия"),
SearchFilter("пираты"),
SearchFilter("подземелье"),
SearchFilter("политика"),
SearchFilter("полиция"),
SearchFilter("преступники / криминал"),
SearchFilter("призраки / духи"),
SearchFilter("прокачка"),
SearchFilter("психодел"),
SearchFilter("путешествия во времени"),
SearchFilter("рабы"),
SearchFilter("разумные расы"),
SearchFilter("ранги силы"),
SearchFilter("реинкарнация"),
SearchFilter("роботы"),
SearchFilter("рыцари"),
SearchFilter("самураи"),
SearchFilter("система"),
SearchFilter("скрытие личности"),
SearchFilter("спасение мира"),
SearchFilter("спортивное тело"),
SearchFilter("средневековье"),
SearchFilter("стимпанк"),
SearchFilter("супергерои"),
SearchFilter("традиционные игры"),
SearchFilter("умный гг"),
SearchFilter("управление территорией"),
SearchFilter("учитель / ученик"),
SearchFilter("философия"),
SearchFilter("хикикомори"),
SearchFilter("холодное оружие"),
SearchFilter("шантаж"),
SearchFilter("эльфы"),
SearchFilter("якудза"),
SearchFilter("япония"),
)
private fun getAgeList() = listOf(
CheckFilter("13+", "ADULT_13"),
CheckFilter("16+", "ADULT_16"),
CheckFilter("18+", "ADULT_18")
) )
companion object { companion object {

View File

@ -1,36 +1,9 @@
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
//Catalog API
@Serializable @Serializable
data class TagsDto( data class PageWrapperDto<T>(
val title: TitleDto val items: List<T>,
)
@Serializable
data class BranchesDto(
val id: Long,
val is_default: Boolean
)
@Serializable
data class ImgsDto(
val large: String,
val small: String,
)
@Serializable
data class ImgDto(
val srcset: ImgsDto,
)
@Serializable
data class TitleDto(
val en: String,
val ru: String
)
@Serializable
data class AuthorDto(
val name: String?
) )
@Serializable @Serializable
@ -40,6 +13,7 @@ data class LibraryDto(
val image: ImgDto val image: ImgDto
) )
//Manga Details
@Serializable @Serializable
data class MangaDetDto( data class MangaDetDto(
val id: Long, val id: Long,
@ -57,10 +31,39 @@ data class MangaDetDto(
) )
@Serializable @Serializable
data class PageWrapperDto<T>( data class TitleDto(
val items: List<T>, val en: String,
val ru: String
) )
@Serializable
data class AuthorDto(
val name: String?
)
@Serializable
data class ImgDto(
val srcset: ImgsDto,
)
@Serializable
data class ImgsDto(
val large: String,
val small: String,
)
@Serializable
data class TagsDto(
val title: TitleDto
)
@Serializable
data class BranchesDto(
val id: Long,
val is_default: Boolean
)
//Chapters
@Serializable @Serializable
data class SeriesWrapperDto<T>( data class SeriesWrapperDto<T>(
val items: T val items: T
@ -82,3 +85,27 @@ data class PageDto(
val id: Int, val id: Int,
val slices: Int? val slices: Int?
) )
//Search NEO in POST Request
@Serializable
data class SearchWrapperDto<T>(
val result: T,
)
@Serializable
data class SubSearchDto<T>(
val hits: List<T>,
)
@Serializable
data class SearchLibraryDto(
val document: DocElementsDto,
)
@Serializable
data class DocElementsDto(
val id: String,
val title_en: String,
val image_large: String,
val image_small: String
)