Remove dependency injection from core module and data module from presentation-widget module
Includes side effects: - No longer need to restart app for user agent string change to take effect - parseAs extension function requires a Json instance in the calling context, which doesn't necessarily need to be the default one provided by Injekt (cherry picked from commit 93523ef50b80ef294866bfb0da54e236cdf2d9f6) # Conflicts: # app/src/main/java/eu/kanade/tachiyomi/data/updater/AppUpdateChecker.kt # app/src/main/java/eu/kanade/tachiyomi/extension/api/ExtensionGithubApi.kt # core/src/main/java/eu/kanade/tachiyomi/network/NetworkHelper.kt # core/src/main/java/eu/kanade/tachiyomi/network/OkHttpExtensions.kt # domain/build.gradle.kts # source-api/build.gradle.kts
This commit is contained in:
parent
2e1c83442e
commit
6563490513
@ -296,6 +296,7 @@ tasks {
|
|||||||
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
// See https://kotlinlang.org/docs/reference/experimental.html#experimental-status-of-experimental-api(-markers)
|
||||||
withType<KotlinCompile> {
|
withType<KotlinCompile> {
|
||||||
kotlinOptions.freeCompilerArgs += listOf(
|
kotlinOptions.freeCompilerArgs += listOf(
|
||||||
|
"-Xcontext-receivers",
|
||||||
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
"-opt-in=coil.annotation.ExperimentalCoilApi",
|
||||||
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
"-opt-in=com.google.accompanist.permissions.ExperimentalPermissionsApi",
|
||||||
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
"-opt-in=androidx.compose.foundation.layout.ExperimentalLayoutApi",
|
||||||
|
@ -281,7 +281,7 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
Preference.PreferenceItem.TextPreference(
|
Preference.PreferenceItem.TextPreference(
|
||||||
title = stringResource(R.string.pref_clear_cookies),
|
title = stringResource(R.string.pref_clear_cookies),
|
||||||
onClick = {
|
onClick = {
|
||||||
networkHelper.cookieManager.removeAll()
|
networkHelper.cookieJar.removeAll()
|
||||||
context.toast(R.string.cookies_cleared)
|
context.toast(R.string.cookies_cleared)
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -339,7 +339,6 @@ object SettingsAdvancedScreen : SearchableSettings {
|
|||||||
context.toast(R.string.error_user_agent_string_invalid)
|
context.toast(R.string.error_user_agent_string_invalid)
|
||||||
return@EditTextPreference false
|
return@EditTextPreference false
|
||||||
}
|
}
|
||||||
context.toast(R.string.requires_app_restart)
|
|
||||||
true
|
true
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -134,7 +134,7 @@ class AppModule(val app: Application) : InjektModule {
|
|||||||
addSingletonFactory { ChapterCache(app) }
|
addSingletonFactory { ChapterCache(app) }
|
||||||
addSingletonFactory { CoverCache(app) }
|
addSingletonFactory { CoverCache(app) }
|
||||||
|
|
||||||
addSingletonFactory { NetworkHelper(app) }
|
addSingletonFactory { NetworkHelper(app, get()) }
|
||||||
addSingletonFactory { JavaScriptEngine(app) }
|
addSingletonFactory { JavaScriptEngine(app) }
|
||||||
|
|
||||||
addSingletonFactory { SourceManager(app, get(), get()) }
|
addSingletonFactory { SourceManager(app, get(), get()) }
|
||||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess
|
|||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonNull
|
import kotlinx.serialization.json.JsonNull
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@ -24,11 +25,14 @@ import kotlinx.serialization.json.putJsonObject
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val authClient = client.newBuilder()
|
private val authClient = client.newBuilder()
|
||||||
.addInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
.rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES)
|
.rateLimit(permits = 85, period = 1, unit = TimeUnit.MINUTES)
|
||||||
@ -53,19 +57,21 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("status", track.toAnilistStatus())
|
put("status", track.toAnilistStatus())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
POST(
|
authClient.newCall(
|
||||||
apiUrl,
|
POST(
|
||||||
body = payload.toString().toRequestBody(jsonMime),
|
apiUrl,
|
||||||
),
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
track.library_id =
|
.let {
|
||||||
it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
track.library_id =
|
||||||
track
|
it["data"]!!.jsonObject["SaveMediaListEntry"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
||||||
}
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -137,21 +143,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("query", search)
|
put("query", search)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
POST(
|
authClient.newCall(
|
||||||
apiUrl,
|
POST(
|
||||||
body = payload.toString().toRequestBody(jsonMime),
|
apiUrl,
|
||||||
),
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { response ->
|
.parseAs<JsonObject>()
|
||||||
val data = response["data"]!!.jsonObject
|
.let { response ->
|
||||||
val page = data["Page"]!!.jsonObject
|
val data = response["data"]!!.jsonObject
|
||||||
val media = page["media"]!!.jsonArray
|
val page = data["Page"]!!.jsonObject
|
||||||
val entries = media.map { jsonToALManga(it.jsonObject) }
|
val media = page["media"]!!.jsonArray
|
||||||
entries.map { it.toTrack() }
|
val entries = media.map { jsonToALManga(it.jsonObject) }
|
||||||
}
|
entries.map { it.toTrack() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,21 +213,23 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
put("manga_id", track.media_id)
|
put("manga_id", track.media_id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
POST(
|
authClient.newCall(
|
||||||
apiUrl,
|
POST(
|
||||||
body = payload.toString().toRequestBody(jsonMime),
|
apiUrl,
|
||||||
),
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { response ->
|
.parseAs<JsonObject>()
|
||||||
val data = response["data"]!!.jsonObject
|
.let { response ->
|
||||||
val page = data["Page"]!!.jsonObject
|
val data = response["data"]!!.jsonObject
|
||||||
val media = page["mediaList"]!!.jsonArray
|
val page = data["Page"]!!.jsonObject
|
||||||
val entries = media.map { jsonToALUserManga(it.jsonObject) }
|
val media = page["mediaList"]!!.jsonArray
|
||||||
entries.firstOrNull()?.toTrack()
|
val entries = media.map { jsonToALUserManga(it.jsonObject) }
|
||||||
}
|
entries.firstOrNull()?.toTrack()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,22 +257,24 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
|
|||||||
val payload = buildJsonObject {
|
val payload = buildJsonObject {
|
||||||
put("query", query)
|
put("query", query)
|
||||||
}
|
}
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
POST(
|
authClient.newCall(
|
||||||
apiUrl,
|
POST(
|
||||||
body = payload.toString().toRequestBody(jsonMime),
|
apiUrl,
|
||||||
),
|
body = payload.toString().toRequestBody(jsonMime),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
val data = it["data"]!!.jsonObject
|
.let {
|
||||||
val viewer = data["Viewer"]!!.jsonObject
|
val data = it["data"]!!.jsonObject
|
||||||
Pair(
|
val viewer = data["Viewer"]!!.jsonObject
|
||||||
viewer["id"]!!.jsonPrimitive.int,
|
Pair(
|
||||||
viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content,
|
viewer["id"]!!.jsonPrimitive.int,
|
||||||
)
|
viewer["mediaListOptions"]!!.jsonObject["scoreFormat"]!!.jsonPrimitive.content,
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,10 +118,12 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
|
|
||||||
suspend fun findLibManga(track: Track): Track? {
|
suspend fun findLibManga(track: Track): Track? {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET("$apiUrl/subject/${track.media_id}"))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { jsonToSearch(it) }
|
.parseAs<JsonObject>()
|
||||||
|
.let { jsonToSearch(it) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -155,9 +157,11 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
|
|
||||||
suspend fun accessToken(code: String): OAuth {
|
suspend fun accessToken(code: String): OAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
client.newCall(accessTokenRequest(code))
|
with(json) {
|
||||||
.awaitSuccess()
|
client.newCall(accessTokenRequest(code))
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +185,6 @@ class BangumiApi(private val client: OkHttpClient, interceptor: BangumiIntercept
|
|||||||
private const val loginUrl = "https://bgm.tv/oauth/authorize"
|
private const val loginUrl = "https://bgm.tv/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://bangumi-auth"
|
private const val redirectUrl = "tachiyomi://bangumi-auth"
|
||||||
private const val baseMangaUrl = "$apiUrl/mangas"
|
|
||||||
|
|
||||||
fun authUrl(): Uri =
|
fun authUrl(): Uri =
|
||||||
loginUrl.toUri().buildUpon()
|
loginUrl.toUri().buildUpon()
|
||||||
|
@ -6,6 +6,7 @@ import eu.kanade.tachiyomi.network.GET
|
|||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import okhttp3.Dns
|
import okhttp3.Dns
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
@ -13,11 +14,14 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
import tachiyomi.core.util.system.logcat
|
import tachiyomi.core.util.system.logcat
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.net.SocketTimeoutException
|
import java.net.SocketTimeoutException
|
||||||
|
|
||||||
class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
|
class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val authClient = client.newBuilder()
|
private val authClient = client.newBuilder()
|
||||||
.dns(Dns.SYSTEM)
|
.dns(Dns.SYSTEM)
|
||||||
.addInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
@ -39,18 +43,20 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
|
body = "{}".toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull()),
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
client.newCall(request).execute().use {
|
with(json) {
|
||||||
when (it.code) {
|
client.newCall(request).execute().use {
|
||||||
200 -> return it.parseAs<AuthenticationDto>().token
|
when (it.code) {
|
||||||
401 -> {
|
200 -> return it.parseAs<AuthenticationDto>().token
|
||||||
logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
|
401 -> {
|
||||||
throw IOException("Unauthorized / api key not valid")
|
logcat(LogPriority.WARN) { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
|
||||||
|
throw IOException("Unauthorized / api key not valid")
|
||||||
|
}
|
||||||
|
500 -> {
|
||||||
|
logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
|
||||||
|
throw IOException("Error fetching JWT token")
|
||||||
|
}
|
||||||
|
else -> {}
|
||||||
}
|
}
|
||||||
500 -> {
|
|
||||||
logcat(LogPriority.WARN) { "Error fetching JWT token. Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
|
|
||||||
throw IOException("Error fetching JWT token")
|
|
||||||
}
|
|
||||||
else -> {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Not sure which one to catch
|
// Not sure which one to catch
|
||||||
@ -86,9 +92,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
private fun getTotalChapters(url: String): Int {
|
private fun getTotalChapters(url: String): Int {
|
||||||
val requestUrl = getApiVolumesUrl(url)
|
val requestUrl = getApiVolumesUrl(url)
|
||||||
try {
|
try {
|
||||||
val listVolumeDto = authClient.newCall(GET(requestUrl))
|
val listVolumeDto = with(json) {
|
||||||
.execute()
|
authClient.newCall(GET(requestUrl))
|
||||||
.parseAs<List<VolumeDto>>()
|
.execute()
|
||||||
|
.parseAs<List<VolumeDto>>()
|
||||||
|
}
|
||||||
var volumeNumber = 0
|
var volumeNumber = 0
|
||||||
var maxChapterNumber = 0
|
var maxChapterNumber = 0
|
||||||
for (volume in listVolumeDto) {
|
for (volume in listVolumeDto) {
|
||||||
@ -110,12 +118,14 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
val serieId = getIdFromUrl(url)
|
val serieId = getIdFromUrl(url)
|
||||||
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
|
val requestUrl = "${getApiFromUrl(url)}/Tachiyomi/latest-chapter?seriesId=$serieId"
|
||||||
try {
|
try {
|
||||||
authClient.newCall(GET(requestUrl)).execute().use {
|
with(json) {
|
||||||
if (it.code == 200) {
|
authClient.newCall(GET(requestUrl)).execute().use {
|
||||||
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
if (it.code == 200) {
|
||||||
}
|
return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
|
||||||
if (it.code == 204) {
|
}
|
||||||
return 0F
|
if (it.code == 204) {
|
||||||
|
return 0F
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@ -127,9 +137,11 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
|
|||||||
|
|
||||||
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
suspend fun getTrackSearch(url: String): TrackSearch = withIOContext {
|
||||||
try {
|
try {
|
||||||
val serieDto: SeriesDto = authClient.newCall(GET(url))
|
val serieDto: SeriesDto = with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url))
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
|
|
||||||
val track = serieDto.toTrack()
|
val track = serieDto.toTrack()
|
||||||
track.apply {
|
track.apply {
|
||||||
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.network.POST
|
|||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.jsonArray
|
import kotlinx.serialization.json.jsonArray
|
||||||
@ -24,11 +25,14 @@ import okhttp3.Request
|
|||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.net.URLEncoder
|
import java.net.URLEncoder
|
||||||
import java.nio.charset.StandardCharsets
|
import java.nio.charset.StandardCharsets
|
||||||
|
|
||||||
class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) {
|
class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track, userId: String): Track {
|
suspend fun addLibManga(track: Track, userId: String): Track {
|
||||||
@ -57,22 +61,25 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
POST(
|
authClient.newCall(
|
||||||
"${baseUrl}library-entries",
|
POST(
|
||||||
headers = headersOf(
|
"${baseUrl}library-entries",
|
||||||
"Content-Type",
|
headers = headersOf(
|
||||||
"application/vnd.api+json",
|
"Content-Type",
|
||||||
|
"application/vnd.api+json",
|
||||||
|
),
|
||||||
|
body = data.toString()
|
||||||
|
.toRequestBody("application/vnd.api+json".toMediaType()),
|
||||||
),
|
),
|
||||||
body = data.toString().toRequestBody("application/vnd.api+json".toMediaType()),
|
)
|
||||||
),
|
.awaitSuccess()
|
||||||
)
|
.parseAs<JsonObject>()
|
||||||
.awaitSuccess()
|
.let {
|
||||||
.parseAs<JsonObject>()
|
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
||||||
.let {
|
track
|
||||||
track.media_id = it["data"]!!.jsonObject["id"]!!.jsonPrimitive.long
|
}
|
||||||
track
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,35 +99,41 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
Request.Builder()
|
authClient.newCall(
|
||||||
.url("${baseUrl}library-entries/${track.media_id}")
|
Request.Builder()
|
||||||
.headers(
|
.url("${baseUrl}library-entries/${track.media_id}")
|
||||||
headersOf(
|
.headers(
|
||||||
"Content-Type",
|
headersOf(
|
||||||
"application/vnd.api+json",
|
"Content-Type",
|
||||||
),
|
"application/vnd.api+json",
|
||||||
)
|
),
|
||||||
.patch(data.toString().toRequestBody("application/vnd.api+json".toMediaType()))
|
)
|
||||||
.build(),
|
.patch(
|
||||||
)
|
data.toString().toRequestBody("application/vnd.api+json".toMediaType()),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.build(),
|
||||||
.let {
|
)
|
||||||
track
|
.awaitSuccess()
|
||||||
}
|
.parseAs<JsonObject>()
|
||||||
|
.let {
|
||||||
|
track
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun search(query: String): List<TrackSearch> {
|
suspend fun search(query: String): List<TrackSearch> {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
authClient.newCall(GET(algoliaKeyUrl))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(algoliaKeyUrl))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content
|
.let {
|
||||||
algoliaSearch(key, query)
|
val key = it["media"]!!.jsonObject["key"]!!.jsonPrimitive.content
|
||||||
}
|
algoliaSearch(key, query)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,26 +143,28 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter")
|
put("params", "query=${URLEncoder.encode(query, StandardCharsets.UTF_8.name())}$algoliaFilter")
|
||||||
}
|
}
|
||||||
|
|
||||||
client.newCall(
|
with(json) {
|
||||||
POST(
|
client.newCall(
|
||||||
algoliaUrl,
|
POST(
|
||||||
headers = headersOf(
|
algoliaUrl,
|
||||||
"X-Algolia-Application-Id",
|
headers = headersOf(
|
||||||
algoliaAppId,
|
"X-Algolia-Application-Id",
|
||||||
"X-Algolia-API-Key",
|
algoliaAppId,
|
||||||
key,
|
"X-Algolia-API-Key",
|
||||||
|
key,
|
||||||
|
),
|
||||||
|
body = jsonObject.toString().toRequestBody(jsonMime),
|
||||||
),
|
),
|
||||||
body = jsonObject.toString().toRequestBody(jsonMime),
|
)
|
||||||
),
|
.awaitSuccess()
|
||||||
)
|
.parseAs<JsonObject>()
|
||||||
.awaitSuccess()
|
.let {
|
||||||
.parseAs<JsonObject>()
|
it["hits"]!!.jsonArray
|
||||||
.let {
|
.map { KitsuSearchManga(it.jsonObject) }
|
||||||
it["hits"]!!.jsonArray
|
.filter { it.subType != "novel" }
|
||||||
.map { KitsuSearchManga(it.jsonObject) }
|
.map { it.toTrack() }
|
||||||
.filter { it.subType != "novel" }
|
}
|
||||||
.map { it.toTrack() }
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,18 +174,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
|
.encodedQuery("filter[manga_id]=${track.media_id}&filter[user_id]=$userId")
|
||||||
.appendQueryParameter("include", "manga")
|
.appendQueryParameter("include", "manga")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
val data = it["data"]!!.jsonArray
|
.let {
|
||||||
if (data.size > 0) {
|
val data = it["data"]!!.jsonArray
|
||||||
val manga = it["included"]!!.jsonArray[0].jsonObject
|
if (data.size > 0) {
|
||||||
KitsuLibManga(data[0].jsonObject, manga).toTrack()
|
val manga = it["included"]!!.jsonArray[0].jsonObject
|
||||||
} else {
|
KitsuLibManga(data[0].jsonObject, manga).toTrack()
|
||||||
null
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,18 +197,20 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
.encodedQuery("filter[id]=${track.media_id}")
|
.encodedQuery("filter[id]=${track.media_id}")
|
||||||
.appendQueryParameter("include", "manga")
|
.appendQueryParameter("include", "manga")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
val data = it["data"]!!.jsonArray
|
.let {
|
||||||
if (data.size > 0) {
|
val data = it["data"]!!.jsonArray
|
||||||
val manga = it["included"]!!.jsonArray[0].jsonObject
|
if (data.size > 0) {
|
||||||
KitsuLibManga(data[0].jsonObject, manga).toTrack()
|
val manga = it["included"]!!.jsonArray[0].jsonObject
|
||||||
} else {
|
KitsuLibManga(data[0].jsonObject, manga).toTrack()
|
||||||
throw Exception("Could not find manga")
|
} else {
|
||||||
|
throw Exception("Could not find manga")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -204,9 +223,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
.add("client_id", clientId)
|
.add("client_id", clientId)
|
||||||
.add("client_secret", clientSecret)
|
.add("client_secret", clientSecret)
|
||||||
.build()
|
.build()
|
||||||
client.newCall(POST(loginUrl, body = formBody))
|
with(json) {
|
||||||
.awaitSuccess()
|
client.newCall(POST(loginUrl, body = formBody))
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,12 +236,14 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
|
|||||||
val url = "${baseUrl}users".toUri().buildUpon()
|
val url = "${baseUrl}users".toUri().buildUpon()
|
||||||
.encodedQuery("filter[self]=true")
|
.encodedQuery("filter[self]=true")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content
|
.let {
|
||||||
}
|
it["data"]!!.jsonArray[0].jsonObject["id"]!!.jsonPrimitive.content
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,25 +26,29 @@ class KomgaApi(private val client: OkHttpClient) {
|
|||||||
suspend fun getTrackSearch(url: String): TrackSearch =
|
suspend fun getTrackSearch(url: String): TrackSearch =
|
||||||
withIOContext {
|
withIOContext {
|
||||||
try {
|
try {
|
||||||
val track = if (url.contains(READLIST_API)) {
|
val track = with(json) {
|
||||||
client.newCall(GET(url))
|
if (url.contains(READLIST_API)) {
|
||||||
.awaitSuccess()
|
client.newCall(GET(url))
|
||||||
.parseAs<ReadListDto>()
|
.awaitSuccess()
|
||||||
.toTrack()
|
.parseAs<ReadListDto>()
|
||||||
} else {
|
.toTrack()
|
||||||
client.newCall(GET(url))
|
} else {
|
||||||
.awaitSuccess()
|
client.newCall(GET(url))
|
||||||
.parseAs<SeriesDto>()
|
.awaitSuccess()
|
||||||
.toTrack()
|
.parseAs<SeriesDto>()
|
||||||
|
.toTrack()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val progress = client
|
val progress = client
|
||||||
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
|
.newCall(GET("${url.replace("/api/v1/series/", "/api/v2/series/")}/read-progress/tachiyomi"))
|
||||||
.awaitSuccess().let {
|
.awaitSuccess().let {
|
||||||
if (url.contains("/api/v1/series/")) {
|
with(json) {
|
||||||
it.parseAs<ReadProgressV2Dto>()
|
if (url.contains("/api/v1/series/")) {
|
||||||
} else {
|
it.parseAs<ReadProgressV2Dto>()
|
||||||
it.parseAs<ReadProgressDto>().toV2()
|
} else {
|
||||||
|
it.parseAs<ReadProgressDto>().toV2()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class MangaUpdatesApi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
|
suspend fun getSeriesListItem(track: Track): Pair<ListItem, Rating?> {
|
||||||
val listItem =
|
val listItem = with(json) {
|
||||||
authClient.newCall(
|
authClient.newCall(
|
||||||
GET(
|
GET(
|
||||||
url = "$baseUrl/v1/lists/series/${track.media_id}",
|
url = "$baseUrl/v1/lists/series/${track.media_id}",
|
||||||
@ -55,6 +55,7 @@ class MangaUpdatesApi(
|
|||||||
)
|
)
|
||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
.parseAs<ListItem>()
|
.parseAs<ListItem>()
|
||||||
|
}
|
||||||
|
|
||||||
val rating = getSeriesRating(track)
|
val rating = getSeriesRating(track)
|
||||||
|
|
||||||
@ -111,13 +112,15 @@ class MangaUpdatesApi(
|
|||||||
|
|
||||||
suspend fun getSeriesRating(track: Track): Rating? {
|
suspend fun getSeriesRating(track: Track): Rating? {
|
||||||
return try {
|
return try {
|
||||||
authClient.newCall(
|
with(json) {
|
||||||
GET(
|
authClient.newCall(
|
||||||
url = "$baseUrl/v1/series/${track.media_id}/rating",
|
GET(
|
||||||
),
|
url = "$baseUrl/v1/series/${track.media_id}/rating",
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<Rating>()
|
.awaitSuccess()
|
||||||
|
.parseAs<Rating>()
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
@ -156,20 +159,22 @@ class MangaUpdatesApi(
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return client.newCall(
|
return with(json) {
|
||||||
POST(
|
client.newCall(
|
||||||
url = "$baseUrl/v1/series/search",
|
POST(
|
||||||
body = body.toString().toRequestBody(contentType),
|
url = "$baseUrl/v1/series/search",
|
||||||
),
|
body = body.toString().toRequestBody(contentType),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { obj ->
|
.parseAs<JsonObject>()
|
||||||
obj["results"]?.jsonArray?.map { element ->
|
.let { obj ->
|
||||||
json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
|
obj["results"]?.jsonArray?.map { element ->
|
||||||
|
json.decodeFromJsonElement<Record>(element.jsonObject["record"]!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.orEmpty()
|
||||||
.orEmpty()
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun authenticate(username: String, password: String): Context? {
|
suspend fun authenticate(username: String, password: String): Context? {
|
||||||
@ -177,21 +182,23 @@ class MangaUpdatesApi(
|
|||||||
put("username", username)
|
put("username", username)
|
||||||
put("password", password)
|
put("password", password)
|
||||||
}
|
}
|
||||||
return client.newCall(
|
return with(json) {
|
||||||
PUT(
|
client.newCall(
|
||||||
url = "$baseUrl/v1/account/login",
|
PUT(
|
||||||
body = body.toString().toRequestBody(contentType),
|
url = "$baseUrl/v1/account/login",
|
||||||
),
|
body = body.toString().toRequestBody(contentType),
|
||||||
)
|
),
|
||||||
.awaitSuccess()
|
)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { obj ->
|
.parseAs<JsonObject>()
|
||||||
try {
|
.let { obj ->
|
||||||
json.decodeFromJsonElement<Context>(obj["context"]!!)
|
try {
|
||||||
} catch (e: Exception) {
|
json.decodeFromJsonElement<Context>(obj["context"]!!)
|
||||||
logcat(LogPriority.ERROR, e)
|
} catch (e: Exception) {
|
||||||
null
|
logcat(LogPriority.ERROR, e)
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import eu.kanade.tachiyomi.network.parseAs
|
|||||||
import eu.kanade.tachiyomi.util.PkceUtil
|
import eu.kanade.tachiyomi.util.PkceUtil
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.boolean
|
import kotlinx.serialization.json.boolean
|
||||||
import kotlinx.serialization.json.contentOrNull
|
import kotlinx.serialization.json.contentOrNull
|
||||||
@ -27,11 +28,14 @@ import okhttp3.OkHttpClient
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody
|
import okhttp3.RequestBody
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun getAccessToken(authCode: String): OAuth {
|
suspend fun getAccessToken(authCode: String): OAuth {
|
||||||
@ -42,9 +46,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.add("code_verifier", codeVerifier)
|
.add("code_verifier", codeVerifier)
|
||||||
.add("grant_type", "authorization_code")
|
.add("grant_type", "authorization_code")
|
||||||
.build()
|
.build()
|
||||||
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
|
with(json) {
|
||||||
.awaitSuccess()
|
client.newCall(POST("$baseOAuthUrl/token", body = formBody))
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,10 +60,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.url("$baseApiUrl/users/@me")
|
.url("$baseApiUrl/users/@me")
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(request)
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(request)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { it["name"]!!.jsonPrimitive.content }
|
.parseAs<JsonObject>()
|
||||||
|
.let { it["name"]!!.jsonPrimitive.content }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,19 +76,21 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.appendQueryParameter("q", query.take(64))
|
.appendQueryParameter("q", query.take(64))
|
||||||
.appendQueryParameter("nsfw", "true")
|
.appendQueryParameter("nsfw", "true")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
it["data"]!!.jsonArray
|
.let {
|
||||||
.map { data -> data.jsonObject["node"]!!.jsonObject }
|
it["data"]!!.jsonArray
|
||||||
.map { node ->
|
.map { data -> data.jsonObject["node"]!!.jsonObject }
|
||||||
val id = node["id"]!!.jsonPrimitive.int
|
.map { node ->
|
||||||
async { getMangaDetails(id) }
|
val id = node["id"]!!.jsonPrimitive.int
|
||||||
}
|
async { getMangaDetails(id) }
|
||||||
.awaitAll()
|
}
|
||||||
.filter { trackSearch -> !trackSearch.publishing_type.contains("novel") }
|
.awaitAll()
|
||||||
}
|
.filter { trackSearch -> !trackSearch.publishing_type.contains("novel") }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,28 +100,34 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.appendPath(id.toString())
|
.appendPath(id.toString())
|
||||||
.appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
|
.appendQueryParameter("fields", "id,title,synopsis,num_chapters,main_picture,status,media_type,start_date")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
val obj = it.jsonObject
|
.let {
|
||||||
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
val obj = it.jsonObject
|
||||||
media_id = obj["id"]!!.jsonPrimitive.long
|
TrackSearch.create(TrackManager.MYANIMELIST).apply {
|
||||||
title = obj["title"]!!.jsonPrimitive.content
|
media_id = obj["id"]!!.jsonPrimitive.long
|
||||||
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
title = obj["title"]!!.jsonPrimitive.content
|
||||||
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
summary = obj["synopsis"]?.jsonPrimitive?.content ?: ""
|
||||||
cover_url = obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content ?: ""
|
total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
||||||
tracking_url = "https://myanimelist.net/manga/$media_id"
|
cover_url =
|
||||||
publishing_status = obj["status"]!!.jsonPrimitive.content.replace("_", " ")
|
obj["main_picture"]?.jsonObject?.get("large")?.jsonPrimitive?.content
|
||||||
publishing_type = obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
|
?: ""
|
||||||
start_date = try {
|
tracking_url = "https://myanimelist.net/manga/$media_id"
|
||||||
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
publishing_status =
|
||||||
outputDf.format(obj["start_date"]!!)
|
obj["status"]!!.jsonPrimitive.content.replace("_", " ")
|
||||||
} catch (e: Exception) {
|
publishing_type =
|
||||||
""
|
obj["media_type"]!!.jsonPrimitive.content.replace("_", " ")
|
||||||
|
start_date = try {
|
||||||
|
val outputDf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||||
|
outputDf.format(obj["start_date"]!!)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,10 +149,12 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.url(mangaUrl(track.media_id).toString())
|
.url(mangaUrl(track.media_id).toString())
|
||||||
.put(formBodyBuilder.build())
|
.put(formBodyBuilder.build())
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(request)
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(request)
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { parseMangaItem(it, track) }
|
.parseAs<JsonObject>()
|
||||||
|
.let { parseMangaItem(it, track) }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,15 +164,17 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.appendPath(track.media_id.toString())
|
.appendPath(track.media_id.toString())
|
||||||
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
.appendQueryParameter("fields", "num_chapters,my_list_status{start_date,finish_date}")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(uri.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(uri.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let { obj ->
|
.parseAs<JsonObject>()
|
||||||
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
.let { obj ->
|
||||||
obj.jsonObject["my_list_status"]?.jsonObject?.let {
|
track.total_chapters = obj["num_chapters"]!!.jsonPrimitive.int
|
||||||
parseMangaItem(it, track)
|
obj.jsonObject["my_list_status"]?.jsonObject?.let {
|
||||||
|
parseMangaItem(it, track)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,9 +218,11 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
|
|||||||
.url(urlBuilder.build().toString())
|
.url(urlBuilder.build().toString())
|
||||||
.get()
|
.get()
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(request)
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(request)
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
package eu.kanade.tachiyomi.data.track.myanimelist
|
package eu.kanade.tachiyomi.data.track.myanimelist
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var token: String?) : Interceptor {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private var oauth: OAuth? = null
|
private var oauth: OAuth? = null
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
@ -69,7 +73,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList, private var t
|
|||||||
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
val oauthResponse = chain.proceed(MyAnimeListApi.refreshTokenRequest(oauth!!))
|
||||||
|
|
||||||
if (oauthResponse.isSuccessful) {
|
if (oauthResponse.isSuccessful) {
|
||||||
oauthResponse.parseAs<OAuth>()
|
with(json) { oauthResponse.parseAs<OAuth>() }
|
||||||
} else {
|
} else {
|
||||||
oauthResponse.close()
|
oauthResponse.close()
|
||||||
null
|
null
|
||||||
|
@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.network.POST
|
|||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.jsonMime
|
import eu.kanade.tachiyomi.network.jsonMime
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@ -24,9 +25,12 @@ import okhttp3.FormBody
|
|||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
|
class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInterceptor) {
|
||||||
|
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
|
||||||
|
|
||||||
suspend fun addLibManga(track: Track, user_id: String): Track {
|
suspend fun addLibManga(track: Track, user_id: String): Track {
|
||||||
@ -60,14 +64,16 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
.appendQueryParameter("search", search)
|
.appendQueryParameter("search", search)
|
||||||
.appendQueryParameter("limit", "20")
|
.appendQueryParameter("limit", "20")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonArray>()
|
.awaitSuccess()
|
||||||
.let { response ->
|
.parseAs<JsonArray>()
|
||||||
response.map {
|
.let { response ->
|
||||||
jsonToSearch(it.jsonObject)
|
response.map {
|
||||||
|
jsonToSearch(it.jsonObject)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +87,7 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
tracking_url = baseUrl + obj["url"]!!.jsonPrimitive.content
|
||||||
publishing_status = obj["status"]!!.jsonPrimitive.content
|
publishing_status = obj["status"]!!.jsonPrimitive.content
|
||||||
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
publishing_type = obj["kind"]!!.jsonPrimitive.content
|
||||||
start_date = obj.get("aired_on")!!.jsonPrimitive.contentOrNull ?: ""
|
start_date = obj["aired_on"]!!.jsonPrimitive.contentOrNull ?: ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,44 +108,52 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
|
val urlMangas = "$apiUrl/mangas".toUri().buildUpon()
|
||||||
.appendPath(track.media_id.toString())
|
.appendPath(track.media_id.toString())
|
||||||
.build()
|
.build()
|
||||||
val mangas = authClient.newCall(GET(urlMangas.toString()))
|
val mangas = with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(urlMangas.toString()))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
}
|
||||||
|
|
||||||
val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
|
val url = "$apiUrl/v2/user_rates".toUri().buildUpon()
|
||||||
.appendQueryParameter("user_id", user_id)
|
.appendQueryParameter("user_id", user_id)
|
||||||
.appendQueryParameter("target_id", track.media_id.toString())
|
.appendQueryParameter("target_id", track.media_id.toString())
|
||||||
.appendQueryParameter("target_type", "Manga")
|
.appendQueryParameter("target_type", "Manga")
|
||||||
.build()
|
.build()
|
||||||
authClient.newCall(GET(url.toString()))
|
with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET(url.toString()))
|
||||||
.parseAs<JsonArray>()
|
.awaitSuccess()
|
||||||
.let { response ->
|
.parseAs<JsonArray>()
|
||||||
if (response.size > 1) {
|
.let { response ->
|
||||||
throw Exception("Too much mangas in response")
|
if (response.size > 1) {
|
||||||
|
throw Exception("Too much mangas in response")
|
||||||
|
}
|
||||||
|
val entry = response.map {
|
||||||
|
jsonToTrack(it.jsonObject, mangas)
|
||||||
|
}
|
||||||
|
entry.firstOrNull()
|
||||||
}
|
}
|
||||||
val entry = response.map {
|
}
|
||||||
jsonToTrack(it.jsonObject, mangas)
|
|
||||||
}
|
|
||||||
entry.firstOrNull()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getCurrentUser(): Int {
|
suspend fun getCurrentUser(): Int {
|
||||||
return authClient.newCall(GET("$apiUrl/users/whoami"))
|
return with(json) {
|
||||||
.awaitSuccess()
|
authClient.newCall(GET("$apiUrl/users/whoami"))
|
||||||
.parseAs<JsonObject>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<JsonObject>()
|
||||||
it["id"]!!.jsonPrimitive.int
|
.let {
|
||||||
}
|
it["id"]!!.jsonPrimitive.int
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun accessToken(code: String): OAuth {
|
suspend fun accessToken(code: String): OAuth {
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
client.newCall(accessTokenRequest(code))
|
with(json) {
|
||||||
.awaitSuccess()
|
client.newCall(accessTokenRequest(code))
|
||||||
.parseAs()
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,18 +178,12 @@ class ShikimoriApi(private val client: OkHttpClient, interceptor: ShikimoriInter
|
|||||||
private const val loginUrl = "$baseUrl/oauth/authorize"
|
private const val loginUrl = "$baseUrl/oauth/authorize"
|
||||||
|
|
||||||
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
private const val redirectUrl = "tachiyomi://shikimori-auth"
|
||||||
private const val baseMangaUrl = "$apiUrl/mangas"
|
|
||||||
|
|
||||||
fun mangaUrl(remoteId: Int): String {
|
fun authUrl() = loginUrl.toUri().buildUpon()
|
||||||
return "$baseMangaUrl/$remoteId"
|
.appendQueryParameter("client_id", clientId)
|
||||||
}
|
.appendQueryParameter("redirect_uri", redirectUrl)
|
||||||
|
.appendQueryParameter("response_type", "code")
|
||||||
fun authUrl() =
|
.build()
|
||||||
loginUrl.toUri().buildUpon()
|
|
||||||
.appendQueryParameter("client_id", clientId)
|
|
||||||
.appendQueryParameter("redirect_uri", redirectUrl)
|
|
||||||
.appendQueryParameter("response_type", "code")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
fun refreshTokenRequest(token: String) = POST(
|
fun refreshTokenRequest(token: String) = POST(
|
||||||
oauthUrl,
|
oauthUrl,
|
||||||
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.network.NetworkHelper
|
|||||||
import eu.kanade.tachiyomi.network.PUT
|
import eu.kanade.tachiyomi.network.PUT
|
||||||
import eu.kanade.tachiyomi.network.awaitSuccess
|
import eu.kanade.tachiyomi.network.awaitSuccess
|
||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Credentials
|
import okhttp3.Credentials
|
||||||
import okhttp3.Dns
|
import okhttp3.Dns
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
@ -23,11 +24,15 @@ import java.nio.charset.Charset
|
|||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
|
|
||||||
class TachideskApi {
|
class TachideskApi {
|
||||||
private val network by injectLazy<NetworkHelper>()
|
|
||||||
|
private val network: NetworkHelper by injectLazy()
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
val client: OkHttpClient =
|
val client: OkHttpClient =
|
||||||
network.client.newBuilder()
|
network.client.newBuilder()
|
||||||
.dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
|
.dns(Dns.SYSTEM) // don't use DNS over HTTPS as it breaks IP addressing
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
|
fun headersBuilder(): Headers.Builder = Headers.Builder().apply {
|
||||||
if (basePassword.isNotEmpty() && baseLogin.isNotEmpty()) {
|
if (basePassword.isNotEmpty() && baseLogin.isNotEmpty()) {
|
||||||
val credentials = Credentials.basic(baseLogin, basePassword)
|
val credentials = Credentials.basic(baseLogin, basePassword)
|
||||||
@ -50,7 +55,11 @@ class TachideskApi {
|
|||||||
trackUrl
|
trackUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs<MangaDataClass>()
|
val manga = with(json) {
|
||||||
|
client.newCall(GET("$url/full", headers))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<MangaDataClass>()
|
||||||
|
}
|
||||||
|
|
||||||
TrackSearch.create(TrackManager.SUWAYOMI).apply {
|
TrackSearch.create(TrackManager.SUWAYOMI).apply {
|
||||||
title = manga.title
|
title = manga.title
|
||||||
@ -70,7 +79,11 @@ class TachideskApi {
|
|||||||
|
|
||||||
suspend fun updateProgress(track: Track): Track {
|
suspend fun updateProgress(track: Track): Track {
|
||||||
val url = track.tracking_url
|
val url = track.tracking_url
|
||||||
val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs<List<ChapterDataClass>>()
|
val chapters = with(json) {
|
||||||
|
client.newCall(GET("$url/chapters", headers))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<List<ChapterDataClass>>()
|
||||||
|
}
|
||||||
val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
|
val lastChapterIndex = chapters.first { it.chapterNumber == track.last_chapter_read }.index
|
||||||
|
|
||||||
client.newCall(
|
client.newCall(
|
||||||
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.util.system.isInstalledFromFDroid
|
|||||||
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
import eu.kanade.tachiyomi.util.system.isPreviewBuildType
|
||||||
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
import eu.kanade.tachiyomi.util.system.isReleaseBuildType
|
||||||
import exh.syDebugVersion
|
import exh.syDebugVersion
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
import tachiyomi.core.util.lang.withIOContext
|
import tachiyomi.core.util.lang.withIOContext
|
||||||
@ -21,6 +22,8 @@ class AppUpdateChecker {
|
|||||||
|
|
||||||
private val networkService: NetworkHelper by injectLazy()
|
private val networkService: NetworkHelper by injectLazy()
|
||||||
private val preferenceStore: PreferenceStore by injectLazy()
|
private val preferenceStore: PreferenceStore by injectLazy()
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val lastAppCheck: Preference<Long> by lazy {
|
private val lastAppCheck: Preference<Long> by lazy {
|
||||||
preferenceStore.getLong("last_app_check", 0)
|
preferenceStore.getLong("last_app_check", 0)
|
||||||
}
|
}
|
||||||
@ -32,24 +35,26 @@ class AppUpdateChecker {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return withIOContext {
|
return withIOContext {
|
||||||
val result = networkService.client
|
val result = with(json) {
|
||||||
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
|
networkService.client
|
||||||
.awaitSuccess()
|
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
|
||||||
.parseAs<GithubRelease>()
|
.awaitSuccess()
|
||||||
.let {
|
.parseAs<GithubRelease>()
|
||||||
lastAppCheck.set(Date().time)
|
.let {
|
||||||
|
lastAppCheck.set(Date().time)
|
||||||
|
|
||||||
// Check if latest version is different from current version
|
// Check if latest version is different from current version
|
||||||
if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) {
|
if (/* SY --> */ isNewVersionSY(it.version) /* SY <-- */) {
|
||||||
if (context.isInstalledFromFDroid()) {
|
if (context.isInstalledFromFDroid()) {
|
||||||
AppUpdateResult.NewUpdateFdroidInstallation
|
AppUpdateResult.NewUpdateFdroidInstallation
|
||||||
|
} else {
|
||||||
|
AppUpdateResult.NewUpdate(it)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
AppUpdateResult.NewUpdate(it)
|
AppUpdateResult.NoNewUpdate
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
AppUpdateResult.NoNewUpdate
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
when (result) {
|
when (result) {
|
||||||
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
|
is AppUpdateResult.NewUpdate -> AppUpdateNotifier(context).promptUpdate(result.release)
|
||||||
|
@ -14,6 +14,7 @@ import eu.kanade.tachiyomi.network.awaitSuccess
|
|||||||
import eu.kanade.tachiyomi.network.parseAs
|
import eu.kanade.tachiyomi.network.parseAs
|
||||||
import exh.source.BlacklistedSources
|
import exh.source.BlacklistedSources
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.preference.Preference
|
import tachiyomi.core.preference.Preference
|
||||||
import tachiyomi.core.preference.PreferenceStore
|
import tachiyomi.core.preference.PreferenceStore
|
||||||
@ -27,10 +28,12 @@ internal class ExtensionGithubApi {
|
|||||||
|
|
||||||
private val networkService: NetworkHelper by injectLazy()
|
private val networkService: NetworkHelper by injectLazy()
|
||||||
private val preferenceStore: PreferenceStore by injectLazy()
|
private val preferenceStore: PreferenceStore by injectLazy()
|
||||||
|
private val extensionManager: ExtensionManager by injectLazy()
|
||||||
|
private val json: Json by injectLazy()
|
||||||
|
|
||||||
private val lastExtCheck: Preference<Long> by lazy {
|
private val lastExtCheck: Preference<Long> by lazy {
|
||||||
preferenceStore.getLong("last_ext_check", 0)
|
preferenceStore.getLong("last_ext_check", 0)
|
||||||
}
|
}
|
||||||
private val extensionManager: ExtensionManager by injectLazy()
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
private val sourcePreferences: SourcePreferences by injectLazy()
|
private val sourcePreferences: SourcePreferences by injectLazy()
|
||||||
@ -61,23 +64,25 @@ internal class ExtensionGithubApi {
|
|||||||
.awaitSuccess()
|
.awaitSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
val extensions = response
|
val extensions = with(json) {
|
||||||
.parseAs<List<ExtensionJsonObject>>()
|
response
|
||||||
.toExtensions() /* SY --> */ + unsortedPreferences.extensionRepos()
|
.parseAs<List<ExtensionJsonObject>>()
|
||||||
.get()
|
.toExtensions() /* SY --> */ + unsortedPreferences.extensionRepos()
|
||||||
.flatMap { repoPath ->
|
.get()
|
||||||
val url = if (requiresFallbackSource) {
|
.flatMap { repoPath ->
|
||||||
"$FALLBACK_BASE_URL$repoPath@repo/"
|
val url = if (requiresFallbackSource) {
|
||||||
} else {
|
"$FALLBACK_BASE_URL$repoPath@repo/"
|
||||||
"$BASE_URL$repoPath/repo/"
|
} else {
|
||||||
|
"$BASE_URL$repoPath/repo/"
|
||||||
|
}
|
||||||
|
networkService.client
|
||||||
|
.newCall(GET("${url}index.min.json"))
|
||||||
|
.awaitSuccess()
|
||||||
|
.parseAs<List<ExtensionJsonObject>>()
|
||||||
|
.toExtensions(url, repoSource = true)
|
||||||
}
|
}
|
||||||
networkService.client
|
// SY <--
|
||||||
.newCall(GET("${url}index.min.json"))
|
}
|
||||||
.awaitSuccess()
|
|
||||||
.parseAs<List<ExtensionJsonObject>>()
|
|
||||||
.toExtensions(url, repoSource = true)
|
|
||||||
}
|
|
||||||
// SY <--
|
|
||||||
|
|
||||||
// Sanity check - a small number of extensions probably means something broke
|
// Sanity check - a small number of extensions probably means something broke
|
||||||
// with the repo generator
|
// with the repo generator
|
||||||
|
@ -112,16 +112,16 @@ class MangaDex(delegate: HttpSource, val context: Context) :
|
|||||||
MangaPlusHandler(network.client)
|
MangaPlusHandler(network.client)
|
||||||
}
|
}
|
||||||
private val comikeyHandler by lazy {
|
private val comikeyHandler by lazy {
|
||||||
ComikeyHandler(network.cloudflareClient, network.defaultUserAgent)
|
ComikeyHandler(network.cloudflareClient, network.defaultUserAgentProvider())
|
||||||
}
|
}
|
||||||
private val bilibiliHandler by lazy {
|
private val bilibiliHandler by lazy {
|
||||||
BilibiliHandler(network.cloudflareClient)
|
BilibiliHandler(network.cloudflareClient)
|
||||||
}
|
}
|
||||||
private val azukHandler by lazy {
|
private val azukHandler by lazy {
|
||||||
AzukiHandler(network.client, network.defaultUserAgent)
|
AzukiHandler(network.client, network.defaultUserAgentProvider())
|
||||||
}
|
}
|
||||||
private val mangaHotHandler by lazy {
|
private val mangaHotHandler by lazy {
|
||||||
MangaHotHandler(network.client, network.defaultUserAgent)
|
MangaHotHandler(network.client, network.defaultUserAgentProvider())
|
||||||
}
|
}
|
||||||
private val pageHandler by lazy {
|
private val pageHandler by lazy {
|
||||||
PageHandler(
|
PageHandler(
|
||||||
|
@ -120,7 +120,7 @@ class ExtensionDetailsScreenModel(
|
|||||||
|
|
||||||
val cleared = urls.sumOf {
|
val cleared = urls.sumOf {
|
||||||
try {
|
try {
|
||||||
network.cookieManager.remove(it.toHttpUrl())
|
network.cookieJar.remove(it.toHttpUrl())
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logcat(LogPriority.ERROR, e) { "Failed to clear cookies for $it" }
|
logcat(LogPriority.ERROR, e) { "Failed to clear cookies for $it" }
|
||||||
0
|
0
|
||||||
|
@ -92,7 +92,7 @@ class WebViewActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun clearCookies(url: String) {
|
private fun clearCookies(url: String) {
|
||||||
val cleared = network.cookieManager.remove(url.toHttpUrl())
|
val cleared = network.cookieJar.remove(url.toHttpUrl())
|
||||||
logcat { "Cleared $cleared cookies for: $url" }
|
logcat { "Cleared $cleared cookies for: $url" }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ class WebViewScreenModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearCookies(url: String) {
|
fun clearCookies(url: String) {
|
||||||
val cleared = network.cookieManager.remove(url.toHttpUrl())
|
val cleared = network.cookieJar.remove(url.toHttpUrl())
|
||||||
logcat { "Cleared $cleared cookies for: $url" }
|
logcat { "Cleared $cleared cookies for: $url" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,6 +10,7 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||||||
import exh.log.xLogD
|
import exh.log.xLogD
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.add
|
import kotlinx.serialization.json.add
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
@ -21,6 +22,7 @@ import okhttp3.Request
|
|||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class BilibiliHandler(currentClient: OkHttpClient) {
|
class BilibiliHandler(currentClient: OkHttpClient) {
|
||||||
@ -35,6 +37,8 @@ class BilibiliHandler(currentClient: OkHttpClient) {
|
|||||||
.rateLimit(1, 1, TimeUnit.SECONDS)
|
.rateLimit(1, 1, TimeUnit.SECONDS)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
|
val json by injectLazy<Json>()
|
||||||
|
|
||||||
suspend fun fetchPageList(externalUrl: String, chapterNumber: String): List<Page> {
|
suspend fun fetchPageList(externalUrl: String, chapterNumber: String): List<Page> {
|
||||||
// Sometimes the urls direct it to the manga page instead, so we try to find the correct chapter
|
// Sometimes the urls direct it to the manga page instead, so we try to find the correct chapter
|
||||||
// Though these seem to be older chapters, so maybe remove this later
|
// Though these seem to be older chapters, so maybe remove this later
|
||||||
@ -97,7 +101,7 @@ class BilibiliHandler(currentClient: OkHttpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun chapterListParse(response: Response): List<SChapter> {
|
fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val result = response.parseAs<BilibiliResultDto<BilibiliComicDto>>()
|
val result = with(json) { response.parseAs<BilibiliResultDto<BilibiliComicDto>>() }
|
||||||
|
|
||||||
if (result.code != 0) {
|
if (result.code != 0) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
@ -140,7 +144,7 @@ class BilibiliHandler(currentClient: OkHttpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun pageListParse(response: Response): List<Page> {
|
private fun pageListParse(response: Response): List<Page> {
|
||||||
val result = response.parseAs<BilibiliResultDto<BilibiliReader>>()
|
val result = with(json) { response.parseAs<BilibiliResultDto<BilibiliReader>>() }
|
||||||
|
|
||||||
if (result.code != 0) {
|
if (result.code != 0) {
|
||||||
return emptyList()
|
return emptyList()
|
||||||
@ -177,7 +181,9 @@ class BilibiliHandler(currentClient: OkHttpClient) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun imageUrlParse(response: Response): String {
|
private fun imageUrlParse(response: Response): String {
|
||||||
val result = response.parseAs<BilibiliResultDto<List<BilibiliPageDto>>>()
|
val result = with(json) {
|
||||||
|
response.parseAs<BilibiliResultDto<List<BilibiliPageDto>>>()
|
||||||
|
}
|
||||||
val page = result.data!![0]
|
val page = result.data!![0]
|
||||||
|
|
||||||
return "${page.url}?token=${page.token}"
|
return "${page.url}?token=${page.token}"
|
||||||
|
@ -80,7 +80,7 @@ class MangaDexAuthInterceptor(
|
|||||||
val oauthResponse = chain.proceed(MdUtil.refreshTokenRequest(oauth!!))
|
val oauthResponse = chain.proceed(MdUtil.refreshTokenRequest(oauth!!))
|
||||||
|
|
||||||
if (oauthResponse.isSuccessful) {
|
if (oauthResponse.isSuccessful) {
|
||||||
oauthResponse.parseAs<OAuth>()
|
with(MdUtil.jsonParser) { oauthResponse.parseAs<OAuth>() }
|
||||||
} else {
|
} else {
|
||||||
oauthResponse.close()
|
oauthResponse.close()
|
||||||
null
|
null
|
||||||
|
@ -35,7 +35,11 @@ class MangaDexLoginHelper(
|
|||||||
.build()
|
.build()
|
||||||
|
|
||||||
val error = kotlin.runCatching {
|
val error = kotlin.runCatching {
|
||||||
val data = client.newCall(POST(MdApi.baseAuthUrl + MdApi.token, body = loginFormBody)).awaitSuccess().parseAs<OAuth>()
|
val data = with(MdUtil.jsonParser) {
|
||||||
|
client.newCall(
|
||||||
|
POST(MdApi.baseAuthUrl + MdApi.token, body = loginFormBody),
|
||||||
|
).awaitSuccess().parseAs<OAuth>()
|
||||||
|
}
|
||||||
mangaDexAuthInterceptor.setAuth(data)
|
mangaDexAuthInterceptor.setAuth(data)
|
||||||
}.exceptionOrNull()
|
}.exceptionOrNull()
|
||||||
|
|
||||||
|
@ -26,147 +26,173 @@ class MangaDexAuthService(
|
|||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun userFollowList(offset: Int): MangaListDto {
|
suspend fun userFollowList(offset: Int): MangaListDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
"${MdApi.userFollows}?limit=100&offset=$offset&includes[]=${MdConstants.Types.coverArt}",
|
GET(
|
||||||
headers,
|
"${MdApi.userFollows}?limit=100&offset=$offset&includes[]=${MdConstants.Types.coverArt}",
|
||||||
CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun readingStatusForManga(mangaId: String): ReadingStatusDto {
|
suspend fun readingStatusForManga(mangaId: String): ReadingStatusDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
"${MdApi.manga}/$mangaId/status",
|
GET(
|
||||||
headers,
|
"${MdApi.manga}/$mangaId/status",
|
||||||
CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun readChaptersForManga(mangaId: String): ReadChapterDto {
|
suspend fun readChaptersForManga(mangaId: String): ReadChapterDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
"${MdApi.manga}/$mangaId/read",
|
GET(
|
||||||
headers,
|
"${MdApi.manga}/$mangaId/read",
|
||||||
CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateReadingStatusForManga(
|
suspend fun updateReadingStatusForManga(
|
||||||
mangaId: String,
|
mangaId: String,
|
||||||
readingStatusDto: ReadingStatusDto,
|
readingStatusDto: ReadingStatusDto,
|
||||||
): ResultDto {
|
): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
POST(
|
client.newCall(
|
||||||
"${MdApi.manga}/$mangaId/status",
|
POST(
|
||||||
headers,
|
"${MdApi.manga}/$mangaId/status",
|
||||||
body = MdUtil.encodeToBody(readingStatusDto),
|
headers,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
body = MdUtil.encodeToBody(readingStatusDto),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun readingStatusAllManga(): ReadingStatusMapDto {
|
suspend fun readingStatusAllManga(): ReadingStatusMapDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.readingStatusForAllManga,
|
GET(
|
||||||
headers,
|
MdApi.readingStatusForAllManga,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun readingStatusByType(status: String): ReadingStatusMapDto {
|
suspend fun readingStatusByType(status: String): ReadingStatusMapDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
"${MdApi.readingStatusForAllManga}?status=$status",
|
GET(
|
||||||
headers,
|
"${MdApi.readingStatusForAllManga}?status=$status",
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markChapterRead(chapterId: String): ResultDto {
|
suspend fun markChapterRead(chapterId: String): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
POST(
|
client.newCall(
|
||||||
"${MdApi.chapter}/$chapterId/read",
|
POST(
|
||||||
headers,
|
"${MdApi.chapter}/$chapterId/read",
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun markChapterUnRead(chapterId: String): ResultDto {
|
suspend fun markChapterUnRead(chapterId: String): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
Request.Builder()
|
client.newCall(
|
||||||
.url("${MdApi.chapter}/$chapterId/read")
|
Request.Builder()
|
||||||
.delete()
|
.url("${MdApi.chapter}/$chapterId/read")
|
||||||
.headers(headers)
|
.delete()
|
||||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
.headers(headers)
|
||||||
.build(),
|
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
.build(),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun followManga(mangaId: String): ResultDto {
|
suspend fun followManga(mangaId: String): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
POST(
|
client.newCall(
|
||||||
"${MdApi.manga}/$mangaId/follow",
|
POST(
|
||||||
headers,
|
"${MdApi.manga}/$mangaId/follow",
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
headers,
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun unfollowManga(mangaId: String): ResultDto {
|
suspend fun unfollowManga(mangaId: String): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
Request.Builder()
|
client.newCall(
|
||||||
.url("${MdApi.manga}/$mangaId/follow")
|
Request.Builder()
|
||||||
.delete()
|
.url("${MdApi.manga}/$mangaId/follow")
|
||||||
.headers(headers)
|
.delete()
|
||||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
.headers(headers)
|
||||||
.build(),
|
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
.build(),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateMangaRating(mangaId: String, rating: Int): ResultDto {
|
suspend fun updateMangaRating(mangaId: String, rating: Int): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
POST(
|
client.newCall(
|
||||||
"${MdApi.rating}/$mangaId",
|
POST(
|
||||||
headers,
|
"${MdApi.rating}/$mangaId",
|
||||||
body = MdUtil.encodeToBody(RatingDto(rating)),
|
headers,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
body = MdUtil.encodeToBody(RatingDto(rating)),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun deleteMangaRating(mangaId: String): ResultDto {
|
suspend fun deleteMangaRating(mangaId: String): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
Request.Builder()
|
client.newCall(
|
||||||
.delete()
|
Request.Builder()
|
||||||
.url("${MdApi.rating}/$mangaId")
|
.delete()
|
||||||
.headers(headers)
|
.url("${MdApi.rating}/$mangaId")
|
||||||
.cacheControl(CacheControl.FORCE_NETWORK)
|
.headers(headers)
|
||||||
.build(),
|
.cacheControl(CacheControl.FORCE_NETWORK)
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
.build(),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun mangasRating(vararg mangaIds: String): RatingResponseDto {
|
suspend fun mangasRating(vararg mangaIds: String): RatingResponseDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.rating.toHttpUrl()
|
GET(
|
||||||
.newBuilder()
|
MdApi.rating.toHttpUrl()
|
||||||
.apply {
|
.newBuilder()
|
||||||
mangaIds.forEach {
|
.apply {
|
||||||
addQueryParameter("manga[]", it)
|
mangaIds.forEach {
|
||||||
|
addQueryParameter("manga[]", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.build(),
|
||||||
.build(),
|
headers,
|
||||||
headers,
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
),
|
||||||
),
|
).awaitSuccess().parseAs()
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,77 +31,85 @@ class MangaDexService(
|
|||||||
suspend fun viewMangas(
|
suspend fun viewMangas(
|
||||||
ids: List<String>,
|
ids: List<String>,
|
||||||
): MangaListDto {
|
): MangaListDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.manga.toHttpUrl()
|
GET(
|
||||||
.newBuilder()
|
MdApi.manga.toHttpUrl()
|
||||||
.apply {
|
.newBuilder()
|
||||||
addQueryParameter("includes[]", MdConstants.Types.coverArt)
|
.apply {
|
||||||
addQueryParameter("limit", ids.size.toString())
|
addQueryParameter("includes[]", MdConstants.Types.coverArt)
|
||||||
ids.forEach {
|
addQueryParameter("limit", ids.size.toString())
|
||||||
addQueryParameter("ids[]", it)
|
ids.forEach {
|
||||||
|
addQueryParameter("ids[]", it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.build(),
|
||||||
.build(),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
),
|
||||||
),
|
).awaitSuccess().parseAs()
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun viewManga(
|
suspend fun viewManga(
|
||||||
id: String,
|
id: String,
|
||||||
): MangaDto {
|
): MangaDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.manga.toHttpUrl()
|
GET(
|
||||||
.newBuilder()
|
MdApi.manga.toHttpUrl()
|
||||||
.apply {
|
.newBuilder()
|
||||||
addPathSegment(id)
|
.apply {
|
||||||
addQueryParameter("includes[]", MdConstants.Types.coverArt)
|
addPathSegment(id)
|
||||||
addQueryParameter("includes[]", MdConstants.Types.author)
|
addQueryParameter("includes[]", MdConstants.Types.coverArt)
|
||||||
addQueryParameter("includes[]", MdConstants.Types.artist)
|
addQueryParameter("includes[]", MdConstants.Types.author)
|
||||||
}
|
addQueryParameter("includes[]", MdConstants.Types.artist)
|
||||||
.build(),
|
}
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
.build(),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun mangasRating(
|
suspend fun mangasRating(
|
||||||
vararg ids: String,
|
vararg ids: String,
|
||||||
): StatisticsDto {
|
): StatisticsDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.statistics.toHttpUrl()
|
GET(
|
||||||
.newBuilder()
|
MdApi.statistics.toHttpUrl()
|
||||||
.apply {
|
.newBuilder()
|
||||||
ids.forEach { id ->
|
.apply {
|
||||||
addQueryParameter("manga[]", id)
|
ids.forEach { id ->
|
||||||
|
addQueryParameter("manga[]", id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.build(),
|
||||||
.build(),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
),
|
||||||
),
|
).awaitSuccess().parseAs()
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun aggregateChapters(
|
suspend fun aggregateChapters(
|
||||||
id: String,
|
id: String,
|
||||||
translatedLanguage: String,
|
translatedLanguage: String,
|
||||||
): AggregateDto {
|
): AggregateDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.manga.toHttpUrl()
|
GET(
|
||||||
.newBuilder()
|
MdApi.manga.toHttpUrl()
|
||||||
.apply {
|
.newBuilder()
|
||||||
addPathSegment(id)
|
.apply {
|
||||||
addPathSegment("aggregate")
|
addPathSegment(id)
|
||||||
addQueryParameter("translatedLanguage[]", translatedLanguage)
|
addPathSegment("aggregate")
|
||||||
}
|
addQueryParameter("translatedLanguage[]", translatedLanguage)
|
||||||
.build(),
|
}
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
.build(),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.splitString() = replace("\n", "").split(',').trimAll().dropEmpty()
|
private fun String.splitString() = replace("\n", "").split(',').trimAll().dropEmpty()
|
||||||
@ -137,56 +145,68 @@ class MangaDexService(
|
|||||||
}
|
}
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
url,
|
GET(
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
url,
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun viewChapter(id: String): ChapterDto {
|
suspend fun viewChapter(id: String): ChapterDto {
|
||||||
return client.newCall(GET("${MdApi.chapter}/$id", cache = CacheControl.FORCE_NETWORK))
|
return with(MdUtil.jsonParser) {
|
||||||
.awaitSuccess()
|
client.newCall(GET("${MdApi.chapter}/$id", cache = CacheControl.FORCE_NETWORK))
|
||||||
.parseAs(MdUtil.jsonParser)
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun randomManga(): MangaDto {
|
suspend fun randomManga(): MangaDto {
|
||||||
return client.newCall(GET("${MdApi.manga}/random", cache = CacheControl.FORCE_NETWORK))
|
return with(MdUtil.jsonParser) {
|
||||||
.awaitSuccess()
|
client.newCall(GET("${MdApi.manga}/random", cache = CacheControl.FORCE_NETWORK))
|
||||||
.parseAs(MdUtil.jsonParser)
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun atHomeImageReport(atHomeImageReportDto: AtHomeImageReportDto): ResultDto {
|
suspend fun atHomeImageReport(atHomeImageReportDto: AtHomeImageReportDto): ResultDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
POST(
|
client.newCall(
|
||||||
MdConstants.atHomeReportUrl,
|
POST(
|
||||||
body = MdUtil.encodeToBody(atHomeImageReportDto),
|
MdConstants.atHomeReportUrl,
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
body = MdUtil.encodeToBody(atHomeImageReportDto),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getAtHomeServer(
|
suspend fun getAtHomeServer(
|
||||||
atHomeRequestUrl: String,
|
atHomeRequestUrl: String,
|
||||||
headers: Headers,
|
headers: Headers,
|
||||||
): AtHomeDto {
|
): AtHomeDto {
|
||||||
return client.newCall(GET(atHomeRequestUrl, headers, CacheControl.FORCE_NETWORK))
|
return with(MdUtil.jsonParser) {
|
||||||
.awaitSuccess()
|
client.newCall(GET(atHomeRequestUrl, headers, CacheControl.FORCE_NETWORK))
|
||||||
.parseAs(MdUtil.jsonParser)
|
.awaitSuccess()
|
||||||
|
.parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun relatedManga(id: String): RelationListDto {
|
suspend fun relatedManga(id: String): RelationListDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
MdApi.manga.toHttpUrl().newBuilder()
|
GET(
|
||||||
.apply {
|
MdApi.manga.toHttpUrl().newBuilder()
|
||||||
addPathSegment(id)
|
.apply {
|
||||||
addPathSegment("relation")
|
addPathSegment(id)
|
||||||
}
|
addPathSegment("relation")
|
||||||
.build(),
|
}
|
||||||
cache = CacheControl.FORCE_NETWORK,
|
.build(),
|
||||||
),
|
cache = CacheControl.FORCE_NETWORK,
|
||||||
).awaitSuccess().parseAs(MdUtil.jsonParser)
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,12 @@ class SimilarService(
|
|||||||
private val client: OkHttpClient,
|
private val client: OkHttpClient,
|
||||||
) {
|
) {
|
||||||
suspend fun getSimilarManga(mangaId: String): SimilarMangaDto {
|
suspend fun getSimilarManga(mangaId: String): SimilarMangaDto {
|
||||||
return client.newCall(
|
return with(MdUtil.jsonParser) {
|
||||||
GET(
|
client.newCall(
|
||||||
"${MdUtil.similarBaseApi}$mangaId.json",
|
GET(
|
||||||
),
|
"${MdUtil.similarBaseApi}$mangaId.json",
|
||||||
).awaitSuccess().parseAs()
|
),
|
||||||
|
).awaitSuccess().parseAs()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.MangasPage
|
|||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import exh.util.MangaType
|
import exh.util.MangaType
|
||||||
import exh.util.mangaType
|
import exh.util.mangaType
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
import kotlinx.serialization.json.JsonElement
|
import kotlinx.serialization.json.JsonElement
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
@ -37,6 +38,7 @@ abstract class API(val endpoint: String) {
|
|||||||
val client by lazy {
|
val client by lazy {
|
||||||
Injekt.get<NetworkHelper>().client
|
Injekt.get<NetworkHelper>().client
|
||||||
}
|
}
|
||||||
|
val json by injectLazy<Json>()
|
||||||
|
|
||||||
abstract suspend fun getRecsBySearch(search: String): List<SManga>
|
abstract suspend fun getRecsBySearch(search: String): List<SManga>
|
||||||
|
|
||||||
@ -52,7 +54,7 @@ class MyAnimeList : API("https://api.jikan.moe/v4/") {
|
|||||||
.addPathSegment("recommendations")
|
.addPathSegment("recommendations")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val data = client.newCall(GET(apiUrl)).awaitSuccess().parseAs<JsonObject>()
|
val data = with(json) { client.newCall(GET(apiUrl)).awaitSuccess().parseAs<JsonObject>() }
|
||||||
return data["data"]!!.jsonArray
|
return data["data"]!!.jsonArray
|
||||||
.map { it.jsonObject["entry"]!!.jsonObject }
|
.map { it.jsonObject["entry"]!!.jsonObject }
|
||||||
.map { rec ->
|
.map { rec ->
|
||||||
@ -88,8 +90,10 @@ class MyAnimeList : API("https://api.jikan.moe/v4/") {
|
|||||||
.addQueryParameter("q", search)
|
.addQueryParameter("q", search)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val data = client.newCall(GET(url)).awaitSuccess()
|
val data = with(json) {
|
||||||
.parseAs<JsonObject>()
|
client.newCall(GET(url)).awaitSuccess()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
}
|
||||||
return getRecsById(data["data"]!!.jsonArray.first().jsonObject["mal_id"]!!.jsonPrimitive.content)
|
return getRecsById(data["data"]!!.jsonArray.first().jsonObject["mal_id"]!!.jsonPrimitive.content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -137,8 +141,10 @@ class Anilist : API("https://graphql.anilist.co/") {
|
|||||||
}
|
}
|
||||||
val payloadBody = payload.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
|
val payloadBody = payload.toString().toRequestBody("application/json; charset=utf-8".toMediaType())
|
||||||
|
|
||||||
val data = client.newCall(POST(endpoint, body = payloadBody)).awaitSuccess()
|
val data = with(json) {
|
||||||
.parseAs<JsonObject>()
|
client.newCall(POST(endpoint, body = payloadBody)).awaitSuccess()
|
||||||
|
.parseAs<JsonObject>()
|
||||||
|
}
|
||||||
|
|
||||||
val media = data["data"]!!
|
val media = data["data"]!!
|
||||||
.jsonObject["Page"]!!
|
.jsonObject["Page"]!!
|
||||||
|
@ -7,6 +7,13 @@ plugins {
|
|||||||
|
|
||||||
android {
|
android {
|
||||||
namespace = "eu.kanade.tachiyomi.core"
|
namespace = "eu.kanade.tachiyomi.core"
|
||||||
|
|
||||||
|
kotlinOptions {
|
||||||
|
freeCompilerArgs += listOf(
|
||||||
|
"-Xcontext-receivers",
|
||||||
|
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -25,12 +32,8 @@ dependencies {
|
|||||||
api(kotlinx.serialization.json)
|
api(kotlinx.serialization.json)
|
||||||
api(kotlinx.serialization.json.okio)
|
api(kotlinx.serialization.json.okio)
|
||||||
|
|
||||||
api(libs.injekt.core)
|
|
||||||
|
|
||||||
api(libs.preferencektx)
|
api(libs.preferencektx)
|
||||||
|
|
||||||
implementation(androidx.corektx)
|
|
||||||
|
|
||||||
// JavaScript engine
|
// JavaScript engine
|
||||||
implementation(libs.bundles.js.engine)
|
implementation(libs.bundles.js.engine)
|
||||||
|
|
||||||
|
@ -7,28 +7,32 @@ import eu.kanade.tachiyomi.network.interceptor.UserAgentInterceptor
|
|||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.logging.HttpLoggingInterceptor
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
/* SY --> */
|
/* SY --> */
|
||||||
open /* SY <-- */ class NetworkHelper(context: Context) {
|
open /* SY <-- */ class NetworkHelper(
|
||||||
|
context: Context,
|
||||||
private val preferences: NetworkPreferences by injectLazy()
|
private val preferences: NetworkPreferences,
|
||||||
|
) {
|
||||||
|
|
||||||
private val cacheDir = File(context.cacheDir, "network_cache")
|
private val cacheDir = File(context.cacheDir, "network_cache")
|
||||||
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
private val cacheSize = 5L * 1024 * 1024 // 5 MiB
|
||||||
|
|
||||||
/* SY --> */
|
/* SY --> */
|
||||||
open /* SY <-- */val cookieManager = AndroidCookieJar()
|
open /* SY <-- */val cookieJar = AndroidCookieJar()
|
||||||
|
|
||||||
private val userAgentInterceptor by lazy { UserAgentInterceptor() }
|
private val userAgentInterceptor by lazy {
|
||||||
private val cloudflareInterceptor by lazy { CloudflareInterceptor(context) }
|
UserAgentInterceptor(::defaultUserAgentProvider)
|
||||||
|
}
|
||||||
|
private val cloudflareInterceptor by lazy {
|
||||||
|
CloudflareInterceptor(context, cookieJar, ::defaultUserAgentProvider)
|
||||||
|
}
|
||||||
|
|
||||||
private val baseClientBuilder: OkHttpClient.Builder
|
private val baseClientBuilder: OkHttpClient.Builder
|
||||||
get() {
|
get() {
|
||||||
val builder = OkHttpClient.Builder()
|
val builder = OkHttpClient.Builder()
|
||||||
.cookieJar(cookieManager)
|
.cookieJar(cookieJar)
|
||||||
.connectTimeout(30, TimeUnit.SECONDS)
|
.connectTimeout(30, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
.callTimeout(2, TimeUnit.MINUTES)
|
.callTimeout(2, TimeUnit.MINUTES)
|
||||||
@ -69,7 +73,5 @@ open /* SY <-- */ class NetworkHelper(context: Context) {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
val defaultUserAgent by lazy {
|
fun defaultUserAgentProvider() = preferences.defaultUserAgent().get().trim()
|
||||||
preferences.defaultUserAgent().get().trim()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network
|
|||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.suspendCancellableCoroutine
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
import kotlinx.serialization.DeserializationStrategy
|
import kotlinx.serialization.DeserializationStrategy
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.okio.decodeFromBufferedSource
|
import kotlinx.serialization.json.okio.decodeFromBufferedSource
|
||||||
import kotlinx.serialization.serializer
|
import kotlinx.serialization.serializer
|
||||||
@ -16,8 +15,6 @@ import okhttp3.Response
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.Producer
|
import rx.Producer
|
||||||
import rx.Subscription
|
import rx.Subscription
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.atomic.AtomicBoolean
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import kotlin.coroutines.resumeWithException
|
import kotlin.coroutines.resumeWithException
|
||||||
@ -131,14 +128,18 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre
|
|||||||
return progressClient.newCall(request)
|
return progressClient.newCall(request)
|
||||||
}
|
}
|
||||||
|
|
||||||
inline fun <reified T> Response.parseAs(/* SY --> */json: Json = Injekt.get()/* SY <-- */): T {
|
context(Json)
|
||||||
return decodeFromJsonResponse(serializer(), this, /* SY --> */ json /* SY <-- */)
|
inline fun <reified T> Response.parseAs(): T {
|
||||||
|
return decodeFromJsonResponse(serializer(), this)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
context(Json)
|
||||||
fun <T> decodeFromJsonResponse(deserializer: DeserializationStrategy<T>, response: Response, /* SY --> */ json: Json /* SY <-- */): T {
|
fun <T> decodeFromJsonResponse(
|
||||||
|
deserializer: DeserializationStrategy<T>,
|
||||||
|
response: Response,
|
||||||
|
): T {
|
||||||
return response.body.source().use {
|
return response.body.source().use {
|
||||||
json.decodeFromBufferedSource(deserializer, it)
|
decodeFromBufferedSource(deserializer, it)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ import android.webkit.WebView
|
|||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import eu.kanade.tachiyomi.core.R
|
import eu.kanade.tachiyomi.core.R
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.AndroidCookieJar
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
import eu.kanade.tachiyomi.util.system.WebViewClientCompat
|
||||||
import eu.kanade.tachiyomi.util.system.isOutdated
|
import eu.kanade.tachiyomi.util.system.isOutdated
|
||||||
import eu.kanade.tachiyomi.util.system.toast
|
import eu.kanade.tachiyomi.util.system.toast
|
||||||
@ -15,16 +15,17 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
|
|||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
|
|
||||||
class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(context) {
|
class CloudflareInterceptor(
|
||||||
|
private val context: Context,
|
||||||
|
private val cookieManager: AndroidCookieJar,
|
||||||
|
defaultUserAgentProvider: () -> String,
|
||||||
|
) : WebViewInterceptor(context, defaultUserAgentProvider) {
|
||||||
|
|
||||||
private val executor = ContextCompat.getMainExecutor(context)
|
private val executor = ContextCompat.getMainExecutor(context)
|
||||||
|
|
||||||
private val networkHelper: NetworkHelper by injectLazy()
|
|
||||||
|
|
||||||
override fun shouldIntercept(response: Response): Boolean {
|
override fun shouldIntercept(response: Response): Boolean {
|
||||||
// Check if Cloudflare anti-bot is on
|
// Check if Cloudflare anti-bot is on
|
||||||
return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
|
return response.code in ERROR_CODES && response.header("Server") in SERVER_CHECK
|
||||||
@ -33,8 +34,8 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
|
|||||||
override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
|
override fun intercept(chain: Interceptor.Chain, request: Request, response: Response): Response {
|
||||||
try {
|
try {
|
||||||
response.close()
|
response.close()
|
||||||
networkHelper.cookieManager.remove(request.url, COOKIE_NAMES, 0)
|
cookieManager.remove(request.url, COOKIE_NAMES, 0)
|
||||||
val oldCookie = networkHelper.cookieManager.get(request.url)
|
val oldCookie = cookieManager.get(request.url)
|
||||||
.firstOrNull { it.name == "cf_clearance" }
|
.firstOrNull { it.name == "cf_clearance" }
|
||||||
resolveWithWebView(request, oldCookie)
|
resolveWithWebView(request, oldCookie)
|
||||||
|
|
||||||
@ -70,7 +71,7 @@ class CloudflareInterceptor(private val context: Context) : WebViewInterceptor(c
|
|||||||
webview?.webViewClient = object : WebViewClientCompat() {
|
webview?.webViewClient = object : WebViewClientCompat() {
|
||||||
override fun onPageFinished(view: WebView, url: String) {
|
override fun onPageFinished(view: WebView, url: String) {
|
||||||
fun isCloudFlareBypassed(): Boolean {
|
fun isCloudFlareBypassed(): Boolean {
|
||||||
return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
|
return cookieManager.get(origRequestUrl.toHttpUrl())
|
||||||
.firstOrNull { it.name == "cf_clearance" }
|
.firstOrNull { it.name == "cf_clearance" }
|
||||||
.let { it != null && it != oldCookie }
|
.let { it != null && it != oldCookie }
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.network.interceptor
|
package eu.kanade.tachiyomi.network.interceptor
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
|
|
||||||
class UserAgentInterceptor : Interceptor {
|
class UserAgentInterceptor(
|
||||||
|
private val defaultUserAgentProvider: () -> String,
|
||||||
private val networkHelper: NetworkHelper by injectLazy()
|
) : Interceptor {
|
||||||
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
val originalRequest = chain.request()
|
val originalRequest = chain.request()
|
||||||
@ -16,7 +14,7 @@ class UserAgentInterceptor : Interceptor {
|
|||||||
val newRequest = originalRequest
|
val newRequest = originalRequest
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.removeHeader("User-Agent")
|
.removeHeader("User-Agent")
|
||||||
.addHeader("User-Agent", networkHelper.defaultUserAgent)
|
.addHeader("User-Agent", defaultUserAgentProvider())
|
||||||
.build()
|
.build()
|
||||||
chain.proceed(newRequest)
|
chain.proceed(newRequest)
|
||||||
} else {
|
} else {
|
||||||
|
@ -6,7 +6,6 @@ import android.webkit.WebSettings
|
|||||||
import android.webkit.WebView
|
import android.webkit.WebView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import eu.kanade.tachiyomi.core.R
|
import eu.kanade.tachiyomi.core.R
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
|
||||||
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
import eu.kanade.tachiyomi.util.system.DeviceUtil
|
||||||
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
import eu.kanade.tachiyomi.util.system.WebViewUtil
|
||||||
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
import eu.kanade.tachiyomi.util.system.setDefaultSettings
|
||||||
@ -16,14 +15,14 @@ import okhttp3.Interceptor
|
|||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import tachiyomi.core.util.lang.launchUI
|
import tachiyomi.core.util.lang.launchUI
|
||||||
import uy.kohesive.injekt.injectLazy
|
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.CountDownLatch
|
import java.util.concurrent.CountDownLatch
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
abstract class WebViewInterceptor(private val context: Context) : Interceptor {
|
abstract class WebViewInterceptor(
|
||||||
|
private val context: Context,
|
||||||
private val networkHelper: NetworkHelper by injectLazy()
|
private val defaultUserAgentProvider: () -> String,
|
||||||
|
) : Interceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When this is called, it initializes the WebView if it wasn't already. We use this to avoid
|
* When this is called, it initializes the WebView if it wasn't already. We use this to avoid
|
||||||
@ -85,7 +84,7 @@ abstract class WebViewInterceptor(private val context: Context) : Interceptor {
|
|||||||
return WebView(context).apply {
|
return WebView(context).apply {
|
||||||
setDefaultSettings()
|
setDefaultSettings()
|
||||||
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
|
// Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
|
||||||
settings.userAgentString = request.header("User-Agent") ?: networkHelper.defaultUserAgent
|
settings.userAgentString = request.header("User-Agent") ?: defaultUserAgentProvider()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ dependencies {
|
|||||||
implementation(project(":source-api"))
|
implementation(project(":source-api"))
|
||||||
implementation(project(":domain"))
|
implementation(project(":domain"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
|
||||||
api(libs.sqldelight.android.driver)
|
api(libs.sqldelight.android.driver)
|
||||||
api(libs.sqldelight.coroutines)
|
api(libs.sqldelight.coroutines)
|
||||||
api(libs.sqldelight.android.paging)
|
api(libs.sqldelight.android.paging)
|
||||||
|
@ -8,9 +8,19 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
|
|||||||
import tachiyomi.domain.updates.repository.UpdatesRepository
|
import tachiyomi.domain.updates.repository.UpdatesRepository
|
||||||
|
|
||||||
class UpdatesRepositoryImpl(
|
class UpdatesRepositoryImpl(
|
||||||
val databaseHandler: DatabaseHandler,
|
private val databaseHandler: DatabaseHandler,
|
||||||
) : UpdatesRepository {
|
) : UpdatesRepository {
|
||||||
|
|
||||||
|
override suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations> {
|
||||||
|
return databaseHandler.awaitList {
|
||||||
|
updatesViewQueries.getUpdatesByReadStatus(
|
||||||
|
read = read,
|
||||||
|
after = after,
|
||||||
|
mapper = updateWithRelationMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
|
override fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>> {
|
||||||
return databaseHandler.subscribeToList {
|
return databaseHandler.subscribeToList {
|
||||||
updatesViewQueries.updates(after, updateWithRelationMapper)
|
updatesViewQueries.updates(after, updateWithRelationMapper)
|
||||||
@ -19,4 +29,14 @@ class UpdatesRepositoryImpl(
|
|||||||
.map(updatesViewMapper)
|
.map(updatesViewMapper)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
|
||||||
|
return databaseHandler.subscribeToList {
|
||||||
|
updatesViewQueries.getUpdatesByReadStatus(
|
||||||
|
read = read,
|
||||||
|
after = after,
|
||||||
|
mapper = updateWithRelationMapper,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,12 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(platform(kotlinx.coroutines.bom))
|
|
||||||
implementation(kotlinx.bundles.coroutines)
|
|
||||||
|
|
||||||
implementation(project(":source-api"))
|
implementation(project(":source-api"))
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
|
||||||
|
implementation(platform(kotlinx.coroutines.bom))
|
||||||
|
implementation(kotlinx.bundles.coroutines)
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
implementation(libs.injekt.core)
|
implementation(libs.injekt.core)
|
||||||
// SY <--
|
// SY <--
|
||||||
|
@ -9,9 +9,17 @@ class GetUpdates(
|
|||||||
private val repository: UpdatesRepository,
|
private val repository: UpdatesRepository,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
suspend fun await(read: Boolean, after: Long): List<UpdatesWithRelations> {
|
||||||
|
return repository.awaitWithRead(read, after)
|
||||||
|
}
|
||||||
|
|
||||||
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> = subscribe(calendar.time.time)
|
fun subscribe(calendar: Calendar): Flow<List<UpdatesWithRelations>> = subscribe(calendar.time.time)
|
||||||
|
|
||||||
fun subscribe(after: Long): Flow<List<UpdatesWithRelations>> {
|
fun subscribe(after: Long): Flow<List<UpdatesWithRelations>> {
|
||||||
return repository.subscribeAll(after)
|
return repository.subscribeAll(after)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun subscribe(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>> {
|
||||||
|
return repository.subscribeWithRead(read, after)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,5 +5,9 @@ import tachiyomi.domain.updates.model.UpdatesWithRelations
|
|||||||
|
|
||||||
interface UpdatesRepository {
|
interface UpdatesRepository {
|
||||||
|
|
||||||
|
suspend fun awaitWithRead(read: Boolean, after: Long): List<UpdatesWithRelations>
|
||||||
|
|
||||||
fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>>
|
fun subscribeAll(after: Long): Flow<List<UpdatesWithRelations>>
|
||||||
|
|
||||||
|
fun subscribeWithRead(read: Boolean, after: Long): Flow<List<UpdatesWithRelations>>
|
||||||
}
|
}
|
||||||
|
@ -22,11 +22,11 @@ android {
|
|||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
implementation(project(":data"))
|
|
||||||
implementation(project(":domain"))
|
implementation(project(":domain"))
|
||||||
implementation(project(":presentation-core"))
|
implementation(project(":presentation-core"))
|
||||||
|
|
||||||
implementation(androidx.glance)
|
implementation(androidx.glance)
|
||||||
|
|
||||||
implementation(libs.coil.core)
|
implementation(libs.coil.core)
|
||||||
|
api(libs.injekt.core)
|
||||||
}
|
}
|
||||||
|
@ -7,21 +7,17 @@ import kotlinx.coroutines.flow.distinctUntilChanged
|
|||||||
import kotlinx.coroutines.flow.drop
|
import kotlinx.coroutines.flow.drop
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
import tachiyomi.data.DatabaseHandler
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
|
|
||||||
class TachiyomiWidgetManager(
|
class TachiyomiWidgetManager(
|
||||||
private val database: DatabaseHandler = Injekt.get(),
|
private val getUpdates: GetUpdates,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun Context.init(scope: LifecycleCoroutineScope) {
|
fun Context.init(scope: LifecycleCoroutineScope) {
|
||||||
database.subscribeToList {
|
getUpdates.subscribe(
|
||||||
updatesViewQueries.getUpdatesByReadStatus(
|
read = false,
|
||||||
read = false,
|
after = UpdatesGridGlanceWidget.DateLimit.timeInMillis,
|
||||||
after = UpdatesGridGlanceWidget.DateLimit.timeInMillis,
|
)
|
||||||
)
|
|
||||||
}
|
|
||||||
.drop(1)
|
.drop(1)
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
.onEach {
|
.onEach {
|
||||||
|
@ -25,13 +25,13 @@ import eu.kanade.tachiyomi.core.security.SecurityPreferences
|
|||||||
import eu.kanade.tachiyomi.util.system.dpToPx
|
import eu.kanade.tachiyomi.util.system.dpToPx
|
||||||
import kotlinx.coroutines.MainScope
|
import kotlinx.coroutines.MainScope
|
||||||
import tachiyomi.core.util.lang.launchIO
|
import tachiyomi.core.util.lang.launchIO
|
||||||
import tachiyomi.data.DatabaseHandler
|
|
||||||
import tachiyomi.domain.manga.model.MangaCover
|
import tachiyomi.domain.manga.model.MangaCover
|
||||||
|
import tachiyomi.domain.updates.interactor.GetUpdates
|
||||||
|
import tachiyomi.domain.updates.model.UpdatesWithRelations
|
||||||
import tachiyomi.presentation.widget.components.CoverHeight
|
import tachiyomi.presentation.widget.components.CoverHeight
|
||||||
import tachiyomi.presentation.widget.components.CoverWidth
|
import tachiyomi.presentation.widget.components.CoverWidth
|
||||||
import tachiyomi.presentation.widget.components.LockedWidget
|
import tachiyomi.presentation.widget.components.LockedWidget
|
||||||
import tachiyomi.presentation.widget.components.UpdatesWidget
|
import tachiyomi.presentation.widget.components.UpdatesWidget
|
||||||
import tachiyomi.view.UpdatesView
|
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
@ -39,6 +39,7 @@ import java.util.Calendar
|
|||||||
import java.util.Date
|
import java.util.Date
|
||||||
|
|
||||||
class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
||||||
|
|
||||||
private val app: Application by injectLazy()
|
private val app: Application by injectLazy()
|
||||||
private val preferences: SecurityPreferences by injectLazy()
|
private val preferences: SecurityPreferences by injectLazy()
|
||||||
|
|
||||||
@ -58,7 +59,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
UpdatesWidget(data)
|
UpdatesWidget(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadData(list: List<UpdatesView>? = null) {
|
fun loadData(list: List<UpdatesWithRelations>? = null) {
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
// Don't show anything when lock is active
|
// Don't show anything when lock is active
|
||||||
if (preferences.useAuthenticator().get()) {
|
if (preferences.useAuthenticator().get()) {
|
||||||
@ -71,13 +72,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
if (ids.isEmpty()) return@launchIO
|
if (ids.isEmpty()) return@launchIO
|
||||||
|
|
||||||
val processList = list
|
val processList = list
|
||||||
?: Injekt.get<DatabaseHandler>()
|
?: Injekt.get<GetUpdates>().await(
|
||||||
.awaitList {
|
read = false,
|
||||||
updatesViewQueries.getUpdatesByReadStatus(
|
after = DateLimit.timeInMillis,
|
||||||
read = false,
|
)
|
||||||
after = DateLimit.timeInMillis,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
val (rowCount, columnCount) = ids
|
val (rowCount, columnCount) = ids
|
||||||
.flatMap { manager.getAppWidgetSizes(it) }
|
.flatMap { manager.getAppWidgetSizes(it) }
|
||||||
.maxBy { it.height.value * it.width.value }
|
.maxBy { it.height.value * it.width.value }
|
||||||
@ -88,7 +86,7 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareList(processList: List<UpdatesView>, take: Int): List<Pair<Long, Bitmap?>> {
|
private fun prepareList(processList: List<UpdatesWithRelations>, take: Int): List<Pair<Long, Bitmap?>> {
|
||||||
// Resize to cover size
|
// Resize to cover size
|
||||||
val widthPx = CoverWidth.value.toInt().dpToPx
|
val widthPx = CoverWidth.value.toInt().dpToPx
|
||||||
val heightPx = CoverHeight.value.toInt().dpToPx
|
val heightPx = CoverHeight.value.toInt().dpToPx
|
||||||
@ -101,10 +99,10 @@ class UpdatesGridGlanceWidget : GlanceAppWidget() {
|
|||||||
.data(
|
.data(
|
||||||
MangaCover(
|
MangaCover(
|
||||||
mangaId = updatesView.mangaId,
|
mangaId = updatesView.mangaId,
|
||||||
sourceId = updatesView.source,
|
sourceId = updatesView.sourceId,
|
||||||
isMangaFavorite = updatesView.favorite,
|
isMangaFavorite = true,
|
||||||
url = updatesView.thumbnailUrl,
|
url = updatesView.coverData.url,
|
||||||
lastModified = updatesView.coverLastModified,
|
lastModified = updatesView.coverData.lastModified,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
.memoryCachePolicy(CachePolicy.DISABLED)
|
.memoryCachePolicy(CachePolicy.DISABLED)
|
||||||
|
@ -11,23 +11,17 @@ android {
|
|||||||
defaultConfig {
|
defaultConfig {
|
||||||
consumerProguardFile("consumer-proguard.pro")
|
consumerProguardFile("consumer-proguard.pro")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(project(":core"))
|
implementation(project(":core"))
|
||||||
|
|
||||||
api(kotlinx.serialization.json)
|
api(kotlinx.serialization.json)
|
||||||
|
api(libs.injekt.core)
|
||||||
api(libs.rxjava)
|
api(libs.rxjava)
|
||||||
|
|
||||||
api(libs.preferencektx)
|
api(libs.preferencektx)
|
||||||
|
|
||||||
api(libs.jsoup)
|
api(libs.jsoup)
|
||||||
|
|
||||||
implementation(androidx.corektx)
|
|
||||||
|
|
||||||
// SY -->
|
// SY -->
|
||||||
implementation(project(":i18n"))
|
implementation(project(":i18n"))
|
||||||
implementation(kotlinx.reflect)
|
implementation(kotlinx.reflect)
|
||||||
|
@ -38,7 +38,7 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
// SY -->
|
// SY -->
|
||||||
protected val network: NetworkHelper by lazy {
|
protected val network: NetworkHelper by lazy {
|
||||||
val network = Injekt.get<NetworkHelper>()
|
val network = Injekt.get<NetworkHelper>()
|
||||||
object : NetworkHelper(Injekt.get<Application>()) {
|
object : NetworkHelper(Injekt.get<Application>(), Injekt.get()) {
|
||||||
override val client: OkHttpClient
|
override val client: OkHttpClient
|
||||||
get() = delegate?.networkHttpClient ?: network.client
|
get() = delegate?.networkHttpClient ?: network.client
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
@ -51,8 +51,8 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
.maybeInjectEHLogger()
|
.maybeInjectEHLogger()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override val cookieManager: AndroidCookieJar
|
override val cookieJar: AndroidCookieJar
|
||||||
get() = network.cookieManager
|
get() = network.cookieJar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// SY <--
|
// SY <--
|
||||||
@ -97,7 +97,7 @@ abstract class HttpSource : CatalogueSource {
|
|||||||
* Headers builder for requests. Implementations can override this method for custom headers.
|
* Headers builder for requests. Implementations can override this method for custom headers.
|
||||||
*/
|
*/
|
||||||
protected open fun headersBuilder() = Headers.Builder().apply {
|
protected open fun headersBuilder() = Headers.Builder().apply {
|
||||||
add("User-Agent", network.defaultUserAgent)
|
add("User-Agent", network.defaultUserAgentProvider())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user