mirror of
https://github.com/Suwayomi/Suwayomi-Server.git
synced 2026-07-04 11:24:35 -05:00
apk installation
This commit is contained in:
@@ -7,6 +7,7 @@ import com.github.salomonbrys.kotson.int
|
|||||||
import eu.kanade.tachiyomi.extension.model.Extension
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||||
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
import eu.kanade.tachiyomi.extension.util.ExtensionLoader
|
||||||
|
import ir.armor.tachidesk.database.model.ExtensionDataClass
|
||||||
//import kotlinx.coroutines.Dispatchers
|
//import kotlinx.coroutines.Dispatchers
|
||||||
//import kotlinx.coroutines.withContext
|
//import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.JsonArray
|
import kotlinx.serialization.json.JsonArray
|
||||||
@@ -75,6 +76,10 @@ internal class ExtensionGithubApi {
|
|||||||
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getApkUrl(extension: ExtensionDataClass): String {
|
||||||
|
return "$REPO_URL_PREFIX/apk/${extension.apkName}"
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val BASE_URL = "https://raw.githubusercontent.com/"
|
const val BASE_URL = "https://raw.githubusercontent.com/"
|
||||||
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo"
|
const val REPO_URL_PREFIX = "${BASE_URL}inorichi/tachiyomi-extensions/repo"
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ import net.harawata.appdirs.AppDirsFactory
|
|||||||
|
|
||||||
object Config {
|
object Config {
|
||||||
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk",null, null)
|
val dataRoot = AppDirsFactory.getInstance().getUserDataDir("Tachidesk",null, null)
|
||||||
|
val extensionsRoot = "$dataRoot/extensions"
|
||||||
}
|
}
|
||||||
@@ -2,32 +2,31 @@ package ir.armor.tachidesk
|
|||||||
|
|
||||||
import com.googlecode.dex2jar.tools.Dex2jarCmd
|
import com.googlecode.dex2jar.tools.Dex2jarCmd
|
||||||
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
import eu.kanade.tachiyomi.extension.api.ExtensionGithubApi
|
||||||
|
import eu.kanade.tachiyomi.extension.model.Extension
|
||||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import io.javalin.Javalin
|
import io.javalin.Javalin
|
||||||
import io.javalin.http.Context
|
|
||||||
import ir.armor.tachidesk.database.DBMangaer
|
|
||||||
import ir.armor.tachidesk.database.makeDataBaseTables
|
import ir.armor.tachidesk.database.makeDataBaseTables
|
||||||
|
import ir.armor.tachidesk.database.model.ExtensionDataClass
|
||||||
import ir.armor.tachidesk.database.model.ExtensionsTable
|
import ir.armor.tachidesk.database.model.ExtensionsTable
|
||||||
import ir.armor.tachidesk.database.model.SourcesTable
|
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import net.harawata.appdirs.AppDirsFactory
|
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okio.buffer
|
import okio.buffer
|
||||||
import okio.sink
|
import okio.sink
|
||||||
import org.jetbrains.exposed.sql.Database
|
import org.jetbrains.exposed.sql.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.net.URLClassLoader
|
import java.net.URLClassLoader
|
||||||
import org.jetbrains.exposed.sql.SchemaUtils
|
|
||||||
import org.jetbrains.exposed.sql.transactions.transaction
|
import org.jetbrains.exposed.sql.transactions.transaction
|
||||||
|
|
||||||
|
|
||||||
class Main {
|
class Main {
|
||||||
companion object {
|
companion object {
|
||||||
|
var lastExtensionCheck: Long = 0
|
||||||
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun downloadAPK(url: String, apkPath: String){
|
fun downloadAPKFile(url: String, apkPath: String) {
|
||||||
val request = Request.Builder().url(url).build()
|
val request = Request.Builder().url(url).build()
|
||||||
val response = NetworkHelper().client.newCall(request).execute();
|
val response = NetworkHelper().client.newCall(request).execute();
|
||||||
|
|
||||||
@@ -38,9 +37,8 @@ class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun testExtensionExecution(){
|
fun testExtensionExecution() {
|
||||||
val contentRoot = Config.dataRoot + "/extensions"
|
File(Config.extensionsRoot).mkdirs()
|
||||||
File(contentRoot).mkdirs()
|
|
||||||
var sourcePkg = ""
|
var sourcePkg = ""
|
||||||
|
|
||||||
// get list of extensions
|
// get list of extensions
|
||||||
@@ -55,13 +53,13 @@ class Main {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val apkFileName = apkToDownload.split("/").last()
|
val apkFileName = apkToDownload.split("/").last()
|
||||||
val apkFilePath = "$contentRoot/$apkFileName"
|
val apkFilePath = "${Config.extensionsRoot}/$apkFileName"
|
||||||
val zipDirPath = apkFilePath.substringBefore(".apk")
|
val zipDirPath = apkFilePath.substringBefore(".apk")
|
||||||
val jarFilePath = "$zipDirPath.jar"
|
val jarFilePath = "$zipDirPath.jar"
|
||||||
val dexFilePath = "$zipDirPath.dex"
|
val dexFilePath = "$zipDirPath.dex"
|
||||||
|
|
||||||
// download apk file
|
// download apk file
|
||||||
downloadAPK(apkToDownload, apkFilePath)
|
downloadAPKFile(apkToDownload, apkFilePath)
|
||||||
|
|
||||||
|
|
||||||
val className = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
val className = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
||||||
@@ -76,40 +74,144 @@ class Main {
|
|||||||
mangasPage.mangas.forEach {
|
mangasPage.mangas.forEach {
|
||||||
println(it.title)
|
println(it.title)
|
||||||
}
|
}
|
||||||
// exitProcess(0)
|
}
|
||||||
|
|
||||||
|
fun extensionDatabaseIsEmtpy(): Boolean {
|
||||||
|
return transaction {
|
||||||
|
return@transaction ExtensionsTable.selectAll().count() == 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getExtensionList(offline: Boolean = false): List<ExtensionDataClass> {
|
||||||
|
// update if 60 seconds has passed or requested offline and database is empty
|
||||||
|
if (lastExtensionCheck + 60 * 1000 < System.currentTimeMillis() || (offline && extensionDatabaseIsEmtpy())) {
|
||||||
|
println("Getting extensions list from the internet")
|
||||||
|
lastExtensionCheck = System.currentTimeMillis()
|
||||||
|
var foundExtensions: List<Extension.Available>
|
||||||
|
runBlocking {
|
||||||
|
val api = ExtensionGithubApi()
|
||||||
|
foundExtensions = api.findExtensions()
|
||||||
|
transaction {
|
||||||
|
foundExtensions.forEach { foundExtension ->
|
||||||
|
val extensionRecord = ExtensionsTable.select { ExtensionsTable.name eq foundExtension.name }.firstOrNull()
|
||||||
|
if (extensionRecord != null) {
|
||||||
|
// update the record
|
||||||
|
ExtensionsTable.update({ ExtensionsTable.name eq foundExtension.name }) {
|
||||||
|
it[name] = foundExtension.name
|
||||||
|
it[pkgName] = foundExtension.pkgName
|
||||||
|
it[versionName] = foundExtension.versionName
|
||||||
|
it[versionCode] = foundExtension.versionCode
|
||||||
|
it[lang] = foundExtension.lang
|
||||||
|
it[isNsfw] = foundExtension.isNsfw
|
||||||
|
it[apkName] = foundExtension.apkName
|
||||||
|
it[iconUrl] = foundExtension.iconUrl
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// insert new record
|
||||||
|
ExtensionsTable.insert {
|
||||||
|
it[name] = foundExtension.name
|
||||||
|
it[pkgName] = foundExtension.pkgName
|
||||||
|
it[versionName] = foundExtension.versionName
|
||||||
|
it[versionCode] = foundExtension.versionCode
|
||||||
|
it[lang] = foundExtension.lang
|
||||||
|
it[isNsfw] = foundExtension.isNsfw
|
||||||
|
it[apkName] = foundExtension.apkName
|
||||||
|
it[iconUrl] = foundExtension.iconUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return transaction {
|
||||||
|
return@transaction ExtensionsTable.selectAll().map {
|
||||||
|
ExtensionDataClass(
|
||||||
|
it[ExtensionsTable.name],
|
||||||
|
it[ExtensionsTable.pkgName],
|
||||||
|
it[ExtensionsTable.versionName],
|
||||||
|
it[ExtensionsTable.versionCode],
|
||||||
|
it[ExtensionsTable.lang],
|
||||||
|
it[ExtensionsTable.isNsfw],
|
||||||
|
it[ExtensionsTable.apkName],
|
||||||
|
it[ExtensionsTable.iconUrl],
|
||||||
|
it[ExtensionsTable.installed],
|
||||||
|
it[ExtensionsTable.classFQName]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun downloadApk(apkName: String): Int {
|
||||||
|
val extension = getExtensionList(true).first { it.apkName == apkName }
|
||||||
|
val fileNameWithoutType = apkName.substringBefore(".apk")
|
||||||
|
val dirPathWithoutType = "${Config.extensionsRoot}/$apkName"
|
||||||
|
|
||||||
|
// check if we don't have the dex file already downloaded
|
||||||
|
val dexPath = "${Config.extensionsRoot}/$fileNameWithoutType.jar"
|
||||||
|
if (!File(dexPath).exists()) {
|
||||||
|
runBlocking {
|
||||||
|
val api = ExtensionGithubApi()
|
||||||
|
val apkToDownload = api.getApkUrl(extension)
|
||||||
|
|
||||||
|
val apkFilePath = "$dirPathWithoutType.apk"
|
||||||
|
val jarFilePath = "$dirPathWithoutType.jar"
|
||||||
|
val dexFilePath = "$dirPathWithoutType.dex"
|
||||||
|
|
||||||
|
// download apk file
|
||||||
|
downloadAPKFile(apkToDownload, apkFilePath)
|
||||||
|
|
||||||
|
|
||||||
|
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
|
||||||
|
|
||||||
|
// dex -> jar
|
||||||
|
Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force")
|
||||||
|
|
||||||
|
File(apkFilePath).delete()
|
||||||
|
|
||||||
|
transaction {
|
||||||
|
ExtensionsTable.update({ ExtensionsTable.name eq extension.name }) {
|
||||||
|
it[installed] = true
|
||||||
|
it[classFQName] = className
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return 201 // we downloaded successfully
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 302
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
// make sure data everything we need exists
|
// make sure everything we need exists
|
||||||
File(Config.dataRoot).mkdirs()
|
File(Config.dataRoot).mkdirs()
|
||||||
|
File(Config.extensionsRoot).mkdirs()
|
||||||
makeDataBaseTables()
|
makeDataBaseTables()
|
||||||
|
|
||||||
|
|
||||||
// val app = Javalin.create().start(4567)
|
val app = Javalin.create().start(4567)
|
||||||
//
|
|
||||||
// app.before() { ctx ->
|
app.before() { ctx ->
|
||||||
// ctx.header("Access-Control-Allow-Origin", "*")
|
ctx.header("Access-Control-Allow-Origin", "*") // allow the client which is running on another port
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// app.get("/api/v1/extensions") { ctx ->
|
app.get("/api/v1/extensions") { ctx ->
|
||||||
// runBlocking {
|
ctx.json(getExtensionList())
|
||||||
// val api = ExtensionGithubApi()
|
}
|
||||||
// val sources = api.findExtensions()
|
|
||||||
// ctx.json(sources)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// app.get("/api/v1/extensions/install/:extensionURL") { ctx ->
|
|
||||||
// ctx.pathParam("extensionURL")
|
|
||||||
// })
|
|
||||||
|
|
||||||
|
|
||||||
// ExtensionTable.new {
|
app.get("/api/v1/extensions/install/:apkName") { ctx ->
|
||||||
// name = "khar"
|
val apkName = ctx.pathParam("apkName")
|
||||||
// pkgName = "eu.khar"
|
ctx.status(
|
||||||
// }
|
downloadApk(apkName)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,16 +15,23 @@ object ExtensionsTable : IntIdTable() {
|
|||||||
val versionName = varchar("version_name", 16)
|
val versionName = varchar("version_name", 16)
|
||||||
val versionCode = integer("version_code")
|
val versionCode = integer("version_code")
|
||||||
val lang = varchar("lang", 5)
|
val lang = varchar("lang", 5)
|
||||||
val isNsfw = bool("is_nsfw")
|
val isNsfw = bool("is_nsfw")
|
||||||
|
val apkName = varchar("apk_name", 1024)
|
||||||
|
val iconUrl = varchar("icon_url", 2048)
|
||||||
|
|
||||||
|
val installed = bool("installed").default(false)
|
||||||
|
val classFQName = varchar("class_name", 256).default("") // fully qualified name
|
||||||
}
|
}
|
||||||
|
|
||||||
//class Extension(id: EntityID<Int>) : IntEntity(id) {
|
data class ExtensionDataClass(
|
||||||
// companion object : IntEntityClass<Extension>(ExtensionsTable)
|
val name: String,
|
||||||
//
|
val pkgName: String,
|
||||||
// val name by ExtensionsTable.name
|
val versionName: String,
|
||||||
// val pkgName by ExtensionsTable.pkgName
|
val versionCode: Int,
|
||||||
// val versionName by ExtensionsTable.versionName
|
val lang: String,
|
||||||
// val versionCode by ExtensionsTable.versionCode
|
val isNsfw: Boolean,
|
||||||
// val lang by ExtensionsTable.lang
|
val apkName: String,
|
||||||
// val isNsfw by ExtensionsTable.isNsfw
|
val iconUrl : String,
|
||||||
//}
|
val installed: Boolean,
|
||||||
|
val classFQName: String,
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
package ir.armor.tachidesk.extension
|
|
||||||
|
|
||||||
class Extension {
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user