fix: remove duplicate extension code (#322)

* fix: remove duplicate extension code

* fix: allow Material You icons to load

* fix: remove unused preference item

* fix: load mono on square setups
This commit is contained in:
TwistedUmbrellaX 2024-04-05 17:50:40 -04:00 committed by GitHub
parent 72c69e7c79
commit f6c7b09d9b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 149 additions and 321 deletions

View file

@ -30,7 +30,6 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
),
AnimeExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
MangaExtensionRepos(Pref(Location.General, Set::class, setOf<String>())),
SharedRepositories(Pref(Location.General, Boolean::class, false)),
AnimeSourcesOrder(Pref(Location.General, List::class, listOf<String>())),
AnimeSearchHistory(Pref(Location.General, Set::class, setOf<String>())),
MangaSourcesOrder(Pref(Location.General, List::class, listOf<String>())),

View file

@ -6,10 +6,10 @@ import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.anime.api.AnimeExtensionGithubApi
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.util.ExtensionInstallReceiver
import eu.kanade.tachiyomi.extension.util.ExtensionInstaller
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
@ -47,7 +47,7 @@ class AnimeExtensionManager(
/**
* API where all the available anime extensions can be found.
*/
private val api = AnimeExtensionGithubApi()
private val api = ExtensionGithubApi()
/**
* The installer which installs, updates and uninstalls the anime extensions.
@ -118,7 +118,7 @@ class AnimeExtensionManager(
*/
suspend fun findAvailableExtensions() {
val extensions: List<AnimeExtension.Available> = try {
api.findExtensions()
api.findAnimeExtensions()
} catch (e: Exception) {
Logger.log(e)
withUIContext { snackString("Failed to get extensions list") }
@ -206,7 +206,7 @@ class AnimeExtensionManager(
* @param extension The anime extension to be installed.
*/
fun installExtension(extension: AnimeExtension.Available): Observable<InstallStep> {
return installer.downloadAndInstall(api.getApkUrl(extension), extension)
return installer.downloadAndInstall(api.getAnimeApkUrl(extension), extension)
}
/**

View file

@ -1,214 +0,0 @@
package eu.kanade.tachiyomi.extension.anime.api
import android.content.Context
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AnimeLoadResult
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
internal class AnimeExtensionGithubApi {
private val networkService: NetworkHelper by injectLazy()
private val preferenceStore: PreferenceStore by injectLazy()
private val animeExtensionManager: AnimeExtensionManager by injectLazy()
private val json: Json by injectLazy()
private val lastExtCheck: Preference<Long> by lazy {
preferenceStore.getLong("last_ext_check", 0)
}
suspend fun findExtensions(): List<AnimeExtension.Available> {
return withIOContext {
val extensions: ArrayList<AnimeExtension.Available> = arrayListOf()
val repos =
PrefManager.getVal<Set<String>>(PrefName.AnimeExtensionRepos).toMutableList()
if (repos.isEmpty()) {
repos.add("https://raw.githubusercontent.com/aniyomiorg/aniyomi-extensions/repo")
PrefManager.setVal(PrefName.AnimeExtensionRepos, repos.toSet())
}
repos.forEach {
try {
val githubResponse = try {
networkService.client
.newCall(GET("${it}/index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
Logger.log("Failed to get repo: $it")
Logger.log(e)
null
}
val response = githubResponse ?: run {
networkService.client
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
.awaitSuccess()
}
val repoExtensions = with(json) {
response
.parseAs<List<AnimeExtensionJsonObject>>()
.toExtensions(it)
}
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (repoExtensions.size < 10) {
throw Exception()
}
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
Logger.log(e)
}
}
extensions
}
}
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false
): List<AnimeExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
}
val extensions = if (fromAvailableExtensionList) {
animeExtensionManager.availableExtensionsFlow.value
} else {
findExtensions().also { lastExtCheck.set(Date().time) }
}
val installedExtensions = ExtensionLoader.loadAnimeExtensions(context)
.filterIsInstance<AnimeLoadResult.Success>()
.map { it.extension }
val extensionsWithUpdate = mutableListOf<AnimeExtension.Installed>()
for (installedExt in installedExtensions) {
val pkgName = installedExt.pkgName
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib)
if (hasUpdate) {
extensionsWithUpdate.add(installedExt)
}
}
if (extensionsWithUpdate.isNotEmpty()) {
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
}
return extensionsWithUpdate
}
private fun List<AnimeExtensionJsonObject>.toExtensions(repository: String): List<AnimeExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.ANIME_LIB_VERSION_MIN && libVersion <= ExtensionLoader.ANIME_LIB_VERSION_MAX
}
.map {
AnimeExtension.Available(
name = it.name.substringAfter("Aniyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toAnimeExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
private fun List<AnimeExtensionSourceJsonObject>.toAnimeExtensionSources(): List<AvailableAnimeSources> {
return this.map {
AvailableAnimeSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
fun getApkUrl(extension: AnimeExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}"
}
private fun fallbackRepoUrl(repoUrl: String): String? {
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
val strippedRepoUrl =
repoUrl.removePrefix("https://").removePrefix("http://").removeSuffix("/")
val repoUrlParts = strippedRepoUrl.split("/")
if (repoUrlParts.size < 3) {
return null
}
val repoOwner = repoUrlParts[1]
val repoName = repoUrlParts[2]
fallbackRepoUrl += "$repoOwner/$repoName"
val repoBranch = if (repoUrlParts.size > 3) {
repoUrlParts[3]
} else {
"main"
}
fallbackRepoUrl += "@$repoBranch"
return fallbackRepoUrl
}
}
private fun AnimeExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}
@Serializable
private data class AnimeExtensionJsonObject(
val name: String,
val pkg: String,
val apk: String,
val lang: String,
val code: Long,
val version: String,
val nsfw: Int,
val hasReadme: Int = 0,
val hasChangelog: Int = 0,
val sources: List<AnimeExtensionSourceJsonObject>?,
)
@Serializable
private data class AnimeExtensionSourceJsonObject(
val id: Long,
val lang: String,
val name: String,
val baseUrl: String,
)

View file

@ -1,14 +1,12 @@
package eu.kanade.tachiyomi.extension.manga.api
package eu.kanade.tachiyomi.extension.api
import android.content.Context
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.extension.ExtensionUpdateNotifier
import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
import eu.kanade.tachiyomi.extension.anime.model.AnimeExtension
import eu.kanade.tachiyomi.extension.anime.model.AvailableAnimeSources
import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.NetworkHelper
@ -16,25 +14,143 @@ import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.network.parseAs
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import tachiyomi.core.preference.Preference
import tachiyomi.core.preference.PreferenceStore
import tachiyomi.core.util.lang.withIOContext
import uy.kohesive.injekt.injectLazy
import java.util.Date
import kotlin.time.Duration.Companion.days
internal class MangaExtensionGithubApi {
internal class ExtensionGithubApi {
private val networkService: NetworkHelper by injectLazy()
private val preferenceStore: PreferenceStore by injectLazy()
private val extensionManager: MangaExtensionManager by injectLazy()
private val json: Json by injectLazy()
private val lastExtCheck: Preference<Long> by lazy {
preferenceStore.getLong("last_ext_check", 0)
private fun List<ExtensionSourceJsonObject>.toAnimeExtensionSources(): List<AvailableAnimeSources> {
return this.map {
AvailableAnimeSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
suspend fun findExtensions(): List<MangaExtension.Available> {
private fun List<ExtensionJsonObject>.toAnimeExtensions(repository: String): List<AnimeExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.ANIME_LIB_VERSION_MIN && libVersion <= ExtensionLoader.ANIME_LIB_VERSION_MAX
}
.map {
AnimeExtension.Available(
name = it.name.substringAfter("Aniyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toAnimeExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
suspend fun findAnimeExtensions(): List<AnimeExtension.Available> {
return withIOContext {
val extensions: ArrayList<AnimeExtension.Available> = arrayListOf()
val repos =
PrefManager.getVal<Set<String>>(PrefName.MangaExtensionRepos).toMutableList()
if (repos.isEmpty()) {
repos.add("https://raw.githubusercontent.com/keiyoushi/extensions/main")
PrefManager.setVal(PrefName.MangaExtensionRepos, repos.toSet())
}
repos.forEach {
try {
val githubResponse = try {
networkService.client
.newCall(GET("${it}/index.min.json"))
.awaitSuccess()
} catch (e: Throwable) {
Logger.log("Failed to get repo: $it")
Logger.log(e)
null
}
val response = githubResponse ?: run {
networkService.client
.newCall(GET(fallbackRepoUrl(it) + "/index.min.json"))
.awaitSuccess()
}
val repoExtensions = with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toAnimeExtensions(it)
}
// Sanity check - a small number of extensions probably means something broke
// with the repo generator
if (repoExtensions.size < 10) {
throw Exception()
}
extensions.addAll(repoExtensions)
} catch (e: Throwable) {
Logger.log("Failed to get extensions from GitHub")
Logger.log(e)
}
}
extensions
}
}
fun getAnimeApkUrl(extension: AnimeExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}"
}
private fun List<ExtensionSourceJsonObject>.toMangaExtensionSources(): List<AvailableMangaSources> {
return this.map {
AvailableMangaSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
private fun List<ExtensionJsonObject>.toMangaExtensions(repository: String): List<MangaExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.MANGA_LIB_VERSION_MIN && libVersion <= ExtensionLoader.MANGA_LIB_VERSION_MAX
}
.map {
MangaExtension.Available(
name = it.name.substringAfter("Tachiyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toMangaExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
suspend fun findMangaExtensions(): List<MangaExtension.Available> {
return withIOContext {
val extensions: ArrayList<MangaExtension.Available> = arrayListOf()
@ -67,7 +183,7 @@ internal class MangaExtensionGithubApi {
val repoExtensions = with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toExtensions(it)
.toMangaExtensions(it)
}
// Sanity check - a small number of extensions probably means something broke
@ -87,88 +203,10 @@ internal class MangaExtensionGithubApi {
}
}
suspend fun checkForUpdates(
context: Context,
fromAvailableExtensionList: Boolean = false
): List<MangaExtension.Installed>? {
// Limit checks to once a day at most
if (fromAvailableExtensionList && Date().time < lastExtCheck.get() + 1.days.inWholeMilliseconds) {
return null
}
val extensions = if (fromAvailableExtensionList) {
extensionManager.availableExtensionsFlow.value
} else {
findExtensions().also { lastExtCheck.set(Date().time) }
}
val installedExtensions = ExtensionLoader.loadMangaExtensions(context)
.filterIsInstance<MangaLoadResult.Success>()
.map { it.extension }
val extensionsWithUpdate = mutableListOf<MangaExtension.Installed>()
for (installedExt in installedExtensions) {
val pkgName = installedExt.pkgName
val availableExt = extensions.find { it.pkgName == pkgName } ?: continue
val hasUpdatedVer = availableExt.versionCode > installedExt.versionCode
val hasUpdatedLib = availableExt.libVersion > installedExt.libVersion
val hasUpdate = installedExt.isUnofficial.not() && (hasUpdatedVer || hasUpdatedLib)
if (hasUpdate) {
extensionsWithUpdate.add(installedExt)
}
}
if (extensionsWithUpdate.isNotEmpty()) {
ExtensionUpdateNotifier(context).promptUpdates(extensionsWithUpdate.map { it.name })
}
return extensionsWithUpdate
}
private fun List<ExtensionJsonObject>.toExtensions(repository: String): List<MangaExtension.Available> {
return this
.filter {
val libVersion = it.extractLibVersion()
libVersion >= ExtensionLoader.MANGA_LIB_VERSION_MIN && libVersion <= ExtensionLoader.MANGA_LIB_VERSION_MAX
}
.map {
MangaExtension.Available(
name = it.name.substringAfter("Tachiyomi: "),
pkgName = it.pkg,
versionName = it.version,
versionCode = it.code,
libVersion = it.extractLibVersion(),
lang = it.lang,
isNsfw = it.nsfw == 1,
hasReadme = it.hasReadme == 1,
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toExtensionSources().orEmpty(),
apkName = it.apk,
repository = repository,
iconUrl = "${repository}/icon/${it.pkg}.png",
)
}
}
private fun List<ExtensionSourceJsonObject>.toExtensionSources(): List<AvailableMangaSources> {
return this.map {
AvailableMangaSources(
id = it.id,
lang = it.lang,
name = it.name,
baseUrl = it.baseUrl,
)
}
}
fun getApkUrl(extension: MangaExtension.Available): String {
fun getMangaApkUrl(extension: MangaExtension.Available): String {
return "${extension.repository}/apk/${extension.apkName}"
}
private fun ExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}
private fun fallbackRepoUrl(repoUrl: String): String? {
var fallbackRepoUrl = "https://gcore.jsdelivr.net/gh/"
val strippedRepoUrl =
@ -211,3 +249,7 @@ private data class ExtensionSourceJsonObject(
val name: String,
val baseUrl: String,
)
private fun ExtensionJsonObject.extractLibVersion(): Double {
return version.substringBeforeLast('.').toDouble()
}

View file

@ -6,7 +6,7 @@ import ani.dantotsu.snackString
import ani.dantotsu.util.Logger
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.InstallStep
import eu.kanade.tachiyomi.extension.manga.api.MangaExtensionGithubApi
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
import eu.kanade.tachiyomi.extension.manga.model.AvailableMangaSources
import eu.kanade.tachiyomi.extension.manga.model.MangaExtension
import eu.kanade.tachiyomi.extension.manga.model.MangaLoadResult
@ -47,7 +47,7 @@ class MangaExtensionManager(
/**
* API where all the available extensions can be found.
*/
private val api = MangaExtensionGithubApi()
private val api = ExtensionGithubApi()
/**
* The installer which installs, updates and uninstalls the extensions.
@ -115,7 +115,7 @@ class MangaExtensionManager(
*/
suspend fun findAvailableExtensions() {
val extensions: List<MangaExtension.Available> = try {
api.findExtensions()
api.findMangaExtensions()
} catch (e: Exception) {
Logger.log(e)
withUIContext { snackString("Failed to get manga extensions") }
@ -203,7 +203,7 @@ class MangaExtensionManager(
* @param extension The extension to be installed.
*/
fun installExtension(extension: MangaExtension.Available): Observable<InstallStep> {
return installer.downloadAndInstall(api.getApkUrl(extension), extension)
return installer.downloadAndInstall(api.getMangaApkUrl(extension), extension)
}
/**

View file

@ -1,6 +1,6 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="768dp"
android:height="768dp"
android:width="200dp"
android:height="200dp"
android:viewportWidth="768"
android:viewportHeight="768">
<group>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_alpha_background" />
<foreground android:drawable="@drawable/ic_launcher_alpha_foreground" />
<monochrome android:drawable="@drawable/mono" />
</adaptive-icon>