Add MangaBuff (#3891)
* Add MangaBuff * style * Update src/ru/mangabuff/src/eu/kanade/tachiyomi/extension/ru/mangabuff/MangaBuff.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * PR comments Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
5b920b207a
commit
121f0591db
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name="eu.kanade.tachiyomi.extension.ru.mangabuff.MangaBuffUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
<data
|
||||
android:host="mangabuff.ru"
|
||||
android:pathPattern="/manga/..*"
|
||||
android:scheme="https" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -0,0 +1,8 @@
|
|||
ext {
|
||||
extName = 'MangaBuff'
|
||||
extClass = '.MangaBuff'
|
||||
extVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 6.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 10 KiB |
Binary file not shown.
After Width: | Height: | Size: 22 KiB |
Binary file not shown.
After Width: | Height: | Size: 37 KiB |
|
@ -0,0 +1,343 @@
|
|||
package eu.kanade.tachiyomi.extension.ru.mangabuff
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.json.Json
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class MangaBuff : ParsedHttpSource() {
|
||||
override val baseUrl = "https://mangabuff.ru"
|
||||
override val lang = "ru"
|
||||
override val name = "MangaBuff"
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.addInterceptor(::tokenInterceptor)
|
||||
.build()
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
// From Akuma - CSRF token
|
||||
private var storedToken: String? = null
|
||||
|
||||
private fun tokenInterceptor(chain: Interceptor.Chain): Response {
|
||||
val request = chain.request()
|
||||
|
||||
if (request.method == "POST" && request.header("X-CSRF-TOKEN") == null) {
|
||||
val newRequest = request.newBuilder()
|
||||
val token = getToken()
|
||||
val response = chain.proceed(
|
||||
newRequest
|
||||
.addHeader("X-CSRF-TOKEN", token)
|
||||
.build(),
|
||||
)
|
||||
|
||||
if (response.code == 419) {
|
||||
response.close()
|
||||
storedToken = null // reset the token
|
||||
val newToken = getToken()
|
||||
return chain.proceed(
|
||||
newRequest
|
||||
.addHeader("X-CSRF-TOKEN", newToken)
|
||||
.build(),
|
||||
)
|
||||
}
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
val response = chain.proceed(request)
|
||||
|
||||
if (response.header("Content-Type")?.contains("text/html") != true) {
|
||||
return response
|
||||
}
|
||||
|
||||
storedToken = Jsoup.parse(response.peekBody(Long.MAX_VALUE).string())
|
||||
.selectFirst("head meta[name*=csrf-token]")
|
||||
?.attr("content")
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
private fun getToken(): String {
|
||||
if (storedToken.isNullOrEmpty()) {
|
||||
val request = GET(baseUrl, headers)
|
||||
client.newCall(request).execute().close() // updates token in interceptor
|
||||
}
|
||||
return storedToken!!
|
||||
}
|
||||
|
||||
// Popular
|
||||
override fun popularMangaRequest(page: Int) = searchMangaRequest(page, "", SortFilter.POPULAR)
|
||||
override fun popularMangaSelector() = searchMangaSelector()
|
||||
override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
|
||||
override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
// Latest
|
||||
override fun latestUpdatesRequest(page: Int) = searchMangaRequest(page, "", SortFilter.LATEST)
|
||||
override fun latestUpdatesSelector() = searchMangaSelector()
|
||||
override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
|
||||
override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
|
||||
|
||||
// Search
|
||||
override fun fetchSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): Observable<MangasPage> {
|
||||
if (!query.startsWith(SEARCH_PREFIX)) {
|
||||
return super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
val request = GET("$baseUrl/manga/${query.substringAfter(SEARCH_PREFIX)}")
|
||||
return client.newCall(request).asObservableSuccess().map { response ->
|
||||
val details = mangaDetailsParse(response)
|
||||
details.setUrlWithoutDomain(request.url.toString())
|
||||
MangasPage(listOf(details), false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
if (query.isNotEmpty()) {
|
||||
val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
|
||||
addQueryParameter("q", query)
|
||||
if (page != 1) addQueryParameter("page", page.toString())
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
val url = "$baseUrl/manga".toHttpUrl().newBuilder().apply {
|
||||
(filters.find { it is GenreFilter } as? GenreFilter)?.let { filter ->
|
||||
filter.included?.forEach { addQueryParameter("genres[]", it) }
|
||||
}
|
||||
(filters.find { it is TypeFilter } as? TypeFilter)?.let { filter ->
|
||||
filter.included?.forEach { addQueryParameter("type_id[]", it) }
|
||||
}
|
||||
(filters.find { it is TagFilter } as? TagFilter)?.let { filter ->
|
||||
filter.included?.forEach { addQueryParameter("tags[]", it) }
|
||||
}
|
||||
(filters.find { it is StatusFilter } as? StatusFilter)?.let { filter ->
|
||||
filter.checked?.forEach { addQueryParameter("status_id[]", it) }
|
||||
}
|
||||
(filters.find { it is AgeFilter } as? AgeFilter)?.let { filter ->
|
||||
filter.checked?.forEach { addQueryParameter("age_rating[]", it) }
|
||||
}
|
||||
(filters.find { it is RatingFilter } as? RatingFilter)?.let { filter ->
|
||||
filter.checked?.forEach { addQueryParameter("rating[]", it) }
|
||||
}
|
||||
(filters.find { it is YearFilter } as? YearFilter)?.let { filter ->
|
||||
filter.checked?.forEach { addQueryParameter("year[]", it) }
|
||||
}
|
||||
(filters.find { it is ChapterCountFilter } as? ChapterCountFilter)?.let { filter ->
|
||||
filter.checked?.forEach { addQueryParameter("chapters[]", it) }
|
||||
}
|
||||
(filters.find { it is GenreFilter } as? GenreFilter)?.let { filter ->
|
||||
filter.excluded?.forEach { addQueryParameter("without_genres[]", it) }
|
||||
}
|
||||
(filters.find { it is TypeFilter } as? TypeFilter)?.let { filter ->
|
||||
filter.excluded?.forEach { addQueryParameter("without_type_id[]", it) }
|
||||
}
|
||||
(filters.find { it is TagFilter } as? TagFilter)?.let { filter ->
|
||||
filter.excluded?.forEach { addQueryParameter("without_tags[]", it) }
|
||||
}
|
||||
(filters.find { it is SortFilter } as? SortFilter)?.let { filter ->
|
||||
addQueryParameter("sort", filter.selected)
|
||||
}
|
||||
if (page != 1) addQueryParameter("page", page.toString())
|
||||
}.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = ".cards .cards__item"
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = SManga.create().apply {
|
||||
setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href"))
|
||||
title = element.selectFirst(".cards__name")!!.text()
|
||||
|
||||
val slug = "$baseUrl$url".toHttpUrl().pathSegments.last()
|
||||
thumbnail_url = "$baseUrl/img/manga/posters/$slug.jpg"
|
||||
}
|
||||
|
||||
override fun searchMangaNextPageSelector() =
|
||||
".pagination .pagination__button--active + li:not(:last-child)"
|
||||
|
||||
// Details
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
title = document.selectFirst("h1, .manga__name, .manga-mobile__name")!!.text()
|
||||
|
||||
description = buildString {
|
||||
document
|
||||
.selectFirst(".manga__description")
|
||||
?.text()
|
||||
?.also { append(it) }
|
||||
|
||||
document // rating%
|
||||
.selectFirst(".manga__rating")
|
||||
?.text()
|
||||
?.toDoubleOrNull()
|
||||
?.let { it / 10.0 }
|
||||
?.also {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
append(String.format(Locale("ru"), "Рейтинг: %.0f%%", it * 100))
|
||||
}
|
||||
|
||||
document // views
|
||||
.selectFirst(".manga__views")
|
||||
?.text()
|
||||
?.replace(" ", "")
|
||||
?.toIntOrNull()
|
||||
?.also {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
append(String.format(Locale("ru"), "Просмотров: %,d", it))
|
||||
}
|
||||
|
||||
document // favorites
|
||||
.selectFirst(".manga")
|
||||
?.attr("data-fav-count")
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.toIntOrNull()
|
||||
?.also {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
append(String.format(Locale("ru"), "Избранное: %,d", it))
|
||||
}
|
||||
|
||||
document // alternative names
|
||||
.select(".manga__name-alt > span, .manga-mobile__name-alt > span")
|
||||
.eachText()
|
||||
.takeIf { it.isNotEmpty() }
|
||||
?.also {
|
||||
if (isNotEmpty()) append("\n\n")
|
||||
append("Альтернативные названия:\n")
|
||||
append(it.joinToString("\n") { "• $it" })
|
||||
}
|
||||
}
|
||||
|
||||
genre = buildList {
|
||||
addAll(document.select(".manga__middle-links > a:not(:last-child)").eachText())
|
||||
addAll(document.select(".manga-mobile__info > a:not(:last-child)").eachText())
|
||||
addAll(document.select(".tags > .tags__item").eachText())
|
||||
}.takeIf { it.isNotEmpty() }?.joinToString()
|
||||
|
||||
status = document
|
||||
.select(".manga__middle-links > a:last-child, .manga-mobile__info > a:last-child")
|
||||
.text()
|
||||
.parseStatus()
|
||||
|
||||
thumbnail_url = document
|
||||
.selectFirst(".manga__img img, img.manga-mobile__image")
|
||||
?.absUrl("src")
|
||||
}
|
||||
|
||||
// Chapters
|
||||
override fun chapterListSelector() = "a.chapters__item"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.absUrl("href"))
|
||||
name = element.select(".chapters__volume, .chapters__value, .chapters__name").text()
|
||||
date_upload = runCatching {
|
||||
dateFormat.parse(element.selectFirst(".chapters__add-date")!!.text())!!.time
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val document = Jsoup.parse(response.peekBody(Long.MAX_VALUE).string())
|
||||
|
||||
val chapters = super.chapterListParse(response)
|
||||
|
||||
// HTML only shows 100 entries. If this class is present it will load more via API
|
||||
if (document.selectFirst(".load-chapters-trigger") == null) {
|
||||
return chapters
|
||||
}
|
||||
|
||||
val mangaId = document.selectFirst(".manga")?.attr("data-id")
|
||||
?: throw Exception("Не удалось найти ID манги")
|
||||
|
||||
val form = FormBody.Builder()
|
||||
.add("manga_id", mangaId)
|
||||
.build()
|
||||
|
||||
val moreChapters = client
|
||||
.newCall(POST("$baseUrl/chapters/load", headers, form))
|
||||
.execute()
|
||||
.parseAs<WrappedHtmlDto>()
|
||||
.content
|
||||
.let(Jsoup::parseBodyFragment)
|
||||
.select(chapterListSelector())
|
||||
.map(::chapterFromElement)
|
||||
|
||||
return chapters + moreChapters
|
||||
}
|
||||
|
||||
// Pages
|
||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select(".reader__pages img").mapIndexed { i, img ->
|
||||
Page(i, document.location(), img.imgAttr())
|
||||
}
|
||||
}
|
||||
|
||||
// Other
|
||||
override fun getFilterList() = FilterList(
|
||||
Filter.Header("ПРИМЕЧАНИЕ: Игнорируется, если используется поиск по тексту!"),
|
||||
Filter.Separator(),
|
||||
SortFilter(),
|
||||
GenreFilter(),
|
||||
TypeFilter(),
|
||||
TagFilter(),
|
||||
StatusFilter(),
|
||||
AgeFilter(),
|
||||
RatingFilter(),
|
||||
YearFilter(),
|
||||
ChapterCountFilter(),
|
||||
)
|
||||
|
||||
private fun String.parseStatus(): Int = when (this.lowercase()) {
|
||||
"завершен" -> SManga.COMPLETED
|
||||
"продолжается" -> SManga.ONGOING
|
||||
"заморожен" -> SManga.ON_HIATUS
|
||||
"заброшен" -> SManga.CANCELLED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
private fun Element.imgAttr(): String = when {
|
||||
hasAttr("data-src") -> absUrl("data-src")
|
||||
else -> absUrl("src")
|
||||
}
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T =
|
||||
json.decodeFromString(body.string())
|
||||
|
||||
@Serializable
|
||||
class WrappedHtmlDto(
|
||||
val content: String,
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val SEARCH_PREFIX = "slug:"
|
||||
private val dateFormat = SimpleDateFormat("dd.MM.yyyy", Locale.ROOT)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
package eu.kanade.tachiyomi.extension.ru.mangabuff
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
abstract class SelectFilter(
|
||||
name: String,
|
||||
private val options: List<Pair<String, String>>,
|
||||
defaultValue: String? = null,
|
||||
) : Filter.Select<String>(
|
||||
name,
|
||||
options.map { it.first }.toTypedArray(),
|
||||
options.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0,
|
||||
) {
|
||||
val selected get() = options[state].second.takeUnless { it.isEmpty() }
|
||||
}
|
||||
|
||||
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name)
|
||||
|
||||
abstract class CheckBoxGroup(
|
||||
name: String,
|
||||
options: List<Pair<String, String>>,
|
||||
) : Filter.Group<CheckBoxFilter>(
|
||||
name,
|
||||
options.map { CheckBoxFilter(it.first, it.second) },
|
||||
) {
|
||||
val checked get() = state.filter { it.state }.map { it.value }.takeUnless { it.isEmpty() }
|
||||
}
|
||||
|
||||
class TriStateFilter(name: String, val value: String) : Filter.TriState(name)
|
||||
|
||||
abstract class TriStateGroup(
|
||||
name: String,
|
||||
private val options: List<Pair<String, String>>,
|
||||
) : Filter.Group<TriStateFilter>(
|
||||
name,
|
||||
options.map { TriStateFilter(it.first, it.second) },
|
||||
) {
|
||||
val included get() = state.filter { it.isIncluded() }.map { it.value }.takeUnless { it.isEmpty() }
|
||||
val excluded get() = state.filter { it.isExcluded() }.map { it.value }.takeUnless { it.isEmpty() }
|
||||
}
|
||||
|
||||
class SortFilter(defaultOrder: String? = null) : SelectFilter("Сортировать по", sort, defaultOrder) {
|
||||
companion object {
|
||||
private val sort = listOf(
|
||||
Pair("Популярные", "views"),
|
||||
Pair("Обновленные", "updated_at"),
|
||||
Pair("По рейтингу", "rating"),
|
||||
Pair("По новинкам", "created_at"),
|
||||
)
|
||||
|
||||
val POPULAR = FilterList(SortFilter("views"))
|
||||
val LATEST = FilterList(SortFilter("updated_at"))
|
||||
}
|
||||
}
|
||||
|
||||
class GenreFilter : TriStateGroup("Жанр", genres) {
|
||||
companion object {
|
||||
private val genres = listOf(
|
||||
Pair("Арт", "1"),
|
||||
Pair("Боевик", "2"),
|
||||
Pair("Боевые искусства", "4"),
|
||||
Pair("Вампиры", "5"),
|
||||
Pair("Гарем", "6"),
|
||||
Pair("Гендерная интрига", "7"),
|
||||
Pair("Героическое фэнтези", "8"),
|
||||
Pair("Детектив", "9"),
|
||||
Pair("Дзёсэй", "10"),
|
||||
Pair("Додзинси", "11"),
|
||||
Pair("Драма", "12"),
|
||||
Pair("Ёнкома", "39"),
|
||||
Pair("Игра", "18"),
|
||||
Pair("История", "13"),
|
||||
Pair("Киберпанк", "21"),
|
||||
Pair("Кодомо", "40"),
|
||||
Pair("Комедия", "14"),
|
||||
Pair("Махо-сёдзе", "20"),
|
||||
Pair("Меха", "15"),
|
||||
Pair("Мистика", "16"),
|
||||
Pair("Научная фантастика", "17"),
|
||||
Pair("Повседневность", "19"),
|
||||
Pair("Постапокалиптика", "22"),
|
||||
Pair("Приключения", "24"),
|
||||
Pair("Психология", "25"),
|
||||
Pair("Романтика", "26"),
|
||||
Pair("Самурайский боевик", "28"),
|
||||
Pair("Сверхъестественное", "30"),
|
||||
Pair("Сёдзё", "31"),
|
||||
Pair("Сёнэн", "29"),
|
||||
Pair("Спорт", "32"),
|
||||
Pair("Сэйнэн", "33"),
|
||||
Pair("Трагедия", "23"),
|
||||
Pair("Триллер", "34"),
|
||||
Pair("Ужасы", "35"),
|
||||
Pair("Фантастика", "27"),
|
||||
Pair("Фэнтези", "36"),
|
||||
Pair("Школа", "3"),
|
||||
Pair("Эротика", "37"),
|
||||
Pair("Этти", "38"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TypeFilter : TriStateGroup("Тип", types) {
|
||||
companion object {
|
||||
private val types = listOf(
|
||||
Pair("Манга", "1"),
|
||||
Pair("OEL-манга", "2"),
|
||||
Pair("Манхва", "3"),
|
||||
Pair("Маньхуа", "4"),
|
||||
Pair("Сингл", "5"),
|
||||
Pair("Руманга", "6"),
|
||||
Pair("Комикс западный", "7"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class TagFilter : TriStateGroup("теги", tags) {
|
||||
companion object {
|
||||
private val tags = listOf(
|
||||
Pair("Азартные игры", "7759"),
|
||||
Pair("Алхимия", "7750"),
|
||||
Pair("Амнезия / Потеря памяти", "7776"),
|
||||
Pair("амнезия/потеря памяти", "7780"),
|
||||
Pair("Ангелы", "7744"),
|
||||
Pair("Антигерой", "7691"),
|
||||
Pair("Антиутопия", "7755"),
|
||||
Pair("Апокалипсис", "7774"),
|
||||
Pair("Армия", "7767"),
|
||||
Pair("Артефакты", "7727"),
|
||||
Pair("Боги", "7679"),
|
||||
Pair("Бои на мечах", "7700"),
|
||||
Pair("Борьба за власть", "7734"),
|
||||
Pair("Брат и сестра", "7725"),
|
||||
Pair("Будущее", "7756"),
|
||||
Pair("в первый раз", "7695"),
|
||||
Pair("Ведьма", "7772"),
|
||||
Pair("Вестерн", "7771"),
|
||||
Pair("Видеоигры", "7704"),
|
||||
Pair("Виртуальная реальность", "7760"),
|
||||
Pair("Владыка демонов", "7743"),
|
||||
Pair("Военные", "7676"),
|
||||
Pair("Война", "7770"),
|
||||
Pair("Волшебники / маги", "7680"),
|
||||
Pair("Волшебные существа", "7721"),
|
||||
Pair("Воспоминания из другого мира", "7713"),
|
||||
Pair("Выживание", "7739"),
|
||||
Pair("ГГ женщина", "7702"),
|
||||
Pair("ГГ имба", "7709"),
|
||||
Pair("ГГ мужчина", "7681"),
|
||||
Pair("Геймеры", "7758"),
|
||||
Pair("Гильдии", "7762"),
|
||||
Pair("Глупый ГГ", "7718"),
|
||||
Pair("Гоблины", "7766"),
|
||||
Pair("Горничные", "7753"),
|
||||
Pair("Гяру", "7773"),
|
||||
Pair("Демоны", "7682"),
|
||||
Pair("Драконы", "7751"),
|
||||
Pair("Дружба", "7703"),
|
||||
Pair("Жестокий мир", "7728"),
|
||||
Pair("Жестокость", "7784"),
|
||||
Pair("Животные компаньоны", "7752"),
|
||||
Pair("Завоевание мира", "7748"),
|
||||
Pair("Зверолюди", "7707"),
|
||||
Pair("Злые духи", "7683"),
|
||||
Pair("Зомби", "7726"),
|
||||
Pair("Игровые элементы", "7723"),
|
||||
Pair("Империи", "7711"),
|
||||
Pair("Квесты", "7735"),
|
||||
Pair("Космос", "7749"),
|
||||
Pair("Кулинария", "7740"),
|
||||
Pair("Культивация", "7731"),
|
||||
Pair("Легендарное оружие", "7714"),
|
||||
Pair("Лоли", "7791"),
|
||||
Pair("Магическая академия", "7684"),
|
||||
Pair("Магия", "7677"),
|
||||
Pair("Мафия", "7690"),
|
||||
Pair("Медицина", "7761"),
|
||||
Pair("Месть", "7741"),
|
||||
Pair("Монстр Девушки", "7719"),
|
||||
Pair("Монстродевушки", "7720"),
|
||||
Pair("Монстры", "7685"),
|
||||
Pair("Музыка", "7675"),
|
||||
Pair("Навыки / способности", "7715"),
|
||||
Pair("Наёмники", "7764"),
|
||||
Pair("Насилие / жестокость", "7692"),
|
||||
Pair("Нежить", "7686"),
|
||||
Pair("Ниндзя", "7732"),
|
||||
Pair("Обмен телами", "7757"),
|
||||
Pair("Обратный Гарем", "7705"),
|
||||
Pair("Огнестрельное оружие", "7777"),
|
||||
Pair("Офисные Работники", "7754"),
|
||||
Pair("Пародия", "7745"),
|
||||
Pair("Пираты", "7724"),
|
||||
Pair("Подземелья", "7722"),
|
||||
Pair("Политика", "7736"),
|
||||
Pair("Полиция", "7693"),
|
||||
Pair("Преступники / Криминал", "7733"),
|
||||
Pair("Призраки / Духи", "7687"),
|
||||
Pair("Путешествие во времени", "7710"),
|
||||
Pair("Путешествия во времени", "7730"),
|
||||
Pair("Рабы", "7765"),
|
||||
Pair("Разумные расы", "7688"),
|
||||
Pair("Ранги силы", "7746"),
|
||||
Pair("Реинкарнация", "7706"),
|
||||
Pair("Роботы", "7769"),
|
||||
Pair("Рыцари", "7701"),
|
||||
Pair("Самураи", "7698"),
|
||||
Pair("Система", "7737"),
|
||||
Pair("Скрытие личности", "7708"),
|
||||
Pair("Спасение мира", "7747"),
|
||||
Pair("Спортивное тело", "7742"),
|
||||
Pair("Средневековье", "7699"),
|
||||
Pair("Стимпанк", "7781"),
|
||||
Pair("Супергерои", "7775"),
|
||||
Pair("Традиционные игры", "7768"),
|
||||
Pair("Умный ГГ", "7716"),
|
||||
Pair("Учитель / ученик", "7717"),
|
||||
Pair("Философия", "7729"),
|
||||
Pair("Хикикомори", "7763"),
|
||||
Pair("Холодное оружие", "7738"),
|
||||
Pair("Шантаж", "7778"),
|
||||
Pair("Эльфы", "7678"),
|
||||
Pair("юные", "7696"),
|
||||
Pair("Якудза", "7689"),
|
||||
Pair("Яндере", "7779"),
|
||||
Pair("Япония", "7674"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class StatusFilter : CheckBoxGroup("Статус", statuses) {
|
||||
companion object {
|
||||
private val statuses = listOf(
|
||||
Pair("Завершен", "1"),
|
||||
Pair("Продолжается", "2"),
|
||||
Pair("Заморожен", "3"),
|
||||
Pair("Заброшен", "4"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class AgeFilter : CheckBoxGroup("Возрастной рейтинг", ages) {
|
||||
companion object {
|
||||
private val ages = listOf(
|
||||
Pair("18+", "18+"),
|
||||
Pair("16+", "16+"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class RatingFilter : CheckBoxGroup("Рейтинг", ratings) {
|
||||
companion object {
|
||||
private val ratings = listOf(
|
||||
Pair("Рейтинг 50%+", "5"),
|
||||
Pair("Рейтинг 60%+", "6"),
|
||||
Pair("Рейтинг 70%+", "7"),
|
||||
Pair("Рейтинг 80%+", "8"),
|
||||
Pair("Рейтинг 90%+", "9"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class YearFilter : CheckBoxGroup("Год выпуска", years) {
|
||||
companion object {
|
||||
private val years = listOf(
|
||||
Pair("2024", "2024"),
|
||||
Pair("2023", "2023"),
|
||||
Pair("2022", "2022"),
|
||||
Pair("2021", "2021"),
|
||||
Pair("2020", "2020"),
|
||||
Pair("2019", "2019"),
|
||||
Pair("2018", "2018"),
|
||||
Pair("2017", "2017"),
|
||||
Pair("2016", "2016"),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ChapterCountFilter : CheckBoxGroup("Колличество глав", chapters) {
|
||||
companion object {
|
||||
private val chapters = listOf(
|
||||
Pair("<50", "0"),
|
||||
Pair("50-100", "50"),
|
||||
Pair("100-200", "100"),
|
||||
Pair(">200", "200"),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package eu.kanade.tachiyomi.extension.ru.mangabuff
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class MangaBuffUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val slug = pathSegments[1]
|
||||
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", "${MangaBuff.SEARCH_PREFIX}$slug")
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("MangaBuffUrlActivity", e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e("MangaBuffUrlActivity", "could not parse uri from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue