Add mature comics and other changes for Tapas.io (#8672)

* Migration to kotlinx.serialization

* Add support for mature results

* Changed preferences implementation
This commit is contained in:
Arraiment 2021-08-20 22:41:59 +08:00 committed by GitHub
parent d09c4a0abb
commit a336be5cca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 162 additions and 74 deletions

View File

@ -1,11 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Tapas'
pkgNameSuffix = 'en.tapastic'
extClass = '.Tapastic'
extVersionCode = 11
extVersionCode = 12
libVersion = '1.2'
}

View File

@ -2,14 +2,8 @@ package eu.kanade.tachiyomi.extension.en.tapastic
import android.app.Application
import android.content.SharedPreferences
import android.net.Uri
import com.github.salomonbrys.kotson.bool
import com.github.salomonbrys.kotson.fromJson
import com.github.salomonbrys.kotson.get
import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.string
import com.google.gson.Gson
import com.google.gson.JsonObject
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Filter
@ -19,7 +13,17 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.boolean
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.Jsoup
@ -27,64 +31,52 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Tapastic : ConfigurableSource, ParsedHttpSource() {
// Preferences Code
// Preferences
private val preferences: SharedPreferences by lazy {
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
}
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
val chapterListPref = androidx.preference.ListPreference(screen.context).apply {
key = SHOW_LOCKED_CHAPTERS_Title
title = SHOW_LOCKED_CHAPTERS_Title
entries = prefsEntriesChapters
entryValues = prefsEntryValuesChapters
summary = "%s"
override fun setupPreferenceScreen(screen: PreferenceScreen) {
val chapterVisibilityPref = SwitchPreferenceCompat(screen.context).apply {
key = CHAPTER_VIS_PREF_KEY
title = "Show paywalled chapters"
summary = "Tapas requires login/payment for some chapters. Enable to always show paywalled chapters."
setDefaultValue(true)
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(SHOW_LOCKED_CHAPTERS, entry).commit()
val checkValue = newValue as Boolean
preferences.edit().putBoolean(CHAPTER_VIS_PREF_KEY, checkValue).commit()
}
}
screen.addPreference(chapterListPref)
screen.addPreference(chapterVisibilityPref)
val lockPref = androidx.preference.ListPreference(screen.context).apply {
key = SHOW_LOCK_Title
title = SHOW_LOCK_Title
entries = prefsEntriesLock
entryValues = prefsEntryValuesLock
summary = "%s"
val lockPref = SwitchPreferenceCompat(screen.context).apply {
key = SHOW_LOCK_PREF_KEY
title = "Show lock icon"
summary = "Enable to continue showing \uD83D\uDD12 for locked chapters after login."
setDefaultValue(false)
setOnPreferenceChangeListener { _, newValue ->
val selected = newValue as String
val index = this.findIndexOfValue(selected)
val entry = entryValues[index] as String
preferences.edit().putString(SHOW_LOCK, entry).commit()
val checkValue = newValue as Boolean
preferences.edit().putBoolean(SHOW_LOCK_PREF_KEY, checkValue).commit()
}
}
screen.addPreference(lockPref)
}
private fun chapterListPref() = preferences.getString(SHOW_LOCKED_CHAPTERS, "free")
private fun lockPref() = preferences.getString(SHOW_LOCK, "yes")
private fun showLockedChapterPref() = preferences.getBoolean(CHAPTER_VIS_PREF_KEY, false)
private fun showLockPref() = preferences.getBoolean(SHOW_LOCK_PREF_KEY, false)
companion object {
private const val SHOW_LOCKED_CHAPTERS_Title = "Tapas requires login/payment for some chapters"
private const val SHOW_LOCKED_CHAPTERS = "tapas_locked_chapters"
private val prefsEntriesChapters = arrayOf("Show all chapters (including pay-to-read)", "Only show free chapters")
private val prefsEntryValuesChapters = arrayOf("all", "free")
private const val SHOW_LOCK_Title = "Show \uD83D\uDD12 for locked chapters after login"
private const val SHOW_LOCK = "tapas_lock"
private val prefsEntriesLock = arrayOf("Yes", "No")
private val prefsEntryValuesLock = arrayOf("yes", "no")
private const val CHAPTER_VIS_PREF_KEY = "lockedChapterVisibility"
private const val SHOW_LOCK_PREF_KEY = "showChapterLock"
}
// Info
@ -94,6 +86,34 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
override val baseUrl = "https://tapas.io"
override val id = 3825434541981130345
override val client: OkHttpClient = super.client.newBuilder()
.cookieJar(
object : CookieJar {
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {}
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
return ArrayList<Cookie>().apply {
add(
Cookie.Builder()
.domain("tapas.io")
.path("/")
.name("birthDate")
.value("1994-01-01")
.build()
)
add(
Cookie.Builder()
.domain("tapas.io")
.path("/")
.name("adjustedBirthDate")
.value("1994-01-01")
.build()
)
}
}
}
)
.build()
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "https://m.tapas.io")
@ -123,24 +143,35 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
// If there is any search text, use text search, otherwise use filter search
val uri = if (query.isNotBlank()) {
Uri.parse("$baseUrl/search")
.buildUpon()
.appendQueryParameter("t", "COMICS")
.appendQueryParameter("q", query)
val filterList = if (filters.isEmpty()) getFilterList() else filters
val url: HttpUrl.Builder
// If there is any search text, use text search, ignoring filters
if (query.isNotBlank()) {
url = "$baseUrl/search".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("q", query)
.addQueryParameter("t", "COMICS")
} else {
val uri = Uri.parse("$baseUrl/comics").buildUpon()
// Append uri filters
filters.forEach {
if (it is UriFilter)
it.addToUri(uri)
// Checking mature filter
val matureFilter = filterList.find { it is MatureFilter } as MatureFilter
if (matureFilter.state) {
url = "$baseUrl/mature".toHttpUrlOrNull()!!.newBuilder()
// Append only mature uri filters
filterList.forEach {
if (it is UriFilter && it.isMature)
it.addToUri(url)
}
} else {
url = "$baseUrl/comics".toHttpUrlOrNull()!!.newBuilder()
// Append only non-mature uri filters
filterList.forEach {
if (it is UriFilter && !it.isMature)
it.addToUri(url)
}
}
uri
}
// Append page number
uri.appendQueryParameter("pageNumber", page.toString())
return GET(uri.toString())
url.addQueryParameter("pageNumber", page.toString())
return GET(url.toString())
}
override fun searchMangaNextPageSelector() =
@ -156,7 +187,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
// Details
override fun mangaDetailsRequest(manga: SManga): Request {
return GET(baseUrl + "${manga.url}/info")
return GET(baseUrl + "${manga.url}/info", headers)
}
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
@ -174,10 +205,10 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
* Checklist: Paginated chapter lists, locked chapters, future chapters, early-access chapters (app only?), chapter order
*/
private val gson by lazy { Gson() }
private val json: Json by injectLazy()
private fun Element.isLockedChapter(): Boolean {
return this.hasClass("js-have-to-sign") || (lockPref() == "yes" && this.hasClass("js-locked"))
return this.hasClass("js-have-to-sign") || (showLockPref() && this.hasClass("js-locked"))
}
override fun chapterListParse(response: Response): List<SChapter> {
@ -188,16 +219,19 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
// recursively build the chapter list
fun parseChapters(page: Int) {
val url = "$baseUrl/series/$mangaId/episodes?page=$page&sort=NEWEST&init_load=0&large=true&last_access=0&"
val json = gson.fromJson<JsonObject>(client.newCall(GET(url, headers)).execute().body!!.string())["data"]
val jsonResponse = client.newCall(GET(url, headers)).execute()
val json = json.parseToJsonElement(jsonResponse.body!!.string()).jsonObject["data"]!!.jsonObject
Jsoup.parse(json["body"].string).select(chapterListSelector())
Jsoup.parse(json["body"]!!.jsonPrimitive.content).select(chapterListSelector())
.let { list ->
// show/don't show locked chapters based on user's preferences
if (chapterListPref() == "free") list.filterNot { it.isLockedChapter() } else list
if (showLockedChapterPref()) list else list.filterNot { it.isLockedChapter() }
}
.map { chapters.add(chapterFromElement(it)) }
if (json["pagination"]["has_next"].bool) parseChapters(json["pagination"]["page"].int)
val hasNextPage = json["pagination"]!!.jsonObject["has_next"]!!.jsonPrimitive.boolean
val nextPage = json["pagination"]!!.jsonObject["page"]!!.jsonPrimitive.int
if (hasNextPage) parseChapters(nextPage)
}
parseChapters(1)
@ -236,21 +270,28 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
override fun getFilterList() = FilterList(
// Tapastic does not support genre filtering and text search at the same time
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Header("NOTE: All filters ignored if using text search!"),
Filter.Separator(),
FilterFilter(),
CategoryFilter(),
GenreFilter(),
StatusFilter(),
Filter.Header("Sort is ignored when category filter is active!"),
SortFilter(),
Filter.Separator(),
Filter.Header("Sort is ignored when filter is active!"),
SortFilter()
Filter.Header("Mature filters"),
MatureFilter("Show Mature Results Only"),
MatureCategoryFilter(),
MatureGenreFilter(),
Filter.Header("Sort is ignored when category filter is active!"),
MatureSortFilter(),
)
private class FilterFilter : UriSelectFilter(
"Filter",
private class CategoryFilter : UriSelectFilter(
"Category",
false,
"b",
arrayOf(
Pair("ALL", "None"),
Pair("ALL", "All"),
Pair("POPULAR", "Popular"),
Pair("TRENDING", "Trending"),
Pair("FRESH", "Fresh"),
@ -263,6 +304,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
private class GenreFilter : UriSelectFilter(
"Genre",
false,
"g",
arrayOf(
Pair("0", "Any"),
@ -284,6 +326,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
private class StatusFilter : UriSelectFilter(
"Status",
false,
"f",
arrayOf(
Pair("NONE", "All"),
@ -294,6 +337,48 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
private class SortFilter : UriSelectFilter(
"Sort",
false,
"s",
arrayOf(
Pair("DATE", "Date"),
Pair("LIKE", "Likes"),
Pair("SUBSCRIBE", "Subscribers")
)
)
private class MatureFilter(name: String) : Filter.CheckBox(name)
private class MatureCategoryFilter : UriSelectFilter(
"Category",
true,
"b",
arrayOf(
Pair("ALL", "All"),
Pair("POPULAR", "Popular"),
Pair("FRESH", "Fresh"),
),
firstIsUnspecified = false,
defaultValue = 1
)
private class MatureGenreFilter : UriSelectFilter(
"Genre",
false,
"g",
arrayOf(
Pair("0", "Any"),
Pair("5", "Romance"),
Pair("8", "Drama"),
Pair("22", "Boys Love"),
Pair("24", "Girls Love"),
Pair("2", "Comedy"),
Pair("6", "Horror"),
)
)
private class MatureSortFilter : UriSelectFilter(
"Sort",
true,
"s",
arrayOf(
Pair("DATE", "Date"),
@ -310,6 +395,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
// vals: <name, display>
private open class UriSelectFilter(
displayName: String,
override val isMature: Boolean,
val uriParam: String,
val vals: Array<Pair<String, String>>,
val firstIsUnspecified: Boolean = true,
@ -317,9 +403,9 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue),
UriFilter {
override fun addToUri(uri: Uri.Builder) {
override fun addToUri(uri: HttpUrl.Builder) {
if (state != 0 || !firstIsUnspecified)
uri.appendQueryParameter(uriParam, vals[state].first)
uri.addQueryParameter(uriParam, vals[state].first)
}
}
@ -327,6 +413,7 @@ class Tapastic : ConfigurableSource, ParsedHttpSource() {
* Represents a filter that is able to modify a URI.
*/
private interface UriFilter {
fun addToUri(uri: Uri.Builder)
val isMature: Boolean
fun addToUri(uri: HttpUrl.Builder)
}
}