diff --git a/src/all/pandachaika/AndroidManifest.xml b/src/all/pandachaika/AndroidManifest.xml new file mode 100644 index 000000000..3fa96d78f --- /dev/null +++ b/src/all/pandachaika/AndroidManifest.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + diff --git a/src/all/pandachaika/build.gradle b/src/all/pandachaika/build.gradle index 6c786858b..69fcca671 100644 --- a/src/all/pandachaika/build.gradle +++ b/src/all/pandachaika/build.gradle @@ -1,7 +1,7 @@ ext { extName = 'PandaChaika' extClass = '.PandaChaikaFactory' - extVersionCode = 1 + extVersionCode = 2 isNsfw = true } diff --git a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaika.kt b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaika.kt index 0ba95d632..a86187594 100644 --- a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaika.kt +++ b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaika.kt @@ -1,6 +1,8 @@ package eu.kanade.tachiyomi.extension.all.pandachaika import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservable +import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.Page @@ -42,6 +44,9 @@ class PandaChaika( private val json: Json by injectLazy() + private val fakkuRegex = Regex("""(?:https?://)?(?:www\.)?fakku\.net/hentai/""") + private val ehentaiRegex = Regex("""(?:https?://)?e-hentai\.org/g/""") + // Popular override fun popularMangaRequest(page: Int): Request { return GET("$baseSearchUrl/?tags=$searchLang&sort=rating&apply=&json=&page=$page", headers) @@ -73,6 +78,76 @@ class PandaChaika( } } + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return when { + query.startsWith(PREFIX_ID_SEARCH) -> { + val id = query.removePrefix(PREFIX_ID_SEARCH).toInt() + client.newCall(GET("$baseUrl/api?archive=$id", headers)) + .asObservable() + .map { response -> + searchMangaByIdParse(response, id) + } + } + query.startsWith(PREFIX_EHEN_ID_SEARCH) -> { + val id = query.removePrefix(PREFIX_EHEN_ID_SEARCH).replace(ehentaiRegex, "") + val baseLink = "https://e-hentai.org/g/" + val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("qsearch", baseLink + id) + addQueryParameter("json", "") + }.build() + client.newCall(GET(fullLink, headers)) + .asObservableSuccess() + .map { + val archive = it.parseAs().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found") + MangasPage(listOf(archive), false) + } + } + query.startsWith(PREFIX_FAK_ID_SEARCH) -> { + val slug = query.removePrefix(PREFIX_FAK_ID_SEARCH).replace(fakkuRegex, "") + val baseLink = "https://www.fakku.net/hentai/" + val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("qsearch", baseLink + slug) + addQueryParameter("json", "") + }.build() + client.newCall(GET(fullLink, headers)) + .asObservableSuccess() + .map { + val archive = it.parseAs().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found") + MangasPage(listOf(archive), false) + } + } + query.startsWith(PREFIX_SOURCE_SEARCH) -> { + val url = query.removePrefix(PREFIX_SOURCE_SEARCH) + client.newCall(GET("$baseSearchUrl/?qsearch=$url&json=", headers)) + .asObservableSuccess() + .map { + val archive = it.parseAs().archives.getOrNull(0)?.toSManga() ?: throw Exception("Not Found") + MangasPage(listOf(archive), false) + } + } + + else -> super.fetchSearchManga(page, query, filters) + } + } + + private fun searchMangaByIdParse(response: Response, id: Int = 0): MangasPage { + val title = response.parseAs().title + val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply { + addQueryParameter("qsearch", title) + addQueryParameter("json", "") + }.build() + val archive = client.newCall(GET(fullLink, headers)) + .execute() + .parseAs().archives + .find { + it.id == id + } + ?.toSManga() + ?: throw Exception("Invalid ID") + + return MangasPage(listOf(archive), false) + } + override fun searchMangaParse(response: Response): MangasPage { val library = response.parseAs() @@ -250,4 +325,11 @@ class PandaChaika( override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() override fun pageListParse(response: Response): List = throw UnsupportedOperationException() override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException() + + companion object { + const val PREFIX_ID_SEARCH = "id:" + const val PREFIX_FAK_ID_SEARCH = "fakku:" + const val PREFIX_EHEN_ID_SEARCH = "ehentai:" + const val PREFIX_SOURCE_SEARCH = "source:" + } } diff --git a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaDto.kt b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaDto.kt index 17ce374a0..0fcf6751d 100644 --- a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaDto.kt +++ b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaDto.kt @@ -8,7 +8,7 @@ import java.util.Date import java.util.Locale val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH) -fun filterTags(include: String = "", exclude: List = emptyList(), tags: List): String { +fun filterTags(include: String = "", exclude: List = emptyList(), tags: List): String? { return tags.filter { it.startsWith("$include:") && exclude.none { substring -> it.startsWith("$substring:") } } .joinToString { it.substringAfter(":").replace("_", " ").split(" ").joinToString(" ") { s -> @@ -16,13 +16,13 @@ fun filterTags(include: String = "", exclude: List = emptyList(), tags: if (sr.isLowerCase()) sr.titlecase(Locale.getDefault()) else sr.toString() } } - } + }.takeIf { it.isNotBlank() } } fun getReadableSize(bytes: Double): String { return when { - bytes >= 300 * 1024 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0 * 1024.0))} GB" - bytes >= 100 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0))} MB" - bytes >= 1024 -> "${"%.2f".format(bytes / (1024.0))} KB" + bytes >= 300 * 1000 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0 * 1000.0))} GB" + bytes >= 100 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0))} MB" + bytes >= 1000 -> "${"%.2f".format(bytes / (1000.0))} kB" else -> "$bytes B" } } @@ -31,13 +31,14 @@ fun getReadableSize(bytes: Double): String { class Archive( val download: String, val posted: Long, + val title: String, ) @Serializable class LongArchive( private val thumbnail: String, private val title: String, - private val id: Int, + val id: Int, private val posted: Long?, private val public_date: Long?, private val filecount: Int, @@ -50,35 +51,47 @@ class LongArchive( val groups = filterTags("group", tags = tags) val artists = filterTags("artist", tags = tags) val publishers = filterTags("publisher", tags = tags) + val characters = filterTags("character", tags = tags) val male = filterTags("male", tags = tags) val female = filterTags("female", tags = tags) val others = filterTags(exclude = listOf("female", "male", "artist", "publisher", "group", "parody"), tags = tags) val parodies = filterTags("parody", tags = tags) + var appended = false + url = id.toString() title = this@LongArchive.title thumbnail_url = thumbnail - author = groups.ifEmpty { artists } + author = groups ?: artists artist = artists genre = listOf(male, female, others).joinToString() description = buildString { append("Uploader: ", uploader.ifEmpty { "Anonymous" }, "\n") - publishers.takeIf { it.isNotBlank() }?.let { - append("Publishers: ", it, "\n\n") + publishers?.let { + append("Publishers: ", it, "\n") } - parodies.takeIf { it.isNotBlank() }?.let { - append("Parodies: ", it, "\n\n") + append("\n") + + parodies?.let { + append("Parodies: ", it, "\n") + appended = true } - male.takeIf { it.isNotBlank() }?.let { + characters?.let { + append("Characters: ", it, "\n") + appended = true + } + if (appended) append("\n") + + male?.let { append("Male tags: ", it, "\n\n") } - female.takeIf { it.isNotBlank() }?.let { + female?.let { append("Female tags: ", it, "\n\n") } - others.takeIf { it.isNotBlank() }?.let { + others?.let { append("Other tags: ", it, "\n\n") } - title_jpn?.let { append("Japanese Title: ", it, "\n") } + title_jpn?.takeIf { it.isNotEmpty() }?.let { append("Japanese Title: ", it, "\n") } append("Pages: ", filecount, "\n") append("File Size: ", getReadableSize(filesize), "\n") diff --git a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaFilters.kt b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaFilters.kt index 4c6f31c22..40f1cc67c 100644 --- a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaFilters.kt +++ b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaFilters.kt @@ -17,6 +17,7 @@ fun getFilters(): FilterList { TextFilter("Female Tags", "female"), TextFilter("Artists", "artist"), TextFilter("Parodies", "parody"), + TextFilter("Characters", "character"), Filter.Separator(), TextFilter("Reason", "reason"), TextFilter("Uploader", "reason"), @@ -52,11 +53,11 @@ private val getTypes = listOf( private val getSortsList: List> = listOf( Pair("Public Date", "public_date"), - Pair("Posted Date", "posted_date"), + Pair("Posted Date", "posted"), Pair("Title", "title"), Pair("Japanese Title", "title_jpn"), Pair("Rating", "rating"), - Pair("Images", "images"), - Pair("File Size", "size"), + Pair("Images", "filecount"), + Pair("File Size", "filesize"), Pair("Category", "category"), ) diff --git a/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaUrlActivity.kt b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaUrlActivity.kt new file mode 100644 index 000000000..e4b53c05e --- /dev/null +++ b/src/all/pandachaika/src/eu/kanade/tachiyomi/extension/all/pandachaika/PandaChaikaUrlActivity.kt @@ -0,0 +1,34 @@ +package eu.kanade.tachiyomi.extension.all.pandachaika + +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 PandaChaikaUrlActivity : Activity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.size > 2) { + val id = "${pathSegments[1]}/${pathSegments[2]}" + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${PandaChaika.PREFIX_ID_SEARCH}$id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e("KoharuUrlActivity", "Could not start activity", e) + } + } else { + Log.e("KoharuUrlActivity", "Could not parse URI from intent $intent") + } + + finish() + exitProcess(0) + } +}