can work with anime extensions successfully
This commit is contained in:
@@ -8,62 +8,67 @@ package suwayomi.anime
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import io.javalin.Javalin
|
||||
import suwayomi.server.JavalinSetup
|
||||
import suwayomi.anime.impl.extension.Extension.getExtensionIcon
|
||||
import suwayomi.anime.impl.extension.Extension.installExtension
|
||||
import suwayomi.anime.impl.extension.Extension.uninstallExtension
|
||||
import suwayomi.anime.impl.extension.Extension.updateExtension
|
||||
import suwayomi.anime.impl.extension.ExtensionsList.getExtensionList
|
||||
import suwayomi.server.JavalinSetup
|
||||
import suwayomi.server.JavalinSetup.future
|
||||
|
||||
object AnimeAPI {
|
||||
fun defineEndpoints(app: Javalin) {
|
||||
// list all extensions
|
||||
app.get("/api/v1/extension/list") { ctx ->
|
||||
app.get("/api/v1/anime/extension/list") { ctx ->
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getExtensionList()
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// // install extension identified with "pkgName"
|
||||
// app.get("/api/v1/extension/install/:pkgName") { ctx ->
|
||||
// val pkgName = ctx.pathParam("pkgName")
|
||||
//
|
||||
// ctx.json(
|
||||
// JavalinSetup.future {
|
||||
// installExtension(pkgName)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // update extension identified with "pkgName"
|
||||
// app.get("/api/v1/extension/update/:pkgName") { ctx ->
|
||||
// val pkgName = ctx.pathParam("pkgName")
|
||||
//
|
||||
// ctx.json(
|
||||
// JavalinSetup.future {
|
||||
// updateExtension(pkgName)
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// // uninstall extension identified with "pkgName"
|
||||
// app.get("/api/v1/extension/uninstall/:pkgName") { ctx ->
|
||||
// val pkgName = ctx.pathParam("pkgName")
|
||||
//
|
||||
// uninstallExtension(pkgName)
|
||||
// ctx.status(200)
|
||||
// }
|
||||
//
|
||||
// // icon for extension named `apkName`
|
||||
// app.get("/api/v1/extension/icon/:apkName") { ctx -> // TODO: move to pkgName
|
||||
// val apkName = ctx.pathParam("apkName")
|
||||
//
|
||||
// ctx.result(
|
||||
// JavalinSetup.future { getExtensionIcon(apkName) }
|
||||
// .thenApply {
|
||||
// ctx.header("content-type", it.second)
|
||||
// it.first
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
// install extension identified with "pkgName"
|
||||
app.get("/api/v1/anime/extension/install/:pkgName") { ctx ->
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
installExtension(pkgName)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// update extension identified with "pkgName"
|
||||
app.get("/api/v1/anime/extension/update/:pkgName") { ctx ->
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
updateExtension(pkgName)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// uninstall extension identified with "pkgName"
|
||||
app.get("/api/v1/anime/extension/uninstall/:pkgName") { ctx ->
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
uninstallExtension(pkgName)
|
||||
ctx.status(200)
|
||||
}
|
||||
|
||||
// icon for extension named `apkName`
|
||||
app.get("/api/v1/anime/extension/icon/:apkName") { ctx -> // TODO: move to pkgName
|
||||
val apkName = ctx.pathParam("apkName")
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getExtensionIcon(apkName) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// // list of sources
|
||||
// app.get("/api/v1/source/list") { ctx ->
|
||||
|
||||
@@ -10,9 +10,9 @@ package suwayomi.anime.impl.extension
|
||||
import android.net.Uri
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.source.AnimeCatalogueSource
|
||||
import eu.kanade.tachiyomi.source.AnimeSource
|
||||
import eu.kanade.tachiyomi.source.AnimeSourceFactory
|
||||
import mu.KotlinLogging
|
||||
import okhttp3.Request
|
||||
import okio.buffer
|
||||
@@ -125,11 +125,11 @@ object Extension {
|
||||
File(dexFilePath).delete()
|
||||
|
||||
// collect sources from the extension
|
||||
val sources: List<CatalogueSource> = when (val instance = loadExtensionSources(jarFilePath, className)) {
|
||||
is Source -> listOf(instance)
|
||||
is SourceFactory -> instance.createSources()
|
||||
val sources: List<AnimeCatalogueSource> = when (val instance = loadExtensionSources(jarFilePath, className)) {
|
||||
is AnimeSource -> listOf(instance)
|
||||
is AnimeSourceFactory -> instance.createSources()
|
||||
else -> throw RuntimeException("Unknown source class type! ${instance.javaClass}")
|
||||
}.map { it as CatalogueSource }
|
||||
}.map { it as AnimeCatalogueSource }
|
||||
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val extensionLang = when (langs.size) {
|
||||
@@ -246,6 +246,6 @@ object Extension {
|
||||
}
|
||||
|
||||
fun getExtensionIconUrl(apkName: String): String {
|
||||
return "/api/v1/extension/icon/$apkName"
|
||||
return "/api/v1/anime/extension/icon/$apkName"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,15 +32,14 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
|
||||
object PackageTools {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
const val EXTENSION_FEATURE = "tachiyomi.extension"
|
||||
const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class"
|
||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.extension.factory"
|
||||
const val METADATA_NSFW = "tachiyomi.extension.nsfw"
|
||||
const val EXTENSION_FEATURE = "tachiyomi.animeextension"
|
||||
const val METADATA_SOURCE_CLASS = "tachiyomi.animeextension.class"
|
||||
const val METADATA_SOURCE_FACTORY = "tachiyomi.animeextension.factory"
|
||||
const val METADATA_NSFW = "tachiyomi.animeextension.nsfw"
|
||||
const val LIB_VERSION_MIN = 1.3
|
||||
const val LIB_VERSION_MAX = 1.3
|
||||
|
||||
@@ -58,20 +57,20 @@ object PackageTools {
|
||||
val reader = MultiDexFileReader.open(Files.readAllBytes(File(dexFile).toPath()))
|
||||
val handler = BaksmaliBaseDexExceptionHandler()
|
||||
Dex2jar
|
||||
.from(reader)
|
||||
.withExceptionHandler(handler)
|
||||
.reUseReg(false)
|
||||
.topoLogicalSort()
|
||||
.skipDebug(true)
|
||||
.optimizeSynchronized(false)
|
||||
.printIR(false)
|
||||
.noCode(false)
|
||||
.skipExceptions(false)
|
||||
.to(jarFilePath)
|
||||
.from(reader)
|
||||
.withExceptionHandler(handler)
|
||||
.reUseReg(false)
|
||||
.topoLogicalSort()
|
||||
.skipDebug(true)
|
||||
.optimizeSynchronized(false)
|
||||
.printIR(false)
|
||||
.noCode(false)
|
||||
.skipExceptions(false)
|
||||
.to(jarFilePath)
|
||||
if (handler.hasException()) {
|
||||
val errorFile: Path = File(applicationDirs.extensionsRoot).toPath().resolve("$fileNameWithoutType-error.txt")
|
||||
logger.error(
|
||||
"""
|
||||
"""
|
||||
Detail Error Information in File $errorFile
|
||||
Please report this file to one of following link if possible (any one).
|
||||
https://sourceforge.net/p/dex2jar/tickets/
|
||||
@@ -101,27 +100,27 @@ object PackageTools {
|
||||
val appTag = doc.getElementsByTagName("application").item(0)
|
||||
|
||||
appTag?.childNodes?.toList()
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.filter {
|
||||
it.nodeType == Node.ELEMENT_NODE
|
||||
}.map {
|
||||
it as Element
|
||||
}.filter {
|
||||
it.tagName == "meta-data"
|
||||
}.forEach {
|
||||
putString(
|
||||
it.attributes.getNamedItem("android:name").nodeValue,
|
||||
it.attributes.getNamedItem("android:value").nodeValue
|
||||
)
|
||||
}
|
||||
.orEmpty()
|
||||
.asSequence()
|
||||
.filter {
|
||||
it.nodeType == Node.ELEMENT_NODE
|
||||
}.map {
|
||||
it as Element
|
||||
}.filter {
|
||||
it.tagName == "meta-data"
|
||||
}.forEach {
|
||||
putString(
|
||||
it.attributes.getNamedItem("android:name").nodeValue,
|
||||
it.attributes.getNamedItem("android:value").nodeValue
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
signatures = (
|
||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
||||
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||
.map { Signature(it.data) }.toTypedArray()
|
||||
parsed.apkSingers.flatMap { it.certificateMetas }
|
||||
/*+ parsed.apkV2Singers.flatMap { it.certificateMetas }*/
|
||||
) // Blocked by: https://github.com/hsiafan/apk-parser/issues/72
|
||||
.map { Signature(it.data) }.toTypedArray()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.future.future
|
||||
import mu.KotlinLogging
|
||||
import suwayomi.anime.AnimeAPI
|
||||
import suwayomi.server.util.Browser
|
||||
import suwayomi.tachidesk.TachideskAPI
|
||||
import java.io.IOException
|
||||
@@ -75,5 +76,6 @@ object JavalinSetup {
|
||||
}
|
||||
|
||||
TachideskAPI.defineEndpoints(app)
|
||||
AnimeAPI.defineEndpoints(app)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package suwayomi.server.database.migration
|
||||
|
||||
import org.jetbrains.exposed.dao.id.IdTable
|
||||
import org.jetbrains.exposed.dao.id.IntIdTable
|
||||
import org.jetbrains.exposed.sql.SchemaUtils
|
||||
import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import suwayomi.server.database.migration.lib.Migration
|
||||
|
||||
/*
|
||||
* Copyright (C) Contributors to the Suwayomi project
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
class M0004_AnimeTablesBatch1 : Migration() {
|
||||
private object AnimeExtensionTable : IntIdTable() {
|
||||
val apkName = varchar("apk_name", 1024)
|
||||
|
||||
// default is the local source icon from tachiyomi
|
||||
val iconUrl = varchar("icon_url", 2048)
|
||||
.default("https://raw.githubusercontent.com/tachiyomiorg/tachiyomi/64ba127e7d43b1d7e6d58a6f5c9b2bd5fe0543f7/app/src/main/res/mipmap-xxxhdpi/ic_local_source.webp")
|
||||
|
||||
val name = varchar("name", 128)
|
||||
val pkgName = varchar("pkg_name", 128)
|
||||
val versionName = varchar("version_name", 16)
|
||||
val versionCode = integer("version_code")
|
||||
val lang = varchar("lang", 10)
|
||||
val isNsfw = bool("is_nsfw")
|
||||
|
||||
val isInstalled = bool("is_installed").default(false)
|
||||
val hasUpdate = bool("has_update").default(false)
|
||||
val isObsolete = bool("is_obsolete").default(false)
|
||||
|
||||
val classFQName = varchar("class_name", 1024).default("") // fully qualified name
|
||||
}
|
||||
|
||||
private object AnimeSourceTable : IdTable<Long>() {
|
||||
override val id = long("id").entityId()
|
||||
val name = varchar("name", 128)
|
||||
val lang = varchar("lang", 10)
|
||||
val extension = reference("extension", suwayomi.anime.model.table.AnimeExtensionTable)
|
||||
val partOfFactorySource = bool("part_of_factory_source").default(false)
|
||||
}
|
||||
|
||||
override fun run() {
|
||||
transaction {
|
||||
SchemaUtils.create(
|
||||
AnimeExtensionTable,
|
||||
AnimeSourceTable
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,6 @@ package suwayomi.tachidesk
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
||||
|
||||
import io.javalin.Javalin
|
||||
import suwayomi.server.JavalinSetup
|
||||
import suwayomi.server.JavalinSetup.future
|
||||
import suwayomi.server.impl.About
|
||||
import suwayomi.tachidesk.impl.Category
|
||||
@@ -47,7 +46,7 @@ object TachideskAPI {
|
||||
// list all extensions
|
||||
app.get("/api/v1/extension/list") { ctx ->
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getExtensionList()
|
||||
}
|
||||
)
|
||||
@@ -58,7 +57,7 @@ object TachideskAPI {
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
installExtension(pkgName)
|
||||
}
|
||||
)
|
||||
@@ -69,7 +68,7 @@ object TachideskAPI {
|
||||
val pkgName = ctx.pathParam("pkgName")
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
updateExtension(pkgName)
|
||||
}
|
||||
)
|
||||
@@ -88,7 +87,7 @@ object TachideskAPI {
|
||||
val apkName = ctx.pathParam("apkName")
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getExtensionIcon(apkName) }
|
||||
future { getExtensionIcon(apkName) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
@@ -112,7 +111,7 @@ object TachideskAPI {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getMangaList(sourceId, pageNum, popular = true)
|
||||
}
|
||||
)
|
||||
@@ -123,7 +122,7 @@ object TachideskAPI {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getMangaList(sourceId, pageNum, popular = false)
|
||||
}
|
||||
)
|
||||
@@ -135,7 +134,7 @@ object TachideskAPI {
|
||||
val onlineFetch = ctx.queryParam("onlineFetch", "false").toBoolean()
|
||||
|
||||
ctx.json(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
getManga(mangaId, onlineFetch)
|
||||
}
|
||||
)
|
||||
@@ -146,7 +145,7 @@ object TachideskAPI {
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getMangaThumbnail(mangaId) }
|
||||
future { getMangaThumbnail(mangaId) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
@@ -182,14 +181,14 @@ object TachideskAPI {
|
||||
|
||||
val onlineFetch = ctx.queryParam("onlineFetch")?.toBoolean()
|
||||
|
||||
ctx.json(JavalinSetup.future { getChapterList(mangaId, onlineFetch) })
|
||||
ctx.json(future { getChapterList(mangaId, onlineFetch) })
|
||||
}
|
||||
|
||||
// used to display a chapter, get a chapter in order to show it's pages
|
||||
app.get("/api/v1/manga/:mangaId/chapter/:chapterIndex") { ctx ->
|
||||
val chapterIndex = ctx.pathParam("chapterIndex").toInt()
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
ctx.json(JavalinSetup.future { getChapter(chapterIndex, mangaId) })
|
||||
ctx.json(future { getChapter(chapterIndex, mangaId) })
|
||||
}
|
||||
|
||||
// used to modify a chapter's parameters
|
||||
@@ -214,7 +213,7 @@ object TachideskAPI {
|
||||
val index = ctx.pathParam("index").toInt()
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { getPageImage(mangaId, chapterIndex, index) }
|
||||
future { getPageImage(mangaId, chapterIndex, index) }
|
||||
.thenApply {
|
||||
ctx.header("content-type", it.second)
|
||||
it.first
|
||||
@@ -243,7 +242,7 @@ object TachideskAPI {
|
||||
val sourceId = ctx.pathParam("sourceId").toLong()
|
||||
val searchTerm = ctx.pathParam("searchTerm")
|
||||
val pageNum = ctx.pathParam("pageNum").toInt()
|
||||
ctx.json(JavalinSetup.future { sourceSearch(sourceId, searchTerm, pageNum) })
|
||||
ctx.json(future { sourceSearch(sourceId, searchTerm, pageNum) })
|
||||
}
|
||||
|
||||
// source filter list
|
||||
@@ -257,7 +256,7 @@ object TachideskAPI {
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { addMangaToLibrary(mangaId) }
|
||||
future { addMangaToLibrary(mangaId) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -266,7 +265,7 @@ object TachideskAPI {
|
||||
val mangaId = ctx.pathParam("mangaId").toInt()
|
||||
|
||||
ctx.result(
|
||||
JavalinSetup.future { removeMangaFromLibrary(mangaId) }
|
||||
future { removeMangaFromLibrary(mangaId) }
|
||||
)
|
||||
}
|
||||
|
||||
@@ -335,7 +334,7 @@ object TachideskAPI {
|
||||
// expects a Tachiyomi legacy backup json as a file upload, the file must be named "backup.json"
|
||||
app.post("/api/v1/backup/legacy/import/file") { ctx ->
|
||||
ctx.result(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
restoreLegacyBackup(ctx.uploadedFile("backup.json")!!.content)
|
||||
}
|
||||
)
|
||||
@@ -345,7 +344,7 @@ object TachideskAPI {
|
||||
app.get("/api/v1/backup/legacy/export") { ctx ->
|
||||
ctx.contentType("application/json")
|
||||
ctx.result(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
createLegacyBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
@@ -367,7 +366,7 @@ object TachideskAPI {
|
||||
|
||||
ctx.header("Content-Disposition", "attachment; filename=\"tachidesk_$currentDate.json\"")
|
||||
ctx.result(
|
||||
JavalinSetup.future {
|
||||
future {
|
||||
createLegacyBackup(
|
||||
BackupFlags(
|
||||
includeManga = true,
|
||||
|
||||
@@ -15,8 +15,8 @@ import org.jetbrains.exposed.sql.transactions.transaction
|
||||
import org.kodein.di.DI
|
||||
import org.kodein.di.conf.global
|
||||
import org.kodein.di.instance
|
||||
import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.server.ApplicationDirs
|
||||
import suwayomi.tachidesk.impl.util.PackageTools.loadExtensionSources
|
||||
import suwayomi.tachidesk.model.table.ExtensionTable
|
||||
import suwayomi.tachidesk.model.table.SourceTable
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
@@ -32,7 +32,6 @@ import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import javax.xml.parsers.DocumentBuilderFactory
|
||||
|
||||
|
||||
object PackageTools {
|
||||
private val logger = KotlinLogging.logger {}
|
||||
private val applicationDirs by DI.global.instance<ApplicationDirs>()
|
||||
|
||||
Reference in New Issue
Block a user