Rewrite LibGroup to use new api (#3219)

* Rewrote LibGroup to use api instead of parsing document

* apply suggestions

* quick fixes

* change preferences variable to functions

* Make getToken sync

* safe & load token

* return new token when refreshing
This commit is contained in:
nedius 2024-06-04 08:14:05 +03:00 committed by GitHub
parent 95c1fe5c1f
commit 91ef9b6571
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 667 additions and 1145 deletions

View File

@ -15,9 +15,9 @@
<!-- LibUrlActivity sites can be added here. -->
<data
android:host="v1.hentailib.org"
android:pathPattern="/..*/v..*"
android:scheme="https" />
android:host="${SOURCEHOST}"
android:pathPattern="/ru/manga/..*"
android:scheme="${SOURCESCHEME}" />
</intent-filter>
</activity>
</application>

View File

@ -2,4 +2,4 @@ plugins {
id("lib-multisrc")
}
baseVersionCode = 25
baseVersionCode = 26

View File

@ -0,0 +1,285 @@
package eu.kanade.tachiyomi.multisrc.libgroup
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
@Serializable
class Data<T>(
val data: T,
)
@Serializable
class Constants(
@SerialName("ageRestriction") val ageRestrictions: List<IdLabelSiteType>,
@SerialName("format") val formats: List<IdNameSiteType>,
val genres: List<IdNameSiteType>,
val imageServers: List<ImageServer>,
@SerialName("scanlateStatus") val scanlateStatuses: List<IdLabelSiteType>,
@SerialName("status") val titleStatuses: List<IdLabelSiteType>,
val tags: List<IdNameSiteType>,
val types: List<IdLabelSiteType>,
) {
@Serializable
class IdLabelSiteType(
val id: Int,
val label: String,
@SerialName("site_ids") val siteIds: List<Int>,
)
@Serializable
class IdNameSiteType(
val id: Int,
val name: String,
@SerialName("site_ids") val siteIds: List<Int>,
)
@Serializable
class ImageServer(
val id: String,
val label: String,
val url: String,
@SerialName("site_ids") val siteIds: List<Int>,
)
fun getServer(isServers: String?, siteId: Int): ImageServer =
if (!isServers.isNullOrBlank()) {
imageServers.first { it.id == isServers && it.siteIds.contains(siteId) }
} else {
imageServers.first { it.siteIds.contains(siteId) }
}
fun getCategories(siteId: Int): List<IdLabelSiteType> = types.filter { it.siteIds.contains(siteId) }
fun getFormats(siteId: Int): List<IdNameSiteType> = formats.filter { it.siteIds.contains(siteId) }
fun getGenres(siteId: Int): List<IdNameSiteType> = genres.filter { it.siteIds.contains(siteId) }
fun getTags(siteId: Int): List<IdNameSiteType> = tags.filter { it.siteIds.contains(siteId) }
fun getScanlateStatuses(siteId: Int): List<IdLabelSiteType> = scanlateStatuses.filter { it.siteIds.contains(siteId) }
fun getTitleStatuses(siteId: Int): List<IdLabelSiteType> = titleStatuses.filter { it.siteIds.contains(siteId) }
fun getAgeRestrictions(siteId: Int): List<IdLabelSiteType> = ageRestrictions.filter { it.siteIds.contains(siteId) }
}
@Serializable
class MangasPageDto(
val data: List<MangaShort>,
val meta: MangaPageMeta,
) {
@Serializable
class MangaPageMeta(
@SerialName("has_next_page") val hasNextPage: Boolean,
)
fun mapToSManga(isEng: String): List<SManga> {
return this.data.map { it.toSManga(isEng) }
}
}
@Serializable
class MangaShort(
val name: String,
@SerialName("rus_name") val rusName: String?,
@SerialName("eng_name") val engName: String?,
@SerialName("slug_url") val slugUrl: String,
val cover: Cover,
) {
@Serializable
data class Cover(
val default: String?,
)
fun toSManga(isEng: String) = SManga.create().apply {
title = getSelectedLanguage(isEng, rusName, engName, name)
thumbnail_url = cover.default.orEmpty()
url = "/$slugUrl"
}
}
@Serializable
class Manga(
val type: LabelType,
val ageRestriction: LabelType,
val rating: Rating,
val genres: List<NameType>,
val tags: List<NameType>,
@SerialName("rus_name") val rusName: String?,
@SerialName("eng_name") val engName: String?,
val name: String,
val cover: MangaShort.Cover,
val authors: List<NameType>,
val artists: List<NameType>,
val status: LabelType,
val scanlateStatus: LabelType,
@SerialName("is_licensed") val isLicensed: Boolean,
val otherNames: List<String>,
val summary: String,
) {
@Serializable
class LabelType(
val label: String,
)
@Serializable
class NameType(
val name: String,
)
@Serializable
class Rating(
val average: Float,
val votes: Int,
)
fun toSManga(isEng: String): SManga = SManga.create().apply {
title = getSelectedLanguage(isEng, rusName, engName, name)
thumbnail_url = cover.default
author = authors.joinToString { it.name }
artist = artists.joinToString { it.name }
status = parseStatus(isLicensed, scanlateStatus.label, this@Manga.status.label)
genre = type.label.ifBlank { "Манга" } + ", " + ageRestriction.label + ", " +
genres.joinToString { it.name.trim() } + ", " + tags.joinToString { it.name.trim() }
description = getOppositeLanguage(isEng, rusName, engName) + rating.average.parseAverage() + " " + rating.average +
" (голосов: " + rating.votes + ")\n" + otherNames.joinAltNames() + summary
}
private fun Float.parseAverage(): String {
return when {
this > 9.5 -> "★★★★★"
this > 8.5 -> "★★★★✬"
this > 7.5 -> "★★★★☆"
this > 6.5 -> "★★★✬☆"
this > 5.5 -> "★★★☆☆"
this > 4.5 -> "★★✬☆☆"
this > 3.5 -> "★★☆☆☆"
this > 2.5 -> "★✬☆☆☆"
this > 1.5 -> "★☆☆☆☆"
this > 0.5 -> "✬☆☆☆☆"
else -> "☆☆☆☆☆"
}
}
private fun parseStatus(isLicensed: Boolean, statusTranslate: String, statusTitle: String): Int = when {
isLicensed -> SManga.LICENSED
statusTranslate == "Завершён" && statusTitle == "Приостановлен" || statusTranslate == "Заморожен" || statusTranslate == "Заброшен" -> SManga.ON_HIATUS
statusTranslate == "Завершён" && statusTitle == "Выпуск прекращён" -> SManga.CANCELLED
statusTranslate == "Продолжается" -> SManga.ONGOING
statusTranslate == "Выходит" -> SManga.ONGOING
statusTranslate == "Завершён" -> SManga.COMPLETED
statusTranslate == "Вышло" -> SManga.PUBLISHING_FINISHED
else -> when (statusTitle) {
"Онгоинг" -> SManga.ONGOING
"Анонс" -> SManga.ONGOING
"Завершён" -> SManga.COMPLETED
"Приостановлен" -> SManga.ON_HIATUS
"Выпуск прекращён" -> SManga.CANCELLED
else -> SManga.UNKNOWN
}
}
private fun List<String>.joinAltNames(): String = when {
this.isNotEmpty() -> "Альтернативные названия:\n" + this.joinToString(" / ") + "\n\n"
else -> ""
}
}
private fun getSelectedLanguage(isEng: String, rusName: String?, engName: String?, name: String): String = when {
isEng == "rus" && rusName.orEmpty().isNotEmpty() -> rusName!!
isEng == "eng" && engName.orEmpty().isNotEmpty() -> engName!!
else -> name
}
private fun getOppositeLanguage(isEng: String, rusName: String?, engName: String?): String = when {
isEng == "eng" && rusName.orEmpty().isNotEmpty() -> rusName + "\n"
isEng == "rus" && engName.orEmpty().isNotEmpty() -> engName + "\n"
else -> ""
}
@Serializable
class Chapter(
val id: Int,
@SerialName("branches_count") val branchesCount: Int,
val branches: List<Branch>,
val name: String?,
val number: String,
val volume: String,
@SerialName("item_number") val itemNumber: Float?,
) {
@Serializable
class Branch(
@SerialName("branch_id") val branchId: Int?,
@SerialName("created_at") val createdAt: String,
val teams: List<Team>,
val user: User,
) {
@Serializable
class Team(
val name: String,
)
@Serializable
class User(
val username: String,
)
}
private fun first(branchId: Int? = null): Branch? {
return runCatching { if (branchId != null) branches.first { it.branchId == branchId } else branches.first() }.getOrNull()
}
private fun getTeamName(branchId: Int? = null): String? {
return runCatching { first(branchId)!!.teams.first().name }.getOrNull()
}
private fun getUserName(branchId: Int? = null): String? {
return runCatching { first(branchId)!!.user.username }.getOrNull()
}
fun toSChapter(slugUrl: String, branchId: Int? = null, isScanUser: Boolean): SChapter = SChapter.create().apply {
val chapterName = "Том $volume. Глава $number"
name = if (this@Chapter.name.isNullOrBlank()) chapterName else "$chapterName - ${this@Chapter.name}"
val branchStr = if (branchId != null) "&branch_id=$branchId" else ""
url = "/$slugUrl/chapter?$branchStr&volume=$volume&number=$number"
scanlator = getTeamName(branchId) ?: if (isScanUser) getUserName(branchId) else null
date_upload = runCatching { LibGroup.simpleDateFormat.parse(first(branchId)!!.createdAt)!!.time }.getOrDefault(0L)
chapter_number = itemNumber ?: -1f
}
}
@Serializable
class Branch(
val id: Int,
)
@Serializable
class Pages(
val pages: List<MangaPage>,
) {
@Serializable
class MangaPage(
val slug: Int,
val url: String,
)
fun toPageList(): List<Page> = pages.map { Page(it.slug, it.url) }
}
@Serializable
class AuthToken(
private val token: Token,
) {
@Serializable
class Token(
val timestamp: Long,
@SerialName("expires_in") val expiresIn: Long,
@SerialName("token_type") val tokenType: String,
@SerialName("access_token") val accessToken: String,
)
fun isExpired(): Boolean {
val currentTime = System.currentTimeMillis()
val expiresIn = token.timestamp + (token.expiresIn * 1000)
return expiresIn < currentTime
}
fun getToken(): String = "${token.tokenType} ${token.accessToken}"
}

View File

@ -19,7 +19,7 @@ class LibUrlActivity : Activity() {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 0) {
val titleid = pathSegments[0]
val titleid = pathSegments[2]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${LibGroup.PREFIX_SLUG_SEARCH}$titleid")

View File

@ -5,17 +5,12 @@ import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import eu.kanade.tachiyomi.multisrc.libgroup.LibGroup
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.Request
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class HentaiLib : LibGroup("HentaiLib", "https://hentailib.me", "ru") {
override val id: Long = 6425650164840473547
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
@ -23,209 +18,17 @@ class HentaiLib : LibGroup("HentaiLib", "https://hentailib.me", "ru") {
private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!!
override val baseUrl: String = domain
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (csrfToken.isEmpty()) {
val tokenResponse = client.newCall(popularMangaRequest(page)).execute()
val resBody = tokenResponse.body.string()
csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value
}
val url = super.searchMangaRequest(page, query, filters).url.newBuilder()
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is TagList -> filter.state.forEach { tag ->
if (tag.state != Filter.TriState.STATE_IGNORE) {
url.addQueryParameter(
if (tag.isIncluded()) "tags[include][]" else "tags[exclude][]",
tag.id,
)
}
}
else -> {}
}
}
return POST(url.toString(), catalogHeaders())
}
override val siteId: Int = 4 // Important in api calls
// Filters
private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
private class TagList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags)
override fun getFilterList(): FilterList {
val filters = super.getFilterList().toMutableList()
filters.add(4, TagList(getTagList()))
if (filters.size > 7) {
filters.removeAt(7) // AgeList
}
return FilterList(filters)
}
private fun getTagList() = listOf(
SearchFilter("3D", "1"),
SearchFilter("Defloration", "287"),
SearchFilter("FPP(Вид от первого лица)", "289"),
SearchFilter("Footfuck", "5"),
SearchFilter("Handjob", "6"),
SearchFilter("Lactation", "7"),
SearchFilter("Living clothes", "284"),
SearchFilter("Mind break", "9"),
SearchFilter("Scat", "13"),
SearchFilter("Selfcest", "286"),
SearchFilter("Shemale", "220"),
SearchFilter("Tomboy", "14"),
SearchFilter("Unbirth", "283"),
SearchFilter("X-Ray", "15"),
SearchFilter("Алкоголь", "16"),
SearchFilter("Анал", "17"),
SearchFilter("Андроид", "18"),
SearchFilter("Анилингус", "19"),
SearchFilter("Анимация (GIF)", "350"),
SearchFilter("Арт", "20"),
SearchFilter("Ахэгао", "2"),
SearchFilter("БДСМ", "22"),
SearchFilter("Бакуню", "21"),
SearchFilter("Бара", "293"),
SearchFilter("Без проникновения", "336"),
SearchFilter("Без текста", "23"),
SearchFilter("Без трусиков", "24"),
SearchFilter("Без цензуры", "25"),
SearchFilter("Беременность", "26"),
SearchFilter("Бикини", "27"),
SearchFilter("Близнецы", "28"),
SearchFilter("Боди-арт", "29"),
SearchFilter("Больница", "30"),
SearchFilter("Большая грудь", "31"),
SearchFilter("Большая попка", "32"),
SearchFilter("Борьба", "33"),
SearchFilter("Буккакэ", "34"),
SearchFilter("В бассейне", "35"),
SearchFilter("В ванной", "36"),
SearchFilter("В государственном учреждении", "37"),
SearchFilter("В общественном месте", "38"),
SearchFilter("В очках", "8"),
SearchFilter("В первый раз", "39"),
SearchFilter("В транспорте", "40"),
SearchFilter("Вампиры", "41"),
SearchFilter("Вибратор", "42"),
SearchFilter("Втроём", "43"),
SearchFilter("Гипноз", "44"),
SearchFilter("Глубокий минет", "45"),
SearchFilter("Горячий источник", "46"),
SearchFilter("Групповой секс", "47"),
SearchFilter("Гуро", "307"),
SearchFilter("Гяру и Гангуро", "48"),
SearchFilter("Двойное проникновение", "49"),
SearchFilter("Девочки-волшебницы", "50"),
SearchFilter("Девушка-туалет", "51"),
SearchFilter("Демон", "52"),
SearchFilter("Дилдо", "53"),
SearchFilter("Домохозяйка", "54"),
SearchFilter("Дыра в стене", "55"),
SearchFilter("Жестокость", "56"),
SearchFilter("Золотой дождь", "57"),
SearchFilter("Зомби", "58"),
SearchFilter("Зоофилия", "351"),
SearchFilter("Зрелые женщины", "59"),
SearchFilter("Избиение", "223"),
SearchFilter("Измена", "60"),
SearchFilter("Изнасилование", "61"),
SearchFilter("Инопланетяне", "62"),
SearchFilter("Инцест", "63"),
SearchFilter("Исполнение желаний", "64"),
SearchFilter("Историческое", "65"),
SearchFilter("Камера", "66"),
SearchFilter("Кляп", "288"),
SearchFilter("Колготки", "67"),
SearchFilter("Косплей", "68"),
SearchFilter("Кримпай", "3"),
SearchFilter("Куннилингус", "69"),
SearchFilter("Купальники", "70"),
SearchFilter("ЛГБТ", "343"),
SearchFilter("Латекс и кожа", "71"),
SearchFilter("Магия", "72"),
SearchFilter("Маленькая грудь", "73"),
SearchFilter("Мастурбация", "74"),
SearchFilter("Медсестра", "221"),
SearchFilter("Мейдочка", "75"),
SearchFilter("Мерзкий дядька", "76"),
SearchFilter("Милф", "77"),
SearchFilter("Много девушек", "78"),
SearchFilter("Много спермы", "79"),
SearchFilter("Молоко", "80"),
SearchFilter("Монашка", "353"),
SearchFilter("Монстродевушки", "81"),
SearchFilter("Монстры", "82"),
SearchFilter("Мочеиспускание", "83"),
SearchFilter("На природе", "84"),
SearchFilter("Наблюдение", "85"),
SearchFilter("Насекомые", "285"),
SearchFilter("Небритая киска", "86"),
SearchFilter("Небритые подмышки", "87"),
SearchFilter("Нетораре", "88"),
SearchFilter("Нэтори", "11"),
SearchFilter("Обмен телами", "89"),
SearchFilter("Обычный секс", "90"),
SearchFilter("Огромная грудь", "91"),
SearchFilter("Огромный член", "92"),
SearchFilter("Омораси", "93"),
SearchFilter("Оральный секс", "94"),
SearchFilter("Орки", "95"),
SearchFilter("Остановка времени", "296"),
SearchFilter("Пайзури", "96"),
SearchFilter("Парень пассив", "97"),
SearchFilter("Переодевание", "98"),
SearchFilter("Пирсинг", "308"),
SearchFilter("Пляж", "99"),
SearchFilter("Повседневность", "100"),
SearchFilter("Подвязки", "282"),
SearchFilter("Подглядывание", "101"),
SearchFilter("Подчинение", "102"),
SearchFilter("Похищение", "103"),
SearchFilter("Превозмогание", "104"),
SearchFilter("Принуждение", "105"),
SearchFilter("Прозрачная одежда", "106"),
SearchFilter("Проституция", "107"),
SearchFilter("Психические отклонения", "108"),
SearchFilter("Публично", "109"),
SearchFilter("Пытки", "224"),
SearchFilter("Пьяные", "110"),
SearchFilter("Рабы", "356"),
SearchFilter("Рабыни", "111"),
SearchFilter("С Сюжетом", "337"),
SearchFilter("Сuminside", "4"),
SearchFilter("Секс-игрушки", "112"),
SearchFilter("Сексуально возбуждённая", "113"),
SearchFilter("Сибари", "114"),
SearchFilter("Спортивная форма", "117"),
SearchFilter("Спортивное тело", "335"),
SearchFilter("Спящие", "118"),
SearchFilter("Страпон", "119"),
SearchFilter("Суккуб", "120"),
SearchFilter("Темнокожие", "121"),
SearchFilter("Тентакли", "122"),
SearchFilter("Толстушки", "123"),
SearchFilter("Трагедия", "124"),
SearchFilter("Трап", "125"),
SearchFilter("Ужасы", "126"),
SearchFilter("Униформа", "127"),
SearchFilter("Учитель и ученик", "352"),
SearchFilter("Ушастые", "128"),
SearchFilter("Фантазии", "129"),
SearchFilter("Фемдом", "130"),
SearchFilter("Фестиваль", "131"),
SearchFilter("Фетиш", "132"),
SearchFilter("Фистинг", "133"),
SearchFilter("Фурри", "134"),
SearchFilter("Футанари", "136"),
SearchFilter("Футанари имеет парня", "137"),
SearchFilter("Цельный купальник", "138"),
SearchFilter("Цундэрэ", "139"),
SearchFilter("Чикан", "140"),
SearchFilter("Чулки", "141"),
SearchFilter("Шлюха", "142"),
SearchFilter("Эксгибиционизм", "143"),
SearchFilter("Эльф", "144"),
SearchFilter("Юные", "145"),
SearchFilter("Яндэрэ", "146"),
)
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
super.setupPreferenceScreen(screen)
EditTextPreference(screen.context).apply {
@ -234,7 +37,7 @@ class HentaiLib : LibGroup("HentaiLib", "https://hentailib.me", "ru") {
summary = domain
this.setDefaultValue(DOMAIN_DEFAULT)
dialogTitle = DOMAIN_TITLE
setOnPreferenceChangeListener { _, newValue ->
setOnPreferenceChangeListener { _, _ ->
val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
true
@ -243,8 +46,6 @@ class HentaiLib : LibGroup("HentaiLib", "https://hentailib.me", "ru") {
}
companion object {
const val PREFIX_SLUG_SEARCH = "slug:"
private const val DOMAIN_TITLE = "Домен"
private const val DOMAIN_DEFAULT = "https://hentailib.me"
}

View File

@ -1,28 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.libgroup.LibUrlActivity"
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" />
<!-- LibUrlActivity sites can be added here. -->
<data
android:host="mangalib.me"
android:pathPattern="/..*/v..*"
android:scheme="https" />
<data
android:host="mangalib.org"
android:pathPattern="/..*/v..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -3,206 +3,41 @@ package eu.kanade.tachiyomi.extension.ru.mangalib
import android.app.Application
import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import androidx.preference.EditTextPreference
import eu.kanade.tachiyomi.multisrc.libgroup.LibGroup
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.Request
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class MangaLib : LibGroup("MangaLib", "https://mangalib.me", "ru") {
override val id: Long = 6111047689498497237
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_${id}_2", 0x0000)
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
private val baseOrig: String = "https://mangalib.me"
private val baseMirr: String = "https://mangalib.org"
private var domain: String? = preferences.getString(DOMAIN_PREF, baseOrig)
override val baseUrl: String = domain.toString()
private var domain: String = preferences.getString(DOMAIN_PREF, DOMAIN_DEFAULT)!!
override val baseUrl: String = domain
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (csrfToken.isEmpty()) {
val tokenResponse = client.newCall(popularMangaRequest(page)).execute()
val resBody = tokenResponse.body.string()
csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value
}
val url = super.searchMangaRequest(page, query, filters).url.newBuilder()
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is AgeList -> filter.state.forEach { age ->
if (age.state != Filter.TriState.STATE_IGNORE) {
url.addQueryParameter(
if (age.isIncluded()) "caution[include][]" else "caution[exclude][]",
age.id,
)
}
}
is TagList -> filter.state.forEach { tag ->
if (tag.state != Filter.TriState.STATE_IGNORE) {
url.addQueryParameter(
if (tag.isIncluded()) "tags[include][]" else "tags[exclude][]",
tag.id,
)
}
}
else -> {}
}
}
return POST(url.toString(), catalogHeaders())
}
override val siteId: Int = 1 // Important in api calls
// Filters
private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
private class TagList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags)
private class AgeList(ages: List<SearchFilter>) : Filter.Group<SearchFilter>("Возрастное ограничение", ages)
override fun getFilterList(): FilterList {
val filters = super.getFilterList().toMutableList()
filters.add(4, TagList(getTagList()))
filters.add(7, AgeList(getAgeList()))
return FilterList(filters)
}
private fun getTagList() = listOf(
SearchFilter("Азартные игры", "304"),
SearchFilter("Алхимия", "225"),
SearchFilter("Ангелы", "226"),
SearchFilter("Антигерой", "175"),
SearchFilter("Антиутопия", "227"),
SearchFilter("Апокалипсис", "228"),
SearchFilter("Армия", "229"),
SearchFilter("Артефакты", "230"),
SearchFilter("Боги", "215"),
SearchFilter("Бои на мечах", "231"),
SearchFilter("Борьба за власть", "231"),
SearchFilter("Брат и сестра", "233"),
SearchFilter("Будущее", "234"),
SearchFilter("Ведьма", "338"),
SearchFilter("Вестерн", "235"),
SearchFilter("Видеоигры", "185"),
SearchFilter("Виртуальная реальность", "195"),
SearchFilter("Владыка демонов", "236"),
SearchFilter("Военные", "179"),
SearchFilter("Война", "237"),
SearchFilter("Волшебники / маги", "281"),
SearchFilter("Волшебные существа", "239"),
SearchFilter("Воспоминания из другого мира", "240"),
SearchFilter("Выживание", "193"),
SearchFilter("ГГ женщина", "243"),
SearchFilter("ГГ имба", "291"),
SearchFilter("ГГ мужчина", "244"),
SearchFilter("Геймеры", "241"),
SearchFilter("Гильдии", "242"),
SearchFilter("Глупый ГГ", "297"),
SearchFilter("Гоблины", "245"),
SearchFilter("Горничные", "169"),
SearchFilter("Гяру", "178"),
SearchFilter("Демоны", "151"),
SearchFilter("Драконы", "246"),
SearchFilter("Дружба", "247"),
SearchFilter("Жестокий мир", "249"),
SearchFilter("Животные компаньоны", "250"),
SearchFilter("Завоевание мира", "251"),
SearchFilter("Зверолюди", "162"),
SearchFilter("Злые духи", "252"),
SearchFilter("Зомби", "149"),
SearchFilter("Игровые элементы", "253"),
SearchFilter("Империи", "254"),
SearchFilter("Квесты", "255"),
SearchFilter("Космос", "256"),
SearchFilter("Кулинария", "152"),
SearchFilter("Культивация", "160"),
SearchFilter("Легендарное оружие", "257"),
SearchFilter("Лоли", "187"),
SearchFilter("Магическая академия", "258"),
SearchFilter("Магия", "168"),
SearchFilter("Мафия", "172"),
SearchFilter("Медицина", "153"),
SearchFilter("Месть", "259"),
SearchFilter("Монстр Девушки", "188"),
SearchFilter("Монстры", "189"),
SearchFilter("Музыка", "190"),
SearchFilter("Навыки / способности", "260"),
SearchFilter("Насилие / жестокость", "262"),
SearchFilter("Наёмники", "261"),
SearchFilter("Нежить", "263"),
SearchFilter("Ниндая", "180"),
SearchFilter("Обратный Гарем", "191"),
SearchFilter("Огнестрельное оружие", "264"),
SearchFilter("Офисные Работники", "181"),
SearchFilter("Пародия", "265"),
SearchFilter("Пираты", "340"),
SearchFilter("Подземелья", "266"),
SearchFilter("Политика", "267"),
SearchFilter("Полиция", "182"),
SearchFilter("Преступники / Криминал", "186"),
SearchFilter("Призраки / Духи", "177"),
SearchFilter("Путешествие во времени", "194"),
SearchFilter("Разумные расы", "268"),
SearchFilter("Ранги силы", "248"),
SearchFilter("Реинкарнация", "148"),
SearchFilter("Роботы", "269"),
SearchFilter("Рыцари", "270"),
SearchFilter("Самураи", "183"),
SearchFilter("Система", "271"),
SearchFilter("Скрытие личности", "273"),
SearchFilter("Спасение мира", "274"),
SearchFilter("Спортивное тело", "334"),
SearchFilter("Средневековье", "173"),
SearchFilter("Стимпанк", "272"),
SearchFilter("Супергерои", "275"),
SearchFilter("Традиционные игры", "184"),
SearchFilter("Умный ГГ", "302"),
SearchFilter("Учитель / ученик", "276"),
SearchFilter("Философия", "277"),
SearchFilter("Хикикомори", "166"),
SearchFilter("Холодное оружие", "278"),
SearchFilter("Шантаж", "279"),
SearchFilter("Эльфы", "216"),
SearchFilter("Якудза", "164"),
SearchFilter("Япония", "280"),
)
private fun getAgeList() = listOf(
SearchFilter("Отсутствует", "0"),
SearchFilter("16+", "1"),
SearchFilter("18+", "2"),
)
companion object {
const val PREFIX_SLUG_SEARCH = "slug:"
private const val DOMAIN_PREF = "MangaLibDomain"
private const val DOMAIN_PREF_Title = "Выбор домена"
}
override fun setupPreferenceScreen(screen: PreferenceScreen) {
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
super.setupPreferenceScreen(screen)
ListPreference(screen.context).apply {
EditTextPreference(screen.context).apply {
key = DOMAIN_PREF
title = DOMAIN_PREF_Title
entries = arrayOf("Основной (mangalib.me)", "Зеркало (mangalib.org)")
entryValues = arrayOf(baseOrig, baseMirr)
summary = "%s"
setDefaultValue(baseOrig)
setOnPreferenceChangeListener { _, newValue ->
try {
val res = preferences.edit().putString(DOMAIN_PREF, newValue as String).commit()
val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
res
} catch (e: Exception) {
e.printStackTrace()
false
}
this.title = DOMAIN_TITLE
summary = domain
this.setDefaultValue(DOMAIN_DEFAULT)
dialogTitle = DOMAIN_TITLE
setOnPreferenceChangeListener { _, _ ->
val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
true
}
}.let(screen::addPreference)
}
companion object {
private const val DOMAIN_PREF = "MangaLibDomain"
private const val DOMAIN_TITLE = "Домен"
private const val DOMAIN_DEFAULT = "https://test-front.mangalib.me"
}
}

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name="eu.kanade.tachiyomi.multisrc.libgroup.LibUrlActivity"
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" />
<!-- LibUrlActivity sites can be added here. -->
<data
android:host="v2.slashlib.me"
android:pathPattern="/..*/v..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -2,7 +2,7 @@ ext {
extName = 'YaoiLib'
extClass = '.YaoiLib'
themePkg = 'libgroup'
baseUrl = 'https://v2.slashlib.me'
baseUrl = 'https://slashlib.me'
overrideVersionCode = 4
isNsfw = true
}

View File

@ -5,14 +5,10 @@ import android.content.SharedPreferences
import android.widget.Toast
import androidx.preference.EditTextPreference
import eu.kanade.tachiyomi.multisrc.libgroup.LibGroup
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import okhttp3.Request
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
class YaoiLib : LibGroup("YaoiLib", "https://v2.slashlib.me", "ru") {
class YaoiLib : LibGroup("YaoiLib", "https://slashlib.me", "ru") {
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
@ -21,156 +17,7 @@ class YaoiLib : LibGroup("YaoiLib", "https://v2.slashlib.me", "ru") {
private var domain: String = preferences.getString(DOMAIN_TITLE, DOMAIN_DEFAULT)!!
override val baseUrl: String = domain
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (csrfToken.isEmpty()) {
val tokenResponse = client.newCall(popularMangaRequest(page)).execute()
val resBody = tokenResponse.body.string()
csrfToken = "_token\" content=\"(.*)\"".toRegex().find(resBody)!!.groups[1]!!.value
}
val url = super.searchMangaRequest(page, query, filters).url.newBuilder()
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) {
is AgeList -> filter.state.forEach { age ->
if (age.state != Filter.TriState.STATE_IGNORE) {
url.addQueryParameter(
if (age.isIncluded()) "caution[include][]" else "caution[exclude][]",
age.id,
)
}
}
is TagList -> filter.state.forEach { tag ->
if (tag.state != Filter.TriState.STATE_IGNORE) {
url.addQueryParameter(
if (tag.isIncluded()) "tags[include][]" else "tags[exclude][]",
tag.id,
)
}
}
else -> {}
}
}
return POST(url.toString(), catalogHeaders())
}
// Filters
private class SearchFilter(name: String, val id: String) : Filter.TriState(name)
private class TagList(tags: List<SearchFilter>) : Filter.Group<SearchFilter>("Теги", tags)
private class AgeList(ages: List<SearchFilter>) : Filter.Group<SearchFilter>("Возрастное ограничение", ages)
override fun getFilterList(): FilterList {
val filters = super.getFilterList().toMutableList()
filters.add(4, TagList(getTagList()))
filters.add(7, AgeList(getAgeList()))
return FilterList(filters)
}
private fun getTagList() = listOf(
SearchFilter("Азартные игры", "304"),
SearchFilter("Алхимия", "225"),
SearchFilter("Ангелы", "226"),
SearchFilter("Антигерой", "175"),
SearchFilter("Антиутопия", "227"),
SearchFilter("Апокалипсис", "228"),
SearchFilter("Армия", "229"),
SearchFilter("Артефакты", "230"),
SearchFilter("Боги", "215"),
SearchFilter("Бои на мечах", "231"),
SearchFilter("Борьба за власть", "231"),
SearchFilter("Брат и сестра", "233"),
SearchFilter("Будущее", "234"),
SearchFilter("Ведьма", "338"),
SearchFilter("Вестерн", "235"),
SearchFilter("Видеоигры", "185"),
SearchFilter("Виртуальная реальность", "195"),
SearchFilter("Владыка демонов", "236"),
SearchFilter("Военные", "179"),
SearchFilter("Война", "237"),
SearchFilter("Волшебники / маги", "281"),
SearchFilter("Волшебные существа", "239"),
SearchFilter("Воспоминания из другого мира", "240"),
SearchFilter("Выживание", "193"),
SearchFilter("ГГ женщина", "243"),
SearchFilter("ГГ имба", "291"),
SearchFilter("ГГ мужчина", "244"),
SearchFilter("Геймеры", "241"),
SearchFilter("Гильдии", "242"),
SearchFilter("Глупый ГГ", "297"),
SearchFilter("Гоблины", "245"),
SearchFilter("Горничные", "169"),
SearchFilter("Гяру", "178"),
SearchFilter("Демоны", "151"),
SearchFilter("Драконы", "246"),
SearchFilter("Дружба", "247"),
SearchFilter("Жестокий мир", "249"),
SearchFilter("Животные компаньоны", "250"),
SearchFilter("Завоевание мира", "251"),
SearchFilter("Зверолюди", "162"),
SearchFilter("Злые духи", "252"),
SearchFilter("Зомби", "149"),
SearchFilter("Игровые элементы", "253"),
SearchFilter("Империи", "254"),
SearchFilter("Квесты", "255"),
SearchFilter("Космос", "256"),
SearchFilter("Кулинария", "152"),
SearchFilter("Культивация", "160"),
SearchFilter("Легендарное оружие", "257"),
SearchFilter("Лоли", "187"),
SearchFilter("Магическая академия", "258"),
SearchFilter("Магия", "168"),
SearchFilter("Мафия", "172"),
SearchFilter("Медицина", "153"),
SearchFilter("Месть", "259"),
SearchFilter("Монстр Девушки", "188"),
SearchFilter("Монстры", "189"),
SearchFilter("Музыка", "190"),
SearchFilter("Навыки / способности", "260"),
SearchFilter("Насилие / жестокость", "262"),
SearchFilter("Наёмники", "261"),
SearchFilter("Нежить", "263"),
SearchFilter("Ниндая", "180"),
SearchFilter("Обратный Гарем", "191"),
SearchFilter("Огнестрельное оружие", "264"),
SearchFilter("Офисные Работники", "181"),
SearchFilter("Пародия", "265"),
SearchFilter("Пираты", "340"),
SearchFilter("Подземелья", "266"),
SearchFilter("Политика", "267"),
SearchFilter("Полиция", "182"),
SearchFilter("Преступники / Криминал", "186"),
SearchFilter("Призраки / Духи", "177"),
SearchFilter("Путешествие во времени", "194"),
SearchFilter("Разумные расы", "268"),
SearchFilter("Ранги силы", "248"),
SearchFilter("Реинкарнация", "148"),
SearchFilter("Роботы", "269"),
SearchFilter("Рыцари", "270"),
SearchFilter("Самураи", "183"),
SearchFilter("Система", "271"),
SearchFilter("Скрытие личности", "273"),
SearchFilter("Спасение мира", "274"),
SearchFilter("Спортивное тело", "334"),
SearchFilter("Средневековье", "173"),
SearchFilter("Стимпанк", "272"),
SearchFilter("Супергерои", "275"),
SearchFilter("Традиционные игры", "184"),
SearchFilter("Умный ГГ", "302"),
SearchFilter("Учитель / ученик", "276"),
SearchFilter("Философия", "277"),
SearchFilter("Хикикомори", "166"),
SearchFilter("Холодное оружие", "278"),
SearchFilter("Шантаж", "279"),
SearchFilter("Эльфы", "216"),
SearchFilter("Якудза", "164"),
SearchFilter("Япония", "280"),
)
private fun getAgeList() = listOf(
SearchFilter("Отсутствует", "0"),
SearchFilter("16+", "1"),
SearchFilter("18+", "2"),
)
override val siteId: Int = 2 // Important in api calls
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
super.setupPreferenceScreen(screen)
@ -180,7 +27,7 @@ class YaoiLib : LibGroup("YaoiLib", "https://v2.slashlib.me", "ru") {
summary = domain
this.setDefaultValue(DOMAIN_DEFAULT)
dialogTitle = DOMAIN_TITLE
setOnPreferenceChangeListener { _, newValue ->
setOnPreferenceChangeListener { _, _ ->
val warning = "Для смены домена необходимо перезапустить приложение с полной остановкой."
Toast.makeText(screen.context, warning, Toast.LENGTH_LONG).show()
true
@ -189,9 +36,7 @@ class YaoiLib : LibGroup("YaoiLib", "https://v2.slashlib.me", "ru") {
}
companion object {
const val PREFIX_SLUG_SEARCH = "slug:"
private const val DOMAIN_TITLE = "Домен"
private const val DOMAIN_DEFAULT = "https://v2.slashlib.me"
private const val DOMAIN_DEFAULT = "https://test-front.slashlib.me"
}
}