diff --git a/src/all/cubari/AndroidManifest.xml b/src/all/cubari/AndroidManifest.xml
index 544187796..c03caeb49 100644
--- a/src/all/cubari/AndroidManifest.xml
+++ b/src/all/cubari/AndroidManifest.xml
@@ -32,19 +32,6 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/all/cubari/README.md b/src/all/cubari/README.md
index d79b828ab..af45374ac 100644
--- a/src/all/cubari/README.md
+++ b/src/all/cubari/README.md
@@ -20,6 +20,6 @@ If you've setup the Remote Storage via WebView the Recent tab shows your recent,
You can visit the [Cubari](https://cubari.moe/) website for for more information.
### How do I add a gallery to Cubari?
-You can directly open a imgur or Cubari link in the extension.
+You can directly open a imgur or Cubari link in the extension or paste the url in cubari browse
[Uncomment this if needed]: <> (## Guides)
diff --git a/src/all/cubari/build.gradle b/src/all/cubari/build.gradle
index d1593a0e3..225d80a12 100644
--- a/src/all/cubari/build.gradle
+++ b/src/all/cubari/build.gradle
@@ -1,7 +1,7 @@
ext {
extName = 'Cubari'
extClass = '.CubariFactory'
- extVersionCode = 25
+ extVersionCode = 26
}
apply from: "$rootDir/common.gradle"
diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt
index fb4f97b48..4b6add9d2 100644
--- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt
+++ b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/Cubari.kt
@@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.extension.all.cubari
-import android.app.Application
import android.os.Build
+import android.util.Base64
import eu.kanade.tachiyomi.AppInfo
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable
@@ -12,7 +12,7 @@ import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
-import kotlinx.serialization.json.Json
+import keiyoushi.utils.parseAs
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.boolean
@@ -20,23 +20,18 @@ import kotlinx.serialization.json.double
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
-import okhttp3.Headers
+import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Response
import rx.Observable
-import uy.kohesive.injekt.Injekt
-import uy.kohesive.injekt.api.get
-import uy.kohesive.injekt.injectLazy
-open class Cubari(override val lang: String) : HttpSource() {
+class Cubari(override val lang: String) : HttpSource() {
- final override val name = "Cubari"
+ override val name = "Cubari"
- final override val baseUrl = "https://cubari.moe"
+ override val baseUrl = "https://cubari.moe"
- final override val supportsLatest = true
-
- private val json: Json by injectLazy()
+ override val supportsLatest = true
override val client = network.cloudflareClient.newBuilder()
.addInterceptor { chain ->
@@ -48,18 +43,17 @@ open class Cubari(override val lang: String) : HttpSource() {
}
.build()
- override fun headersBuilder() = Headers.Builder().apply {
- add(
+ private val cubariHeaders = super.headersBuilder()
+ .set(
"User-Agent",
"(Android ${Build.VERSION.RELEASE}; " +
"${Build.MANUFACTURER} ${Build.MODEL}) " +
- "Tachiyomi/${AppInfo.getVersionName()} " +
- Build.ID,
- )
- }
+ "Tachiyomi/${AppInfo.getVersionName()} ${Build.ID} " +
+ "Keiyoushi",
+ ).build()
override fun latestUpdatesRequest(page: Int): Request {
- return GET("$baseUrl/", headers)
+ return GET("$baseUrl/", cubariHeaders)
}
override fun fetchLatestUpdates(page: Int): Observable {
@@ -72,12 +66,12 @@ open class Cubari(override val lang: String) : HttpSource() {
}
override fun latestUpdatesParse(response: Response): MangasPage {
- val result = json.parseToJsonElement(response.body.string()).jsonArray
+ val result = response.parseAs()
return parseMangaList(result, SortType.UNPINNED)
}
override fun popularMangaRequest(page: Int): Request {
- return GET("$baseUrl/", headers)
+ return GET("$baseUrl/", cubariHeaders)
}
override fun fetchPopularManga(page: Int): Observable {
@@ -90,19 +84,22 @@ open class Cubari(override val lang: String) : HttpSource() {
}
override fun popularMangaParse(response: Response): MangasPage {
- val result = json.parseToJsonElement(response.body.string()).jsonArray
+ val result = response.parseAs()
return parseMangaList(result, SortType.PINNED)
}
override fun fetchMangaDetails(manga: SManga): Observable {
- return client.newCall(chapterListRequest(manga))
+ return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess()
.map { response -> mangaDetailsParse(response, manga) }
}
- // Called when the series is loaded, or when opening in browser
+ override fun getMangaUrl(manga: SManga): String {
+ return "$baseUrl${manga.url}"
+ }
+
override fun mangaDetailsRequest(manga: SManga): Request {
- return GET("$baseUrl${manga.url}", headers)
+ return chapterListRequest(manga)
}
override fun mangaDetailsParse(response: Response): SManga {
@@ -110,7 +107,7 @@ open class Cubari(override val lang: String) : HttpSource() {
}
private fun mangaDetailsParse(response: Response, manga: SManga): SManga {
- val result = json.parseToJsonElement(response.body.string()).jsonObject
+ val result = response.parseAs()
return parseManga(result, manga)
}
@@ -126,17 +123,16 @@ open class Cubari(override val lang: String) : HttpSource() {
val source = urlComponents[2]
val slug = urlComponents[3]
- return GET("$baseUrl/read/api/$source/series/$slug/", headers)
+ return GET("$baseUrl/read/api/$source/series/$slug/", cubariHeaders)
}
override fun chapterListParse(response: Response): List {
- throw Exception("Unused")
+ throw UnsupportedOperationException()
}
// Called after the request
private fun chapterListParse(response: Response, manga: SManga): List {
- val res = response.body.string()
- return parseChapterList(res, manga)
+ return parseChapterList(response, manga)
}
override fun fetchPageList(chapter: SChapter): Observable> {
@@ -161,21 +157,20 @@ open class Cubari(override val lang: String) : HttpSource() {
override fun pageListRequest(chapter: SChapter): Request {
return when {
chapter.url.contains("/chapter/") -> {
- GET("$baseUrl${chapter.url}", headers)
+ GET("$baseUrl${chapter.url}", cubariHeaders)
}
else -> {
val url = chapter.url.split("/")
val source = url[2]
val slug = url[3]
- GET("$baseUrl/read/api/$source/series/$slug/", headers)
+ GET("$baseUrl/read/api/$source/series/$slug/", cubariHeaders)
}
}
}
private fun directPageListParse(response: Response): List {
- val res = response.body.string()
- val pages = json.parseToJsonElement(res).jsonArray
+ val pages = response.parseAs()
return pages.mapIndexed { i, jsonEl ->
val page = if (jsonEl is JsonObject) {
@@ -189,7 +184,7 @@ open class Cubari(override val lang: String) : HttpSource() {
}
private fun seriesJsonPageListParse(response: Response, chapter: SChapter): List {
- val jsonObj = json.parseToJsonElement(response.body.string()).jsonObject
+ val jsonObj = response.parseAs()
val groups = jsonObj["groups"]!!.jsonObject
val groupMap = groups.entries.associateBy({ it.value.jsonPrimitive.content.ifEmpty { "default" } }, { it.key })
val chapterScanlator = chapter.scanlator ?: "default" // workaround for "" as group causing NullPointerException (#13772)
@@ -222,23 +217,29 @@ open class Cubari(override val lang: String) : HttpSource() {
}
}
- // Stub
override fun pageListParse(response: Response): List {
- throw Exception("Unused")
+ throw UnsupportedOperationException()
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
return when {
- query.startsWith(PROXY_PREFIX) -> {
- val trimmedQuery = query.removePrefix(PROXY_PREFIX)
+ // handle direct links or old cubari:source/id format
+ query.startsWith("https://") || query.startsWith("cubari:") -> {
+ val (source, slug) = deepLinkHandler(query)
// Only tag for recently read on search
client.newBuilder()
.addInterceptor(RemoteStorageUtils.TagInterceptor())
.build()
- .newCall(proxySearchRequest(trimmedQuery))
+ .newCall(GET("$baseUrl/read/api/$source/series/$slug/", cubariHeaders))
.asObservableSuccess()
.map { response ->
- proxySearchParse(response, trimmedQuery)
+ val result = response.parseAs()
+ val manga = SManga.create().apply {
+ url = "/read/$source/$slug"
+ }
+ val mangaList = listOf(parseManga(result, manga))
+
+ MangasPage(mangaList, false)
}
}
else -> {
@@ -259,18 +260,57 @@ open class Cubari(override val lang: String) : HttpSource() {
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
- return GET("$baseUrl/", headers)
+ return GET("$baseUrl/", cubariHeaders)
}
- private fun proxySearchRequest(query: String): Request {
- try {
- val queryFragments = query.split("/")
- val source = queryFragments[0]
- val slug = queryFragments[1]
+ private fun deepLinkHandler(query: String): Pair {
+ return if (query.startsWith("cubari:")) { // legacy cubari:source/slug format
+ val queryFragments = query.substringAfter("cubari:").split("/", limit = 2)
+ queryFragments[0] to queryFragments[1]
+ } else { // direct url searching
+ val url = query.toHttpUrl()
+ val host = url.host
+ val pathSegments = url.pathSegments
- return GET("$baseUrl/read/api/$source/series/$slug/", headers)
- } catch (e: Exception) {
- throw Exception(SEARCH_FALLBACK_MSG)
+ if (
+ host.endsWith("imgur.com") &&
+ pathSegments.size >= 2 &&
+ pathSegments[0] in listOf("a", "gallery")
+ ) {
+ "imgur" to pathSegments[1]
+ } else if (
+ host.endsWith("reddit.com") &&
+ pathSegments.size >= 2 &&
+ pathSegments[0] == "gallery"
+ ) {
+ "reddit" to pathSegments[1]
+ } else if (
+ host == "imgchest.com" &&
+ pathSegments.size >= 2 &&
+ pathSegments[0] == "p"
+ ) {
+ "imgchest" to pathSegments[1]
+ } else if (
+ host.endsWith("catbox.moe") &&
+ pathSegments.size >= 2 &&
+ pathSegments[0] == "c"
+ ) {
+ "catbox" to pathSegments[1]
+ } else if (
+ host.endsWith("cubari.moe") &&
+ pathSegments.size >= 3
+ ) {
+ pathSegments[1] to pathSegments[2]
+ } else if (
+ host.endsWith(".githubusercontent.com")
+ ) {
+ val src = host.substringBefore(".")
+ val path = url.encodedPath
+
+ "gist" to Base64.encodeToString("$src$path".toByteArray(), Base64.NO_PADDING)
+ } else {
+ throw Exception(SEARCH_FALLBACK_MSG)
+ }
}
}
@@ -279,7 +319,7 @@ open class Cubari(override val lang: String) : HttpSource() {
}
private fun searchMangaParse(response: Response, query: String): MangasPage {
- val result = json.parseToJsonElement(response.body.string()).jsonArray
+ val result = response.parseAs()
val filterList = result.asSequence()
.map { it as JsonObject }
@@ -289,23 +329,14 @@ open class Cubari(override val lang: String) : HttpSource() {
return parseMangaList(JsonArray(filterList), SortType.ALL)
}
- private fun proxySearchParse(response: Response, query: String): MangasPage {
- val result = json.parseToJsonElement(response.body.string()).jsonObject
- return parseSearchList(result, query)
- }
-
// ------------- Helpers and whatnot ---------------
private val volumeNotSpecifiedTerms = setOf("Uncategorized", "null", "")
- private fun parseChapterList(payload: String, manga: SManga): List {
- val jsonObj = json.parseToJsonElement(payload).jsonObject
+ private fun parseChapterList(response: Response, manga: SManga): List {
+ val jsonObj = response.parseAs()
val groups = jsonObj["groups"]!!.jsonObject
val chapters = jsonObj["chapters"]!!.jsonObject
- val seriesSlug = jsonObj["slug"]!!.jsonPrimitive.content
-
- val seriesPrefs = Injekt.get().getSharedPreferences("source_${id}_updateTime:$seriesSlug", 0)
- val seriesPrefsEditor = seriesPrefs.edit()
val chapterList = chapters.entries.flatMap { chapterEntry ->
val chapterNum = chapterEntry.key
@@ -327,13 +358,7 @@ open class Cubari(override val lang: String) : HttpSource() {
date_upload = if (releaseDate != null) {
releaseDate.jsonPrimitive.double.toLong() * 1000
} else {
- val currentTimeMillis = System.currentTimeMillis()
-
- if (!seriesPrefs.contains(chapterNum)) {
- seriesPrefsEditor.putLong(chapterNum, currentTimeMillis)
- }
-
- seriesPrefs.getLong(chapterNum, currentTimeMillis)
+ 0L
}
name = buildString {
@@ -351,8 +376,6 @@ open class Cubari(override val lang: String) : HttpSource() {
}
}
- seriesPrefsEditor.apply()
-
return chapterList.sortedByDescending { it.chapter_number }
}
@@ -375,16 +398,6 @@ open class Cubari(override val lang: String) : HttpSource() {
return MangasPage(mangaList, false)
}
- private fun parseSearchList(payload: JsonObject, query: String): MangasPage {
- val tempManga = SManga.create().apply {
- url = "/read/$query"
- }
-
- val mangaList = listOf(parseManga(payload, tempManga))
-
- return MangasPage(mangaList, false)
- }
-
private fun parseManga(jsonObj: JsonObject, mangaReference: SManga? = null): SManga =
SManga.create().apply {
title = jsonObj["title"]!!.jsonPrimitive.content
@@ -413,11 +426,10 @@ open class Cubari(override val lang: String) : HttpSource() {
}
companion object {
- const val PROXY_PREFIX = "cubari:"
const val AUTHOR_FALLBACK = "Unknown"
const val ARTIST_FALLBACK = "Unknown"
const val DESCRIPTION_FALLBACK = "No description."
- const val SEARCH_FALLBACK_MSG = "Unable to parse. Is your query in the format of $PROXY_PREFIX/?"
+ const val SEARCH_FALLBACK_MSG = "Please enter a valid Cubari URL"
enum class SortType {
PINNED,
diff --git a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt
index aafb34e65..aaae83920 100644
--- a/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt
+++ b/src/all/cubari/src/eu/kanade/tachiyomi/extension/all/cubari/CubariUrlActivity.kt
@@ -11,59 +11,20 @@ class CubariUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val host = intent?.data?.host
- val pathSegments = intent?.data?.pathSegments
- if (host != null && pathSegments != null) {
- val query = with(host) {
- when {
- equals("m.imgur.com") || equals("imgur.com") -> fromSource("imgur", pathSegments)
- equals("m.reddit.com") || equals("reddit.com") || equals("www.reddit.com") -> fromSource("reddit", pathSegments)
- equals("imgchest.com") -> fromSource("imgchest", pathSegments)
- equals("catbox.moe") || equals("www.catbox.moe") -> fromSource("catbox", pathSegments)
- else -> fromCubari(pathSegments)
- }
- }
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", intent.data.toString())
+ putExtra("filter", packageName)
+ }
- if (query == null) {
- Log.e("CubariUrlActivity", "Unable to parse URI from intent $intent")
- finish()
- exitProcess(1)
- }
-
- val mainIntent = Intent().apply {
- action = "eu.kanade.tachiyomi.SEARCH"
- putExtra("query", query)
- putExtra("filter", packageName)
- }
-
- try {
- startActivity(mainIntent)
- } catch (e: ActivityNotFoundException) {
- Log.e("CubariUrlActivity", e.toString())
- }
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("CubariUrlActivity", "Unable to find activity", e)
}
finish()
exitProcess(0)
}
-
- private fun fromSource(source: String, pathSegments: List): String? {
- if (pathSegments.size >= 2) {
- val id = pathSegments[1]
-
- return "${Cubari.PROXY_PREFIX}$source/$id"
- }
- return null
- }
-
- private fun fromCubari(pathSegments: MutableList): String? {
- return if (pathSegments.size >= 3) {
- val source = pathSegments[1]
- val slug = pathSegments[2]
- "${Cubari.PROXY_PREFIX}$source/$slug"
- } else {
- null
- }
- }
}