can work with anime extensions successfully

This commit is contained in:
Aria Moradi
2021-05-27 05:13:01 +04:30
parent 994ae97256
commit c17e3bd04f
33 changed files with 546 additions and 206 deletions
@@ -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>()