Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(MangaDex): use tracker links to associate mangas automatically with trackers #1387

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,11 @@ class TrackPreferences(
"pref_auto_update_manga_on_mark_read",
AutoTrackState.ALWAYS,
)

// SY -->
fun resolveUsingSourceMetadata() = preferenceStore.getBoolean(
"pref_resolve_using_source_metadata_key",
true,
)
// SY <--
}
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.source.service.SourceManager
import tachiyomi.i18n.MR
import tachiyomi.i18n.sy.SYMR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
import uy.kohesive.injekt.Injekt
Expand Down Expand Up @@ -135,6 +136,13 @@ object SettingsTrackingScreen : SearchableSettings {
.associateWith { stringResource(it.titleRes) }
.toPersistentMap(),
),
// SY -->
Preference.PreferenceItem.SwitchPreference(
pref = trackPreferences.resolveUsingSourceMetadata(),
title = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata),
subtitle = stringResource(SYMR.strings.pref_tracker_resolve_using_source_metadata_summary),
),
// SY <--
Preference.PreferenceGroup(
title = stringResource(MR.strings.services),
preferenceItems = persistentListOf(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.domain.track.service.TrackPreferences
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.model.TrackMangaMetadata
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.system.toast
import kotlinx.coroutines.flow.Flow
Expand Down Expand Up @@ -125,6 +126,12 @@ abstract class BaseTracker(
throw NotImplementedError("Not implemented.")
}

// SY -->
override suspend fun searchById(id: String): TrackSearch? {
throw NotImplementedError("Not implemented.")
}
// SY <--

private suspend fun updateRemote(track: Track): Unit = withIOContext {
try {
update(track)
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/java/eu/kanade/tachiyomi/data/track/Tracker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -85,4 +85,8 @@ interface Tracker {
suspend fun setRemoteFinishDate(track: Track, epochMillis: Long)

suspend fun getMangaMetadata(track: DomainTrack): TrackMangaMetadata?

// SY -->
suspend fun searchById(id: String): TrackSearch?
// SY <--
}
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ class Anilist(id: Long) : BaseTracker(id, "AniList"), DeletableTracker {
return api.getMangaMetadata(track)
}

// SY -->
override suspend fun searchById(id: String): TrackSearch {
return api.searchById(id)
}
// SY <--

fun saveOAuth(alOAuth: ALOAuth?) {
trackPreferences.trackToken(this).set(json.encodeToString(alOAuth))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import androidx.core.net.toUri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.anilist.dto.ALAddMangaResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALCurrentUserResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALIdSearchResult
import eu.kanade.tachiyomi.data.track.anilist.dto.ALMangaMetadata
import eu.kanade.tachiyomi.data.track.anilist.dto.ALOAuth
import eu.kanade.tachiyomi.data.track.anilist.dto.ALSearchResult
Expand Down Expand Up @@ -356,6 +357,56 @@ class AnilistApi(val client: OkHttpClient, interceptor: AnilistInterceptor) {
}
}

// SY -->
suspend fun searchById(id: String): TrackSearch {
return withIOContext {
val query = """
|query (${'$'}mangaId: Int!) {
|Media (id: ${'$'}mangaId) {
|id
|title {
|userPreferred
|}
|coverImage {
|large
|}
|format
|status
|chapters
|description
|startDate {
|year
|month
|day
|}
|averageScore
|}
|}
|
""".trimMargin()
val payload = buildJsonObject {
put("query", query)
putJsonObject("variables") {
put("mangaId", id)
}
}
with(json) {
authClient.newCall(
POST(
API_URL,
body = payload.toString().toRequestBody(jsonMime),
),
)
.awaitSuccess()
.parseAs<ALIdSearchResult>()
.data.media
.toALManga()
.toTrack()
}
}
}
// SY <--

private fun createDate(dateValue: Long): JsonObject {
if (dateValue == 0L) {
return buildJsonObject {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,16 @@ data class ALSearchPage(
data class ALSearchMedia(
val media: List<ALSearchItem>,
)

// SY -->
@Serializable
data class ALIdSearchResult(
val data: ALIdSearchMedia,
)

@Serializable
data class ALIdSearchMedia(
@SerialName("Media")
val media: ALSearchItem,
)
// SY <--
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,29 @@ class MangaUpdates(id: Long) : BaseTracker(id, "MangaUpdates"), DeletableTracker
}
}

// SY -->
override suspend fun searchById(id: String): TrackSearch? {
/*
* MangaUpdates uses newer base36 IDs (in URLs displayed as an encoded string, internally as a long)
* as well as older sequential numeric IDs, which were phased out to prevent heavy load caused by
* database scraping. Unfortunately, sites like MD sometimes still provides links with the old IDs,
* so we need to convert them.
* Because the API only accepts the newer IDs, we are forced to access the legacy non-API website
* (ex. https://www.mangaupdates.com/series.html?id=15), which is a permanent redirect (HTTP 308) to the new one.
*/

val base36Id = if (id.matches(Regex("""^\d+$"""))) {
api.convertToNewId(id.toInt()) ?: return null
} else {
id
}

return base36Id.toLong(36).let { longId ->
api.getSeries(longId).toTrackSearch(this.id)
}
}
// SY <--

fun restoreSession(): String? {
return trackPreferences.trackPassword(this).get().ifBlank { null }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import eu.kanade.tachiyomi.network.DELETE
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.PUT
import eu.kanade.tachiyomi.network.await
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.json.Json
Expand All @@ -25,6 +26,7 @@ import kotlinx.serialization.json.putJsonObject
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
import tachiyomi.domain.track.model.Track as DomainTrack

Expand Down Expand Up @@ -190,14 +192,35 @@ class MangaUpdatesApi(
}
}

suspend fun getSeries(track: DomainTrack): MURecord {
suspend fun getSeries(track: DomainTrack): MURecord =
getSeries(track.remoteId)

// SY -->
suspend fun getSeries(remoteId: Long): MURecord {
return with(json) {
client.newCall(GET("$BASE_URL/v1/series/${track.remoteId}"))
client.newCall(GET("$BASE_URL/v1/series/$remoteId"))
.awaitSuccess()
.parseAs<MURecord>()
}
}

suspend fun convertToNewId(legacyId: Int): String? =
client.newBuilder()
.followRedirects(false)
.build()
.newCall(GET("https://www.mangaupdates.com/series.html?id=$legacyId"))
.await()
.takeIf(Response::isRedirect)
?.header("Location")
?.let {
// Extract the new id from the redirected URL
Regex("""/series/(\w+)(/([\w-]+)?)?/?${'$'}""")
.find(it)
?.groups?.get(1)
?.value
}
// SY <--

companion object {
private const val BASE_URL = "https://api.mangaupdates.com"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.data.track.myanimelist.dto.MALOAuth
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import tachiyomi.i18n.MR
import uy.kohesive.injekt.injectLazy
Expand Down Expand Up @@ -161,6 +160,12 @@ class MyAnimeList(id: Long) : BaseTracker(id, "MyAnimeList"), DeletableTracker {
return api.getMangaMetadata(track)
}

// SY -->
override suspend fun searchById(id: String): TrackSearch {
return api.getMangaDetails(id.toInt())
}
// SY <--

fun getIfAuthExpired(): Boolean {
return trackPreferences.trackAuthExpired(this).get()
}
Expand Down
Loading
Loading