From 9e308025c377de0fa341f7fea3fd7f1790fc5f43 Mon Sep 17 00:00:00 2001 From: Aria Moradi Date: Wed, 3 Feb 2021 22:07:39 +0330 Subject: [PATCH] some initial code for MangaDex login --- .../tachiyomi/network/MemoryCookieJar.kt | 72 ++++++++++++++ .../kanade/tachiyomi/network/NetworkHelper.kt | 4 +- .../tachiyomi/source/online/HttpSource.kt | 2 +- .../ir/armor/tachidesk/util/MangaDexHelper.kt | 93 +++++++++++++++++++ 4 files changed, 168 insertions(+), 3 deletions(-) create mode 100644 server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt create mode 100644 server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt new file mode 100644 index 0000000..3ea7a03 --- /dev/null +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/MemoryCookieJar.kt @@ -0,0 +1,72 @@ +package eu.kanade.tachiyomi.network + +import okhttp3.Cookie +import okhttp3.CookieJar +import okhttp3.HttpUrl + +class MemoryCookieJar : CookieJar { + private val cache = mutableSetOf() + + @Synchronized + override fun loadForRequest(url: HttpUrl): List { + val cookiesToRemove = mutableSetOf() + val validCookies = mutableSetOf() + + cache.forEach { cookie -> + if (cookie.isExpired()) { + cookiesToRemove.add(cookie) + } else if (cookie.matches(url)) { + validCookies.add(cookie) + } + } + + cache.removeAll(cookiesToRemove) + + return validCookies.toList().map(WrappedCookie::unwrap) + } + + @Synchronized + override fun saveFromResponse(url: HttpUrl, cookies: List) { + val cookiesToAdd = cookies.map { WrappedCookie.wrap(it) } + + cache.removeAll(cookiesToAdd) + cache.addAll(cookiesToAdd) + } + + @Synchronized + fun clear() { + cache.clear() + } +} + +class WrappedCookie private constructor(val cookie: Cookie) { + fun unwrap() = cookie + + fun isExpired() = cookie.expiresAt < System.currentTimeMillis() + + fun matches(url: HttpUrl) = cookie.matches(url) + + override fun equals(other: Any?): Boolean { + if (other !is WrappedCookie) return false + + return other.cookie.name == cookie.name && + other.cookie.domain == cookie.domain && + other.cookie.path == cookie.path && + other.cookie.secure == cookie.secure && + other.cookie.hostOnly == cookie.hostOnly + } + + override fun hashCode(): Int { + var hash = 17 + hash = 31 * hash + cookie.name.hashCode() + hash = 31 * hash + cookie.domain.hashCode() + hash = 31 * hash + cookie.path.hashCode() + hash = 31 * hash + if (cookie.secure) 0 else 1 + hash = 31 * hash + if (cookie.hostOnly) 0 else 1 + return hash + } + + companion object { + fun wrap(cookie: Cookie) = WrappedCookie(cookie) + } +} \ No newline at end of file diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt index 22a03b3..df1e07e 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/network/NetworkHelper.kt @@ -19,11 +19,11 @@ class NetworkHelper(context: Context) { private val cacheSize = 5L * 1024 * 1024 // 5 MiB -// val cookieManager = AndroidCookieJar() + val cookieManager = MemoryCookieJar() val client by lazy { val builder = OkHttpClient.Builder() -// .cookieJar(cookieManager) + .cookieJar(cookieManager) // .cache(Cache(cacheDir, cacheSize)) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) diff --git a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt index a613f13..7379989 100644 --- a/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt +++ b/server/src/main/kotlin/eu/kanade/tachiyomi/source/online/HttpSource.kt @@ -29,7 +29,7 @@ abstract class HttpSource : CatalogueSource { /** * Network service. */ - protected val network: NetworkHelper by injectLazy() + val network: NetworkHelper by injectLazy() // /** // * Preferences that a source may need. diff --git a/server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt new file mode 100644 index 0000000..061ab0b --- /dev/null +++ b/server/src/main/kotlin/ir/armor/tachidesk/util/MangaDexHelper.kt @@ -0,0 +1,93 @@ +package ir.armor.tachidesk.util + +import com.android.dx.util.ExceptionWithContext.withContext +import eu.kanade.tachiyomi.network.POST +import eu.kanade.tachiyomi.source.online.HttpSource +import kotlinx.coroutines.Dispatchers +import okhttp3.FormBody +import okhttp3.OkHttpClient +import java.net.URLEncoder + +class MangaDexHelper(private val mangaDexSource: HttpSource) { + + private fun clientBuilder(): OkHttpClient = clientBuilder(0) + + private fun clientBuilder( + r18Toggle: Int, + okHttpClient: OkHttpClient = mangaDexSource.network.client + ): OkHttpClient = okHttpClient.newBuilder() + .addNetworkInterceptor { chain -> + val originalCookies = chain.request().header("Cookie") ?: "" + val newReq = chain + .request() + .newBuilder() + .header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle)}") + .build() + chain.proceed(newReq) + }.build() + + private fun cookiesHeader(r18Toggle: Int): String { + val cookies = mutableMapOf() + cookies["mangadex_h_toggle"] = r18Toggle.toString() + return buildCookies(cookies) + } + + private fun buildCookies(cookies: Map) = + cookies.entries.joinToString(separator = "; ", postfix = ";") { + "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" + } + +// fun isLogged(): Boolean { +// val httpUrl = mangaDexSource.baseUrl.toHttpUrlOrNull()!! +// return network.cookieManager.get(httpUrl).any { it.name == REMEMBER_ME } +// } + + fun login(username: String, password: String, twoFactorCode: String = ""): Boolean { + val formBody = FormBody.Builder() + .add("login_username", username) + .add("login_password", password) + .add("no_js", "1") + .add("remember_me", "1") + + twoFactorCode.let { + formBody.add("two_factor", it) + } + + val response = clientBuilder().newCall( + POST( + "${mangaDexSource.baseUrl}/ajax/actions.ajax.php?function=login", + mangaDexSource.headers, + formBody.build() + ) + ).execute() + return response.body!!.string().isEmpty() + } +// +// fun logout(): Boolean { +// return withContext(Dispatchers.IO) { +// // https://mangadex.org/ajax/actions.ajax.php?function=logout +// val httpUrl = baseUrl.toHttpUrlOrNull()!! +// val listOfDexCookies = network.cookieManager.get(httpUrl) +// val cookie = listOfDexCookies.find { it.name == REMEMBER_ME } +// val token = cookie?.value +// if (token.isNullOrEmpty()) { +// return@withContext true +// } +// val result = clientBuilder().newCall( +// POSTWithCookie( +// "$baseUrl/ajax/actions.ajax.php?function=logout", +// REMEMBER_ME, +// token, +// headers +// ) +// ).execute() +// val resultStr = result.body!!.string() +// if (resultStr.contains("success", true)) { +// network.cookieManager.remove(httpUrl) +// return@withContext true +// } +// +// false +// } +// } +} \ No newline at end of file