Manhuarm Fixes (#11342)
* Manhuarm.kt * ManhuarmDto.kt fixed error bad base64 and chapters not loading for some entries * Update Manhuarm.kt Improved nonce extraction by scanning <script> tags for const nonce and adding a fallback value for better reliability. * Manhuarm.kt * Update build.gradle * Update Manhuarm.kt Fixed popular and latest not showing Added more robust Nonce regex Added Custom User Agent feature * Update ManhuarmDto.kt * Update build.gradle Changed BaseUrl * Update Manhuarm.kt Changed Url * Update build.gradle * removed base64 method since it's not used anymore * Updated new ocr data fetch * changed ratelimit
This commit is contained in:
parent
9128e25848
commit
6bf6da4db8
@ -3,7 +3,7 @@ ext {
|
|||||||
extClass = '.ManhuarmFactory'
|
extClass = '.ManhuarmFactory'
|
||||||
themePkg = 'madara'
|
themePkg = 'madara'
|
||||||
baseUrl = 'https://manhuarmmtl.com'
|
baseUrl = 'https://manhuarmmtl.com'
|
||||||
overrideVersionCode = 4
|
overrideVersionCode = 5
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import android.content.SharedPreferences
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.annotation.RequiresApi
|
import androidx.annotation.RequiresApi
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
import androidx.preference.ListPreference
|
import androidx.preference.ListPreference
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
import androidx.preference.PreferenceScreen
|
import androidx.preference.PreferenceScreen
|
||||||
@ -16,19 +17,21 @@ import eu.kanade.tachiyomi.lib.i18n.Intl
|
|||||||
import eu.kanade.tachiyomi.lib.i18n.Intl.Companion.createDefaultMessageFileName
|
import eu.kanade.tachiyomi.lib.i18n.Intl.Companion.createDefaultMessageFileName
|
||||||
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
|
import eu.kanade.tachiyomi.multisrc.machinetranslations.translator.TranslatorEngine
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import keiyoushi.utils.getPreferencesLazy
|
import keiyoushi.utils.getPreferencesLazy
|
||||||
import keiyoushi.utils.parseAs
|
import keiyoushi.utils.parseAs
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import okhttp3.FormBody
|
import okhttp3.Headers
|
||||||
import okhttp3.Interceptor
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
import java.util.Calendar
|
import java.util.Calendar
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -80,6 +83,10 @@ class Manhuarm(
|
|||||||
get() = preferences.getBoolean(DISABLE_TRANSLATOR_PREF, language.disableTranslator)
|
get() = preferences.getBoolean(DISABLE_TRANSLATOR_PREF, language.disableTranslator)
|
||||||
set(value) = preferences.edit().putBoolean(DISABLE_TRANSLATOR_PREF, value).apply()
|
set(value) = preferences.edit().putBoolean(DISABLE_TRANSLATOR_PREF, value).apply()
|
||||||
|
|
||||||
|
private var customUserAgent: String
|
||||||
|
get() = preferences.getString(CUSTOM_UA_PREF, "")!!
|
||||||
|
set(value) = preferences.edit().putString(CUSTOM_UA_PREF, value).apply()
|
||||||
|
|
||||||
private val i18n = Intl(
|
private val i18n = Intl(
|
||||||
language = language.lang,
|
language = language.lang,
|
||||||
baseLanguage = "en",
|
baseLanguage = "en",
|
||||||
@ -133,7 +140,7 @@ class Manhuarm(
|
|||||||
return network.cloudflareClient.newBuilder()
|
return network.cloudflareClient.newBuilder()
|
||||||
.connectTimeout(1, TimeUnit.MINUTES)
|
.connectTimeout(1, TimeUnit.MINUTES)
|
||||||
.readTimeout(2, TimeUnit.MINUTES)
|
.readTimeout(2, TimeUnit.MINUTES)
|
||||||
.rateLimit(3)
|
.rateLimit(2, 1)
|
||||||
.addInterceptorIf(
|
.addInterceptorIf(
|
||||||
!disableTranslator && language.lang != language.origin,
|
!disableTranslator && language.lang != language.origin,
|
||||||
TranslationInterceptor(settings, translator),
|
TranslationInterceptor(settings, translator),
|
||||||
@ -141,6 +148,15 @@ class Manhuarm(
|
|||||||
.addInterceptor(ComposedImageInterceptor(settings))
|
.addInterceptor(ComposedImageInterceptor(settings))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun headersBuilder(): Headers.Builder {
|
||||||
|
val builder = super.headersBuilder()
|
||||||
|
val ua = customUserAgent.trim()
|
||||||
|
if (ua.isNotEmpty()) {
|
||||||
|
builder.set("User-Agent", ua)
|
||||||
|
}
|
||||||
|
return builder
|
||||||
|
}
|
||||||
|
|
||||||
private fun OkHttpClient.Builder.addInterceptorIf(condition: Boolean, interceptor: Interceptor): OkHttpClient.Builder {
|
private fun OkHttpClient.Builder.addInterceptorIf(condition: Boolean, interceptor: Interceptor): OkHttpClient.Builder {
|
||||||
return this.takeIf { condition.not() } ?: this.addInterceptor(interceptor)
|
return this.takeIf { condition.not() } ?: this.addInterceptor(interceptor)
|
||||||
}
|
}
|
||||||
@ -159,21 +175,14 @@ class Manhuarm(
|
|||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
val pages = super.pageListParse(document)
|
val pages = super.pageListParse(document)
|
||||||
val chapterId = document.selectFirst("#wp-manga-current-chap")!!.attr("data-id")
|
val chapterId = document.selectFirst("#wp-manga-current-chap")!!.attr("data-id")
|
||||||
val nonce = document.selectFirst("#manga-ocr-display-script-js-extra")!!.data().let {
|
|
||||||
NONCE_REGEX.find(it)!!.groupValues.last()
|
|
||||||
}
|
|
||||||
|
|
||||||
val form = FormBody.Builder()
|
val dialog = client.newCall(GET("$baseUrl/wp-content/uploads/ocr-data/$chapterId.json", headers))
|
||||||
.add("action", "get_ocr_data")
|
|
||||||
.add("chapter_id", chapterId)
|
|
||||||
.add("nonce", nonce)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val dialogDto = client.newCall(POST("$baseUrl/wp-admin/admin-ajax.php", headers, form))
|
|
||||||
.execute()
|
.execute()
|
||||||
.parseAs<DialogDto>()
|
.parseAs<List<PageDto>>()
|
||||||
|
|
||||||
val dialog = dialogDto.content.parseAs<List<PageDto>>()
|
if (dialog.isEmpty()) {
|
||||||
|
return pages
|
||||||
|
}
|
||||||
|
|
||||||
return dialog.mapIndexed { index, dto ->
|
return dialog.mapIndexed { index, dto ->
|
||||||
val page = pages.first { it.imageUrl?.contains(dto.imageUrl, true)!! }
|
val page = pages.first { it.imageUrl?.contains(dto.imageUrl, true)!! }
|
||||||
@ -188,14 +197,54 @@ class Manhuarm(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun String.fixJsonFormat(): String {
|
override fun popularMangaRequest(page: Int): okhttp3.Request {
|
||||||
return JSON_FORMAT_REGEX.replace(this) { matchResult ->
|
val url = if (page == 1) {
|
||||||
val content = matchResult.groupValues.last()
|
"$baseUrl/manga/?m_orderby=trending"
|
||||||
val modifiedContent = content.replace("\"", "'")
|
} else {
|
||||||
""""text": "${modifiedContent.trimIndent()}", "box""""
|
"$baseUrl/manga/page/$page/?m_orderby=trending"
|
||||||
}
|
}
|
||||||
|
return GET(url, headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun popularMangaSelector(): String = ".page-item-detail, .manga-card"
|
||||||
|
|
||||||
|
override fun popularMangaFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
val titleEl = element.selectFirst(".post-title a, .manga-title a")
|
||||||
|
val thumbEl = element.selectFirst(".item-thumb img, .manga-thumb img, img")
|
||||||
|
manga.setUrlWithoutDomain(titleEl!!.attr("href"))
|
||||||
|
manga.title = titleEl.text()
|
||||||
|
manga.thumbnail_url = thumbEl?.absUrl("data-src") ?: thumbEl?.absUrl("src")
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector(): String? = "a.next, a.nextpostslink, .pagination a.next, .navigation-ajax #navigation-ajax"
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): okhttp3.Request {
|
||||||
|
val url = if (page == 1) {
|
||||||
|
"$baseUrl/manga/?m_orderby=latest"
|
||||||
|
} else {
|
||||||
|
"$baseUrl/manga/page/$page/?m_orderby=latest"
|
||||||
|
}
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesSelector(): String = ".page-item-detail, .manga-card"
|
||||||
|
|
||||||
|
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||||
|
val manga = SManga.create()
|
||||||
|
val titleEl = element.selectFirst(".manga-title a")
|
||||||
|
?: element.selectFirst(".post-title a, h3.h5 a, .post-title h3 a")
|
||||||
|
val thumbEl = element.selectFirst(".manga-thumb img")
|
||||||
|
?: element.selectFirst(".item-thumb img, img")
|
||||||
|
manga.setUrlWithoutDomain(titleEl!!.attr("href"))
|
||||||
|
manga.title = titleEl.text()
|
||||||
|
manga.thumbnail_url = thumbEl?.absUrl("src")
|
||||||
|
return manga
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesNextPageSelector(): String? = "a.next, a.nextpostslink, .pagination a.next, .navigation-ajax #navigation-ajax"
|
||||||
|
|
||||||
// Prevent bad fragments
|
// Prevent bad fragments
|
||||||
fun String.toFragment(): String = "#${this.replace("#", "*")}"
|
fun String.toFragment(): String = "#${this.replace("#", "*")}"
|
||||||
|
|
||||||
@ -329,6 +378,17 @@ class Manhuarm(
|
|||||||
}
|
}
|
||||||
}.also(screen::addPreference)
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
|
EditTextPreference(screen.context).apply {
|
||||||
|
key = CUSTOM_UA_PREF
|
||||||
|
title = "Custom User-Agent"
|
||||||
|
summary = "Set a custom User-Agent for requests. Leave blank to use the default."
|
||||||
|
setDefaultValue(customUserAgent)
|
||||||
|
setOnPreferenceChange { _, newValue ->
|
||||||
|
customUserAgent = (newValue as String).trim()
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
|
||||||
if (language.target == language.origin) {
|
if (language.target == language.origin) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -391,8 +451,7 @@ class Manhuarm(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
|
val PAGE_REGEX = Regex(".*?\\.(webp|png|jpg|jpeg)#\\[.*?]", RegexOption.IGNORE_CASE)
|
||||||
val JSON_FORMAT_REGEX = """(?:"text":\s+?".*?)([\s\S]*?)(?:",\s+?"box")""".toRegex()
|
val NONCE_REGEX = """(?:const\s+nonce\s*=\s*'|\"nonce\"\s*:\s*\")(.*?)['\"]""".toRegex()
|
||||||
val NONCE_REGEX = """(?:nonce":")([^"]+)""".toRegex()
|
|
||||||
|
|
||||||
const val DEVICE_FONT = "device:"
|
const val DEVICE_FONT = "device:"
|
||||||
private const val FONT_SIZE_PREF = "fontSizePref"
|
private const val FONT_SIZE_PREF = "fontSizePref"
|
||||||
@ -401,6 +460,7 @@ class Manhuarm(
|
|||||||
private const val DISABLE_WORD_BREAK_PREF = "disableWordBreakPref"
|
private const val DISABLE_WORD_BREAK_PREF = "disableWordBreakPref"
|
||||||
private const val DISABLE_TRANSLATOR_PREF = "disableTranslatorPref"
|
private const val DISABLE_TRANSLATOR_PREF = "disableTranslatorPref"
|
||||||
private const val TRANSLATOR_PROVIDER_PREF = "translatorProviderPref"
|
private const val TRANSLATOR_PROVIDER_PREF = "translatorProviderPref"
|
||||||
|
private const val CUSTOM_UA_PREF = "customUserAgentPref"
|
||||||
private const val DEFAULT_FONT_SIZE = "28"
|
private const val DEFAULT_FONT_SIZE = "28"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,5 @@
|
|||||||
package eu.kanade.tachiyomi.extension.all.manhuarm
|
package eu.kanade.tachiyomi.extension.all.manhuarm
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import android.util.Base64
|
|
||||||
import androidx.annotation.RequiresApi
|
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.builtins.ListSerializer
|
import kotlinx.serialization.builtins.ListSerializer
|
||||||
@ -21,23 +18,12 @@ import java.io.IOException
|
|||||||
class PageDto(
|
class PageDto(
|
||||||
@SerialName("image")
|
@SerialName("image")
|
||||||
val imageUrl: String,
|
val imageUrl: String,
|
||||||
|
|
||||||
@SerialName("texts")
|
@SerialName("texts")
|
||||||
@Serializable(with = DialogListSerializer::class)
|
@Serializable(with = DialogListSerializer::class)
|
||||||
val dialogues: List<Dialog> = emptyList(),
|
val dialogues: List<Dialog> = emptyList(),
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
class DialogDto(
|
|
||||||
private val data: String,
|
|
||||||
) {
|
|
||||||
val content: String by lazy {
|
|
||||||
Base64.decode(data, Base64.DEFAULT).toString(Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
@RequiresApi(Build.VERSION_CODES.O)
|
|
||||||
data class Dialog(
|
data class Dialog(
|
||||||
val x: Float,
|
val x: Float,
|
||||||
val y: Float,
|
val y: Float,
|
||||||
@ -47,7 +33,6 @@ data class Dialog(
|
|||||||
val textByLanguage: Map<String, String> = emptyMap(),
|
val textByLanguage: Map<String, String> = emptyMap(),
|
||||||
) {
|
) {
|
||||||
var scale: Float = 1F
|
var scale: Float = 1F
|
||||||
|
|
||||||
val height: Float get() = scale * _height
|
val height: Float get() = scale * _height
|
||||||
val width: Float get() = scale * _width
|
val width: Float get() = scale * _width
|
||||||
|
|
||||||
@ -62,18 +47,34 @@ data class Dialog(
|
|||||||
|
|
||||||
private object DialogListSerializer :
|
private object DialogListSerializer :
|
||||||
JsonTransformingSerializer<List<Dialog>>(ListSerializer(Dialog.serializer())) {
|
JsonTransformingSerializer<List<Dialog>>(ListSerializer(Dialog.serializer())) {
|
||||||
override fun transformDeserialize(element: JsonElement): JsonElement {
|
|
||||||
return JsonArray(
|
|
||||||
element.jsonArray.map { jsonElement ->
|
|
||||||
val coordinates = getCoordinates(jsonElement)
|
|
||||||
val textByLanguage = getDialogs(jsonElement)
|
|
||||||
|
|
||||||
buildJsonObject {
|
override fun transformDeserialize(element: JsonElement): JsonElement {
|
||||||
put("x", coordinates[0])
|
if (element !is JsonArray) {
|
||||||
put("y", coordinates[1])
|
return JsonArray(emptyList())
|
||||||
put("_width", coordinates[2])
|
}
|
||||||
put("_height", coordinates[3])
|
|
||||||
put("textByLanguage", textByLanguage)
|
if (element.jsonArray.isEmpty()) {
|
||||||
|
return JsonArray(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
|
return JsonArray(
|
||||||
|
element.jsonArray.mapNotNull { jsonElement ->
|
||||||
|
try {
|
||||||
|
val coordinates = getCoordinates(jsonElement) ?: return@mapNotNull null
|
||||||
|
val textByLanguage = getDialogs(jsonElement)
|
||||||
|
|
||||||
|
// Validate coordinates array has at least 4 elements
|
||||||
|
if (coordinates.size < 4) return@mapNotNull null
|
||||||
|
|
||||||
|
buildJsonObject {
|
||||||
|
put("x", coordinates[0])
|
||||||
|
put("y", coordinates[1])
|
||||||
|
put("_width", coordinates[2])
|
||||||
|
put("_height", coordinates[3])
|
||||||
|
put("textByLanguage", textByLanguage)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -86,6 +87,7 @@ private object DialogListSerializer :
|
|||||||
?: throw IOException("Dialog box position not found")
|
?: throw IOException("Dialog box position not found")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getDialogs(element: JsonElement): JsonObject {
|
private fun getDialogs(element: JsonElement): JsonObject {
|
||||||
return buildJsonObject {
|
return buildJsonObject {
|
||||||
when (element) {
|
when (element) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user