[RU]ComX adult pages solve captcha (#12219)

* [RU]ComX adult pages solve captcha

* extVersionCode

* LICENSED message

* LICENSED 404

* fix logged-in

* normal/full chapter name

* empty chapter name

* chapter_number & simpleDateFormat companion
This commit is contained in:
Ejan 2022-06-17 07:00:09 +05:00 committed by GitHub
parent a9bd85d728
commit b11102c1bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 78 additions and 19 deletions

View File

@ -6,7 +6,7 @@ ext {
extName = 'Com-X' extName = 'Com-X'
pkgNameSuffix = 'ru.comx' pkgNameSuffix = 'ru.comx'
extClass = '.ComX' extClass = '.ComX'
extVersionCode = 19 extVersionCode = 20
} }
apply from: "$rootDir/common.gradle" apply from: "$rootDir/common.gradle"

View File

@ -1,7 +1,9 @@
package eu.kanade.tachiyomi.extension.ru.comx package eu.kanade.tachiyomi.extension.ru.comx
import android.webkit.CookieManager
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.network.interceptor.rateLimit import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
@ -14,20 +16,24 @@ import eu.kanade.tachiyomi.util.asJsoup
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.float
import kotlinx.serialization.json.jsonArray import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie
import okhttp3.CookieJar
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale import java.util.Locale
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -42,11 +48,49 @@ class ComX : ParsedHttpSource() {
override val lang = "ru" override val lang = "ru"
override val supportsLatest = true override val supportsLatest = true
private val cookieManager by lazy { CookieManager.getInstance() }
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.rateLimit(3) .rateLimit(3)
.cookieJar(
object : CookieJar {
// Syncs okhttp with WebView cookies, allowing logged-in users do logged-in stuff
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
for (cookie in cookies) {
cookieManager.setCookie(url.toString(), cookie.toString())
}
}
override fun loadForRequest(url: HttpUrl): MutableList<Cookie> {
val cookiesString = cookieManager.getCookie(url.toString())
if (cookiesString != null && cookiesString.isNotEmpty()) {
val cookieHeaders = cookiesString.split("; ").toList()
val cookies = mutableListOf<Cookie>()
for (header in cookieHeaders) {
cookies.add(Cookie.parse(url, header)!!)
}
// Adds age verification cookies to access mature comics
return if (url.toString().contains("/reader/")) cookies.apply {
add(
Cookie.Builder()
.domain(baseUrl.substringAfter("//"))
.path("/")
.name("adult")
.value(
url.toString().substringAfter("/reader/")
.substringBefore("/")
)
.build()
)
} else cookies
} else {
return mutableListOf()
}
}
}
)
.build() .build()
override fun headersBuilder(): Headers.Builder = Headers.Builder() override fun headersBuilder(): Headers.Builder = Headers.Builder()
@ -190,11 +234,11 @@ class ComX : ParsedHttpSource() {
ratingValue > 0.5 -> "✬☆☆☆☆" ratingValue > 0.5 -> "✬☆☆☆☆"
else -> "☆☆☆☆☆" else -> "☆☆☆☆☆"
} }
val rawAgeStop = if (document.toString().contains("ВНИМАНИЕ! 18+")) "18+" else ""
val manga = SManga.create() val manga = SManga.create()
manga.title = infoElement.select(".page__title-original").text().replace(" / ", " | ").split(" | ").first() manga.title = infoElement.select(".page__title-original").text().replace(" / ", " | ").split(" | ").first()
manga.author = infoElement.select(".page__list li:contains(Издатель)").text() manga.author = infoElement.select(".page__list li:contains(Издатель)").text()
manga.genre = infoElement.select(".page__tags a").joinToString { it.text() } manga.genre = rawAgeStop + ", " + infoElement.select(".page__tags a").joinToString { it.text() }
manga.status = parseStatus(infoElement.select(".page__list li:contains(Статус)").text()) manga.status = parseStatus(infoElement.select(".page__list li:contains(Статус)").text())
manga.description = infoElement.select(".page__header h1").text().replace(" / ", " | ").split(" | ").first() + "\n" + ratingStar + " " + ratingValue + " (голосов: " + ratingVotes + ")\n" + Jsoup.parse(infoElement.select(".page__text ").first().html().replace("<br>", "REPLACbR")).text().replace("REPLACbR", "\n") manga.description = infoElement.select(".page__header h1").text().replace(" / ", " | ").split(" | ").first() + "\n" + ratingStar + " " + ratingValue + " (голосов: " + ratingVotes + ")\n" + Jsoup.parse(infoElement.select(".page__text ").first().html().replace("<br>", "REPLACbR")).text().replace("REPLACbR", "\n")
@ -224,8 +268,7 @@ class ComX : ParsedHttpSource() {
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()
if (document.toString().contains("ВНИМАНИЕ! 18+") && document.select(".login__title.stretch-free-width.ws-nowrap").first().text().contains("Войти"))
throw Exception("Для просмотра 18+ контента необходима авторизация через WebView")
val dataStr = document val dataStr = document
.toString() .toString()
.substringAfter("window.__DATA__ = ") .substringAfter("window.__DATA__ = ")
@ -236,30 +279,29 @@ class ComX : ParsedHttpSource() {
val chaptersList = data["chapters"]?.jsonArray val chaptersList = data["chapters"]?.jsonArray
val chapters: List<SChapter>? = chaptersList?.map { val chapters: List<SChapter>? = chaptersList?.map {
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.name = it.jsonObject["title"]!!.jsonPrimitive.content // title_en is full chapter name, no english name
chapter.date_upload = parseDate(it.jsonObject["date"]!!.jsonPrimitive.content) chapter.name = it.jsonObject["title_en"]!!.jsonPrimitive.content
if (chapter.name.isEmpty())
chapter.name = it.jsonObject["title"]!!.jsonPrimitive.content
chapter.date_upload = simpleDateFormat.parse(it.jsonObject["date"]!!.jsonPrimitive.content)?.time ?: 0L
chapter.chapter_number = it.jsonObject["posi"]!!.jsonPrimitive.float
chapter.setUrlWithoutDomain("/readcomix/" + data["news_id"] + "/" + it.jsonObject["id"]!!.jsonPrimitive.content + ".html") chapter.setUrlWithoutDomain("/readcomix/" + data["news_id"] + "/" + it.jsonObject["id"]!!.jsonPrimitive.content + ".html")
chapter chapter
} }
return chapters ?: emptyList() return chapters ?: emptyList()
} }
private val simpleDateFormat by lazy { SimpleDateFormat("dd.MM.yyyy", Locale.US) }
private fun parseDate(date: String?): Long {
date ?: return 0L
return try {
simpleDateFormat.parse(date)!!.time
} catch (_: Exception) {
Date().time
}
}
override fun chapterFromElement(element: Element): SChapter = override fun chapterFromElement(element: Element): SChapter =
throw NotImplementedError("Unused") throw NotImplementedError("Unused")
// Pages // Pages
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val html = response.body!!.string() val html = response.body!!.string()
// Comics 18+
if (html.contains("adult__header"))
throw Exception("Комикс 18+ (что-то сломалось)")
val baseImgUrl = "https://img.com-x.life/comix/" val baseImgUrl = "https://img.com-x.life/comix/"
val beginTag = "\"images\":[" val beginTag = "\"images\":["
@ -280,6 +322,19 @@ class ComX : ParsedHttpSource() {
return pages return pages
} }
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter))
.asObservable().doOnNext { response ->
if (!response.isSuccessful) {
if (response.code == 404 && response.asJsoup().toString().contains("Выпуск был удален по требованию правообладателя"))
throw Exception("Лицензировано. Возможно может помочь авторизация через WebView") else throw Exception("HTTP error ${response.code}")
}
}
.map { response ->
pageListParse(response)
}
}
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
throw Exception("Not used") throw Exception("Not used")
} }
@ -463,4 +518,8 @@ class ComX : ParsedHttpSource() {
CheckFilter("Яой", "120"), CheckFilter("Яой", "120"),
CheckFilter("Ёнкома", "121"), CheckFilter("Ёнкома", "121"),
) )
companion object {
private val simpleDateFormat by lazy { SimpleDateFormat("dd.MM.yyyy", Locale.US) }
}
} }