remove Anchira (#3339)
This commit is contained in:
parent
9e8bbb7f0b
commit
7348576150
|
@ -1,22 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<application>
|
|
||||||
<activity
|
|
||||||
android:name=".en.anchira.AnchiraUrlActivity"
|
|
||||||
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:scheme="https" />
|
|
||||||
<data android:host="anchira.to" />
|
|
||||||
<data android:pathPattern="/g/.*/..*" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
</application>
|
|
||||||
</manifest>
|
|
|
@ -1,8 +0,0 @@
|
||||||
ext {
|
|
||||||
extName = 'Anchira'
|
|
||||||
extClass = '.Anchira'
|
|
||||||
extVersionCode = 14
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
Binary file not shown.
Before Width: | Height: | Size: 9.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 4.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
Before Width: | Height: | Size: 31 KiB |
Binary file not shown.
Before Width: | Height: | Size: 52 KiB |
|
@ -1,477 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.anchira
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import androidx.preference.ListPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import androidx.preference.SwitchPreferenceCompat
|
|
||||||
import eu.kanade.tachiyomi.extension.en.anchira.AnchiraHelper.createChapter
|
|
||||||
import eu.kanade.tachiyomi.extension.en.anchira.AnchiraHelper.getCdn
|
|
||||||
import eu.kanade.tachiyomi.extension.en.anchira.AnchiraHelper.getPathFromUrl
|
|
||||||
import eu.kanade.tachiyomi.extension.en.anchira.AnchiraHelper.prepareTags
|
|
||||||
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.model.UpdateStrategy
|
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.decodeFromStream
|
|
||||||
import okhttp3.HttpUrl
|
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import okhttp3.Response
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import kotlin.math.ceil
|
|
||||||
import kotlin.math.min
|
|
||||||
|
|
||||||
class Anchira : HttpSource(), ConfigurableSource {
|
|
||||||
override val name = "Anchira"
|
|
||||||
|
|
||||||
override val baseUrl = "https://anchira.to"
|
|
||||||
|
|
||||||
private val apiUrl = baseUrl.replace("://", "://api.")
|
|
||||||
|
|
||||||
private val libraryUrl = "$apiUrl/library"
|
|
||||||
|
|
||||||
private val cdnUrl = "https://kisakisexo.xyz"
|
|
||||||
|
|
||||||
override val lang = "en"
|
|
||||||
|
|
||||||
override val supportsLatest = true
|
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
|
||||||
.rateLimit(3, 1, TimeUnit.SECONDS)
|
|
||||||
.addInterceptor { resampledInterceptor(it) }
|
|
||||||
.build()
|
|
||||||
|
|
||||||
private val json = Json { ignoreUnknownKeys = true }
|
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
|
||||||
.add("Referer", "$baseUrl/")
|
|
||||||
.add("Origin", baseUrl)
|
|
||||||
|
|
||||||
// Latest
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = GET("$libraryUrl?page=$page", headers)
|
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
|
||||||
// Ugly but it works
|
|
||||||
anchiraData.isNotEmpty()
|
|
||||||
|
|
||||||
val data = json.decodeFromString<LibraryResponse>(response.body.string())
|
|
||||||
|
|
||||||
return MangasPage(
|
|
||||||
data.entries.map {
|
|
||||||
SManga.create().apply {
|
|
||||||
url = "/g/${it.id}/${it.key}"
|
|
||||||
title = it.title
|
|
||||||
thumbnail_url = "$cdnUrl/${it.id}/${it.key}/m/${it.cover?.name}"
|
|
||||||
val art = it.tags.filter { it.namespace == 1 }.joinToString(", ") { it.name }
|
|
||||||
.ifEmpty { null }
|
|
||||||
artist = art
|
|
||||||
author = it.tags.filter { it.namespace == 2 }.joinToString(", ") { it.name }
|
|
||||||
.ifEmpty { art }
|
|
||||||
genre = prepareTags(it.tags, preferences.useTagGrouping)
|
|
||||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
|
||||||
status = SManga.COMPLETED
|
|
||||||
}
|
|
||||||
}.toList(),
|
|
||||||
data.page * data.limit < data.total,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Popular
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$libraryUrl?sort=32&page=$page", headers)
|
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response) = latestUpdatesParse(response)
|
|
||||||
|
|
||||||
// Search
|
|
||||||
|
|
||||||
override fun fetchSearchManga(
|
|
||||||
page: Int,
|
|
||||||
query: String,
|
|
||||||
filters: FilterList,
|
|
||||||
): Observable<MangasPage> {
|
|
||||||
return if (query.startsWith(SLUG_SEARCH_PREFIX)) {
|
|
||||||
// url deep link
|
|
||||||
val idKey = query.substringAfter(SLUG_SEARCH_PREFIX)
|
|
||||||
val manga = SManga.create().apply { this.url = "/g/$idKey" }
|
|
||||||
fetchMangaDetails(manga).map {
|
|
||||||
MangasPage(listOf(it), false)
|
|
||||||
}
|
|
||||||
} else if (query.startsWith(SLUG_BUNDLE_PREFIX)) {
|
|
||||||
// bundle entries as chapters
|
|
||||||
val url = applyFilters(
|
|
||||||
page,
|
|
||||||
query.substringAfter(SLUG_BUNDLE_PREFIX),
|
|
||||||
filters,
|
|
||||||
).removeAllQueryParameters("page")
|
|
||||||
val manga = SManga.create()
|
|
||||||
.apply { this.url = "?${url.build().query}" }
|
|
||||||
fetchMangaDetails(manga).map {
|
|
||||||
MangasPage(listOf(it), false)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// regular filtering without text search
|
|
||||||
client.newCall(searchMangaRequest(page, query, filters))
|
|
||||||
.asObservableSuccess()
|
|
||||||
.map(::searchMangaParse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
|
||||||
GET(applyFilters(page, query, filters).build(), headers)
|
|
||||||
|
|
||||||
private fun applyFilters(page: Int, query: String, filters: FilterList): HttpUrl.Builder {
|
|
||||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
|
||||||
val trendingFilter = filterList.findInstance<TrendingFilter>()
|
|
||||||
val sortTrendingFilter = filters.findInstance<SortTrendingFilter>()
|
|
||||||
var url = libraryUrl.toHttpUrl().newBuilder()
|
|
||||||
|
|
||||||
if (trendingFilter?.state == true) {
|
|
||||||
val interval = when (sortTrendingFilter?.state) {
|
|
||||||
1 -> "3"
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (interval.isNotBlank()) url.setQueryParameter("interval", interval)
|
|
||||||
|
|
||||||
url = url.toString().replace("library", "trending").toHttpUrl()
|
|
||||||
.newBuilder()
|
|
||||||
} else {
|
|
||||||
if (query.isNotBlank()) {
|
|
||||||
url.setQueryParameter("s", query)
|
|
||||||
}
|
|
||||||
|
|
||||||
filters.forEach { filter ->
|
|
||||||
when (filter) {
|
|
||||||
is CategoryGroup -> {
|
|
||||||
var sum = 0
|
|
||||||
|
|
||||||
filter.state.forEach { category ->
|
|
||||||
when (category.name) {
|
|
||||||
"Manga" -> if (category.state) sum = sum or 1
|
|
||||||
"Doujinshi" -> if (category.state) sum = sum or 2
|
|
||||||
"Illustration" -> if (category.state) sum = sum or 4
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sum > 0) url.setQueryParameter("cat", sum.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
is SortFilter -> {
|
|
||||||
val sort = when (filter.state?.index) {
|
|
||||||
0 -> "1"
|
|
||||||
1 -> "2"
|
|
||||||
2 -> "4"
|
|
||||||
4 -> "32"
|
|
||||||
else -> ""
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sort.isNotEmpty()) url.setQueryParameter("sort", sort)
|
|
||||||
if (filter.state?.ascending == true) url.setQueryParameter("order", "1")
|
|
||||||
}
|
|
||||||
|
|
||||||
is FavoritesFilter -> {
|
|
||||||
if (filter.state) {
|
|
||||||
if (!isLoggedIn()) {
|
|
||||||
throw IOException("No login cookie found")
|
|
||||||
}
|
|
||||||
|
|
||||||
url = url.toString().replace("library", "user/favorites").toHttpUrl()
|
|
||||||
.newBuilder()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (page > 1) {
|
|
||||||
url.setQueryParameter("page", page.toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
return url
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
|
|
||||||
|
|
||||||
// Details
|
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
|
||||||
return if (manga.url.startsWith("?")) {
|
|
||||||
GET(libraryUrl + manga.url, headers)
|
|
||||||
} else {
|
|
||||||
GET("$libraryUrl/${getPathFromUrl(manga.url)}", headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
|
||||||
return if (response.request.url.pathSegments.count() == libraryUrl.toHttpUrl().pathSegments.count()) {
|
|
||||||
val manga = latestUpdatesParse(response).mangas.first()
|
|
||||||
val query = response.request.url.queryParameter("s")
|
|
||||||
val cleanTitle = CHAPTER_SUFFIX_RE.replace(manga.title, "").trim()
|
|
||||||
manga.apply {
|
|
||||||
url = "?${response.request.url.query}"
|
|
||||||
description = "Bundled from $query"
|
|
||||||
title = "[Bundle] $cleanTitle"
|
|
||||||
update_strategy = UpdateStrategy.ALWAYS_UPDATE
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val data = json.decodeFromString<Entry>(response.body.string())
|
|
||||||
|
|
||||||
SManga.create().apply {
|
|
||||||
url = "/g/${data.id}/${data.key}"
|
|
||||||
title = data.title
|
|
||||||
thumbnail_url =
|
|
||||||
"$cdnUrl/${data.id}/${data.key}/l/${data.images[data.thumbnailIndex].name}"
|
|
||||||
val art = data.tags.filter { it.namespace == 1 }.joinToString(", ") { it.name }
|
|
||||||
.ifEmpty { null }
|
|
||||||
artist = art
|
|
||||||
author = data.tags.filter { it.namespace == 2 }.joinToString(", ") { it.name }
|
|
||||||
.ifEmpty { art }
|
|
||||||
genre = prepareTags(data.tags, preferences.useTagGrouping)
|
|
||||||
update_strategy = UpdateStrategy.ONLY_FETCH_ONCE
|
|
||||||
status = SManga.COMPLETED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga) =
|
|
||||||
if (preferences.openSource && !manga.url.startsWith("?")) {
|
|
||||||
val id = manga.url.split("/").reversed()[1].toInt()
|
|
||||||
anchiraData.find { it.id == id }?.url ?: "$baseUrl${manga.url}"
|
|
||||||
} else {
|
|
||||||
"$baseUrl${manga.url}"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Chapter
|
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
|
||||||
return if (manga.url.startsWith("?")) {
|
|
||||||
GET(libraryUrl + manga.url, headers)
|
|
||||||
} else {
|
|
||||||
GET("$libraryUrl/${getPathFromUrl(manga.url)}", headers)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
val chapterList = mutableListOf<SChapter>()
|
|
||||||
if (response.request.url.pathSegments.count() == libraryUrl.toHttpUrl().pathSegments.count()) {
|
|
||||||
var results = json.decodeFromString<LibraryResponse>(response.body.string())
|
|
||||||
val pages = min(5, ceil((results.total.toFloat() / results.limit)).toInt())
|
|
||||||
for (page in 1..pages) {
|
|
||||||
results.entries.forEach { data ->
|
|
||||||
chapterList.add(
|
|
||||||
createChapter(data, anchiraData),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (page < pages) {
|
|
||||||
results = json.decodeFromString<LibraryResponse>(
|
|
||||||
client.newCall(
|
|
||||||
GET(
|
|
||||||
response.request.url.newBuilder()
|
|
||||||
.setQueryParameter("page", (page + 1).toString()).build(),
|
|
||||||
headers,
|
|
||||||
),
|
|
||||||
).execute().body.string(),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val data = json.decodeFromString<Entry>(response.body.string())
|
|
||||||
chapterList.add(
|
|
||||||
createChapter(data, anchiraData),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return chapterList
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getChapterUrl(chapter: SChapter) = "$baseUrl/g/${getPathFromUrl(chapter.url)}"
|
|
||||||
|
|
||||||
// Page List
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter) =
|
|
||||||
GET("$libraryUrl/${getPathFromUrl(chapter.url)}", headers)
|
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
|
||||||
val data = json.decodeFromString<Entry>(response.body.string())
|
|
||||||
val imageData = getImageData(data)
|
|
||||||
|
|
||||||
return data.images.mapIndexed { i, image ->
|
|
||||||
Page(
|
|
||||||
i,
|
|
||||||
imageUrl = "${getCdn(i)}/${imageData.id}/${imageData.key}/${imageData.hash}/b/${image.name}",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getImageData(entry: Entry): ImageData {
|
|
||||||
val keys = anchiraData.find { it.id == entry.id }
|
|
||||||
|
|
||||||
if (keys?.key != null && keys.hash != null) {
|
|
||||||
return ImageData(keys.id, keys.key, keys.hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
val response =
|
|
||||||
client.newCall(GET("$libraryUrl/${entry.id}/${entry.key}/data", headers)).execute()
|
|
||||||
|
|
||||||
return json.decodeFromString(response.body.string())
|
|
||||||
} catch (_: IOException) {
|
|
||||||
throw IOException("Complete a Captcha in the site to continue")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageRequest(page: Page): Request {
|
|
||||||
return GET(page.imageUrl!!.replace("/b/", "/${preferences.imageQuality}/"), headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
// Settings
|
|
||||||
|
|
||||||
@SuppressLint("SetTextI18n")
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val imageQualityPref = ListPreference(screen.context).apply {
|
|
||||||
key = IMAGE_QUALITY_PREF
|
|
||||||
title = "Image quality"
|
|
||||||
entries = arrayOf("Original", "Resampled")
|
|
||||||
entryValues = arrayOf("a", "b")
|
|
||||||
setDefaultValue("b")
|
|
||||||
summary = "%s"
|
|
||||||
}
|
|
||||||
|
|
||||||
val openSourcePref = SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = OPEN_SOURCE_PREF
|
|
||||||
title = "Open source website in WebView"
|
|
||||||
summary =
|
|
||||||
"Enable to open the original source website of the gallery (if available) instead of Anchira."
|
|
||||||
setDefaultValue(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val useTagGrouping = SwitchPreferenceCompat(screen.context).apply {
|
|
||||||
key = USE_TAG_GROUPING
|
|
||||||
title = "Group tags"
|
|
||||||
summary =
|
|
||||||
"Enable to group tags together by artist, circle, parody, magazine and general tags"
|
|
||||||
setDefaultValue(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
screen.addPreference(imageQualityPref)
|
|
||||||
screen.addPreference(openSourcePref)
|
|
||||||
screen.addPreference(useTagGrouping)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
|
||||||
CategoryGroup(),
|
|
||||||
SortFilter(),
|
|
||||||
FavoritesFilter(),
|
|
||||||
Filter.Separator(),
|
|
||||||
Filter.Header("Others are ignored if trending only"),
|
|
||||||
TrendingFilter(),
|
|
||||||
SortTrendingFilter(),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class CategoryFilter(name: String) : Filter.CheckBox(name, false)
|
|
||||||
|
|
||||||
private class FavoritesFilter : Filter.CheckBox(
|
|
||||||
"Show only my favorites",
|
|
||||||
)
|
|
||||||
|
|
||||||
private class CategoryGroup : Filter.Group<CategoryFilter>(
|
|
||||||
"Categories",
|
|
||||||
listOf("Manga", "Doujinshi", "Illustration").map { CategoryFilter(it) },
|
|
||||||
)
|
|
||||||
|
|
||||||
private class SortFilter : Filter.Sort(
|
|
||||||
"Sort",
|
|
||||||
arrayOf("Title", "Pages", "Date uploaded", "Date published", "Popularity"),
|
|
||||||
Selection(2, false),
|
|
||||||
)
|
|
||||||
|
|
||||||
private class TrendingFilter : Filter.CheckBox(
|
|
||||||
"Show only trending",
|
|
||||||
)
|
|
||||||
|
|
||||||
private class SortTrendingFilter : PartFilter(
|
|
||||||
"Sort By",
|
|
||||||
arrayOf("Trending: Weekly", "Trending: Monthly"),
|
|
||||||
)
|
|
||||||
|
|
||||||
private open class PartFilter(displayName: String, value: Array<String>) :
|
|
||||||
Filter.Select<String>(displayName, value)
|
|
||||||
|
|
||||||
private val SharedPreferences.imageQuality
|
|
||||||
get() = getString(IMAGE_QUALITY_PREF, "b")!!
|
|
||||||
|
|
||||||
private val SharedPreferences.openSource
|
|
||||||
get() = getBoolean(OPEN_SOURCE_PREF, false)
|
|
||||||
|
|
||||||
private val SharedPreferences.useTagGrouping
|
|
||||||
get() = getBoolean(USE_TAG_GROUPING, false)
|
|
||||||
|
|
||||||
private fun resampledInterceptor(chain: Interceptor.Chain): Response {
|
|
||||||
val request = chain.request()
|
|
||||||
val url = request.url.toString()
|
|
||||||
|
|
||||||
return if (url.contains("sexo.xyz")) {
|
|
||||||
val response = chain.proceed(request)
|
|
||||||
|
|
||||||
if (response.isSuccessful) {
|
|
||||||
return response
|
|
||||||
} else if (url.contains("/b/")) {
|
|
||||||
return chain.proceed(request.newBuilder().url(url.replace("/b/", "/a/")).build())
|
|
||||||
}
|
|
||||||
|
|
||||||
throw IOException("An error occurred while loading the image - ${response.code}")
|
|
||||||
} else {
|
|
||||||
chain.proceed(request)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun isLoggedIn() = client.cookieJar.loadForRequest(baseUrl.toHttpUrl()).any {
|
|
||||||
it.name == "session"
|
|
||||||
}
|
|
||||||
|
|
||||||
private val anchiraData by lazy {
|
|
||||||
client.newCall(GET(DATA_JSON, headers)).execute()
|
|
||||||
.use { json.decodeFromStream<List<EntryKey>>(it.body.byteStream()) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val SLUG_SEARCH_PREFIX = "id:"
|
|
||||||
private const val SLUG_BUNDLE_PREFIX = "bundle:"
|
|
||||||
private const val IMAGE_QUALITY_PREF = "image_quality"
|
|
||||||
private const val OPEN_SOURCE_PREF = "use_manga_source"
|
|
||||||
private const val USE_TAG_GROUPING = "use_tag_grouping"
|
|
||||||
private const val DATA_JSON =
|
|
||||||
"https://raw.githubusercontent.com/LetrixZ/gallery-data/main/extension_data.min.json"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val CHAPTER_SUFFIX_RE =
|
|
||||||
Regex("\\W*(?:Ch\\.?|Chapter|Part|Vol\\.?|Volume|#)?\\W?(?<!20\\d{2}-?)\\b[\\d.]{1,4}\\W?")
|
|
|
@ -1,53 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.anchira
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Tag(
|
|
||||||
var name: String,
|
|
||||||
var namespace: Int? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class LibraryResponse(
|
|
||||||
val entries: List<Entry> = emptyList(),
|
|
||||||
val total: Int,
|
|
||||||
val page: Int,
|
|
||||||
val limit: Int,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Entry(
|
|
||||||
val id: Int,
|
|
||||||
val key: String,
|
|
||||||
@SerialName("published_at") val publishedAt: Long = 0L,
|
|
||||||
val title: String,
|
|
||||||
@SerialName("thumb_index") val thumbnailIndex: Int = 0,
|
|
||||||
val tags: List<Tag> = emptyList(),
|
|
||||||
val url: String? = null,
|
|
||||||
val pages: Int = 1,
|
|
||||||
val cover: Image? = null,
|
|
||||||
@SerialName("data")
|
|
||||||
val images: List<Image> = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class ImageData(
|
|
||||||
val id: Int,
|
|
||||||
val key: String,
|
|
||||||
val hash: String,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class EntryKey(
|
|
||||||
val id: Int,
|
|
||||||
val key: String? = null,
|
|
||||||
val hash: String? = null,
|
|
||||||
val url: String? = null,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
class Image(
|
|
||||||
@SerialName("n") val name: String,
|
|
||||||
)
|
|
|
@ -1,62 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.anchira
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
object AnchiraHelper {
|
|
||||||
fun getPathFromUrl(url: String) = "${url.split("/").reversed()[1]}/${url.split("/").last()}"
|
|
||||||
|
|
||||||
fun prepareTags(tags: List<Tag>, group: Boolean) = tags.map {
|
|
||||||
if (it.namespace == null) {
|
|
||||||
it.namespace = 6
|
|
||||||
}
|
|
||||||
it
|
|
||||||
}
|
|
||||||
.sortedBy { it.name }
|
|
||||||
.sortedBy { it.namespace }
|
|
||||||
.map {
|
|
||||||
val tag = it.name.lowercase()
|
|
||||||
return@map if (group) {
|
|
||||||
when (it.namespace) {
|
|
||||||
1 -> "artist:$tag"
|
|
||||||
2 -> "circle:$tag"
|
|
||||||
3 -> "parody:$tag"
|
|
||||||
4 -> "magazine:$tag"
|
|
||||||
else -> "tag:$tag"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
tag
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.joinToString(", ") { it }
|
|
||||||
|
|
||||||
fun createChapter(entry: Entry, anchiraData: List<EntryKey>) =
|
|
||||||
SChapter.create().apply {
|
|
||||||
val chSuffix = CHAPTER_SUFFIX_RE.find(entry.title)?.value.orEmpty()
|
|
||||||
val chNumber =
|
|
||||||
chSuffix.replace(Regex("[^.\\d]"), "").trim('.').takeUnless { it.isEmpty() } ?: "1"
|
|
||||||
val source = Regex("fakku|irodori").find(
|
|
||||||
anchiraData.find { it.id == entry.id }?.url.orEmpty(),
|
|
||||||
)?.value.orEmpty().titleCase()
|
|
||||||
url = "/g/${entry.id}/${entry.key}"
|
|
||||||
name = "$chNumber. ${entry.title.removeSuffix(chSuffix)}"
|
|
||||||
date_upload = entry.publishedAt * 1000
|
|
||||||
chapter_number = chNumber.toFloat()
|
|
||||||
scanlator = buildString {
|
|
||||||
if (source.isNotEmpty()) {
|
|
||||||
append("$source - ")
|
|
||||||
}
|
|
||||||
append("${entry.pages} pages")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getCdn(page: Int) = if (page % 2 == 0) "https://kisakisexo.xyz" else "https://aronasexo.xyz"
|
|
||||||
|
|
||||||
private fun String.titleCase() = replaceFirstChar {
|
|
||||||
if (it.isLowerCase()) {
|
|
||||||
it.titlecase(Locale.getDefault())
|
|
||||||
} else {
|
|
||||||
it.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.anchira
|
|
||||||
|
|
||||||
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 AnchiraUrlActivity : Activity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val pathSegments = intent?.data?.pathSegments
|
|
||||||
if (pathSegments != null && pathSegments.size > 2) {
|
|
||||||
val id = pathSegments[1]
|
|
||||||
val key = pathSegments[2]
|
|
||||||
val mainIntent = Intent().apply {
|
|
||||||
action = "eu.kanade.tachiyomi.SEARCH"
|
|
||||||
putExtra("query", "${Anchira.SLUG_SEARCH_PREFIX}$id/$key")
|
|
||||||
putExtra("filter", packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
startActivity(mainIntent)
|
|
||||||
} catch (e: ActivityNotFoundException) {
|
|
||||||
Log.e("AnchiraUrlActivity", e.toString())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Log.e("AnchiraUrlActivity", "could not parse uri from intent $intent")
|
|
||||||
}
|
|
||||||
|
|
||||||
finish()
|
|
||||||
exitProcess(0)
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue