parent
750fdbabf6
commit
b5a949c4ac
|
@ -1,2 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -1,17 +0,0 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
apply plugin: 'kotlinx-serialization'
|
||||
|
||||
ext {
|
||||
extName = 'HentaiMimi'
|
||||
pkgNameSuffix = 'en.hentaimimi'
|
||||
extClass = '.HentaiMimi'
|
||||
extVersionCode = 5
|
||||
containsNsfw = true
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation project(':lib-ratelimit')
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
Before Width: | Height: | Size: 5.9 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.4 KiB |
Binary file not shown.
Before Width: | Height: | Size: 8.5 KiB |
Binary file not shown.
Before Width: | Height: | Size: 16 KiB |
Binary file not shown.
Before Width: | Height: | Size: 24 KiB |
Binary file not shown.
Before Width: | Height: | Size: 117 KiB |
|
@ -1,148 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.hentaimimi
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.annotation.TargetApi
|
||||
import android.os.Build
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Document
|
||||
import java.io.PrintWriter
|
||||
import java.security.cert.CertificateException
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.concurrent.TimeUnit
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
/*
|
||||
* Based on the one in MMRCMS extension
|
||||
*/
|
||||
class FilterOptGen {
|
||||
init {
|
||||
System.setProperty("https.protocols", "TLSv1,TLSv1.1,TLSv1.2,TLSv1.3")
|
||||
}
|
||||
val types = listOf("artists", "parodies", "langs", "pubs", "tags")
|
||||
@TargetApi(Build.VERSION_CODES.O)
|
||||
fun generate() {
|
||||
val buffer = StringBuffer()
|
||||
val dateTime = ZonedDateTime.now()
|
||||
val formattedDate = dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME)
|
||||
buffer.append("package eu.kanade.tachiyomi.extension.en.hentaimimi")
|
||||
buffer.append("\n\n// GENERATED FILE, DO NOT MODIFY!\n// Generated $formattedDate\n\n")
|
||||
var number = 1
|
||||
types.forEach {
|
||||
try {
|
||||
val document = getDocument("https://hentaimimi.com/search")
|
||||
val ids = parseIds(document!!, it).sortedBy { id -> id.first }
|
||||
|
||||
buffer.append("fun $it() = listOf(\n")
|
||||
for (id in ids) {
|
||||
when (it) {
|
||||
"tags" -> buffer.append(" HentaiMimi.TriStateFilterOption(\"${id.first}\", \"${id.second}\"),\n")
|
||||
else -> buffer.append(" HentaiMimi.CheckboxFilterOption(\"${id.first}\", \"${id.second}\"),\n")
|
||||
}
|
||||
}
|
||||
buffer.append(")\n\n")
|
||||
number++
|
||||
} catch (e: Exception) {
|
||||
println("error generating source $it ${e.printStackTrace()}")
|
||||
}
|
||||
}
|
||||
buffer.setLength(buffer.length - 1)
|
||||
println("Post-run types: ${number - 1}")
|
||||
val writer = PrintWriter(relativePath)
|
||||
writer.write(buffer.toString())
|
||||
writer.close()
|
||||
}
|
||||
|
||||
private fun getDocument(url: String): Document? {
|
||||
val serverCheck = arrayOf("cloudflare-nginx", "cloudflare")
|
||||
|
||||
try {
|
||||
val request = Request.Builder().url(url)
|
||||
getOkHttpClient().newCall(request.build()).execute().let { response ->
|
||||
// Bypass Cloudflare ("Please wait 5 seconds" page)
|
||||
if (response.code == 503 && response.header("Server") in serverCheck) {
|
||||
var cookie = "${response.header("Set-Cookie")!!.substringBefore(";")}; "
|
||||
Jsoup.parse(response.body!!.string()).let { document ->
|
||||
val path = document.select("[id=\"challenge-form\"]").attr("action")
|
||||
val chk = document.select("[name=\"s\"]").attr("value")
|
||||
getOkHttpClient().newCall(Request.Builder().url("$url/$path?s=$chk").build()).execute().let { solved ->
|
||||
cookie += solved.header("Set-Cookie")!!.substringBefore(";")
|
||||
request.addHeader("Cookie", cookie).build().let {
|
||||
return Jsoup.parse(getOkHttpClient().newCall(it).execute().body?.string())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (response.code == 200) {
|
||||
return Jsoup.parse(response.body?.string())
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun parseIds(Document: Document, type: String): List<Pair<String, String>> {
|
||||
val elements = Document.select("[name=\"$type${"[]"}\"] > option")
|
||||
val ids = mutableListOf<Pair<String, String>>()
|
||||
if (elements.isEmpty()) {
|
||||
return ids
|
||||
}
|
||||
elements.forEach {
|
||||
ids.add(Pair(it.text(), it.attr("value")))
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
@Throws(Exception::class)
|
||||
private fun getOkHttpClient(): OkHttpClient {
|
||||
val trustAllCerts = arrayOf<TrustManager>(
|
||||
object : X509TrustManager {
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
@SuppressLint("TrustAllX509TrustManager")
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<java.security.cert.X509Certificate>, authType: String) {
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<java.security.cert.X509Certificate> {
|
||||
return arrayOf()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
|
||||
val sc = SSLContext.getInstance("SSL")
|
||||
sc.init(null, trustAllCerts, java.security.SecureRandom())
|
||||
val sslSocketFactory = sc.socketFactory
|
||||
|
||||
// Create all-trusting host name verifier
|
||||
// Install the all-trusting host verifier
|
||||
|
||||
return OkHttpClient.Builder()
|
||||
.sslSocketFactory(sslSocketFactory, trustAllCerts[0] as X509TrustManager)
|
||||
.hostnameVerifier { _, _ -> true }
|
||||
.connectTimeout(1, TimeUnit.MINUTES)
|
||||
.readTimeout(1, TimeUnit.MINUTES)
|
||||
.writeTimeout(1, TimeUnit.MINUTES)
|
||||
.build()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val relativePath = System.getProperty("user.dir") + "/src/en/hentaimimi/src/eu/kanade/tachiyomi/extension/en/hentaimimi/HentaiMimiIDs.kt"
|
||||
|
||||
@JvmStatic
|
||||
fun main(args: Array<String>) {
|
||||
FilterOptGen().generate()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,190 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.hentaimimi
|
||||
|
||||
import eu.kanade.tachiyomi.annotations.Nsfw
|
||||
import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.model.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/*
|
||||
* To Update Filter Values Run "FilterOptGen.kt"
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Nsfw
|
||||
class HentaiMimi : ParsedHttpSource() {
|
||||
// Meta Data
|
||||
override val baseUrl = "https://hentaimimi.com"
|
||||
override val lang = "en"
|
||||
override val name = "HentaiMimi"
|
||||
override val supportsLatest = true
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
override val client: OkHttpClient = network.client.newBuilder()
|
||||
.addInterceptor(RateLimitInterceptor(1, 1, TimeUnit.SECONDS))
|
||||
.build()
|
||||
|
||||
// Latest
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
val title = element.select(".white-text")
|
||||
manga.url = title.attr("abs:href").replace(baseUrl, "").trim()
|
||||
manga.title = title.text()
|
||||
manga.thumbnail_url = element.select(".card-img-top").attr("abs:src")
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector(): String? = ":not(*)"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET(baseUrl, headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector(): String = "div.row:nth-child(2) > div"
|
||||
|
||||
// Popular
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element)
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = "li.page-item.active + li.page-item"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/index?page=$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector(): String = "div.row:nth-child(4) > div"
|
||||
|
||||
// Search
|
||||
|
||||
override fun searchMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = "$baseUrl/search".toHttpUrlOrNull()?.newBuilder()!!
|
||||
if (query.isNotEmpty()) url.addQueryParameter("title", query)
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is ArtistGroupFilter -> {
|
||||
filter.state.filter { it.state }.map { it.value }.forEach { url.addQueryParameter("artists[]", it) }
|
||||
}
|
||||
is ParodiesGroupFilter -> {
|
||||
filter.state.filter { it.state }.map { it.value }.forEach { url.addQueryParameter("parodies[]", it) }
|
||||
}
|
||||
is LanguageGroupFilter -> {
|
||||
filter.state.filter { it.state }.map { it.value }.forEach { url.addQueryParameter("langs[]", it) }
|
||||
}
|
||||
is PublishersGroupFilter -> {
|
||||
filter.state.filter { it.state }.map { it.value }.forEach { url.addQueryParameter("pubs[]", it) }
|
||||
}
|
||||
is TagGroupFilter -> {
|
||||
filter.state.filter { it.isExcluded() }.map { it.value }.forEach { url.addQueryParameter("tags_ex[]", it) }
|
||||
filter.state.filter { it.isIncluded() }.map { it.value }.forEach { url.addQueryParameter("tags[]", it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
url.addQueryParameter("page", page.toString())
|
||||
return GET(url.toString(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector(): String = "div.row:nth-child(2) > div"
|
||||
|
||||
// Mangas
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val manga = SManga.create()
|
||||
val details = document.select("div.p-0")
|
||||
val tags = mutableListOf<String>()
|
||||
manga.title = details.select("h3").text()
|
||||
manga.url = document.location().replace(baseUrl, "").trim()
|
||||
manga.thumbnail_url = document.select("div.col-md-4 img").attr("abs:src")
|
||||
details.select(".mb-3").forEach {
|
||||
when (it.select(".lead").text()) {
|
||||
"Artist" -> {
|
||||
manga.artist = it.select("p:nth-child(2)").text()
|
||||
manga.author = manga.artist
|
||||
}
|
||||
"Description" -> manga.description = it.select("p:nth-child(2)").text()
|
||||
"Tags" -> it.select("p:nth-child(2) > a > span").forEach { tag ->
|
||||
tags.add(tag.text())
|
||||
}
|
||||
"Language" -> tags.add(it.select("p:nth-child(2)").text())
|
||||
"Magazine" -> tags.add(it.select("p:nth-child(2)").text())
|
||||
"Publisher" -> tags.add(it.select("p:nth-child(2)").text())
|
||||
}
|
||||
}
|
||||
manga.status = 2
|
||||
manga.genre = tags.joinToString(", ")
|
||||
return manga
|
||||
}
|
||||
|
||||
// Chapters
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.just(
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
name = "Chapter"
|
||||
url = manga.url
|
||||
chapter_number = 1F
|
||||
date_upload = System.currentTimeMillis()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
override fun chapterFromElement(element: Element): SChapter = throw UnsupportedOperationException("Not used")
|
||||
|
||||
override fun chapterListSelector(): String = throw UnsupportedOperationException("Not used")
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pagesJsSource = document.select("body main script").html()
|
||||
val pagesJsArray = pagesJsSource.substring(pagesJsSource.indexOf("["))
|
||||
|
||||
val pages = mutableListOf<Page>()
|
||||
|
||||
// note: a simple JS array is also a valid JSON array - just unmarshal it
|
||||
json.parseToJsonElement(pagesJsArray).jsonArray.mapIndexed { index, jsonElement ->
|
||||
val url = "$baseUrl/${jsonElement.jsonPrimitive.content.replace("#", "%23")}"
|
||||
pages.add(Page(index, url, url))
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
// Filters
|
||||
|
||||
class TriStateFilterOption(name: String, val value: String) : Filter.TriState(name)
|
||||
class CheckboxFilterOption(name: String, val value: String, default: Boolean = false) : Filter.CheckBox(name, default)
|
||||
|
||||
private class TagGroupFilter(filters: List<TriStateFilterOption>) : Filter.Group<TriStateFilterOption>("Tags", filters)
|
||||
private class LanguageGroupFilter(options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>("Languages", options)
|
||||
private class ArtistGroupFilter(options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>("Artist", options)
|
||||
private class ParodiesGroupFilter(options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>("Parodies", options)
|
||||
private class PublishersGroupFilter(options: List<CheckboxFilterOption>) : Filter.Group<CheckboxFilterOption>("Publishers", options)
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
ArtistGroupFilter(artists()),
|
||||
ParodiesGroupFilter(parodies()),
|
||||
LanguageGroupFilter(langs()),
|
||||
PublishersGroupFilter(pubs()),
|
||||
TagGroupFilter(tags()),
|
||||
)
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue