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 {
extName = 'PandaChaika'
extClass = '.PandaChaikaFactory'
extVersionCode = 1
extVersionCode = 2
isNsfw = true
}

View File

@ -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<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 {
val library = response.parseAs<ArchiveResponse>()
@ -250,4 +325,11 @@ class PandaChaika(
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun pageListParse(response: Response): List<Page> = 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
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:") } }
.joinToString {
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()
}
}
}
}.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")

View File

@ -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<Pair<String, String>> = 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"),
)

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