Add Bruttal source (#5024)
* Add Bruttal. * Fix wrong name in build.gradle.
This commit is contained in:
parent
6e97dfab20
commit
ec3a78f0ef
12
src/pt/bruttal/build.gradle
Normal file
12
src/pt/bruttal/build.gradle
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Bruttal'
|
||||||
|
pkgNameSuffix = 'pt.bruttal'
|
||||||
|
extClass = '.Bruttal'
|
||||||
|
extVersionCode = 1
|
||||||
|
libVersion = '1.2'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/pt/bruttal/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/pt/bruttal/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
src/pt/bruttal/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/pt/bruttal/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
BIN
src/pt/bruttal/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/pt/bruttal/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
src/pt/bruttal/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/pt/bruttal/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.0 KiB |
BIN
src/pt/bruttal/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/pt/bruttal/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
BIN
src/pt/bruttal/res/web_hi_res_512.png
Normal file
BIN
src/pt/bruttal/res/web_hi_res_512.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
@ -0,0 +1,188 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.pt.bruttal
|
||||||
|
|
||||||
|
import com.github.salomonbrys.kotson.array
|
||||||
|
import com.github.salomonbrys.kotson.get
|
||||||
|
import com.github.salomonbrys.kotson.obj
|
||||||
|
import com.github.salomonbrys.kotson.string
|
||||||
|
import com.google.gson.JsonElement
|
||||||
|
import com.google.gson.JsonObject
|
||||||
|
import com.google.gson.JsonParser
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import eu.kanade.tachiyomi.source.model.MangasPage
|
||||||
|
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.HttpSource
|
||||||
|
import okhttp3.Headers
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
class Bruttal : HttpSource() {
|
||||||
|
|
||||||
|
override val name = "Bruttal"
|
||||||
|
|
||||||
|
override val baseUrl = "https://originals.omelete.com.br"
|
||||||
|
|
||||||
|
override val lang = "pt-BR"
|
||||||
|
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||||
|
.add("Referer", "$baseUrl/bruttal/")
|
||||||
|
.add("User-Agent", USER_AGENT)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
val newHeaders = headersBuilder()
|
||||||
|
.add("Accept", "application/json, text/plain, */*")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET("$baseUrl/bruttal/data/home.json", newHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val json = response.asJson().obj
|
||||||
|
|
||||||
|
val titles = json["list"].array.map { jsonEl ->
|
||||||
|
popularMangaFromObject(jsonEl.obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
return MangasPage(titles, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun popularMangaFromObject(obj: JsonObject): SManga = SManga.create().apply {
|
||||||
|
title = obj["title"].string
|
||||||
|
thumbnail_url = "$baseUrl/bruttal/" + obj["image_mobile"].string.removePrefix("./")
|
||||||
|
url = "/bruttal" + obj["url"].string
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
return super.fetchSearchManga(page, query, filters)
|
||||||
|
.map { mp ->
|
||||||
|
val filteredTitles = mp.mangas.filter { it.title.contains(query, true) }
|
||||||
|
MangasPage(filteredTitles, mp.hasNextPage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = popularMangaRequest(page)
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
// Workaround to allow "Open in browser" use the real URL.
|
||||||
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
|
return client.newCall(mangaDetailsApiRequest(manga))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response ->
|
||||||
|
mangaDetailsParse(response).apply { initialized = true }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mangaDetailsApiRequest(manga: SManga): Request {
|
||||||
|
val newHeaders = headersBuilder()
|
||||||
|
.add("Accept", "application/json, text/plain, */*")
|
||||||
|
.set("Referer", baseUrl + manga.url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET("$baseUrl/bruttal/data/comicbooks.json", newHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val json = response.asJson().array
|
||||||
|
|
||||||
|
val titleUrl = response.request().header("Referer")!!.substringAfter("/bruttal")
|
||||||
|
val titleObj = json.first { it.obj["url"].string == titleUrl }.obj
|
||||||
|
val soonText = titleObj["soon_text"].string
|
||||||
|
|
||||||
|
return SManga.create().apply {
|
||||||
|
title = titleObj["title"].string
|
||||||
|
thumbnail_url = "$baseUrl/bruttal/" + titleObj["image_mobile"].string.removePrefix("./")
|
||||||
|
description = titleObj["synopsis"].string +
|
||||||
|
(if (soonText.isEmpty()) "" else "\n\n$soonText")
|
||||||
|
artist = titleObj["illustrator"].string
|
||||||
|
author = titleObj["author"].string
|
||||||
|
genre = titleObj["keywords"].string.replace("; ", ", ")
|
||||||
|
status = SManga.ONGOING
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters are available in the same url of the manga details.
|
||||||
|
override fun chapterListRequest(manga: SManga): Request = mangaDetailsApiRequest(manga)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val json = response.asJson().array
|
||||||
|
|
||||||
|
val titleUrl = response.request().header("Referer")!!.substringAfter("/bruttal")
|
||||||
|
val title = json.first { it.obj["url"].string == titleUrl }.obj
|
||||||
|
|
||||||
|
return title["seasons"].array
|
||||||
|
.flatMap { it.obj["chapters"].array }
|
||||||
|
.map { jsonEl -> chapterFromObject(jsonEl.obj) }
|
||||||
|
.reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun chapterFromObject(obj: JsonObject): SChapter = SChapter.create().apply {
|
||||||
|
name = obj["title"].string
|
||||||
|
chapter_number = obj["share_title"].string.removePrefix("Capítulo ").toFloatOrNull() ?: -1f
|
||||||
|
url = "/bruttal" + obj["url"].string
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
val newHeaders = headersBuilder()
|
||||||
|
.add("Accept", "application/json, text/plain, */*")
|
||||||
|
.set("Referer", baseUrl + chapter.url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET("$baseUrl/bruttal/data/comicbooks.json", newHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val json = response.asJson().array
|
||||||
|
|
||||||
|
val chapterUrl = response.request().header("Referer")!!
|
||||||
|
val titleSlug = chapterUrl.substringAfter("bruttal/").substringBefore("/")
|
||||||
|
val season = chapterUrl.substringAfter("temporada-").substringBefore("/").toInt()
|
||||||
|
val chapter = chapterUrl.substringAfter("capitulo-")
|
||||||
|
|
||||||
|
val titleObj = json.first { it.obj["url"].string == "/$titleSlug" }.obj
|
||||||
|
val seasonObj = titleObj["seasons"].array[season - 1].obj
|
||||||
|
val chapterObj = seasonObj["chapters"].array.first {
|
||||||
|
it.obj["alias"].string.substringAfter("-") == chapter
|
||||||
|
}
|
||||||
|
|
||||||
|
return chapterObj["images"].array
|
||||||
|
.mapIndexed { i, jsonEl ->
|
||||||
|
val imageUrl = "$baseUrl/bruttal/" + jsonEl.obj["image"].string.removePrefix("./")
|
||||||
|
Page(i, chapterUrl, imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||||
|
return Observable.just(page.imageUrl!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = ""
|
||||||
|
|
||||||
|
override fun imageRequest(page: Page): Request {
|
||||||
|
val newHeaders = headersBuilder()
|
||||||
|
.add("Accept", "image/avif,image/webp,image/apng,image/*,*/*;q=0.8")
|
||||||
|
.set("Referer", page.url)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return GET(page.imageUrl!!, newHeaders)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used")
|
||||||
|
|
||||||
|
private fun Response.asJson(): JsonElement = JSON_PARSER.parse(body()!!.string())
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
|
||||||
|
|
||||||
|
private val JSON_PARSER by lazy { JsonParser() }
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user