refactor: Don't use context receiver

Deprecated on Kotlin 2.x, scheduled for removal in v2.1.x, will be replaced with context parameters

REF: https://github.com/Kotlin/KEEP/issues/259#issuecomment-2278319746
REF: https://youtrack.jetbrains.com/issue/KT-67119/Migration-warning-from-context-receivers-to-context-parameters
REF: https://github.com/Kotlin/KEEP/issues/367
This commit is contained in:
Ahmad Ansori Palembani 2025-01-01 07:10:30 +07:00
parent 1b92ae2e5f
commit d02f1bdd11
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
17 changed files with 265 additions and 347 deletions

View file

@ -278,7 +278,6 @@ 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> {
compilerOptions.freeCompilerArgs.addAll( compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers",
// "-opt-in=kotlin.Experimental", // "-opt-in=kotlin.Experimental",
"-opt-in=kotlin.RequiresOptIn", "-opt-in=kotlin.RequiresOptIn",
"-opt-in=kotlin.ExperimentalStdlibApi", "-opt-in=kotlin.ExperimentalStdlibApi",

View file

@ -49,15 +49,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date)) put("completedAt", createDate(track.finished_reading_date))
} }
} }
with(json) { authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) .awaitSuccess()
.awaitSuccess() .parseAs<ALAddMangaResult>()
.parseAs<ALAddMangaResult>() .let {
.let { track.library_id = it.data.entry.id
track.library_id = it.data.entry.id track
track }
}
}
} }
} }
@ -74,11 +72,9 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("completedAt", createDate(track.finished_reading_date)) put("completedAt", createDate(track.finished_reading_date))
} }
} }
with(json) { authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) .awaitSuccess()
.awaitSuccess() track
track
}
} }
} }
@ -90,13 +86,11 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("query", search) put("query", search)
} }
} }
with(json) { authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) .awaitSuccess()
.awaitSuccess() .parseAs<ALSearchResult>()
.parseAs<ALSearchResult>() .data.page.media
.data.page.media .map { it.toALManga().toTrack() }
.map { it.toALManga().toTrack() }
}
} }
} }
@ -109,15 +103,13 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
put("manga_id", track.media_id) put("manga_id", track.media_id)
} }
} }
with(json) { authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime)))
authClient.newCall(POST(API_URL, body = payload.toString().toRequestBody(jsonMime))) .awaitSuccess()
.awaitSuccess() .parseAs<ALUserListMangaQueryResult>()
.parseAs<ALUserListMangaQueryResult>() .data.page.mediaList
.data.page.mediaList .map { it.toALUserManga() }
.map { it.toALUserManga() } .firstOrNull()
.firstOrNull() ?.toTrack()
?.toTrack()
}
} }
} }

View file

@ -75,29 +75,25 @@ class BangumiApi(
.appendQueryParameter("responseGroup", "large") .appendQueryParameter("responseGroup", "large")
.appendQueryParameter("max_results", "20") .appendQueryParameter("max_results", "20")
.build() .build()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<BGMSearchResult>()
.parseAs<BGMSearchResult>() .let { result ->
.let { result -> if (result.code == 404) emptyList<TrackSearch>()
if (result.code == 404) emptyList<TrackSearch>()
result.list result.list
?.map { it.toTrackSearch(trackId) } ?.map { it.toTrackSearch(trackId) }
.orEmpty() .orEmpty()
} }
}
} }
} }
suspend fun findLibManga(track: Track): Track? { suspend fun findLibManga(track: Track): Track? {
return withIOContext { return withIOContext {
with(json) { authClient.newCall(GET("$API_URL/subject/${track.media_id}"))
authClient.newCall(GET("$API_URL/subject/${track.media_id}")) .awaitSuccess()
.awaitSuccess() .parseAs<BGMSearchItem>()
.parseAs<BGMSearchItem>() .toTrackSearch(trackId)
.toTrackSearch(trackId)
}
} }
} }
@ -111,29 +107,25 @@ class BangumiApi(
.build() .build()
// TODO: get user readed chapter here // TODO: get user readed chapter here
with(json) { authClient.newCall(requestUserRead)
authClient.newCall(requestUserRead) .awaitSuccess()
.awaitSuccess() .parseAs<BGMCollectionResponse>()
.parseAs<BGMCollectionResponse>() .let {
.let { if (it.code == 400) return@let null
if (it.code == 400) return@let null
track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS track.status = it.status?.id?.toInt() ?: Bangumi.DEFAULT_STATUS
track.last_chapter_read = it.epStatus!!.toFloat() track.last_chapter_read = it.epStatus!!.toFloat()
track.score = it.rating!!.toFloat() track.score = it.rating!!.toFloat()
track track
} }
}
} }
} }
suspend fun accessToken(code: String): BGMOAuth { suspend fun accessToken(code: String): BGMOAuth {
return withIOContext { return withIOContext {
with(json) { client.newCall(accessTokenRequest(code))
client.newCall(accessTokenRequest(code)) .awaitSuccess()
.awaitSuccess() .parseAs()
.parseAs()
}
} }
} }

View file

@ -44,7 +44,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try { try {
client.newCall(request).execute().use { client.newCall(request).execute().use {
when (it.code) { when (it.code) {
200 -> return with(json) { it.parseAs<AuthenticationDto>().token } 200 -> return it.parseAs<AuthenticationDto>().token
401 -> { 401 -> {
Logger.w { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" } Logger.w { "Unauthorized / api key not valid: Cleaned api URL: $apiUrl, Api key is empty: ${apiKey.isEmpty()}" }
throw IOException("Unauthorized / api key not valid") throw IOException("Unauthorized / api key not valid")
@ -89,11 +89,10 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
private fun getTotalChapters(url: String): Long { private fun getTotalChapters(url: String): Long {
val requestUrl = getApiVolumesUrl(url) val requestUrl = getApiVolumesUrl(url)
try { try {
val listVolumeDto = with(json) { val listVolumeDto =
authClient.newCall(GET(requestUrl)) authClient.newCall(GET(requestUrl))
.execute() .execute()
.parseAs<List<VolumeDto>>() .parseAs<List<VolumeDto>>()
}
var volumeNumber = 0L var volumeNumber = 0L
var maxChapterNumber = 0L var maxChapterNumber = 0L
for (volume in listVolumeDto) { for (volume in listVolumeDto) {
@ -117,9 +116,7 @@ class KavitaApi(private val client: OkHttpClient, interceptor: KavitaInterceptor
try { try {
authClient.newCall(GET(requestUrl)).execute().use { authClient.newCall(GET(requestUrl)).execute().use {
if (it.code == 200) { if (it.code == 200) {
return with(json) { return it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
it.parseAs<ChapterDto>().number!!.replace(",", ".").toFloat()
}
} }
if (it.code == 204) { if (it.code == 204) {
return 0F return 0F
@ -134,11 +131,10 @@ 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 = with(json) { val serieDto: SeriesDto =
authClient.newCall(GET(url)) authClient.newCall(GET(url))
.awaitSuccess() .awaitSuccess()
.parseAs() .parseAs()
}
val track = serieDto.toTrack() val track = serieDto.toTrack()
track.apply { track.apply {

View file

@ -131,14 +131,12 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
suspend fun search(query: String): List<TrackSearch> { suspend fun search(query: String): List<TrackSearch> {
return withIOContext { return withIOContext {
with(json) { authClient.newCall(GET(ALGOLIA_KEY_URL))
authClient.newCall(GET(ALGOLIA_KEY_URL)) .awaitSuccess()
.awaitSuccess() .parseAs<KitsuSearchResult>()
.parseAs<KitsuSearchResult>() .let {
.let { algoliaSearch(it.media.key, query)
algoliaSearch(it.media.key, query) }
}
}
} }
} }
@ -147,25 +145,23 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
val jsonObject = buildJsonObject { val jsonObject = buildJsonObject {
put("params", "query=$query$ALGOLIA_FILTER") put("params", "query=$query$ALGOLIA_FILTER")
} }
with(json) { client.newCall(
client.newCall( POST(
POST( ALGOLIA_URL,
ALGOLIA_URL, headers = headersOf(
headers = headersOf( "X-Algolia-Application-Id",
"X-Algolia-Application-Id", ALGOLIA_APP_ID,
ALGOLIA_APP_ID, "X-Algolia-API-Key",
"X-Algolia-API-Key", key,
key,
),
body = jsonObject.toString().toRequestBody(jsonMime),
), ),
) body = jsonObject.toString().toRequestBody(jsonMime),
.awaitSuccess() ),
.parseAs<KitsuAlgoliaSearchResult>() )
.hits .awaitSuccess()
.filter { it.subtype != "novel" } .parseAs<KitsuAlgoliaSearchResult>()
.map { it.toTrack() } .hits
} .filter { it.subtype != "novel" }
.map { it.toTrack() }
} }
} }
@ -175,18 +171,16 @@ 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()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<KitsuListSearchResult>()
.parseAs<KitsuListSearchResult>() .let {
.let { if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
if (it.data.isNotEmpty() && it.included.isNotEmpty()) { it.firstToTrack()
it.firstToTrack() } else {
} else { null
null
}
} }
} }
} }
} }
@ -196,18 +190,16 @@ 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()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<KitsuListSearchResult>()
.parseAs<KitsuListSearchResult>() .let {
.let { if (it.data.isNotEmpty() && it.included.isNotEmpty()) {
if (it.data.isNotEmpty() && it.included.isNotEmpty()) { it.firstToTrack()
it.firstToTrack() } else {
} else { throw Exception("Could not find manga")
throw Exception("Could not find manga")
}
} }
} }
} }
} }
@ -220,11 +212,9 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
.add("client_id", CLIENT_ID) .add("client_id", CLIENT_ID)
.add("client_secret", CLIENT_SECRET) .add("client_secret", CLIENT_SECRET)
.build() .build()
with(json) { client.newCall(POST(LOGIN_URL, body = formBody))
client.newCall(POST(LOGIN_URL, body = formBody)) .awaitSuccess()
.awaitSuccess() .parseAs()
.parseAs()
}
} }
} }
@ -233,13 +223,11 @@ class KitsuApi(private val client: OkHttpClient, interceptor: KitsuInterceptor)
val url = "${BASE_URL}users".toUri().buildUpon() val url = "${BASE_URL}users".toUri().buildUpon()
.encodedQuery("filter[self]=true") .encodedQuery("filter[self]=true")
.build() .build()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<KitsuCurrentUserResult>()
.parseAs<KitsuCurrentUserResult>() .data[0]
.data[0] .id
.id
}
} }
} }

View file

@ -26,21 +26,19 @@ class KomgaApi(private val client: OkHttpClient) {
withIOContext { withIOContext {
try { try {
val track = val track =
with(json) { if (url.contains(READLIST_API)) {
if (url.contains(READLIST_API)) { client.newCall(GET(url))
client.newCall(GET(url)) .awaitSuccess()
.awaitSuccess() .parseAs<ReadListDto>()
.parseAs<ReadListDto>() .toTrack()
.toTrack() } else {
} else { client.newCall(GET(url))
client.newCall(GET(url)) .awaitSuccess()
.awaitSuccess() .parseAs<SeriesDto>()
.parseAs<SeriesDto>() .toTrack()
.toTrack()
}
} }
val progress = with(json) { val progress =
client client
.newCall( .newCall(
GET( GET(
@ -59,7 +57,6 @@ class KomgaApi(private val client: OkHttpClient) {
it.parseAs<ReadProgressDto>().toV2() it.parseAs<ReadProgressDto>().toV2()
} }
} }
}
track.apply { track.apply {
cover_url = "$url/thumbnail" cover_url = "$url/thumbnail"
tracking_url = url tracking_url = url

View file

@ -37,15 +37,13 @@ class MangaUpdatesApi(
suspend fun getSeriesListItem(track: Track): Pair<MUListItem, MURating?> { suspend fun getSeriesListItem(track: Track): Pair<MUListItem, MURating?> {
val listItem = val listItem =
with(json) { authClient.newCall(
authClient.newCall( GET(
GET( url = "$BASE_URL/v1/lists/series/${track.media_id}",
url = "$BASE_URL/v1/lists/series/${track.media_id}", ),
), )
) .awaitSuccess()
.awaitSuccess() .parseAs<MUListItem>()
.parseAs<MUListItem>()
}
val rating = getSeriesRating(track) val rating = getSeriesRating(track)
@ -104,15 +102,13 @@ class MangaUpdatesApi(
private suspend fun getSeriesRating(track: Track): MURating? { private suspend fun getSeriesRating(track: Track): MURating? {
return try { return try {
with(json) { authClient.newCall(
authClient.newCall( GET(
GET( url = "$BASE_URL/v1/series/${track.media_id}/rating",
url = "$BASE_URL/v1/series/${track.media_id}/rating", ),
), )
) .awaitSuccess()
.awaitSuccess() .parseAs<MURating>()
.parseAs<MURating>()
}
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
@ -151,18 +147,16 @@ class MangaUpdatesApi(
}, },
) )
} }
return with(json) { return client.newCall(
client.newCall( POST(
POST( url = "$BASE_URL/v1/series/search",
url = "$BASE_URL/v1/series/search", body = body.toString().toRequestBody(CONTENT_TYPE),
body = body.toString().toRequestBody(CONTENT_TYPE), ),
), )
) .awaitSuccess()
.awaitSuccess() .parseAs<MUSearchResult>()
.parseAs<MUSearchResult>() .results
.results .map { it.record }
.map { it.record }
}
} }
suspend fun authenticate(username: String, password: String): MUContext? { suspend fun authenticate(username: String, password: String): MUContext? {
@ -170,17 +164,15 @@ class MangaUpdatesApi(
put("username", username) put("username", username)
put("password", password) put("password", password)
} }
return with(json) { return client.newCall(
client.newCall( PUT(
PUT( url = "$BASE_URL/v1/account/login",
url = "$BASE_URL/v1/account/login", body = body.toString().toRequestBody(CONTENT_TYPE),
body = body.toString().toRequestBody(CONTENT_TYPE), ),
), )
) .awaitSuccess()
.awaitSuccess() .parseAs<MULoginResponse>()
.parseAs<MULoginResponse>() .context
.context
}
} }
companion object { companion object {

View file

@ -45,11 +45,9 @@ 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()
with(json) { client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody))
client.newCall(POST("$BASE_OAUTH_URL/token", body = formBody)) .awaitSuccess()
.awaitSuccess() .parseAs()
.parseAs()
}
} }
} }
@ -59,12 +57,10 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url("$BASE_API_URL/users/@me") .url("$BASE_API_URL/users/@me")
.get() .get()
.build() .build()
with(json) { authClient.newCall(request)
authClient.newCall(request) .awaitSuccess()
.awaitSuccess() .parseAs<MALUser>()
.parseAs<MALUser>() .name
.name
}
} }
} }
@ -75,15 +71,13 @@ 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()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<MALSearchResult>()
.parseAs<MALSearchResult>() .data
.data .map { async { getMangaDetails(it.node.id) } }
.map { async { getMangaDetails(it.node.id) } } .awaitAll()
.awaitAll() .filter { !it.publishing_type.contains("novel") }
.filter { !it.publishing_type.contains("novel") }
}
} }
} }
@ -93,24 +87,22 @@ 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()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<MALManga>()
.parseAs<MALManga>() .let {
.let { TrackSearch.create(TrackManager.MYANIMELIST).apply {
TrackSearch.create(TrackManager.MYANIMELIST).apply { media_id = it.id
media_id = it.id title = it.title
title = it.title summary = it.synopsis
summary = it.synopsis total_chapters = it.numChapters
total_chapters = it.numChapters cover_url = it.covers.large
cover_url = it.covers.large tracking_url = "https://myanimelist.net/manga/$media_id"
tracking_url = "https://myanimelist.net/manga/$media_id" publishing_status = it.status.replace("_", " ")
publishing_status = it.status.replace("_", " ") publishing_type = it.mediaType.replace("_", " ")
publishing_type = it.mediaType.replace("_", " ") start_date = it.startDate ?: ""
start_date = it.startDate ?: ""
}
} }
} }
} }
} }
@ -132,12 +124,10 @@ 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()
with(json) { authClient.newCall(request)
authClient.newCall(request) .awaitSuccess()
.awaitSuccess() .parseAs<MALListItemStatus>()
.parseAs<MALListItemStatus>() .let { parseMangaItem(it, track) }
.let { parseMangaItem(it, track) }
}
} }
} }
@ -147,15 +137,13 @@ 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()
with(json) { authClient.newCall(GET(uri.toString()))
authClient.newCall(GET(uri.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<MALListItem>()
.parseAs<MALListItem>() .let { item ->
.let { item -> track.total_chapters = item.numChapters
track.total_chapters = item.numChapters item.myListStatus?.let { parseMangaItem(it, track) }
item.myListStatus?.let { parseMangaItem(it, track) } }
}
}
} }
} }
@ -190,11 +178,9 @@ class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListI
.url(urlBuilder.build().toString()) .url(urlBuilder.build().toString())
.get() .get()
.build() .build()
with(json) { authClient.newCall(request)
authClient.newCall(request) .awaitSuccess()
.awaitSuccess() .parseAs()
.parseAs()
}
} }
} }

View file

@ -66,7 +66,7 @@ class MyAnimeListInterceptor(private val myanimelist: MyAnimeList) : Interceptor
return runCatching { return runCatching {
if (response.isSuccessful) { if (response.isSuccessful) {
with(json) { response.parseAs<MALOAuth>() } response.parseAs<MALOAuth>()
} else { } else {
response.close() response.close()
null null

View file

@ -74,12 +74,10 @@ class ShikimoriApi(
.appendQueryParameter("search", search) .appendQueryParameter("search", search)
.appendQueryParameter("limit", "20") .appendQueryParameter("limit", "20")
.build() .build()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<List<SMManga>>()
.parseAs<List<SMManga>>() .map { it.toTrack(trackId) }
.map { it.toTrack(trackId) }
}
} }
} }
@ -102,51 +100,44 @@ class ShikimoriApi(
val urlMangas = "$API_URL/mangas".toUri().buildUpon() val urlMangas = "$API_URL/mangas".toUri().buildUpon()
.appendPath(track.media_id.toString()) .appendPath(track.media_id.toString())
.build() .build()
val manga = with(json) { val manga =
authClient.newCall(GET(urlMangas.toString())) authClient.newCall(GET(urlMangas.toString()))
.awaitSuccess() .awaitSuccess()
.parseAs<SMManga>() .parseAs<SMManga>()
}
val url = "$API_URL/v2/user_rates".toUri().buildUpon() val url = "$API_URL/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()
with(json) { authClient.newCall(GET(url.toString()))
authClient.newCall(GET(url.toString())) .execute()
.execute() .parseAs<List<SMUserListEntry>>()
.parseAs<List<SMUserListEntry>>() .let { entries ->
.let { entries -> if (entries.size > 1) {
if (entries.size > 1) { throw Exception("Too manga manga in response")
throw Exception("Too manga manga in response")
}
entries
.map { it.toTrack(trackId, manga) }
.firstOrNull()
} }
} entries
.map { it.toTrack(trackId, manga) }
.firstOrNull()
}
} }
} }
suspend fun getCurrentUser(): Int { suspend fun getCurrentUser(): Int {
return withIOContext { return withIOContext {
with(json) { authClient.newCall(GET("$API_URL/users/whoami"))
authClient.newCall(GET("$API_URL/users/whoami")) .awaitSuccess()
.awaitSuccess() .parseAs<SMUser>()
.parseAs<SMUser>() .id
.id
}
} }
} }
suspend fun accessToken(code: String): SMOAuth { suspend fun accessToken(code: String): SMOAuth {
return withIOContext { return withIOContext {
with(json) { client.newCall(accessTokenRequest(code))
client.newCall(accessTokenRequest(code)) .awaitSuccess()
.awaitSuccess() .parseAs()
.parseAs()
}
} }
} }

View file

@ -52,9 +52,7 @@ class TachideskApi {
trackUrl trackUrl
} }
val manga = with(json) { val manga = client.newCall(GET("$url/full", headers)).awaitSuccess().parseAs<MangaDataClass>()
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
@ -74,9 +72,7 @@ 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 = with(json) { val chapters = client.newCall(GET("$url/chapters", headers)).awaitSuccess().parseAs<List<ChapterDataClass>>()
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(

View file

@ -11,12 +11,12 @@ import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.parseAs import eu.kanade.tachiyomi.network.parseAs
import eu.kanade.tachiyomi.util.system.localeContext import eu.kanade.tachiyomi.util.system.localeContext
import eu.kanade.tachiyomi.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withIOContext
import java.util.Date
import java.util.concurrent.TimeUnit
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import yokai.domain.base.models.Version import yokai.domain.base.models.Version
import java.util.*
import java.util.concurrent.*
class AppUpdateChecker( class AppUpdateChecker(
private val json: Json = Injekt.get(), private val json: Json = Injekt.get(),
@ -31,48 +31,46 @@ class AppUpdateChecker(
} }
return withIOContext { return withIOContext {
val result = with(json) { val result = if (preferences.checkForBetas().get()) {
if (preferences.checkForBetas().get()) { networkService.client
networkService.client .newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases"))
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases")) .await()
.await() .parseAs<List<GithubRelease>>()
.parseAs<List<GithubRelease>>() .let { githubReleases ->
.let { githubReleases -> val releases =
val releases = githubReleases.take(10).filter { isNewVersion(it.version) }
githubReleases.take(10).filter { isNewVersion(it.version) } // Check if any of the latest versions are newer than the current version
// Check if any of the latest versions are newer than the current version val release = releases
val release = releases .maxWithOrNull { r1, r2 ->
.maxWithOrNull { r1, r2 -> when {
when { r1.version == r2.version -> 0
r1.version == r2.version -> 0 isNewVersion(r2.version, r1.version) -> -1
isNewVersion(r2.version, r1.version) -> -1 else -> 1
else -> 1
}
} }
preferences.lastAppCheck().set(Date().time)
if (release != null) {
AppUpdateResult.NewUpdate(release)
} else {
AppUpdateResult.NoNewUpdate
} }
} preferences.lastAppCheck().set(Date().time)
} else {
networkService.client
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
.await()
.parseAs<GithubRelease>()
.let {
preferences.lastAppCheck().set(Date().time)
// Check if latest version is newer than the current version if (release != null) {
if (isNewVersion(it.version)) { AppUpdateResult.NewUpdate(release)
AppUpdateResult.NewUpdate(it) } else {
} else { AppUpdateResult.NoNewUpdate
AppUpdateResult.NoNewUpdate
}
} }
} }
} else {
networkService.client
.newCall(GET("https://api.github.com/repos/$GITHUB_REPO/releases/latest"))
.await()
.parseAs<GithubRelease>()
.let {
preferences.lastAppCheck().set(Date().time)
// Check if latest version is newer than the current version
if (isNewVersion(it.version)) {
AppUpdateResult.NewUpdate(it)
} else {
AppUpdateResult.NoNewUpdate
}
}
} }
if (doExtrasAfterNewUpdate && result is AppUpdateResult.NewUpdate) { if (doExtrasAfterNewUpdate && result is AppUpdateResult.NewUpdate) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S &&

View file

@ -14,7 +14,6 @@ import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
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
@ -24,7 +23,6 @@ import yokai.domain.extension.repo.model.ExtensionRepo
internal class ExtensionApi { internal class ExtensionApi {
private val json: Json by injectLazy()
private val networkService: NetworkHelper by injectLazy() private val networkService: NetworkHelper by injectLazy()
private val getExtensionRepo: GetExtensionRepo by injectLazy() private val getExtensionRepo: GetExtensionRepo by injectLazy()
private val updateExtensionRepo: UpdateExtensionRepo by injectLazy() private val updateExtensionRepo: UpdateExtensionRepo by injectLazy()
@ -47,11 +45,9 @@ internal class ExtensionApi {
.newCall(GET("$repoBaseUrl/index.min.json")) .newCall(GET("$repoBaseUrl/index.min.json"))
.awaitSuccess() .awaitSuccess()
with(json) { response
response .parseAs<List<ExtensionJsonObject>>()
.parseAs<List<ExtensionJsonObject>>() .toExtensions(repoBaseUrl)
.toExtensions(repoBaseUrl)
}
} catch (e: Throwable) { } catch (e: Throwable) {
Logger.e(e) { "Failed to get extensions from $repoBaseUrl" } Logger.e(e) { "Failed to get extensions from $repoBaseUrl" }
emptyList() emptyList()

View file

@ -25,6 +25,7 @@ import eu.kanade.tachiyomi.util.chapter.ChapterFilter
import eu.kanade.tachiyomi.util.manga.MangaShortcutManager import eu.kanade.tachiyomi.util.manga.MangaShortcutManager
import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.protobuf.ProtoBuf
import nl.adaptivity.xmlutil.XmlDeclMode import nl.adaptivity.xmlutil.XmlDeclMode
import nl.adaptivity.xmlutil.core.XmlVersion import nl.adaptivity.xmlutil.core.XmlVersion
import nl.adaptivity.xmlutil.serialization.XML import nl.adaptivity.xmlutil.serialization.XML
@ -145,6 +146,9 @@ fun appModule(app: Application) = module {
xmlVersion = XmlVersion.XML10 xmlVersion = XmlVersion.XML10
} }
} }
single<ProtoBuf> {
ProtoBuf
}
single { ChapterFilter() } single { ChapterFilter() }

View file

@ -6,17 +6,13 @@ import eu.kanade.tachiyomi.network.GET
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 eu.kanade.tachiyomi.util.system.withIOContext import eu.kanade.tachiyomi.util.system.withIOContext
import kotlinx.serialization.json.Json
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import uy.kohesive.injekt.injectLazy
import yokai.domain.extension.repo.model.ExtensionRepo import yokai.domain.extension.repo.model.ExtensionRepo
class ExtensionRepoService( class ExtensionRepoService(
private val client: OkHttpClient, private val client: OkHttpClient,
) { ) {
private val json: Json by injectLazy()
suspend fun fetchRepoDetails( suspend fun fetchRepoDetails(
repo: String, repo: String,
): ExtensionRepo? { ): ExtensionRepo? {
@ -24,12 +20,10 @@ class ExtensionRepoService(
val url = "$repo/repo.json".toUri() val url = "$repo/repo.json".toUri()
try { try {
with(json) { client.newCall(GET(url.toString()))
client.newCall(GET(url.toString())) .awaitSuccess()
.awaitSuccess() .parseAs<ExtensionRepoMetaDto>()
.parseAs<ExtensionRepoMetaDto>() .toExtensionRepo(baseUrl = repo)
.toExtensionRepo(baseUrl = repo)
}
} catch (e: Exception) { } catch (e: Exception) {
Logger.e(e) { "Failed to fetch repo details" } Logger.e(e) { "Failed to fetch repo details" }
null null

View file

@ -66,7 +66,6 @@ android {
tasks { tasks {
withType<KotlinCompile> { withType<KotlinCompile> {
compilerOptions.freeCompilerArgs.addAll( compilerOptions.freeCompilerArgs.addAll(
"-Xcontext-receivers",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi", "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.serialization.ExperimentalSerializationApi", "-opt-in=kotlinx.serialization.ExperimentalSerializationApi",
) )

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.network
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
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
@ -18,6 +17,8 @@ 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
val jsonMime = "application/json; charset=utf-8".toMediaType() val jsonMime = "application/json; charset=utf-8".toMediaType()
@ -68,7 +69,6 @@ fun Call.asObservableSuccess(): Observable<Response> {
} }
// Based on https://github.com/gildor/kotlin-coroutines-okhttp // Based on https://github.com/gildor/kotlin-coroutines-okhttp
@OptIn(ExperimentalCoroutinesApi::class)
private suspend fun Call.await(callStack: Array<StackTraceElement>): Response { private suspend fun Call.await(callStack: Array<StackTraceElement>): Response {
return suspendCancellableCoroutine { continuation -> return suspendCancellableCoroutine { continuation ->
val callback = val callback =
@ -131,13 +131,11 @@ fun OkHttpClient.newCachelessCallWithProgress(request: Request, listener: Progre
return progressClient.newCall(request) return progressClient.newCall(request)
} }
context(Json)
inline fun <reified T> Response.parseAs(): T { inline fun <reified T> Response.parseAs(): T {
return decodeFromJsonResponse(serializer(), this) return Injekt.get<Json>().decodeFromJsonResponse(serializer(), this)
} }
context(Json) fun <T> Json.decodeFromJsonResponse(
fun <T> decodeFromJsonResponse(
deserializer: DeserializationStrategy<T>, deserializer: DeserializationStrategy<T>,
response: Response, response: Response,
): T { ): T {