Remove 3 dead sources (#8502)
Remove AsuraScansFree, Inmoral No FanSub, YuriNeko
@ -1,10 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Asura Scans Free (unoriginal)'
|
|
||||||
extClass = '.AsuraScansFree'
|
|
||||||
themePkg = 'mangathemesia'
|
|
||||||
baseUrl = 'https://asurascansfree.com'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 6.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 20 KiB |
@ -1,10 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.en.asurascansfree
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
||||||
|
|
||||||
class AsuraScansFree : MangaThemesia(
|
|
||||||
"Asura Scans Free (unoriginal)",
|
|
||||||
"https://asurascansfree.com",
|
|
||||||
"en",
|
|
||||||
"/serie",
|
|
||||||
)
|
|
@ -1,10 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Inmoral No Fansub'
|
|
||||||
extClass = '.InmoralNoFansub'
|
|
||||||
themePkg = 'madara'
|
|
||||||
baseUrl = 'https://inmoralnofansub.xyz'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 21 KiB |
Before Width: | Height: | Size: 34 KiB |
@ -1,21 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.es.inmoralnofansub
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
class InmoralNoFansub : Madara(
|
|
||||||
"Inmoral No Fansub",
|
|
||||||
"https://inmoralnofansub.xyz",
|
|
||||||
"es",
|
|
||||||
SimpleDateFormat("dd/MM/yyyy", Locale("es")),
|
|
||||||
) {
|
|
||||||
override val useLoadMoreRequest = LoadMoreStrategy.Always
|
|
||||||
override val useNewChapterEndpoint = true
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.rateLimit(2)
|
|
||||||
.build()
|
|
||||||
}
|
|
@ -1,27 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<application>
|
|
||||||
<activity
|
|
||||||
android:name=".vi.yurineko.YuriNekoUrlActivity"
|
|
||||||
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="yurineko.site"
|
|
||||||
android:scheme="https" />
|
|
||||||
|
|
||||||
<data android:pathPattern="/manga/..*" />
|
|
||||||
<data android:pathPattern="/origin/..*" />
|
|
||||||
<data android:pathPattern="/author/..*" />
|
|
||||||
<data android:pathPattern="/tag/..*" />
|
|
||||||
<data android:pathPattern="/couple/..*" />
|
|
||||||
<data android:pathPattern="/team/..*" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
@ -1,8 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'YuriNeko'
|
|
||||||
extClass = '.YuriNeko'
|
|
||||||
extVersionCode = 8
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 12 KiB |
@ -1,453 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko
|
|
||||||
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.preference.EditTextPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.extension.vi.yurineko.dto.ErrorResponseDto
|
|
||||||
import eu.kanade.tachiyomi.extension.vi.yurineko.dto.MangaDto
|
|
||||||
import eu.kanade.tachiyomi.extension.vi.yurineko.dto.MangaListDto
|
|
||||||
import eu.kanade.tachiyomi.extension.vi.yurineko.dto.ReadResponseDto
|
|
||||||
import eu.kanade.tachiyomi.extension.vi.yurineko.dto.UserDto
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
|
||||||
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.HttpSource
|
|
||||||
import keiyoushi.utils.getPreferences
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import okhttp3.CacheControl
|
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import rx.Observable
|
|
||||||
import java.io.IOException
|
|
||||||
import java.net.URLDecoder
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class YuriNeko : HttpSource(), ConfigurableSource {
|
|
||||||
|
|
||||||
override val name = "YuriNeko"
|
|
||||||
|
|
||||||
private val defaultDomain = "yurineko.site"
|
|
||||||
|
|
||||||
override val baseUrl by lazy { "https://${getPrefDomain()}" }
|
|
||||||
|
|
||||||
override val lang = "vi"
|
|
||||||
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val apiUrl by lazy { "https://api.${getPrefDomain()}" }
|
|
||||||
|
|
||||||
private val storageUrl by lazy { "https://storage.${getPrefDomain()}" }
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
|
||||||
.rateLimit(3, 1, TimeUnit.SECONDS)
|
|
||||||
.addInterceptor(::authIntercept)
|
|
||||||
.addInterceptor(::errorIntercept)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun headersBuilder() = Headers.Builder().add("Referer", "$baseUrl/")
|
|
||||||
|
|
||||||
private fun authIntercept(chain: Interceptor.Chain): Response {
|
|
||||||
val request = chain.request()
|
|
||||||
val cookies = client.cookieJar.loadForRequest(baseUrl.toHttpUrl())
|
|
||||||
val authCookie = cookies
|
|
||||||
.firstOrNull { it.name == "user" }
|
|
||||||
?.let { URLDecoder.decode(it.value, "UTF-8") }
|
|
||||||
?.let { json.decodeFromString<UserDto>(it) }
|
|
||||||
?: return chain.proceed(request)
|
|
||||||
|
|
||||||
val authRequest = request.newBuilder().apply {
|
|
||||||
addHeader("Authorization", "Bearer ${authCookie.token}")
|
|
||||||
}.build()
|
|
||||||
return chain.proceed(authRequest)
|
|
||||||
}
|
|
||||||
private fun errorIntercept(chain: Interceptor.Chain): Response {
|
|
||||||
val response = chain.proceed(chain.request())
|
|
||||||
|
|
||||||
if (response.code >= 400) {
|
|
||||||
val error = try {
|
|
||||||
response.parseAs<ErrorResponseDto>()
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
response.close()
|
|
||||||
throw IOException("${error.message}\nĐăng nhập qua WebView và thử lại.")
|
|
||||||
}
|
|
||||||
return response
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request = GET(
|
|
||||||
url = apiUrl.toHttpUrl().newBuilder().apply {
|
|
||||||
addPathSegment("lastest2")
|
|
||||||
addQueryParameter("page", page.toString())
|
|
||||||
}.build().toString(),
|
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
|
||||||
)
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
|
||||||
val mangaListDto = response.parseAs<MangaListDto>()
|
|
||||||
val currentPage = response.request.url.queryParameter("page")!!.toFloat()
|
|
||||||
return mangaListDto.toMangasPage(currentPage, storageUrl)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
|
||||||
return when {
|
|
||||||
query.startsWith(PREFIX_ID_SEARCH) -> {
|
|
||||||
val id = query.removePrefix(PREFIX_ID_SEARCH).trim()
|
|
||||||
if (id.toIntOrNull() == null) {
|
|
||||||
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
|
|
||||||
}
|
|
||||||
fetchMangaDetails(
|
|
||||||
SManga.create().apply {
|
|
||||||
url = "/manga/$id"
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map { MangasPage(listOf(it), false) }
|
|
||||||
}
|
|
||||||
else -> super.fetchSearchManga(page, query, filters)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
|
||||||
return when {
|
|
||||||
query.startsWith(PREFIX_TAG_SEARCH) ||
|
|
||||||
query.startsWith(PREFIX_COUPLE_SEARCH) ||
|
|
||||||
query.startsWith(PREFIX_DOUJIN_SEARCH) ||
|
|
||||||
query.startsWith(PREFIX_AUTHOR_SEARCH) ||
|
|
||||||
query.startsWith(PREFIX_TEAM_SEARCH) -> {
|
|
||||||
val items = query.split(":")
|
|
||||||
val searchType = items[0]
|
|
||||||
val actualQuery = items[1].trim()
|
|
||||||
if (actualQuery.toIntOrNull() == null) {
|
|
||||||
throw Exception("ID tìm kiếm không hợp lệ (phải là một số).")
|
|
||||||
}
|
|
||||||
GET(
|
|
||||||
apiUrl.toHttpUrl().newBuilder().apply {
|
|
||||||
addPathSegment("searchType")
|
|
||||||
addQueryParameter("type", searchType)
|
|
||||||
addQueryParameter("id", actualQuery)
|
|
||||||
addQueryParameter("page", page.toString())
|
|
||||||
}.build().toString(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
query.isNotEmpty() -> {
|
|
||||||
GET(
|
|
||||||
apiUrl.toHttpUrl().newBuilder().apply {
|
|
||||||
addPathSegment("search")
|
|
||||||
addQueryParameter("query", query)
|
|
||||||
addQueryParameter("page", page.toString())
|
|
||||||
}.build().toString(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
for (filter in (if (filters.isEmpty()) getFilterList() else filters)) {
|
|
||||||
when (filter) {
|
|
||||||
is UriPartFilter -> if (filter.state != 0) {
|
|
||||||
when (filter.name) {
|
|
||||||
"Tag" -> return GET(
|
|
||||||
apiUrl.toHttpUrl().newBuilder().apply {
|
|
||||||
addPathSegment("searchType")
|
|
||||||
addQueryParameter("type", "tag")
|
|
||||||
addQueryParameter("id", filter.toUriPart())
|
|
||||||
addQueryParameter("page", page.toString())
|
|
||||||
}.build().toString(),
|
|
||||||
)
|
|
||||||
else -> continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return popularMangaRequest(page)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
|
|
||||||
client.newCall(GET("$apiUrl${manga.url}"))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map { mangaDetailsParse(it) }
|
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl${manga.url}")
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga =
|
|
||||||
response.parseAs<MangaDto>().toSManga(storageUrl)
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request = GET("$apiUrl${manga.url}")
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
val mangaDto = response.parseAs<MangaDto>()
|
|
||||||
val scanlator = mangaDto.team.joinToString(", ") { it.name }
|
|
||||||
return mangaDto.chapters?.map { it.toSChapter(scanlator) } ?: emptyList()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request = GET("$apiUrl${chapter.url}")
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> =
|
|
||||||
response.parseAs<ReadResponseDto>().toPageList(storageUrl)
|
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
open class UriPartFilter(displayName: String, private val vals: Array<Pair<String, String>>) :
|
|
||||||
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
|
|
||||||
fun toUriPart() = vals[state].second
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
Filter.Header("Lưu ý rằng không thể vừa tìm kiếm vừa lọc bằng tag."),
|
|
||||||
Filter.Header("Tìm kiếm sẽ được ưu tiên."),
|
|
||||||
UriPartFilter("Tag", getGenreList()),
|
|
||||||
)
|
|
||||||
|
|
||||||
private fun getGenreList() = arrayOf(
|
|
||||||
Pair("Sao cũng được", "0"),
|
|
||||||
Pair("4-koma", "149"),
|
|
||||||
Pair(">", "306"),
|
|
||||||
Pair("Action", "113"),
|
|
||||||
Pair("Adventure", "114"),
|
|
||||||
Pair("Adult Life", "143"),
|
|
||||||
Pair("Animal Ears", "175"),
|
|
||||||
Pair("Age Gap", "179"),
|
|
||||||
Pair("Anal", "209"),
|
|
||||||
Pair("Ahegao", "211"),
|
|
||||||
Pair("Anime", "214"),
|
|
||||||
Pair("Amnesia", "242"),
|
|
||||||
Pair("Autobiographical", "255"),
|
|
||||||
Pair("Alien", "262"),
|
|
||||||
Pair("Amputee", "277"),
|
|
||||||
Pair("Assassin", "283"),
|
|
||||||
Pair("Angel", "298"),
|
|
||||||
Pair("Abuse", "300"),
|
|
||||||
Pair("Anilingus", "308"),
|
|
||||||
Pair("Blushing", "157"),
|
|
||||||
Pair("Body Swap", "158"),
|
|
||||||
Pair("Bisexual", "176"),
|
|
||||||
Pair("Birthday", "194"),
|
|
||||||
Pair("Big Breasts", "195"),
|
|
||||||
Pair("Butts", "196"),
|
|
||||||
Pair("BDSM", "199"),
|
|
||||||
Pair("Boob Sex", "210"),
|
|
||||||
Pair("Bath", "226"),
|
|
||||||
Pair("Bullying", "241"),
|
|
||||||
Pair("Biting", "270"),
|
|
||||||
Pair("Blackmail", "280"),
|
|
||||||
Pair("Biographical", "285"),
|
|
||||||
Pair("Beach", "289"),
|
|
||||||
Pair("BHTT", "304"),
|
|
||||||
Pair("Comedy", "115"),
|
|
||||||
Pair("College", "145"),
|
|
||||||
Pair("Co-worker", "180"),
|
|
||||||
Pair("Childhood Friends", "182"),
|
|
||||||
Pair("Christmas", "189"),
|
|
||||||
Pair("Creepy", "220"),
|
|
||||||
Pair("Childification", "239"),
|
|
||||||
Pair("Cheating", "267"),
|
|
||||||
Pair("Clones", "271"),
|
|
||||||
Pair("Cross-dressing", "288"),
|
|
||||||
Pair("Chibi", "307"),
|
|
||||||
Pair("Demon", "116"),
|
|
||||||
Pair("Drama", "117"),
|
|
||||||
Pair("Dark Skin", "208"),
|
|
||||||
Pair("Drunk", "219"),
|
|
||||||
Pair("Drugs", "236"),
|
|
||||||
Pair("Disability", "252"),
|
|
||||||
Pair("Delinquent", "258"),
|
|
||||||
Pair("Deity", "265"),
|
|
||||||
Pair("Depressing as fuck", "290"),
|
|
||||||
Pair("Ecchi", "118"),
|
|
||||||
Pair("Excuse me WTF?", "161"),
|
|
||||||
Pair("Exhibitionism", "245"),
|
|
||||||
Pair("Fantasy", "119"),
|
|
||||||
Pair("Full Color", "148"),
|
|
||||||
Pair("FBI Warning!!", "163"),
|
|
||||||
Pair("Futanari", "201"),
|
|
||||||
Pair("Food", "232"),
|
|
||||||
Pair("Feet", "256"),
|
|
||||||
Pair("Furry", "303"),
|
|
||||||
Pair("Game", "120"),
|
|
||||||
Pair("Gender Bender", "121"),
|
|
||||||
Pair("Glasses", "156"),
|
|
||||||
Pair("Guro", "206"),
|
|
||||||
Pair("Ghost", "244"),
|
|
||||||
Pair("Gyaru", "246"),
|
|
||||||
Pair("Harem", "122"),
|
|
||||||
Pair("Historical", "123"),
|
|
||||||
Pair("Horror", "124"),
|
|
||||||
Pair("Hints", "152"),
|
|
||||||
Pair("Het", "160"),
|
|
||||||
Pair("Halloween", "190"),
|
|
||||||
Pair("Hypnosis", "254"),
|
|
||||||
Pair("Height Gap", "281"),
|
|
||||||
Pair("Hardcore", "292"),
|
|
||||||
Pair("Isekai", "144"),
|
|
||||||
Pair("Idol", "169"),
|
|
||||||
Pair("Incest", "187"),
|
|
||||||
Pair("Idiot Couple", "282"),
|
|
||||||
Pair("Introspective", "286"),
|
|
||||||
Pair("Insane Amounts of Sex", "296"),
|
|
||||||
Pair("Kuudere", "235"),
|
|
||||||
Pair("Lỗi: không tìm thấy trai", "153"),
|
|
||||||
Pair("Love Triangle", "183"),
|
|
||||||
Pair("Loli", "197"),
|
|
||||||
Pair("Light Novel", "216"),
|
|
||||||
Pair("Lactation", "260"),
|
|
||||||
Pair("Lots of sex", "269"),
|
|
||||||
Pair("Martial Arts", "125"),
|
|
||||||
Pair("Mecha", "126"),
|
|
||||||
Pair("Military", "127"),
|
|
||||||
Pair("Music", "128"),
|
|
||||||
Pair("Mystery", "129"),
|
|
||||||
Pair("Manhua", "146"),
|
|
||||||
Pair("Manhwa", "147"),
|
|
||||||
Pair("Moe Paradise", "164"),
|
|
||||||
Pair("Mahou Shoujo", "168"),
|
|
||||||
Pair("Maid", "172"),
|
|
||||||
Pair("Monster Girl", "173"),
|
|
||||||
Pair("Marriage", "188"),
|
|
||||||
Pair("Massage", "204"),
|
|
||||||
Pair("Masturbation", "205"),
|
|
||||||
Pair("Mangaka", "227"),
|
|
||||||
Pair("Mermaid", "234"),
|
|
||||||
Pair("Moderate amounts of sex", "268"),
|
|
||||||
Pair("Miko", "301"),
|
|
||||||
Pair("No Text", "150"),
|
|
||||||
Pair("New Year's", "191"),
|
|
||||||
Pair("Netorare", "198"),
|
|
||||||
Pair("NSFW", "229"),
|
|
||||||
Pair("Ninja", "287"),
|
|
||||||
Pair("Non-moe art", "302"),
|
|
||||||
Pair("Office Lady", "174"),
|
|
||||||
Pair("Oneshot", "218"),
|
|
||||||
Pair("Official", "222"),
|
|
||||||
Pair("Orgy", "261"),
|
|
||||||
Pair("Omegaverse", "276"),
|
|
||||||
Pair("Parody", "130"),
|
|
||||||
Pair("Psychological", "131"),
|
|
||||||
Pair("Pay for Gay", "162"),
|
|
||||||
Pair("Polyamory", "185"),
|
|
||||||
Pair("Pocky Game", "212"),
|
|
||||||
Pair("Prostitution", "240"),
|
|
||||||
Pair("Player", "257"),
|
|
||||||
Pair("Prequel", "272"),
|
|
||||||
Pair("Post-Apocalyptic", "273"),
|
|
||||||
Pair("Philosophical", "274"),
|
|
||||||
Pair("R18", "1"),
|
|
||||||
Pair("Romance", "132"),
|
|
||||||
Pair("Reversal", "159"),
|
|
||||||
Pair("Roommates", "181"),
|
|
||||||
Pair("Rape", "203"),
|
|
||||||
Pair("Robot", "264"),
|
|
||||||
Pair("School Life", "133"),
|
|
||||||
Pair("Sci-Fi", "134"),
|
|
||||||
Pair("Slice of Life", "137"),
|
|
||||||
Pair("Sports", "138"),
|
|
||||||
Pair("Supernatural", "139"),
|
|
||||||
Pair("Science Babies", "165"),
|
|
||||||
Pair("Student x Teacher", "166"),
|
|
||||||
Pair("Siscon", "167"),
|
|
||||||
Pair("School Girl", "215"),
|
|
||||||
Pair("Spin-off", "223"),
|
|
||||||
Pair("Subtext", "231"),
|
|
||||||
Pair("Sleeping", "249"),
|
|
||||||
Pair("Sequel", "251"),
|
|
||||||
Pair("Swimsuits", "263"),
|
|
||||||
Pair("Stalking", "266"),
|
|
||||||
Pair("Space", "291"),
|
|
||||||
Pair("Spanking", "299"),
|
|
||||||
Pair("Tragedy", "142"),
|
|
||||||
Pair("Tomboy", "170"),
|
|
||||||
Pair("Tsundere", "177"),
|
|
||||||
Pair("Threesome", "184"),
|
|
||||||
Pair("Twins", "186"),
|
|
||||||
Pair("Thất Tịch", "193"),
|
|
||||||
Pair("Toys", "200"),
|
|
||||||
Pair("Tentacles", "202"),
|
|
||||||
Pair("Tailsex", "237"),
|
|
||||||
Pair("Time Travel", "243"),
|
|
||||||
Pair("Transgender", "284"),
|
|
||||||
Pair("Vampire", "140"),
|
|
||||||
Pair("Violence", "141"),
|
|
||||||
Pair("Valentine", "192"),
|
|
||||||
Pair("Watersports", "278"),
|
|
||||||
Pair("Wholesome", "279"),
|
|
||||||
Pair("Witch", "293"),
|
|
||||||
Pair("Web Novel", "305"),
|
|
||||||
Pair("Yuri", "151"),
|
|
||||||
Pair("Yankee", "171"),
|
|
||||||
Pair("Yandere", "178"),
|
|
||||||
Pair("Yuri Crush", "228"),
|
|
||||||
Pair("Yaoi", "230"),
|
|
||||||
Pair("Zombies", "238"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private val json = Json {
|
|
||||||
isLenient = true
|
|
||||||
ignoreUnknownKeys = true
|
|
||||||
prettyPrint = true
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Response.parseAs(): T = use {
|
|
||||||
json.decodeFromString(body.string())
|
|
||||||
}
|
|
||||||
private val preferences: SharedPreferences = getPreferences()
|
|
||||||
|
|
||||||
init {
|
|
||||||
preferences.getString(DEFAULT_DOMAIN_PREF, null).let { prefDefaultDomain ->
|
|
||||||
if (prefDefaultDomain != defaultDomain) {
|
|
||||||
preferences.edit()
|
|
||||||
.putString(BASE_DOMAIN_PREF, defaultDomain)
|
|
||||||
.putString(DEFAULT_DOMAIN_PREF, defaultDomain)
|
|
||||||
.apply()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
EditTextPreference(screen.context).apply {
|
|
||||||
key = BASE_DOMAIN_PREF
|
|
||||||
title = BASE_DOMAIN_PREF_TITLE
|
|
||||||
summary = BASE_DOMAIN_PREF_SUMMARY
|
|
||||||
setDefaultValue(defaultDomain)
|
|
||||||
dialogTitle = BASE_DOMAIN_PREF_TITLE
|
|
||||||
dialogMessage = "Default: $defaultDomain"
|
|
||||||
|
|
||||||
setOnPreferenceChangeListener { _, _ ->
|
|
||||||
Toast.makeText(screen.context, RESTART_APP, Toast.LENGTH_LONG).show()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}.let(screen::addPreference)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getPrefDomain(): String = preferences.getString(BASE_DOMAIN_PREF, defaultDomain)!!
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val PREFIX_ID_SEARCH = "id:"
|
|
||||||
const val PREFIX_TAG_SEARCH = "tag:"
|
|
||||||
const val PREFIX_TEAM_SEARCH = "team:"
|
|
||||||
const val PREFIX_AUTHOR_SEARCH = "author:"
|
|
||||||
const val PREFIX_DOUJIN_SEARCH = "origin:"
|
|
||||||
const val PREFIX_COUPLE_SEARCH = "couple:"
|
|
||||||
private const val DEFAULT_DOMAIN_PREF = "defaultDomain"
|
|
||||||
private const val RESTART_APP = "Khởi chạy lại ứng dụng để áp dụng thay đổi."
|
|
||||||
private const val BASE_DOMAIN_PREF_TITLE = "Ghi đè URL cơ sở"
|
|
||||||
private const val BASE_DOMAIN_PREF = "overrideDomain"
|
|
||||||
private const val BASE_DOMAIN_PREF_SUMMARY =
|
|
||||||
"Dành cho sử dụng tạm thời, cập nhật tiện ích sẽ xóa cài đặt."
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,44 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko
|
|
||||||
|
|
||||||
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 YuriNekoUrlActivity : Activity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val pathSegments = intent?.data?.pathSegments
|
|
||||||
if (pathSegments != null && pathSegments.size > 1) {
|
|
||||||
val id = pathSegments[1]
|
|
||||||
try {
|
|
||||||
startActivity(
|
|
||||||
Intent().apply {
|
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
|
||||||
with(pathSegments[0]) {
|
|
||||||
when {
|
|
||||||
equals("manga") -> putExtra("query", "${YuriNeko.PREFIX_ID_SEARCH}$id")
|
|
||||||
equals("origin") -> putExtra("query", "${YuriNeko.PREFIX_DOUJIN_SEARCH}$id")
|
|
||||||
equals("author") -> putExtra("query", "${YuriNeko.PREFIX_AUTHOR_SEARCH}$id")
|
|
||||||
equals("tag") -> putExtra("query", "${YuriNeko.PREFIX_TAG_SEARCH}$id")
|
|
||||||
equals("couple") -> putExtra("query", "${YuriNeko.PREFIX_COUPLE_SEARCH}$id")
|
|
||||||
equals("team") -> putExtra("query", "${YuriNeko.PREFIX_TEAM_SEARCH}$id")
|
|
||||||
else -> putExtra("query", "${YuriNeko.PREFIX_ID_SEARCH}$id")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
putExtra("filter", packageName)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Log.e("YuriNekoUrlActivity", e.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("YuriNekoUrlActivity", "Could not parse URI from intent $intent")
|
|
||||||
}
|
|
||||||
|
|
||||||
finish()
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,54 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko.dto
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
val DATE_FORMATTER = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
|
|
||||||
timeZone = TimeZone.getTimeZone("Asia/Ho_Chi_Minh")
|
|
||||||
}
|
|
||||||
|
|
||||||
val CHAPTER_NUMBER_REGEX = Regex("""[+\-]?([0-9]*[\.])?[0-9]+""", RegexOption.IGNORE_CASE)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ChapterDto(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val date: String? = null,
|
|
||||||
val mangaID: Int? = null,
|
|
||||||
val maxID: Int? = null,
|
|
||||||
val likeCount: Int? = null,
|
|
||||||
) {
|
|
||||||
fun toSChapter(teams: String): SChapter = SChapter.create().apply {
|
|
||||||
val dto = this@ChapterDto
|
|
||||||
url = "/read/${dto.mangaID}/${dto.id}"
|
|
||||||
name = dto.name
|
|
||||||
if (!dto.date.isNullOrEmpty()) {
|
|
||||||
date_upload = runCatching {
|
|
||||||
DATE_FORMATTER.parse(dto.date)?.time
|
|
||||||
}.getOrNull() ?: 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
val match = CHAPTER_NUMBER_REGEX.findAll(dto.name)
|
|
||||||
chapter_number = if (match.count() > 1 && dto.name.lowercase().startsWith("vol")) {
|
|
||||||
match.elementAt(1)
|
|
||||||
} else {
|
|
||||||
match.elementAtOrNull(0)
|
|
||||||
}?.value?.toFloat() ?: -1f
|
|
||||||
scanlator = teams
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ReadResponseDto(
|
|
||||||
val listChapter: List<ChapterDto>,
|
|
||||||
val chapterInfo: ChapterDto,
|
|
||||||
val url: List<String>,
|
|
||||||
) {
|
|
||||||
fun toPageList(storageUrl: String): List<Page> = this@ReadResponseDto
|
|
||||||
.url
|
|
||||||
.mapIndexed { index, url -> Page(index, imageUrl = storageUrl + url) }
|
|
||||||
}
|
|
@ -1,71 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko.dto
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import org.jsoup.Jsoup
|
|
||||||
import kotlin.math.ceil
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MangaDto(
|
|
||||||
val id: Int,
|
|
||||||
val originalName: String,
|
|
||||||
val otherName: String,
|
|
||||||
val description: String,
|
|
||||||
val status: Int,
|
|
||||||
val thumbnail: String,
|
|
||||||
val type: String,
|
|
||||||
val lastUpdate: String,
|
|
||||||
val totalView: Int? = null,
|
|
||||||
val totalFollow: Int? = null,
|
|
||||||
val likeCount: Int? = null,
|
|
||||||
val team: List<TagDto>,
|
|
||||||
val origin: List<TagDto>,
|
|
||||||
val author: List<TagDto>,
|
|
||||||
val tag: List<TagDto>,
|
|
||||||
val couple: List<TagDto>,
|
|
||||||
val lastChapter: ChapterDto? = null,
|
|
||||||
val chapters: List<ChapterDto>? = null,
|
|
||||||
) {
|
|
||||||
fun toSManga(storageUrl: String): SManga = SManga.create().apply {
|
|
||||||
val dto = this@MangaDto
|
|
||||||
url = "/manga/${dto.id}"
|
|
||||||
title = dto.originalName
|
|
||||||
author = dto.author.joinToString(", ") { author -> author.name }
|
|
||||||
val descElem = Jsoup.parseBodyFragment(dto.description).select("p")
|
|
||||||
.joinToString("\n") { it.wholeText() }.trim()
|
|
||||||
description = if (dto.otherName.isNotEmpty()) {
|
|
||||||
"Tên khác: ${dto.otherName}\n\n" + descElem
|
|
||||||
} else {
|
|
||||||
descElem
|
|
||||||
}
|
|
||||||
|
|
||||||
genre = dto.tag.joinToString(", ") { tag -> tag.name }
|
|
||||||
status = when (dto.status) {
|
|
||||||
1 -> SManga.UNKNOWN // "Chưa ra mắt" -> Not released
|
|
||||||
2 -> SManga.COMPLETED
|
|
||||||
3 -> SManga.UNKNOWN // "Sắp ra mắt" -> Upcoming
|
|
||||||
4 -> SManga.ONGOING
|
|
||||||
5 -> SManga.CANCELLED // "Ngừng dịch" -> source not translating it anymomre
|
|
||||||
6 -> SManga.ON_HIATUS
|
|
||||||
7 -> SManga.CANCELLED // "Ngừng xuất bản" -> No more publications
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
thumbnail_url = "$storageUrl/" + dto.thumbnail
|
|
||||||
initialized = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class MangaListDto(
|
|
||||||
val result: List<MangaDto>,
|
|
||||||
val resultCount: Int,
|
|
||||||
) {
|
|
||||||
fun toMangasPage(currentPage: Float = 1f, storageUrl: String): MangasPage {
|
|
||||||
val dto = this@MangaListDto
|
|
||||||
return MangasPage(
|
|
||||||
dto.result.map { it.toSManga(storageUrl) },
|
|
||||||
currentPage + 1f <= ceil(dto.resultCount.toFloat() / 20f),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko.dto
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ErrorResponseDto(
|
|
||||||
val message: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class UserDto(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val email: String,
|
|
||||||
val avatar: String,
|
|
||||||
val role: Int,
|
|
||||||
val money: Int,
|
|
||||||
val username: String,
|
|
||||||
val isBanned: Int,
|
|
||||||
val isPremium: Int,
|
|
||||||
val token: String,
|
|
||||||
)
|
|
@ -1,11 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.vi.yurineko.dto
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TagDto(
|
|
||||||
val id: Int,
|
|
||||||
val name: String,
|
|
||||||
val url: String,
|
|
||||||
val origin: String? = null,
|
|
||||||
)
|
|