bacamanga new website (#5119)

* Change BacaManga website and parsing

* BacaManga.cc new extension icons

* Update version code in build.gradle
This commit is contained in:
antonycaporossi 2020-12-14 14:07:54 +01:00 committed by GitHub
parent df408c356a
commit f3444bf6f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 112 additions and 142 deletions

View File

@ -5,7 +5,7 @@ ext {
extName = 'Baca Manga' extName = 'Baca Manga'
pkgNameSuffix = 'id.bacamanga' pkgNameSuffix = 'id.bacamanga'
extClass = '.BacaManga' extClass = '.BacaManga'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 18 KiB

View File

@ -1,5 +1,5 @@
package eu.kanade.tachiyomi.extension.id.bacamanga package eu.kanade.tachiyomi.extension.id.bacamanga
import android.util.Base64
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
@ -8,83 +8,79 @@ 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 eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.io.UnsupportedEncodingException
import java.text.SimpleDateFormat
import java.util.Locale
class BacaManga : ParsedHttpSource() { class BacaManga : ParsedHttpSource() {
override val name = "BacaManga" override val name = "BacaManga"
override val baseUrl = "https://bacamanga.co" override val baseUrl = "https://bacamanga.cc"
override val lang = "id" override val lang = "id"
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/manga/page/$page/?order=popular", headers) return GET("$baseUrl/komik-populer/page/$page/", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/manga/page/$page/?order=update", headers) return GET("$baseUrl/manga/page/$page/", headers)
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val builtUrl = "$baseUrl/manga/page/$page/" val url = if (query.isNotBlank()) {
val url = HttpUrl.parse(builtUrl)!!.newBuilder() val url = HttpUrl.parse("$baseUrl/page/$page")!!.newBuilder()
url.addQueryParameter("title", query) val pattern = "\\s+".toRegex()
filters.forEach { filter -> val q = query.replace(pattern, "+")
if (query.isNotEmpty()) {
url.addQueryParameter("s", q)
} else {
url.addQueryParameter("s", "")
}
url.toString()
} else {
val url = HttpUrl.parse("$baseUrl/daftar-komik/page/$page")!!.newBuilder()
var orderBy: String
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is AuthorFilter -> { is Status -> url.addQueryParameter("status", arrayOf("", "ongoing", "completed")[filter.state])
url.addQueryParameter("author", filter.state)
}
is YearFilter -> {
url.addQueryParameter("yearx", filter.state)
}
is StatusFilter -> {
val status = when (filter.state) {
Filter.TriState.STATE_INCLUDE -> "completed"
Filter.TriState.STATE_EXCLUDE -> "ongoing"
else -> ""
}
url.addQueryParameter("status", status)
}
is TypeFilter -> {
url.addQueryParameter("type", filter.toUriPart())
}
is OrderByFilter -> {
url.addQueryParameter("order", filter.toUriPart())
}
is GenreList -> { is GenreList -> {
val genreInclude = mutableListOf<String>()
filter.state.forEach { filter.state.forEach {
if (it.state) { if (it.state == 1) {
url.addQueryParameter("genre[]", it.id) genreInclude.add(it.id)
}
}
if (genreInclude.isNotEmpty()) {
genreInclude.forEach { genre ->
url.addQueryParameter("genre[]", genre)
} }
} }
} }
is SortBy -> {
orderBy = filter.toUriPart()
url.addQueryParameter("order", orderBy)
} }
} }
return GET(url.build().toString(), headers) }
url.toString()
}
return GET(url, headers)
} }
override fun popularMangaSelector() = "div.bs" override fun popularMangaSelector() = "div.animepost"
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
manga.thumbnail_url = element.select("div.limit img").attr("src") manga.thumbnail_url = element.select("img").attr("data-lazy-src")
element.select("a").first().let { manga.setUrlWithoutDomain(element.select(".bigor > a").attr("href"))
manga.setUrlWithoutDomain(it.attr("href")) manga.title = element.select(".bigor .tt h2").text()
manga.title = it.attr("title")
}
return manga return manga
} }
@ -96,104 +92,87 @@ class BacaManga : ParsedHttpSource() {
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select(".spe") val infoElement = document.select(".infox").first()
val sepName = infoElement.select("span:nth-child(4)").last() val sepName = infoElement.select(".spe > span:nth-child(4)").last()
val manga = SManga.create() val manga = SManga.create()
manga.author = sepName.ownText() manga.author = infoElement.select(".spe span:contains(Pengarang)").text().replace("Pengarang: ", "").trim()
manga.artist = sepName.ownText() manga.artist = sepName.ownText()
manga.genre = infoElement.select("span:nth-child(1) > a") val genres = mutableListOf<String>()
.joinToString(", ") { it.text() } infoElement.select(".genre-info > a").forEach { element ->
manga.status = parseStatus(infoElement.select("span:nth-child(2)").text()) val genre = element.text()
manga.description = document.select("div[itemprop=articleBody]").last().text() genres.add(genre)
manga.thumbnail_url = document.select(".thumb > img:nth-child(1)").attr("src") }
manga.genre = genres.joinToString(", ")
manga.status = parseStatus(infoElement.select(".spe span:contains(Status)").text())
manga.description = document.select("div[^itemprop]").last().text()
manga.thumbnail_url = document.select(".thumb noscript img").first().attr("src")
return manga return manga
} }
private fun parseStatus(element: String): Int = when { private fun parseStatus(element: String): Int = when {
element.contains("ongoing", ignoreCase = true) -> SManga.ONGOING
element.contains("completed", ignoreCase = true) -> SManga.COMPLETED element.toLowerCase().contains("berjalan") -> SManga.ONGOING
element.toLowerCase().contains("tamat") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListSelector() = "div#chapter_list ul li"
val document = response.asJsoup()
val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
// Add date for latest chapter only
chapters[0].date_upload = parseDate(document.select(".lchx+span.dt .dt-small").first().text())
return chapters
}
private fun parseDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH).parse(date)?.time ?: 0L
}
override fun chapterListSelector() = ".lchx"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select(".lchx a").first()
val timeElement = element.select("span.rightoff").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = 0
return chapter return chapter
} }
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
val basic = Regex("""Chapter\s([0-9]+)""")
when {
basic.containsMatchIn(chapter.name) -> {
basic.find(chapter.name)?.let {
chapter.chapter_number = it.groups[1]?.value!!.toFloat()
}
}
}
}
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
val scriptToParse = document.select("script[src*=cache]").first().attr("src")
val script = document.select("div#content script:nth-child(3)").html() val slideaid = client.newCall(GET(scriptToParse, headers)).execute().body()!!.string()
val encodedImagesList = script.substringAfter("JSON['parse'](window[").substringAfter("\"").substringBefore("\"") val imagesList = slideaid.substringAfter("var imgch").substringBefore(";").substringAfter("=").trim()
val decodedImagesList = decodeBase64(encodedImagesList.rot13Decode()) val img_url = slideaid.substringAfter("#chimg").substringBefore("onError").substringAfter("src=\"").substringBefore("'").trim()
val json = JsonParser().parse(decodedImagesList).asJsonArray val json = JsonParser().parse(imagesList).asJsonArray
json.forEachIndexed { i, url -> json.forEachIndexed { i, url ->
/* REMOVING QUOTES AROUND STRING */
val url_clean = url.toString().removeSurrounding("\"") val url_clean = url.toString().removeSurrounding("\"")
pages.add(Page(i, "", url_clean)) // BASE URL HARD CODED
pages.add(Page(i, "", "$img_url$url_clean"))
} }
return pages return pages
} }
private fun decodeBase64(coded: String): String {
var valueDecoded = ByteArray(0)
try {
valueDecoded = Base64.decode(coded.toByteArray(charset("UTF-8")), Base64.DEFAULT)
} catch (e: UnsupportedEncodingException) {
}
return String(valueDecoded)
}
/**
* rot13 decoding
* More aboure rot13 https://rosettacode.org/wiki/Rot-13
* Kotlin implementation https://rosettacode.org/wiki/Rot-13#Kotlin
*/
private fun String.rot13Decode() = map {
when {
it.isUpperCase() -> { val x = it + 13; if (x > 'Z') x - 26 else x }
it.isLowerCase() -> { val x = it + 13; if (x > 'z') x - 26 else x }
else -> it
}
}.toCharArray().joinToString("")
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request { override fun imageRequest(page: Page): Request {
return if (page.imageUrl!!.contains("i0.wp.com")) {
val headers = Headers.Builder() val headers = Headers.Builder()
headers.apply {
add("Referer", baseUrl)
add("User-Agent", "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/76.0.3809.100 Mobile Safari/537.36")
}
if (page.imageUrl!!.contains("i0.wp.com")) {
headers.apply { headers.apply {
add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3") add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3")
} }
GET(page.imageUrl!!, headers.build()) }
} else GET(page.imageUrl!!, headers) return GET(page.imageUrl!!, headers.build())
} }
private class AuthorFilter : Filter.Text("Author") private class SortBy : UriPartFilter(
private class YearFilter : Filter.Text("Year")
private class OrderByFilter : UriPartFilter(
"Sort by", "Sort by",
arrayOf( arrayOf(
Pair("Default", ""), Pair("Default", ""),
@ -205,33 +184,24 @@ class BacaManga : ParsedHttpSource() {
) )
) )
private class StatusFilter : Filter.TriState("Completed") private class Status : UriPartFilter(
"Status",
private class TypeFilter : UriPartFilter(
"Type",
arrayOf( arrayOf(
Pair("All", ""), Pair("All", ""),
Pair("Manga", "Manga"), Pair("Ongoing", "Ongoing"),
Pair("Manhua", "Manhua"), Pair("Completed", "Completed")
Pair("Manhwa", "Manhwa")
) )
) )
private class Genre(name: String, val id: String = name) : Filter.CheckBox(name) private class Genre(name: String, val id: String = name) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Filter.Header("NOTE: Ignored if using text search!"), Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(), Filter.Separator(),
AuthorFilter(), SortBy(),
Filter.Separator(), Filter.Separator(),
YearFilter(), Status(),
Filter.Separator(),
StatusFilter(),
Filter.Separator(),
OrderByFilter(),
Filter.Separator(),
TypeFilter(),
Filter.Separator(), Filter.Separator(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
@ -241,46 +211,49 @@ class BacaManga : ParsedHttpSource() {
Genre("Adult", "adult"), Genre("Adult", "adult"),
Genre("Adventure", "adventure"), Genre("Adventure", "adventure"),
Genre("Comedy", "comedy"), Genre("Comedy", "comedy"),
Genre("Crime", "crime"), Genre("Demon", "demon"),
Genre("Demons", "demons"), Genre("Demons", "demons"),
Genre("Doujinshi", "doujinshi"), Genre("Doujinshi", "doujinshi"),
Genre("Drama", "drama"), Genre("Drama", "drama"),
Genre("Ecchi", "ecchi"), Genre("Ecchi", "ecchi"),
Genre("Echi", "echi"),
Genre("Fantasy", "fantasy"), Genre("Fantasy", "fantasy"),
Genre("Game", "game"), Genre("Game", "game"),
Genre("Gender Bender", "gender-bender"), Genre("Gender Bender", "gender-bender"),
Genre("Genres: Action", "genres-action"),
Genre("Gore", "gore"),
Genre("Harem", "harem"), Genre("Harem", "harem"),
Genre("Historical", "historical"), Genre("Historical", "historical"),
Genre("Horor", "horor"), Genre("Horor", "horor"),
Genre("Horror", "horror"), Genre("Horror", "horror"),
Genre("Isekai", "isekai"), Genre("Isekai", "isekai"),
Genre("Josei", "josei"), Genre("Josei", "josei"),
Genre("Lolicon", "lolicon"),
Genre("Magic", "magic"), Genre("Magic", "magic"),
Genre("Manhua", "manhua"), Genre("Manhua", "manhua"),
Genre("Manhwa", "manhwa"),
Genre("Martial Art", "martial-art"), Genre("Martial Art", "martial-art"),
Genre("Martial Arts", "martial-arts"), Genre("Martial Arts", "martial-arts"),
Genre("Mature", "mature"), Genre("Mature", "mature"),
Genre("Mecha", "mecha"), Genre("Mecha", "mecha"),
Genre("Medical", "medical"), Genre("Medical", "medical"),
Genre("Military", "military"), Genre("Military", "military"),
Genre("Monster", "monster"), Genre("Mistery", "mistery"),
Genre("Monster Girls", "monster-girls"),
Genre("Music", "music"), Genre("Music", "music"),
Genre("Mystery", "mystery"), Genre("Mystery", "mystery"),
Genre("Post-Apocalyptic", "post-apocalyptic"), Genre("Project", "project"),
Genre("Psychological", "psychological"), Genre("Psychological", "psychological"),
Genre("Reincarnation", "reincarnation"),
Genre("Romance", "romance"), Genre("Romance", "romance"),
Genre("School", "school"), Genre("School", "school"),
Genre("School Life", "school-life"), Genre("School Life", "school-life"),
Genre("Sci-Fi", "sci-fi"), Genre("school of life", "school-of-life"),
Genre("Sci-fi", "sci-fi"),
Genre("Seinen", "seinen"), Genre("Seinen", "seinen"),
Genre("Shoujo Ai", "shoujo-ai"), Genre("sepernatural", "sepernatural"),
Genre("Shotacon", "shotacon"),
Genre("Shoujo", "shoujo"), Genre("Shoujo", "shoujo"),
Genre("Shounen Ai", "shounen-ai"), Genre("Shoujo Ai", "shoujo-ai"),
Genre("Shounen", "shounen"), Genre("Shounen", "shounen"),
Genre("Si-fi", "si-fi"), Genre("Shounen Ai", "shounen-ai"),
Genre("Slice of Life", "slice-of-life"), Genre("Slice of Life", "slice-of-life"),
Genre("Smut", "smut"), Genre("Smut", "smut"),
Genre("Sports", "sports"), Genre("Sports", "sports"),
@ -288,12 +261,9 @@ class BacaManga : ParsedHttpSource() {
Genre("Supernatural", "supernatural"), Genre("Supernatural", "supernatural"),
Genre("Thriller", "thriller"), Genre("Thriller", "thriller"),
Genre("Tragedy", "tragedy"), Genre("Tragedy", "tragedy"),
Genre("Vampire", "vampire"),
Genre("Webtoon", "webtoon"),
Genre("Webtoons", "webtoons"), Genre("Webtoons", "webtoons"),
Genre("Yaoi", "yaoi"), Genre("Worn and Torn Newbie", "worn-and-torn-newbie"),
Genre("Yuri", "yuri"), Genre("Yuri", "yuri")
Genre("Zombies", "zombies")
) )
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :