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:
parent
2ef807ca07
commit
734c7a1e85
23
src/all/pandachaika/AndroidManifest.xml
Normal file
23
src/all/pandachaika/AndroidManifest.xml
Normal 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>
|
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'PandaChaika'
|
extName = 'PandaChaika'
|
||||||
extClass = '.PandaChaikaFactory'
|
extClass = '.PandaChaikaFactory'
|
||||||
extVersionCode = 1
|
extVersionCode = 2
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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:"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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")
|
||||||
|
|
||||||
|
@ -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"),
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user