diff --git a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt index 7e3bd8622..916b75de1 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/DetailsModule.kt @@ -8,6 +8,6 @@ val detailsModule get() = module { viewModel { intent -> - DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get()) + DetailsViewModel(intent.get(), get(), get(), get(), get(), get(), get(), get()) } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt index 7c5fe8574..722a5bf1a 100644 --- a/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt +++ b/app/src/main/java/org/koitharu/kotatsu/details/ui/DetailsViewModel.kt @@ -1,6 +1,7 @@ package org.koitharu.kotatsu.details.ui import androidx.core.os.LocaleListCompat +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.asFlow import androidx.lifecycle.asLiveData import androidx.lifecycle.viewModelScope @@ -26,6 +27,8 @@ import org.koitharu.kotatsu.parsers.model.MangaChapter import org.koitharu.kotatsu.parsers.model.MangaSource import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.toTitleCase +import org.koitharu.kotatsu.shikimori.data.ShikimoriRepository +import org.koitharu.kotatsu.shikimori.data.model.ShikimoriMangaInfo import org.koitharu.kotatsu.tracker.domain.TrackingRepository import org.koitharu.kotatsu.utils.SingleLiveEvent import org.koitharu.kotatsu.utils.ext.iterator @@ -39,6 +42,7 @@ class DetailsViewModel( private val trackingRepository: TrackingRepository, private val mangaDataRepository: MangaDataRepository, private val settings: AppSettings, + private val shikimoriRepository: ShikimoriRepository, ) : BaseViewModel() { private var loadingJob: Job @@ -85,6 +89,7 @@ class DetailsViewModel( .asLiveData(viewModelScope.coroutineContext) val onMangaRemoved = SingleLiveEvent() + val shikimoriInfo = MutableLiveData() val branches = mangaData.map { it?.chapters?.mapToSet { x -> x.branch }?.sortedBy { x -> x }.orEmpty() @@ -204,6 +209,7 @@ class DetailsViewModel( localMangaRepository.findSavedManga(manga) } }.getOrNull() + findShikimoriManga(manga) } private fun mapChapters( @@ -312,4 +318,18 @@ class DetailsViewModel( it.chapter.name.contains(query, ignoreCase = true) } } + + private fun findShikimoriManga(manga: Manga) { + if (!shikimoriRepository.isAuthorized) { + return + } + launchJob(Dispatchers.Default) { + val data = runCatching { + shikimoriRepository.findMangaInfo(manga) + }.getOrNull() + if (data != null) { + shikimoriInfo.postValue(data) + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/shikimori/data/ShikimoriRepository.kt b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/ShikimoriRepository.kt index 65a2f828c..c4ae8d251 100644 --- a/app/src/main/java/org/koitharu/kotatsu/shikimori/data/ShikimoriRepository.kt +++ b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/ShikimoriRepository.kt @@ -3,8 +3,11 @@ package org.koitharu.kotatsu.shikimori.data import okhttp3.FormBody import okhttp3.OkHttpClient import okhttp3.Request -import org.koitharu.kotatsu.parsers.util.await -import org.koitharu.kotatsu.parsers.util.parseJson +import org.koitharu.kotatsu.parsers.model.Manga +import org.koitharu.kotatsu.parsers.util.* +import org.koitharu.kotatsu.parsers.util.json.mapJSON +import org.koitharu.kotatsu.shikimori.data.model.ShikimoriManga +import org.koitharu.kotatsu.shikimori.data.model.ShikimoriMangaInfo import org.koitharu.kotatsu.shikimori.data.model.ShikimoriUser private const val CLIENT_ID = "Mw6F0tPEOgyV7F9U9Twg50Q8SndMY7hzIOfXg0AX_XU" @@ -49,4 +52,41 @@ class ShikimoriRepository( val response = okHttp.newCall(request.build()).await().parseJson() return ShikimoriUser(response) } + + suspend fun findMangaInfo(manga: Manga): ShikimoriMangaInfo? { + val q = manga.title.urlEncoded() + val request = Request.Builder() + .get() + .url("https://shikimori.one/api/mangas?limit=20&search=$q&censored=false") + val response = okHttp.newCall(request.build()).await().parseJsonArray() + val candidates = response.mapJSON { ShikimoriManga(it) } + val bestCandidate = candidates.minByOrNull { + it.name.levenshteinDistance(manga.title) + } ?: return null + return getMangaInfo(bestCandidate.id) + } + + suspend fun getRelatedManga(id: Long): List { + val request = Request.Builder() + .get() + .url("https://shikimori.one/api/mangas/$id/related") + val response = okHttp.newCall(request.build()).await().parseJsonArray() + return response.mapJSON { jo -> ShikimoriManga(jo) } + } + + suspend fun getSimilarManga(id: Long): List { + val request = Request.Builder() + .get() + .url("https://shikimori.one/api/mangas/$id/similar") + val response = okHttp.newCall(request.build()).await().parseJsonArray() + return response.mapJSON { jo -> ShikimoriManga(jo) } + } + + suspend fun getMangaInfo(id: Long): ShikimoriMangaInfo { + val request = Request.Builder() + .get() + .url("https://shikimori.one/api/mangas/$id") + val response = okHttp.newCall(request.build()).await().parseJson() + return ShikimoriMangaInfo(response) + } } \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriManga.kt b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriManga.kt new file mode 100644 index 000000000..8ac0411fb --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriManga.kt @@ -0,0 +1,44 @@ +package org.koitharu.kotatsu.shikimori.data.model + +import org.json.JSONObject + +class ShikimoriManga( + val id: Long, + val name: String, + val cover: String, + val url: String, +) { + + constructor(json: JSONObject) : this( + id = json.getLong("id"), + name = json.getString("name"), + cover = json.getJSONObject("image").getString("preview"), + url = json.getString("url"), + ) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as ShikimoriManga + + if (id != other.id) return false + if (name != other.name) return false + if (cover != other.cover) return false + if (url != other.url) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + name.hashCode() + result = 31 * result + cover.hashCode() + result = 31 * result + url.hashCode() + return result + } + + override fun toString(): String { + return "ShikimoriManga #$id \"$name\" $url" + } +} \ No newline at end of file diff --git a/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriMangaInfo.kt b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriMangaInfo.kt new file mode 100644 index 000000000..22adad008 --- /dev/null +++ b/app/src/main/java/org/koitharu/kotatsu/shikimori/data/model/ShikimoriMangaInfo.kt @@ -0,0 +1,20 @@ +package org.koitharu.kotatsu.shikimori.data.model + +import org.json.JSONObject + +class ShikimoriMangaInfo( + val id: Long, + val name: String, + val cover: String, + val url: String, + val descriptionHtml: String, +) { + + constructor(json: JSONObject) : this( + id = json.getLong("id"), + name = json.getString("name"), + cover = json.getJSONObject("image").getString("preview"), + url = json.getString("url"), + descriptionHtml = json.getString("description_html"), + ) +} \ No newline at end of file