Initial adding of Kitsu scrobbler
This commit is contained in:
@@ -19,7 +19,7 @@ Kotatsu is a free and open source manga reader for Android.
|
||||
* Tablet-optimized Material You UI
|
||||
* Standard and Webtoon-optimized reader
|
||||
* Notifications about new chapters with updates feed
|
||||
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList
|
||||
* Integration with manga tracking services: Shikimori, AniList, MyAnimeList, Kitsu
|
||||
* Password/fingerprint protect access to the app
|
||||
* History and favourites [synchronization](https://github.com/KotatsuApp/kotatsu-syncserver) across devices
|
||||
|
||||
|
||||
@@ -381,6 +381,7 @@ class AppSettings @Inject constructor(@ApplicationContext context: Context) {
|
||||
const val KEY_SHIKIMORI = "shikimori"
|
||||
const val KEY_ANILIST = "anilist"
|
||||
const val KEY_MAL = "mal"
|
||||
const val KEY_KITSU = "kitsu"
|
||||
const val KEY_DOWNLOADS_PARALLELISM = "downloads_parallelism"
|
||||
const val KEY_DOWNLOADS_SLOWDOWN = "downloads_slowdown"
|
||||
const val KEY_ALL_FAVOURITES_VISIBLE = "all_favourites_visible"
|
||||
|
||||
@@ -19,6 +19,10 @@ import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuAuthenticator
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuInterceptor
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.domain.KitsuScrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.mal.data.MALAuthenticator
|
||||
import org.koitharu.kotatsu.scrobbling.mal.data.MALInterceptor
|
||||
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
|
||||
@@ -87,6 +91,24 @@ object ScrobblingModule {
|
||||
return AniListRepository(context, okHttp, storage, database)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideKitsuRepository(
|
||||
@ApplicationContext context: Context,
|
||||
@ScrobblerType(ScrobblerService.KITSU) storage: ScrobblerStorage,
|
||||
database: MangaDatabase,
|
||||
authenticator: KitsuAuthenticator,
|
||||
): KitsuRepository {
|
||||
val okHttp = OkHttpClient.Builder().apply {
|
||||
authenticator(authenticator)
|
||||
addInterceptor(KitsuInterceptor(storage))
|
||||
if (BuildConfig.DEBUG) {
|
||||
addInterceptor(CurlLoggingInterceptor())
|
||||
}
|
||||
}.build()
|
||||
return KitsuRepository(context, okHttp, storage, database)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ScrobblerType(ScrobblerService.ANILIST)
|
||||
@@ -108,11 +130,19 @@ object ScrobblingModule {
|
||||
@ApplicationContext context: Context,
|
||||
): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.MAL)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
@ScrobblerType(ScrobblerService.KITSU)
|
||||
fun provideKitsuStorage(
|
||||
@ApplicationContext context: Context,
|
||||
): ScrobblerStorage = ScrobblerStorage(context, ScrobblerService.KITSU)
|
||||
|
||||
@Provides
|
||||
@ElementsIntoSet
|
||||
fun provideScrobblers(
|
||||
shikimoriScrobbler: ShikimoriScrobbler,
|
||||
aniListScrobbler: AniListScrobbler,
|
||||
malScrobbler: MALScrobbler,
|
||||
): Set<@JvmSuppressWildcards Scrobbler> = setOf(shikimoriScrobbler, aniListScrobbler, malScrobbler)
|
||||
kitsuScrobbler: KitsuScrobbler
|
||||
): Set<@JvmSuppressWildcards Scrobbler> = setOf(shikimoriScrobbler, aniListScrobbler, malScrobbler, kitsuScrobbler)
|
||||
}
|
||||
|
||||
@@ -12,5 +12,6 @@ enum class ScrobblerService(
|
||||
|
||||
SHIKIMORI(1, R.string.shikimori, R.drawable.ic_shikimori),
|
||||
ANILIST(2, R.string.anilist, R.drawable.ic_anilist),
|
||||
MAL(3, R.string.mal, R.drawable.ic_mal)
|
||||
MAL(3, R.string.mal, R.drawable.ic_mal),
|
||||
KITSU(4, R.string.kitsu, R.drawable.ic_script)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||
|
||||
import okhttp3.Authenticator
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import okhttp3.Route
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerType
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Provider
|
||||
|
||||
class KitsuAuthenticator @Inject constructor(
|
||||
@ScrobblerType(ScrobblerService.KITSU) private val storage: ScrobblerStorage,
|
||||
private val repositoryProvider: Provider<KitsuRepository>,
|
||||
) : Authenticator {
|
||||
|
||||
override fun authenticate(route: Route?, response: Response): Request? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.koitharu.kotatsu.core.network.CommonHeaders
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
|
||||
private const val JSON = "application/json"
|
||||
|
||||
class KitsuInterceptor(private val storage: ScrobblerStorage) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val sourceRequest = chain.request()
|
||||
val request = sourceRequest.newBuilder()
|
||||
request.header(CommonHeaders.CONTENT_TYPE, JSON)
|
||||
request.header(CommonHeaders.ACCEPT, JSON)
|
||||
if (!sourceRequest.url.pathSegments.contains("oauth")) {
|
||||
storage.accessToken?.let {
|
||||
request.header(CommonHeaders.AUTHORIZATION, "Bearer $it")
|
||||
}
|
||||
}
|
||||
return chain.proceed(request.build())
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package org.koitharu.kotatsu.scrobbling.kitsu.data
|
||||
|
||||
import android.content.Context
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerRepository
|
||||
import org.koitharu.kotatsu.scrobbling.common.data.ScrobblerStorage
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerManga
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerMangaInfo
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerUser
|
||||
|
||||
private const val BASE_WEB_URL = "https://kitsu.io"
|
||||
|
||||
class KitsuRepository(
|
||||
@ApplicationContext context: Context,
|
||||
private val okHttp: OkHttpClient,
|
||||
private val storage: ScrobblerStorage,
|
||||
private val db: MangaDatabase,
|
||||
) : ScrobblerRepository {
|
||||
|
||||
private val clientId = context.getString(R.string.kitsu_clientId)
|
||||
private val clientSecret = context.getString(R.string.kitsu_clientSecret)
|
||||
|
||||
override val oauthUrl: String
|
||||
get() = "${BASE_WEB_URL}/api/oauth2/token" +
|
||||
"?username=..." + // Get from AlertDialog...
|
||||
"&password=..." + // Get from AlertDialog...
|
||||
"&grant_type=password" +
|
||||
"&client_id=$clientId" +
|
||||
"&client_secret=$clientSecret"
|
||||
|
||||
override val isAuthorized: Boolean
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
override val cachedUser: ScrobblerUser?
|
||||
get() = TODO("Not yet implemented")
|
||||
|
||||
override suspend fun authorize(code: String?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun loadUser(): ScrobblerUser {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun logout() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun unregister(mangaId: Long) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun findManga(query: String, offset: Int): List<ScrobblerManga> {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun getMangaInfo(id: Long): ScrobblerMangaInfo {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun createRate(mangaId: Long, scrobblerMangaId: Long) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun updateRate(rateId: Int, mangaId: Long, chapter: MangaChapter) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun updateRate(rateId: Int, mangaId: Long, rating: Float, status: String?, comment: String?) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package org.koitharu.kotatsu.scrobbling.kitsu.domain
|
||||
|
||||
import org.koitharu.kotatsu.core.db.MangaDatabase
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class KitsuScrobbler @Inject constructor(
|
||||
private val repository: KitsuRepository,
|
||||
db: MangaDatabase,
|
||||
) : Scrobbler(db, ScrobblerService.KITSU, repository) {
|
||||
|
||||
init {
|
||||
statuses[ScrobblingStatus.PLANNED] = "planned"
|
||||
statuses[ScrobblingStatus.READING] = "current"
|
||||
statuses[ScrobblingStatus.COMPLETED] = "completed"
|
||||
statuses[ScrobblingStatus.ON_HOLD] = "on_hold"
|
||||
statuses[ScrobblingStatus.DROPPED] = "dropped"
|
||||
}
|
||||
|
||||
override suspend fun updateScrobblingInfo(
|
||||
mangaId: Long,
|
||||
rating: Float,
|
||||
status: ScrobblingStatus?,
|
||||
comment: String?
|
||||
) {
|
||||
val entity = db.scrobblingDao.find(scrobblerService.id, mangaId)
|
||||
requireNotNull(entity) { "Scrobbling info for manga $mangaId not found" }
|
||||
repository.updateRate(
|
||||
rateId = entity.id,
|
||||
mangaId = entity.mangaId,
|
||||
rating = rating,
|
||||
status = statuses[status],
|
||||
comment = comment,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,6 +17,7 @@ import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.scrobbling.anilist.data.AniListRepository
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblerService
|
||||
import org.koitharu.kotatsu.scrobbling.common.ui.config.ScrobblerConfigActivity
|
||||
import org.koitharu.kotatsu.scrobbling.kitsu.data.KitsuRepository
|
||||
import org.koitharu.kotatsu.scrobbling.mal.data.MALRepository
|
||||
import org.koitharu.kotatsu.scrobbling.shikimori.data.ShikimoriRepository
|
||||
import org.koitharu.kotatsu.sync.domain.SyncController
|
||||
@@ -38,6 +39,9 @@ class ServicesSettingsFragment : BasePreferenceFragment(R.string.services) {
|
||||
@Inject
|
||||
lateinit var malRepository: MALRepository
|
||||
|
||||
@Inject
|
||||
lateinit var kitsuRepository: KitsuRepository
|
||||
|
||||
@Inject
|
||||
lateinit var syncController: SyncController
|
||||
|
||||
@@ -50,6 +54,7 @@ class ServicesSettingsFragment : BasePreferenceFragment(R.string.services) {
|
||||
bindScrobblerSummary(AppSettings.KEY_SHIKIMORI, shikimoriRepository)
|
||||
bindScrobblerSummary(AppSettings.KEY_ANILIST, aniListRepository)
|
||||
bindScrobblerSummary(AppSettings.KEY_MAL, malRepository)
|
||||
bindScrobblerSummary(AppSettings.KEY_KITSU, kitsuRepository)
|
||||
bindSyncSummary()
|
||||
}
|
||||
|
||||
@@ -82,6 +87,15 @@ class ServicesSettingsFragment : BasePreferenceFragment(R.string.services) {
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_KITSU -> {
|
||||
if (!kitsuRepository.isAuthorized) {
|
||||
launchScrobblerAuth(kitsuRepository)
|
||||
} else {
|
||||
startActivity(ScrobblerConfigActivity.newIntent(preference.context, ScrobblerService.KITSU))
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
AppSettings.KEY_SYNC -> {
|
||||
val am = AccountManager.get(requireContext())
|
||||
val accountType = getString(R.string.account_type_sync)
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
<string name="anilist_clientId" translatable="false">9887</string>
|
||||
<string name="anilist_clientSecret" translatable="false">wrMqFosItQWsmB8dtAHfIFPDt15FfQi2ZGiKkJoW</string>
|
||||
<string name="mal_clientId" translatable="false">6cd8e6349e9a36bc1fc1ab97703c9fd1</string>
|
||||
<string name="kitsu_clientId" translatable="false">dd031b32d2f56c990b1425efe6c42ad847e7fe3ab46bf1299f05ecd856bdb7dd</string>
|
||||
<string name="kitsu_clientSecret" translatable="false">54d7307928f63414defd96399fc31ba847961ceaecef3a5fd93144e960c0e151</string>
|
||||
<string name="acra_login" translatable="false">SxhkCVnqVLbGogvi</string>
|
||||
<string name="acra_password" translatable="false">xPDACTLHnHU9Nfjv</string>
|
||||
<string name="sync_authority_history" translatable="false">org.koitharu.kotatsu.history</string>
|
||||
|
||||
@@ -435,4 +435,5 @@
|
||||
<string name="show_on_shelf">Show on the Shelf</string>
|
||||
<string name="sync_auth_hint">You can sign in into an existing account or create a new one</string>
|
||||
<string name="find_similar">Find similar</string>
|
||||
<string name="kitsu" translatable="false">Kitsu</string>
|
||||
</resources>
|
||||
|
||||
Reference in New Issue
Block a user