Panda Chaika: Add Character Filter, Fix Sort Filters, Add ID search (#4049)

* Add Character Filter, ID search

* Fix Sort Filters

* Apply Suggestions

* ‎

* Apply Suggestion

* typo fix
This commit is contained in:
KenjieDec 2024-07-17 17:26:00 +07:00 committed by Draff
parent 2ef807ca07
commit 734c7a1e85
No known key found for this signature in database
GPG Key ID: E8A89F3211677653
6 changed files with 172 additions and 19 deletions

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<application>
<activity
android:name=".all.pandachaika.PandaChaikaUrlActivity"
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="panda.chaika.moe"
android:pathPattern="/archive/..*"
android:scheme="https"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,7 +1,7 @@
ext { ext {
extName = 'PandaChaika' extName = 'PandaChaika'
extClass = '.PandaChaikaFactory' extClass = '.PandaChaikaFactory'
extVersionCode = 1 extVersionCode = 2
isNsfw = true isNsfw = true
} }

View File

@ -1,6 +1,8 @@
package eu.kanade.tachiyomi.extension.all.pandachaika package eu.kanade.tachiyomi.extension.all.pandachaika
import eu.kanade.tachiyomi.network.GET 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.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.model.Page
@ -42,6 +44,9 @@ class PandaChaika(
private val json: Json by injectLazy() 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 // Popular
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseSearchUrl/?tags=$searchLang&sort=rating&apply=&json=&page=$page", headers) 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<MangasPage> {
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<ArchiveResponse>().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<ArchiveResponse>().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<ArchiveResponse>().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<Archive>().title
val fullLink = baseSearchUrl.toHttpUrl().newBuilder().apply {
addQueryParameter("qsearch", title)
addQueryParameter("json", "")
}.build()
val archive = client.newCall(GET(fullLink, headers))
.execute()
.parseAs<ArchiveResponse>().archives
.find {
it.id == id
}
?.toSManga()
?: throw Exception("Invalid ID")
return MangasPage(listOf(archive), false)
}
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val library = response.parseAs<ArchiveResponse>() val library = response.parseAs<ArchiveResponse>()
@ -250,4 +325,11 @@ class PandaChaika(
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException() override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException() override fun pageListParse(response: Response): List<Page> = throw UnsupportedOperationException()
override fun mangaDetailsParse(response: Response): SManga = 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:"
}
} }

View File

@ -8,7 +8,7 @@ import java.util.Date
import java.util.Locale import java.util.Locale
val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH) val dateReformat = SimpleDateFormat("EEEE, d MMM yyyy HH:mm (z)", Locale.ENGLISH)
fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags: List<String>): String { fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags: List<String>): String? {
return tags.filter { it.startsWith("$include:") && exclude.none { substring -> it.startsWith("$substring:") } } return tags.filter { it.startsWith("$include:") && exclude.none { substring -> it.startsWith("$substring:") } }
.joinToString { .joinToString {
it.substringAfter(":").replace("_", " ").split(" ").joinToString(" ") { s -> it.substringAfter(":").replace("_", " ").split(" ").joinToString(" ") { s ->
@ -16,13 +16,13 @@ fun filterTags(include: String = "", exclude: List<String> = emptyList(), tags:
if (sr.isLowerCase()) sr.titlecase(Locale.getDefault()) else sr.toString() if (sr.isLowerCase()) sr.titlecase(Locale.getDefault()) else sr.toString()
} }
} }
} }.takeIf { it.isNotBlank() }
} }
fun getReadableSize(bytes: Double): String { fun getReadableSize(bytes: Double): String {
return when { return when {
bytes >= 300 * 1024 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0 * 1024.0))} GB" bytes >= 300 * 1000 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0 * 1000.0))} GB"
bytes >= 100 * 1024 -> "${"%.2f".format(bytes / (1024.0 * 1024.0))} MB" bytes >= 100 * 1000 -> "${"%.2f".format(bytes / (1000.0 * 1000.0))} MB"
bytes >= 1024 -> "${"%.2f".format(bytes / (1024.0))} KB" bytes >= 1000 -> "${"%.2f".format(bytes / (1000.0))} kB"
else -> "$bytes B" else -> "$bytes B"
} }
} }
@ -31,13 +31,14 @@ fun getReadableSize(bytes: Double): String {
class Archive( class Archive(
val download: String, val download: String,
val posted: Long, val posted: Long,
val title: String,
) )
@Serializable @Serializable
class LongArchive( class LongArchive(
private val thumbnail: String, private val thumbnail: String,
private val title: String, private val title: String,
private val id: Int, val id: Int,
private val posted: Long?, private val posted: Long?,
private val public_date: Long?, private val public_date: Long?,
private val filecount: Int, private val filecount: Int,
@ -50,35 +51,47 @@ class LongArchive(
val groups = filterTags("group", tags = tags) val groups = filterTags("group", tags = tags)
val artists = filterTags("artist", tags = tags) val artists = filterTags("artist", tags = tags)
val publishers = filterTags("publisher", tags = tags) val publishers = filterTags("publisher", tags = tags)
val characters = filterTags("character", tags = tags)
val male = filterTags("male", tags = tags) val male = filterTags("male", tags = tags)
val female = filterTags("female", tags = tags) val female = filterTags("female", tags = tags)
val others = filterTags(exclude = listOf("female", "male", "artist", "publisher", "group", "parody"), tags = tags) val others = filterTags(exclude = listOf("female", "male", "artist", "publisher", "group", "parody"), tags = tags)
val parodies = filterTags("parody", tags = tags) val parodies = filterTags("parody", tags = tags)
var appended = false
url = id.toString() url = id.toString()
title = this@LongArchive.title title = this@LongArchive.title
thumbnail_url = thumbnail thumbnail_url = thumbnail
author = groups.ifEmpty { artists } author = groups ?: artists
artist = artists artist = artists
genre = listOf(male, female, others).joinToString() genre = listOf(male, female, others).joinToString()
description = buildString { description = buildString {
append("Uploader: ", uploader.ifEmpty { "Anonymous" }, "\n") append("Uploader: ", uploader.ifEmpty { "Anonymous" }, "\n")
publishers.takeIf { it.isNotBlank() }?.let { publishers?.let {
append("Publishers: ", it, "\n\n") append("Publishers: ", it, "\n")
} }
parodies.takeIf { it.isNotBlank() }?.let { append("\n")
append("Parodies: ", it, "\n\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") append("Male tags: ", it, "\n\n")
} }
female.takeIf { it.isNotBlank() }?.let { female?.let {
append("Female tags: ", it, "\n\n") append("Female tags: ", it, "\n\n")
} }
others.takeIf { it.isNotBlank() }?.let { others?.let {
append("Other tags: ", it, "\n\n") 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("Pages: ", filecount, "\n")
append("File Size: ", getReadableSize(filesize), "\n") append("File Size: ", getReadableSize(filesize), "\n")

View File

@ -17,6 +17,7 @@ fun getFilters(): FilterList {
TextFilter("Female Tags", "female"), TextFilter("Female Tags", "female"),
TextFilter("Artists", "artist"), TextFilter("Artists", "artist"),
TextFilter("Parodies", "parody"), TextFilter("Parodies", "parody"),
TextFilter("Characters", "character"),
Filter.Separator(), Filter.Separator(),
TextFilter("Reason", "reason"), TextFilter("Reason", "reason"),
TextFilter("Uploader", "reason"), TextFilter("Uploader", "reason"),
@ -52,11 +53,11 @@ private val getTypes = listOf(
private val getSortsList: List<Pair<String, String>> = listOf( private val getSortsList: List<Pair<String, String>> = listOf(
Pair("Public Date", "public_date"), Pair("Public Date", "public_date"),
Pair("Posted Date", "posted_date"), Pair("Posted Date", "posted"),
Pair("Title", "title"), Pair("Title", "title"),
Pair("Japanese Title", "title_jpn"), Pair("Japanese Title", "title_jpn"),
Pair("Rating", "rating"), Pair("Rating", "rating"),
Pair("Images", "images"), Pair("Images", "filecount"),
Pair("File Size", "size"), Pair("File Size", "filesize"),
Pair("Category", "category"), Pair("Category", "category"),
) )

View File

@ -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)
}
}