save sources list

This commit is contained in:
Aria Moradi 2020-12-25 05:35:21 +03:30
parent 4176300ee2
commit 2f2b0fae7f
5 changed files with 296 additions and 30 deletions

View File

@ -0,0 +1,177 @@
package eu.kanade.tachiyomi.network
//import android.annotation.SuppressLint
//import android.content.Context
//import android.os.Build
//import android.os.Handler
//import android.os.Looper
//import android.webkit.WebSettings
//import android.webkit.WebView
//import android.widget.Toast
//import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.source.online.HttpSource
//import eu.kanade.tachiyomi.util.lang.launchUI
//import eu.kanade.tachiyomi.util.system.WebViewClientCompat
//import eu.kanade.tachiyomi.util.system.WebViewUtil
//import eu.kanade.tachiyomi.util.system.isOutdated
//import eu.kanade.tachiyomi.util.system.setDefaultSettings
//import eu.kanade.tachiyomi.util.system.toast
import okhttp3.Cookie
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
//import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
class CloudflareInterceptor() : Interceptor {
// private val handler = Handler(Looper.getMainLooper())
// private val networkHelper = NetworkHelper()
/**
* When this is called, it initializes the WebView if it wasn't already. We use this to avoid
* blocking the main thread too much. If used too often we could consider moving it to the
* Application class.
*/
// private val initWebView by lazy {
// WebSettings.getDefaultUserAgent(context)
// }
@Synchronized
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
return chain.proceed(originalRequest)
// if (!WebViewUtil.supportsWebView(context)) {
// launchUI {
// context.toast(R.string.information_webview_required, Toast.LENGTH_LONG)
// }
// return chain.proceed(originalRequest)
// }
//
// initWebView
//
// val response = chain.proceed(originalRequest)
//
// // Check if Cloudflare anti-bot is on
// if (response.code != 503 || response.header("Server") !in SERVER_CHECK) {
// return response
// }
//
// try {
// response.close()
// networkHelper.cookieManager.remove(originalRequest.url, COOKIE_NAMES, 0)
// val oldCookie = networkHelper.cookieManager.get(originalRequest.url)
// .firstOrNull { it.name == "cf_clearance" }
// resolveWithWebView(originalRequest, oldCookie)
//
// return chain.proceed(originalRequest)
// } catch (e: Exception) {
// // Because OkHttp's enqueue only handles IOExceptions, wrap the exception so that
// // we don't crash the entire app
// throw IOException(e)
// }
}
//
//// @SuppressLint("SetJavaScriptEnabled")
// private fun resolveWithWebView(request: Request, oldCookie: Cookie?) {
// // We need to lock this thread until the WebView finds the challenge solution url, because
// // OkHttp doesn't support asynchronous interceptors.
// val latch = CountDownLatch(1)
//
// var webView: WebView? = null
//
// var challengeFound = false
// var cloudflareBypassed = false
// var isWebViewOutdated = false
//
// val origRequestUrl = request.url.toString()
// val headers = request.headers.toMultimap().mapValues { it.value.getOrNull(0) ?: "" }.toMutableMap()
// headers["X-Requested-With"] = WebViewUtil.REQUESTED_WITH
//
// handler.post {
// val webview = WebView(context)
// webView = webview
// webview.setDefaultSettings()
//
// // Avoid sending empty User-Agent, Chromium WebView will reset to default if empty
// webview.settings.userAgentString = request.header("User-Agent")
// ?: HttpSource.DEFAULT_USERAGENT
//
// webview.webViewClient = object : WebViewClientCompat() {
// override fun onPageFinished(view: WebView, url: String) {
// fun isCloudFlareBypassed(): Boolean {
// return networkHelper.cookieManager.get(origRequestUrl.toHttpUrl())
// .firstOrNull { it.name == "cf_clearance" }
// .let { it != null && it != oldCookie }
// }
//
// if (isCloudFlareBypassed()) {
// cloudflareBypassed = true
// latch.countDown()
// }
//
// // HTTP error codes are only received since M
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
// url == origRequestUrl && !challengeFound
// ) {
// // The first request didn't return the challenge, abort.
// latch.countDown()
// }
// }
//
// override fun onReceivedErrorCompat(
// view: WebView,
// errorCode: Int,
// description: String?,
// failingUrl: String,
// isMainFrame: Boolean
// ) {
// if (isMainFrame) {
// if (errorCode == 503) {
// // Found the Cloudflare challenge page.
// challengeFound = true
// } else {
// // Unlock thread, the challenge wasn't found.
// latch.countDown()
// }
// }
// }
// }
//
// webView?.loadUrl(origRequestUrl, headers)
// }
//
// // Wait a reasonable amount of time to retrieve the solution. The minimum should be
// // around 4 seconds but it can take more due to slow networks or server issues.
// latch.await(12, TimeUnit.SECONDS)
//
// handler.post {
// if (!cloudflareBypassed) {
// isWebViewOutdated = webView?.isOutdated() == true
// }
//
// webView?.stopLoading()
// webView?.destroy()
// }
//
// // Throw exception if we failed to bypass Cloudflare
// if (!cloudflareBypassed) {
// // Prompt user to update WebView if it seems too outdated
// if (isWebViewOutdated) {
// context.toast(R.string.information_webview_outdated, Toast.LENGTH_LONG)
// }
//
// throw Exception(context.getString(R.string.information_cloudflare_bypass_failure))
// }
// }
//
// companion object {
// private val SERVER_CHECK = arrayOf("cloudflare-nginx", "cloudflare")
// private val COOKIE_NAMES = listOf("__cfduid", "cf_clearance")
// }
}

View File

@ -62,9 +62,9 @@ class NetworkHelper() {
builder.build() builder.build()
} }
// val cloudflareClient by lazy { val cloudflareClient by lazy {
// client.newBuilder() client.newBuilder()
// .addInterceptor(CloudflareInterceptor(context)) .addInterceptor(CloudflareInterceptor())
// .build() .build()
// } }
} }

View File

@ -4,12 +4,12 @@ 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.extension.model.Extension
import eu.kanade.tachiyomi.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceFactory
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 ir.armor.tachidesk.database.makeDataBaseTables import ir.armor.tachidesk.database.makeDataBaseTables
import ir.armor.tachidesk.database.model.ExtensionDataClass import ir.armor.tachidesk.database.model.*
import ir.armor.tachidesk.database.model.ExtensionsTable
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import okhttp3.Request import okhttp3.Request
import okio.buffer import okio.buffer
@ -144,7 +144,7 @@ class Main {
} }
fun downloadApk(apkName: String): Int { fun downloadApk(apkName: String): Int {
val extension = getExtensionList(true).first { it.apkName == apkName } val extensionRecord = getExtensionList(true).first { it.apkName == apkName }
val fileNameWithoutType = apkName.substringBefore(".apk") val fileNameWithoutType = apkName.substringBefore(".apk")
val dirPathWithoutType = "${Config.extensionsRoot}/$apkName" val dirPathWithoutType = "${Config.extensionsRoot}/$apkName"
@ -153,7 +153,7 @@ class Main {
if (!File(dexPath).exists()) { if (!File(dexPath).exists()) {
runBlocking { runBlocking {
val api = ExtensionGithubApi() val api = ExtensionGithubApi()
val apkToDownload = api.getApkUrl(extension) val apkToDownload = api.getApkUrl(extensionRecord)
val apkFilePath = "$dirPathWithoutType.apk" val apkFilePath = "$dirPathWithoutType.apk"
val jarFilePath = "$dirPathWithoutType.jar" val jarFilePath = "$dirPathWithoutType.jar"
@ -164,14 +164,67 @@ class Main {
val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath) val className: String = APKExtractor.extract_dex_and_read_className(apkFilePath, dexFilePath)
println(className)
// dex -> jar // dex -> jar
Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force") Dex2jarCmd.main(dexFilePath, "-o", jarFilePath, "--force")
File(apkFilePath).delete() File(apkFilePath).delete()
// update sources of the extension
val child = URLClassLoader(arrayOf<URL>(URL("file:$jarFilePath")), this::class.java.classLoader)
val classToLoad = Class.forName(className, true, child)
val instance = classToLoad.newInstance()
val extensionId = transaction {
return@transaction ExtensionsTable.select { ExtensionsTable.name eq extensionRecord.name }.first()[ExtensionsTable.id]
}
if (instance is HttpSource) {// single source
val httpSource = instance as HttpSource
transaction {
// SourceEntity.new {
// sourceId = httpSource.id
// name = httpSource.name
// this.extension = ExtensionEntity.find { ExtensionsTable.name eq extension.name }.first().id
// }
if (SourcesTable.select { SourcesTable.sourceId eq httpSource.id }.count() == 0L) {
SourcesTable.insert {
it[this.sourceId] = httpSource.id
it[name] = httpSource.name
it[this.lang] = httpSource.lang
it[extension] = extensionId
}
}
// println(httpSource.id)
// println(httpSource.name)
// println()
}
} else { // multi source
val sourceFactory = instance as SourceFactory
transaction {
sourceFactory.createSources().forEachIndexed { index, source ->
val httpSource = source as HttpSource
if (SourcesTable.select { SourcesTable.sourceId eq httpSource.id }.count() == 0L) {
SourcesTable.insert {
it[this.sourceId] = httpSource.id
it[name] = httpSource.name
it[this.lang] = httpSource.lang
it[extension] = extensionId
it[partOfFactorySource] = true
it[positionInFactorySource] = index
}
}
// println(httpSource.id)
// println(httpSource.name)
// println()
}
}
}
// update extension info
transaction { transaction {
ExtensionsTable.update({ ExtensionsTable.name eq extension.name }) { ExtensionsTable.update({ ExtensionsTable.name eq extensionRecord.name }) {
it[installed] = true it[installed] = true
it[classFQName] = className it[classFQName] = className
} }
@ -179,8 +232,7 @@ class Main {
} }
return 201 // we downloaded successfully return 201 // we downloaded successfully
} } else {
else {
return 302 return 302
} }
} }

View File

@ -31,7 +31,22 @@ data class ExtensionDataClass(
val lang: String, val lang: String,
val isNsfw: Boolean, val isNsfw: Boolean,
val apkName: String, val apkName: String,
val iconUrl : String, val iconUrl: String,
val installed: Boolean, val installed: Boolean,
val classFQName: String, val classFQName: String,
) )
class ExtensionEntity(id: EntityID<Int>) : IntEntity(id) {
companion object : IntEntityClass<ExtensionEntity>(ExtensionsTable)
var name by ExtensionsTable.name
var pkgName by ExtensionsTable.pkgName
var versionName by ExtensionsTable.versionName
var versionCode by ExtensionsTable.versionCode
var lang by ExtensionsTable.lang
var isNsfw by ExtensionsTable.isNsfw
var apkName by ExtensionsTable.apkName
var iconUrl by ExtensionsTable.iconUrl
var installed by ExtensionsTable.installed
var classFQName by ExtensionsTable.classFQName
}

View File

@ -1,26 +1,48 @@
package ir.armor.tachidesk.database.model package ir.armor.tachidesk.database.model
import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.*
import org.jetbrains.exposed.dao.IntEntity
import org.jetbrains.exposed.dao.IntEntityClass
import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.EntityID
import org.jetbrains.exposed.dao.id.IdTable
import org.jetbrains.exposed.dao.id.IntIdTable
import org.jetbrains.exposed.sql.Column import org.jetbrains.exposed.sql.Column
import org.jetbrains.exposed.sql.Table import org.jetbrains.exposed.sql.Table
object SourcesTable : Table() { object SourcesTable : IntIdTable() {
val id: Column<Long> = long("id") val sourceId = long("source_id")
val name: Column<String> = varchar("name", 128) val name= varchar("name", 128)
val lang= varchar("lang", 5)
val extension = reference("extension", ExtensionsTable) val extension = reference("extension", ExtensionsTable)
override val primaryKey = PrimaryKey(id) val partOfFactorySource = bool("part_of_factory_source").default(false)
val positionInFactorySource = integer("position_in_factory_source").nullable()
} }
//class Source : Entity() { class SourceEntity(id: EntityID<Int>) : IntEntity(id) {
// companion object : Entity<Source>(SourcesTable) companion object : IntEntityClass<SourceEntity>(SourcesTable)
var sourceId by SourcesTable.sourceId
var name by SourcesTable.name
var lang by SourcesTable.lang
var extension by ExtensionEntity referencedOn SourcesTable.extension
var partOfFactorySource by SourcesTable.partOfFactorySource
var positionInFactorySource by SourcesTable.positionInFactorySource
}
//object SourcesTable : IdTable<Long>() {
// override val id = long("id").entityId()
// val name= varchar("name", 128)
// val extension = reference("extension", ExtensionsTable)
// val partOfFactorySource = bool("part_of_factory_source").default(false)
// val positionInFactorySource = integer("position_in_factory_source").nullable()
// //
// val name by ExtensionsTable.name // override val primaryKey = PrimaryKey(id)
// val pkgName by ExtensionsTable.pkgName //}
// val versionName by ExtensionsTable.versionName //
// val versionCode by ExtensionsTable.versionCode //class SourceEntity(id: EntityID<Long>) : LongEntity(id) {
// val lang by ExtensionsTable.lang // companion object : LongEntityClass<SourceEntity>(SourcesTable)
// val isNsfw by ExtensionsTable.isNsfw //
// var name by SourcesTable.name
// var extension by SourcesTable.extension
// var partOfFactorySource by SourcesTable.partOfFactorySource
// var positionInFactorySource by SourcesTable.positionInFactorySource
//} //}