diff --git a/multisrc/overrides/gattsu/default/additional.gradle.kts b/multisrc/overrides/gattsu/default/additional.gradle.kts new file mode 100644 index 000000000..10beb8157 --- /dev/null +++ b/multisrc/overrides/gattsu/default/additional.gradle.kts @@ -0,0 +1,4 @@ + +dependencies { + implementation project(':lib-ratelimit') +} diff --git a/multisrc/overrides/gattsu/default/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/gattsu/default/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..719f2d5cd Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/default/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/gattsu/default/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..038bca431 Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/default/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/gattsu/default/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..cf3e9d703 Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/default/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/default/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d6d617c10 Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/default/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/default/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..959bd60d9 Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/default/res/web_hi_res_512.png b/multisrc/overrides/gattsu/default/res/web_hi_res_512.png new file mode 100644 index 000000000..52482b2d7 Binary files /dev/null and b/multisrc/overrides/gattsu/default/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaikai/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..cdd6b6e20 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaikai/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..e3b70f130 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..c732b9242 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..b10bcc4fb Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..4ee2c1204 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/res/web_hi_res_512.png b/multisrc/overrides/gattsu/hentaikai/res/web_hi_res_512.png new file mode 100644 index 000000000..e0235e1bc Binary files /dev/null and b/multisrc/overrides/gattsu/hentaikai/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/gattsu/hentaikai/src/HentaiKai.kt b/multisrc/overrides/gattsu/hentaikai/src/HentaiKai.kt new file mode 100644 index 000000000..01b6f6f94 --- /dev/null +++ b/multisrc/overrides/gattsu/hentaikai/src/HentaiKai.kt @@ -0,0 +1,43 @@ +package eu.kanade.tachiyomi.extension.pt.hentaikai + +import eu.kanade.tachiyomi.annotations.Nsfw +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.multisrc.gattsu.Gattsu +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.model.SManga +import okhttp3.OkHttpClient +import okhttp3.Request +import org.jsoup.nodes.Element +import java.util.concurrent.TimeUnit + +@Nsfw +class HentaiKai : Gattsu( + "Hentai Kai", + "https://hentaikai.com", + "pt-BR" +) { + + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) + .build() + + override fun latestUpdatesRequest(page: Int): Request { + val pagePath = if (page > 1) "page/$page" else "" + + return GET("$baseUrl/mangas-e-hqs-recentes/$pagePath", headers) + } + + override fun latestUpdatesSelector() = + "div.meio div#fotos div.listaFotoConteudo > a[href^=$baseUrl]:not([title*=Episódio]):first-of-type" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.attr("title") + thumbnail_url = + element.select("span.listaFotoThumb img.wp-post-image").first()!!.attr("src") + setUrlWithoutDomain(element.attr("href")) + } + + override fun latestUpdatesNextPageSelector() = "div.meio div#fotos ul.paginacao li.next > a" + + override fun searchMangaSelector() = "div.meio div.lista div.listaFotoConteudo > a[href^=$baseUrl]:not([title*=Episódio]):first-of-type" +} diff --git a/multisrc/overrides/gattsu/hentaiseason/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..872ad3814 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-hdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..db2a56ff3 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-mdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..a604638a0 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..d438d4503 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..56487fa09 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/res/web_hi_res_512.png b/multisrc/overrides/gattsu/hentaiseason/res/web_hi_res_512.png new file mode 100644 index 000000000..b3e3b5ba7 Binary files /dev/null and b/multisrc/overrides/gattsu/hentaiseason/res/web_hi_res_512.png differ diff --git a/multisrc/overrides/gattsu/hentaiseason/src/HentaiSeason.kt b/multisrc/overrides/gattsu/hentaiseason/src/HentaiSeason.kt new file mode 100644 index 000000000..b07a6ef7a --- /dev/null +++ b/multisrc/overrides/gattsu/hentaiseason/src/HentaiSeason.kt @@ -0,0 +1,19 @@ +package eu.kanade.tachiyomi.extension.pt.hentaiseason + +import eu.kanade.tachiyomi.annotations.Nsfw +import eu.kanade.tachiyomi.lib.ratelimit.RateLimitInterceptor +import eu.kanade.tachiyomi.multisrc.gattsu.Gattsu +import okhttp3.OkHttpClient +import java.util.concurrent.TimeUnit + +@Nsfw +class HentaiSeason : Gattsu( + "Hentai Season", + "https://hentaiseason.com", + "pt-BR" +) { + + override val client: OkHttpClient = super.client.newBuilder() + .addInterceptor(RateLimitInterceptor(1, 2, TimeUnit.SECONDS)) + .build() +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt new file mode 100644 index 000000000..a36ec3e86 --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/Gattsu.kt @@ -0,0 +1,154 @@ +package eu.kanade.tachiyomi.multisrc.gattsu + +import eu.kanade.tachiyomi.network.GET +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 eu.kanade.tachiyomi.util.asJsoup +import okhttp3.Headers +import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.Locale + +abstract class Gattsu( + override val name: String, + override val baseUrl: String, + override val lang: String +) : ParsedHttpSource() { + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + override fun headersBuilder() = Headers.Builder() + .add("Accept", ACCEPT) + .add("Accept-Language", ACCEPT_LANGUAGE) + .add("Referer", baseUrl) + + // Website does not have a popular, so use latest instead. + override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page) + + override fun popularMangaSelector(): String = latestUpdatesSelector() + + override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element) + + override fun popularMangaNextPageSelector(): String? = latestUpdatesNextPageSelector() + + override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers) + + override fun latestUpdatesSelector() = "div.meio div.lista ul li a[href^=$baseUrl]" + + override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply { + title = element.select("span.thumb-titulo").first()!!.text() + thumbnail_url = element.select("span.thumb-imagem img.wp-post-image").first()!!.attr("src") + setUrlWithoutDomain(element.attr("href")) + } + + override fun latestUpdatesNextPageSelector(): String? = null + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val searchUrl = "$baseUrl/page/$page/".toHttpUrlOrNull()!!.newBuilder() + .addQueryParameter("s", query) + .addQueryParameter("post_type", "post") + .toString() + + return GET(searchUrl, headers) + } + + override fun searchMangaSelector() = latestUpdatesSelector() + + override fun searchMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element) + + override fun searchMangaNextPageSelector() = "ul.paginacao li.next > a" + + override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply { + val postBox = document.select("div.meio div.post-box").first()!! + + title = postBox.select("h1.post-titulo").first()!!.text() + author = postBox.select("ul.post-itens li:contains(Artista) a").firstOrNull()?.text() + genre = postBox.select("ul.post-itens li:contains(Tags) a") + .joinToString(", ") { it.text() } + description = postBox.select("div.post-texto p") + .joinToString("\n\n") { it.text() } + .replace("Sinopse :", "") + .trim() + status = SManga.COMPLETED + thumbnail_url = postBox.select("div.post-capa > img.wp-post-image") + .attr("src") + .withoutSize() + } + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + + if (document.select(pageListSelector()).firstOrNull() == null) { + return emptyList() + } + + return document.select(chapterListSelector()) + .map { chapterFromElement(it) } + } + + override fun chapterListSelector() = "div.meio div.post-box:first-of-type" + + override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply { + name = "Capítulo único" + scanlator = element.select("ul.post-itens li:contains(Tradutor) a").firstOrNull()?.text() + date_upload = element.ownerDocument().select("meta[property=article:published_time]").firstOrNull() + ?.attr("content") + .orEmpty() + .toDate() + setUrlWithoutDomain(element.ownerDocument().location()) + } + + protected open fun pageListSelector(): String = "div.meio div.post-box ul.post-fotos li a img" + + override fun pageListParse(document: Document): List { + return document.select(pageListSelector()) + .mapIndexed { i, el -> + Page(i, document.location(), el.attr("src").withoutSize()) + } + } + + override fun imageUrlParse(document: Document) = "" + + override fun imageRequest(page: Page): Request { + val imageHeaders = headersBuilder() + .add("Accept", ACCEPT_IMAGE) + .add("Referer", page.url) + .build() + + return GET(page.imageUrl!!, imageHeaders) + } + + private fun String.toDate(): Long { + return try { + DATE_FORMATTER.parse(this.substringBefore("T"))?.time ?: 0L + } catch (e: ParseException) { + 0L + } + } + + private fun String.withoutSize(): String = this.replace(THUMB_SIZE_REGEX, ".") + + companion object { + private const val ACCEPT = "text/html,application/xhtml+xml,application/xml;q=0.9," + + "image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + private const val ACCEPT_IMAGE = "image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8" + private const val ACCEPT_LANGUAGE = "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,gl;q=0.5" + + private val THUMB_SIZE_REGEX = "-\\d+x\\d+\\.".toRegex() + + private val DATE_FORMATTER by lazy { + SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH) + } + } +} diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/GattsuGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/GattsuGenerator.kt new file mode 100644 index 000000000..10b51567d --- /dev/null +++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/gattsu/GattsuGenerator.kt @@ -0,0 +1,26 @@ +package eu.kanade.tachiyomi.multisrc.gattsu + +import generator.ThemeSourceData.SingleLang +import generator.ThemeSourceGenerator + +class GattsuGenerator : ThemeSourceGenerator { + + override val themePkg = "gattsu" + + override val themeClass = "Gattsu" + + override val baseVersionCode: Int = 1 + + override val sources = listOf( + SingleLang("Hentai Kai", "https://hentaikai.com", "pt-BR", isNsfw = true), + SingleLang("Hentai Season", "https://hentaiseason.com", "pt-BR", isNsfw = true) + ) + + companion object { + @JvmStatic + fun main(args: Array) { + GattsuGenerator().createAll() + } + } +} +