Ru: Remanga. Fix login from cookie and webview due to google captcha on login api request (#10690)
This commit is contained in:
parent
81e98ae6ae
commit
05c2a47a17
|
@ -6,7 +6,7 @@ ext {
|
||||||
extName = 'Remanga'
|
extName = 'Remanga'
|
||||||
pkgNameSuffix = 'ru.remanga'
|
pkgNameSuffix = 'ru.remanga'
|
||||||
extClass = '.Remanga'
|
extClass = '.Remanga'
|
||||||
extVersionCode = 43
|
extVersionCode = 44
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
|
@ -16,12 +16,10 @@ import android.annotation.TargetApi
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.text.InputType
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
|
import eu.kanade.tachiyomi.lib.dataimage.DataImageInterceptor
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
|
||||||
import eu.kanade.tachiyomi.network.asObservable
|
import eu.kanade.tachiyomi.network.asObservable
|
||||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
|
@ -36,27 +34,26 @@ import kotlinx.serialization.SerializationException
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
|
||||||
import kotlinx.serialization.json.decodeFromJsonElement
|
import kotlinx.serialization.json.decodeFromJsonElement
|
||||||
import kotlinx.serialization.json.put
|
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
import okhttp3.HttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.Jsoup
|
import org.jsoup.Jsoup
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.net.URLDecoder
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import kotlin.math.absoluteValue
|
import kotlin.math.absoluteValue
|
||||||
import kotlin.random.Random
|
import kotlin.random.Random
|
||||||
|
|
||||||
class Remanga : ConfigurableSource, HttpSource() {
|
class Remanga : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
override val name = "Remanga"
|
override val name = "Remanga"
|
||||||
|
@ -69,13 +66,13 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
private val baseOrig: String = "https://api.remanga.org"
|
private val baseOrig: String = "https://api.remanga.org"
|
||||||
private val baseMirr: String = "https://api.xn--80aaig9ahr.xn--c1avg" // https://реманга.орг
|
private val baseMirr: String = "https://api.xn--80aaig9ahr.xn--c1avg" // https://реманга.орг
|
||||||
|
private val baseCookieUrl: HttpUrl = "https://remanga.org".toHttpUrl()
|
||||||
private val domain: String? = preferences.getString(DOMAIN_PREF, baseOrig)
|
private val domain: String? = preferences.getString(DOMAIN_PREF, baseOrig)
|
||||||
|
|
||||||
override val baseUrl = domain.toString()
|
override val baseUrl = domain.toString()
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
|
||||||
private var token: String = ""
|
|
||||||
|
|
||||||
private val userAgentRandomizer = " ${Random.nextInt().absoluteValue}"
|
private val userAgentRandomizer = " ${Random.nextInt().absoluteValue}"
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
|
@ -85,15 +82,17 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
private fun authIntercept(chain: Interceptor.Chain): Response {
|
private fun authIntercept(chain: Interceptor.Chain): Response {
|
||||||
val request = chain.request()
|
val request = chain.request()
|
||||||
if (username.isEmpty() or password.isEmpty()) {
|
|
||||||
return chain.proceed(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token.isEmpty()) {
|
val cookies = client.cookieJar.loadForRequest(baseCookieUrl)
|
||||||
token = this.login(chain, username, password)
|
val authCookie = cookies
|
||||||
}
|
.firstOrNull { cookie -> cookie.name == USER_COOKIE_NAME }
|
||||||
|
?.let { cookie -> URLDecoder.decode(cookie.value, "UTF-8") }
|
||||||
|
?.let { jsonString -> json.decodeFromString<UserDto>(jsonString) }
|
||||||
|
?: return chain.proceed(request)
|
||||||
|
|
||||||
|
USER_ID = authCookie.id.toString()
|
||||||
val authRequest = request.newBuilder()
|
val authRequest = request.newBuilder()
|
||||||
.addHeader("Authorization", "bearer $token")
|
.addHeader("Authorization", "bearer ${authCookie.access_token}")
|
||||||
.build()
|
.build()
|
||||||
return chain.proceed(authRequest)
|
return chain.proceed(authRequest)
|
||||||
}
|
}
|
||||||
|
@ -108,21 +107,6 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
|
|
||||||
private var branches = mutableMapOf<String, List<BranchesDto>>()
|
private var branches = mutableMapOf<String, List<BranchesDto>>()
|
||||||
|
|
||||||
private fun login(chain: Interceptor.Chain, username: String, password: String): String {
|
|
||||||
val jsonObject = buildJsonObject {
|
|
||||||
put("user", username)
|
|
||||||
put("password", password)
|
|
||||||
}
|
|
||||||
val body = jsonObject.toString().toRequestBody(MEDIA_TYPE)
|
|
||||||
val response = chain.proceed(POST("$baseUrl/api/users/login/", headers, body))
|
|
||||||
if (response.code >= 400) {
|
|
||||||
throw Exception("Не удалось войти")
|
|
||||||
}
|
|
||||||
val user = json.decodeFromString<SeriesWrapperDto<UserDto>>(response.body!!.string()).content
|
|
||||||
USER_ID = user.id.toString()
|
|
||||||
return user.access_token
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/api/search/catalog/?ordering=-rating&count=$count&page=$page", headers)
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/api/search/catalog/?ordering=-rating&count=$count&page=$page", headers)
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
|
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
|
||||||
|
@ -173,9 +157,9 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
var url = "$baseUrl/api/search/catalog/?page=$page".toHttpUrlOrNull()!!.newBuilder()
|
var url = "$baseUrl/api/search/catalog/?page=$page".toHttpUrl().newBuilder()
|
||||||
if (query.isNotEmpty()) {
|
if (query.isNotEmpty()) {
|
||||||
url = "$baseUrl/api/search/?page=$page".toHttpUrlOrNull()!!.newBuilder()
|
url = "$baseUrl/api/search/?page=$page".toHttpUrl().newBuilder()
|
||||||
url.addQueryParameter("query", query)
|
url.addQueryParameter("query", query)
|
||||||
}
|
}
|
||||||
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
|
||||||
|
@ -215,7 +199,7 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
throw Exception("Пользователь не найден")
|
throw Exception("Пользователь не найден")
|
||||||
}
|
}
|
||||||
val TypeQ = getMyList()[filter.state].id
|
val TypeQ = getMyList()[filter.state].id
|
||||||
val UserProfileUrl = "$baseUrl/api/users/$USER_ID/bookmarks/?type=$TypeQ&page=$page".toHttpUrlOrNull()!!.newBuilder()
|
val UserProfileUrl = "$baseUrl/api/users/$USER_ID/bookmarks/?type=$TypeQ&page=$page".toHttpUrl().newBuilder()
|
||||||
return GET(UserProfileUrl.toString(), headers)
|
return GET(UserProfileUrl.toString(), headers)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -648,32 +632,6 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
MyListUnit("Не интересно ", "5")
|
MyListUnit("Не интересно ", "5")
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun androidx.preference.PreferenceScreen.editTextPreference(title: String, default: String, value: String, isPassword: Boolean = false): androidx.preference.EditTextPreference {
|
|
||||||
return androidx.preference.EditTextPreference(context).apply {
|
|
||||||
key = title
|
|
||||||
this.title = title
|
|
||||||
summary = value
|
|
||||||
this.setDefaultValue(default)
|
|
||||||
dialogTitle = title
|
|
||||||
|
|
||||||
if (isPassword) {
|
|
||||||
if (value.isNotBlank()) { summary = "*****" }
|
|
||||||
setOnBindEditTextListener {
|
|
||||||
it.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_PASSWORD
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
try {
|
|
||||||
val res = preferences.edit().putString(title, newValue as String).commit()
|
|
||||||
Toast.makeText(context, "Перезапустите Tachiyomi, чтобы применить новую настройку.", Toast.LENGTH_LONG).show()
|
|
||||||
res
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
|
override fun setupPreferenceScreen(screen: androidx.preference.PreferenceScreen) {
|
||||||
val domainPref = ListPreference(screen.context).apply {
|
val domainPref = ListPreference(screen.context).apply {
|
||||||
key = DOMAIN_PREF
|
key = DOMAIN_PREF
|
||||||
|
@ -719,26 +677,14 @@ class Remanga : ConfigurableSource, HttpSource() {
|
||||||
screen.addPreference(domainPref)
|
screen.addPreference(domainPref)
|
||||||
screen.addPreference(paidChapterShow)
|
screen.addPreference(paidChapterShow)
|
||||||
screen.addPreference(bookmarksHide)
|
screen.addPreference(bookmarksHide)
|
||||||
screen.addPreference(screen.editTextPreference(USERNAME_TITLE, USERNAME_DEFAULT, username))
|
|
||||||
screen.addPreference(screen.editTextPreference(PASSWORD_TITLE, PASSWORD_DEFAULT, password, true))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getPrefUsername(): String = preferences.getString(USERNAME_TITLE, USERNAME_DEFAULT)!!
|
|
||||||
private fun getPrefPassword(): String = preferences.getString(PASSWORD_TITLE, PASSWORD_DEFAULT)!!
|
|
||||||
|
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
private val username by lazy { getPrefUsername() }
|
|
||||||
private val password by lazy { getPrefPassword() }
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private var USER_ID = ""
|
private var USER_ID = ""
|
||||||
|
|
||||||
private val MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
private const val USER_COOKIE_NAME = "user"
|
||||||
|
|
||||||
private const val USERNAME_TITLE = "Username"
|
|
||||||
private const val USERNAME_DEFAULT = ""
|
|
||||||
private const val PASSWORD_TITLE = "Password"
|
|
||||||
private const val PASSWORD_DEFAULT = ""
|
|
||||||
|
|
||||||
const val PREFIX_SLUG_SEARCH = "slug:"
|
const val PREFIX_SLUG_SEARCH = "slug:"
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue