Support Custom Repos (#803)

* Support custom repos

* Fix migration

* Make extension after update optional
This commit is contained in:
Mitchell Syer
2024-01-05 19:14:09 -05:00
committed by GitHub
parent abf1af41a3
commit 230427e758
19 changed files with 149 additions and 22 deletions

View File

@@ -143,7 +143,7 @@ object Chapter {
val chapterNumber = ChapterRecognition.parseChapterNumber(manga.title, chapter.name, chapter.chapter_number.toDouble())
chapter.chapter_number = chapterNumber.toFloat()
chapter.name = chapter.name.sanitize(manga.title)
chapter.scanlator = chapter.scanlator?.ifBlank { null }
chapter.scanlator = chapter.scanlator?.ifBlank { null }?.trim()
}
val now = Instant.now().epochSecond

View File

@@ -23,6 +23,7 @@ import suwayomi.tachidesk.manga.impl.extension.github.ExtensionGithubApi
import suwayomi.tachidesk.manga.impl.extension.github.OnlineExtension
import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import suwayomi.tachidesk.manga.model.table.ExtensionTable
import suwayomi.tachidesk.server.serverConfig
import java.util.concurrent.ConcurrentHashMap
import kotlin.time.Duration.Companion.seconds
@@ -38,7 +39,17 @@ object ExtensionsList {
logger.debug("Getting extensions list from the internet")
lastUpdateCheck = System.currentTimeMillis()
val foundExtensions = ExtensionGithubApi.findExtensions()
val extensions =
(listOf(ExtensionGithubApi.REPO_URL_PREFIX) + serverConfig.extensionRepos.value).map { repo ->
kotlin.runCatching {
ExtensionGithubApi.findExtensions(repo)
}.onFailure {
logger.warn(it) {
"Failed to fetch extensions for repo: $repo"
}
}
}
val foundExtensions = extensions.mapNotNull { it.getOrNull() }.flatten()
updateExtensionDatabase(foundExtensions)
} else {
logger.debug("used cached extension list")
@@ -54,6 +65,7 @@ object ExtensionsList {
transaction {
ExtensionTable.selectAll().filter { it[ExtensionTable.name] != LocalSource.EXTENSION_NAME }.map {
ExtensionDataClass(
it[ExtensionTable.repo],
it[ExtensionTable.apkName],
getExtensionIconUrl(it[ExtensionTable.apkName]),
it[ExtensionTable.name],
@@ -77,7 +89,7 @@ object ExtensionsList {
val extensionsToUpdate = mutableListOf<Pair<OnlineExtension, ResultRow>>()
val extensionsToInsert = mutableListOf<OnlineExtension>()
val extensionsToDelete =
installedExtensions.mapNotNull { (pkgName, extension) ->
installedExtensions.filter { it.value[ExtensionTable.repo] != null }.mapNotNull { (pkgName, extension) ->
extension.takeUnless { foundExtensions.any { it.pkgName == pkgName } }
}
foundExtensions.forEach {
@@ -124,6 +136,7 @@ object ExtensionsList {
extensionsToFullyUpdate.forEach { (foundExtension, extensionRecord) ->
addBatch(EntityID(extensionRecord[ExtensionTable.id].value, ExtensionTable))
// extension is not installed, so we can overwrite the data without a care
this[ExtensionTable.repo] = foundExtension.repo
this[ExtensionTable.name] = foundExtension.name
this[ExtensionTable.versionName] = foundExtension.versionName
this[ExtensionTable.versionCode] = foundExtension.versionCode
@@ -138,6 +151,7 @@ object ExtensionsList {
}
if (extensionsToInsert.isNotEmpty()) {
ExtensionTable.batchInsert(extensionsToInsert) { foundExtension ->
this[ExtensionTable.repo] = foundExtension.repo
this[ExtensionTable.name] = foundExtension.name
this[ExtensionTable.pkgName] = foundExtension.pkgName
this[ExtensionTable.versionName] = foundExtension.versionName

View File

@@ -20,7 +20,7 @@ import suwayomi.tachidesk.manga.model.dataclass.ExtensionDataClass
import uy.kohesive.injekt.injectLazy
object ExtensionGithubApi {
private const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/"
const val REPO_URL_PREFIX = "https://raw.githubusercontent.com/tachiyomiorg/tachiyomi-extensions/repo/"
private const val FALLBACK_REPO_URL_PREFIX = "https://gcore.jsdelivr.net/gh/tachiyomiorg/tachiyomi-extensions@repo/"
private val logger = KotlinLogging.logger {}
private val json: Json by injectLazy()
@@ -49,13 +49,13 @@ object ExtensionGithubApi {
private var requiresFallbackSource = false
suspend fun findExtensions(): List<OnlineExtension> {
suspend fun findExtensions(repo: String): List<OnlineExtension> {
val githubResponse =
if (requiresFallbackSource) {
null
} else {
try {
client.newCall(GET("${REPO_URL_PREFIX}index.min.json")).awaitSuccess()
client.newCall(GET("${repo.repoUrlReplace()}index.min.json")).awaitSuccess()
} catch (e: Throwable) {
logger.error(e) { "Failed to get extensions from GitHub" }
requiresFallbackSource = true
@@ -65,18 +65,18 @@ object ExtensionGithubApi {
val response =
githubResponse ?: run {
client.newCall(GET("${FALLBACK_REPO_URL_PREFIX}index.min.json")).awaitSuccess()
client.newCall(GET("${repo.fallbackRepoUrlReplace()}index.min.json")).awaitSuccess()
}
return with(json) {
response
.parseAs<List<ExtensionJsonObject>>()
.toExtensions()
.toExtensions(repo.repoUrlReplace())
}
}
fun getApkUrl(extension: ExtensionDataClass): String {
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
return "${extension.repo!!.repoUrlReplace()}/apk/${extension.apkName}"
}
private val client by lazy {
@@ -91,7 +91,7 @@ object ExtensionGithubApi {
.build()
}
private fun List<ExtensionJsonObject>.toExtensions(): List<OnlineExtension> {
private fun List<ExtensionJsonObject>.toExtensions(repo: String): List<OnlineExtension> {
return this
.filter {
val libVersion = it.version.substringBeforeLast('.').toDouble()
@@ -99,6 +99,7 @@ object ExtensionGithubApi {
}
.map {
OnlineExtension(
repo = repo,
name = it.name.substringAfter("Tachiyomi: "),
pkgName = it.pkg,
versionName = it.version,
@@ -109,7 +110,7 @@ object ExtensionGithubApi {
hasChangelog = it.hasChangelog == 1,
sources = it.sources?.toExtensionSources() ?: emptyList(),
apkName = it.apk,
iconUrl = "${REPO_URL_PREFIX}icon/${it.pkg}.png",
iconUrl = "${repo}icon/${it.pkg}.png",
)
}
}
@@ -124,4 +125,22 @@ object ExtensionGithubApi {
)
}
}
private fun String.repoUrlReplace() =
replace(repoMatchRegex) {
"https://raw.githubusercontent.com/${it.groupValues[1]}/${it.groupValues[2]}/" +
"${it.groupValues.getOrNull(3)?.ifBlank { null } ?: "repo"}/"
}
private fun String.fallbackRepoUrlReplace() =
replace(repoMatchRegex) {
"https://gcore.jsdelivr.net/gh/${it.groupValues[1]}/${it.groupValues[2]}@" +
"${it.groupValues.getOrNull(3)?.ifBlank { null } ?: "repo"}/"
}
private val repoMatchRegex =
(
"https:\\/\\/(?:www|raw)?(?:github|githubusercontent)\\.com" +
"\\/([^\\/]+)\\/([^\\/]+)(?:\\/(?:tree|blob)\\/(.*))?\\/?"
).toRegex()
}

View File

@@ -15,6 +15,7 @@ data class OnlineExtensionSource(
)
data class OnlineExtension(
val repo: String,
val name: String,
val pkgName: String,
val apkName: String,

View File

@@ -44,8 +44,7 @@ object PackageTools {
const val LIB_VERSION_MAX = 1.5
private const val OFFICIAL_SIGNATURE = "7ce04da7773d41b489f4693a366c36bcd0a11fc39b547168553c285bd7348e23" // inorichi's key
private const val UNOFFICIAL_SIGNATURE = "64feb21075ba97ebc9cc981243645b331595c111cef1b0d084236a0403b00581" // ArMor's key
val trustedSignatures = setOf(OFFICIAL_SIGNATURE, UNOFFICIAL_SIGNATURE)
val trustedSignatures = setOf(OFFICIAL_SIGNATURE)
/**
* Convert dex to jar, a wrapper for the dex2jar library

View File

@@ -8,6 +8,7 @@ package suwayomi.tachidesk.manga.model.dataclass
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
data class ExtensionDataClass(
val repo: String?,
val apkName: String,
val iconUrl: String,
val name: String,

View File

@@ -11,6 +11,7 @@ import org.jetbrains.exposed.dao.id.IntIdTable
object ExtensionTable : IntIdTable() {
val apkName = varchar("apk_name", 1024)
val repo = varchar("repo", 1024).nullable()
// default is the local source icon from tachiyomi
@Suppress("ktlint:standard:max-line-length")

View File

@@ -80,6 +80,6 @@ enum class MangaStatus(val value: Int) {
;
companion object {
fun valueOf(value: Int): MangaStatus = values().find { it.value == value } ?: UNKNOWN
fun valueOf(value: Int): MangaStatus = entries.find { it.value == value } ?: UNKNOWN
}
}