MangaBox - add Mangakakalots (#2810)

MangaBox - add Mangakakalots
This commit is contained in:
Mike 2020-04-24 21:19:31 -04:00 committed by GitHub
parent f4d6e5dd18
commit 2cb370f061
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 119 additions and 115 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: MangaBox (Mangakakalot and others)' appName = 'Tachiyomi: MangaBox (Mangakakalot and others)'
pkgNameSuffix = 'all.mangabox' pkgNameSuffix = 'all.mangabox'
extClass = '.MangaBoxFactory' extClass = '.MangaBoxFactory'
extVersionCode = 18 extVersionCode = 19
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -118,14 +118,9 @@ abstract class MangaBox (
title = infoElement.select("h1, h2").first().text() title = infoElement.select("h1, h2").first().text()
author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td").text() author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td").text()
status = parseStatus(infoElement.select("li:contains(status), td:containsOwn(status) + td").text()) status = parseStatus(infoElement.select("li:contains(status), td:containsOwn(status) + td").text())
genre = infoElement.select("div.manga-info-top li:contains(genres)").let { kakalotE -> genre = infoElement.select("div.manga-info-top li:contains(genres)").firstOrNull()
if (kakalotE.isNotEmpty()) { ?.select("a")?.joinToString { it.text() } // kakalot
kakalotE.text().substringAfter(": ") ?: infoElement.select("td:containsOwn(genres) + td a").joinToString { it.text() } // nelo
} else {
// Nelo
infoElement.select("td:containsOwn(genres) + td a").joinToString { it.text() }
}
}
description = document.select(descriptionSelector)?.first()?.ownText() description = document.select(descriptionSelector)?.first()?.ownText()
?.replace("""^$title summary:\s""".toRegex(), "") ?.replace("""^$title summary:\s""".toRegex(), "")
?.replace("""<\s*br\s*\/?>""".toRegex(), "\n") ?.replace("""<\s*br\s*\/?>""".toRegex(), "\n")
@ -155,6 +150,7 @@ abstract class MangaBox (
element.select("a").let { element.select("a").let {
url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
name = it.text() name = it.text()
scanlator = HttpUrl.parse(it.attr("abs:href"))!!.host() // show where chapters are actually from
} }
date_upload = parseChapterDate(element.select("span").last().text(), element.ownerDocument().location()) ?: 0 date_upload = parseChapterDate(element.select("span").last().text(), element.ownerDocument().location()) ?: 0
} }
@ -228,8 +224,8 @@ abstract class MangaBox (
Filter.Header("NOTE: Ignored if using text search!"), Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(), Filter.Separator(),
SortFilter(), SortFilter(),
StatusFilter(), StatusFilter(getStatusPairs()),
GenreFilter() GenreFilter(getGenrePairs())
) )
private class SortFilter : UriPartFilter("Sort", arrayOf( private class SortFilter : UriPartFilter("Sort", arrayOf(
@ -238,56 +234,60 @@ abstract class MangaBox (
Pair("topview", "Top read") Pair("topview", "Top read")
)) ))
private class StatusFilter : UriPartFilter("Status", arrayOf( open class StatusFilter(val statusPairs: Array<Pair<String, String>>) : UriPartFilter("Status", statusPairs)
Pair("all", "ALL"),
Pair("completed", "Completed"),
Pair("ongoing", "Ongoing"),
Pair("drop", "Dropped")
))
private class GenreFilter : UriPartFilter("Category", arrayOf( open fun getStatusPairs() = arrayOf(
Pair("all", "ALL"), Pair("all", "ALL"),
Pair("2", "Action"), Pair("completed", "Completed"),
Pair("3", "Adult"), Pair("ongoing", "Ongoing"),
Pair("4", "Adventure"), Pair("drop", "Dropped")
Pair("6", "Comedy"), )
Pair("7", "Cooking"),
Pair("9", "Doujinshi"), private class GenreFilter(val genrePairs: Array<Pair<String, String>>) : UriPartFilter("Category", genrePairs)
Pair("10", "Drama"),
Pair("11", "Ecchi"), open fun getGenrePairs(): Array<Pair<String, String>> = arrayOf(
Pair("12", "Fantasy"), Pair("all", "ALL"),
Pair("13", "Gender bender"), Pair("2", "Action"),
Pair("14", "Harem"), Pair("3", "Adult"),
Pair("15", "Historical"), Pair("4", "Adventure"),
Pair("16", "Horror"), Pair("6", "Comedy"),
Pair("45", "Isekai"), Pair("7", "Cooking"),
Pair("17", "Josei"), Pair("9", "Doujinshi"),
Pair("44", "Manhua"), Pair("10", "Drama"),
Pair("43", "Manhwa"), Pair("11", "Ecchi"),
Pair("19", "Martial arts"), Pair("12", "Fantasy"),
Pair("20", "Mature"), Pair("13", "Gender bender"),
Pair("21", "Mecha"), Pair("14", "Harem"),
Pair("22", "Medical"), Pair("15", "Historical"),
Pair("24", "Mystery"), Pair("16", "Horror"),
Pair("25", "One shot"), Pair("45", "Isekai"),
Pair("26", "Psychological"), Pair("17", "Josei"),
Pair("27", "Romance"), Pair("44", "Manhua"),
Pair("28", "School life"), Pair("43", "Manhwa"),
Pair("29", "Sci fi"), Pair("19", "Martial arts"),
Pair("30", "Seinen"), Pair("20", "Mature"),
Pair("31", "Shoujo"), Pair("21", "Mecha"),
Pair("32", "Shoujo ai"), Pair("22", "Medical"),
Pair("33", "Shounen"), Pair("24", "Mystery"),
Pair("34", "Shounen ai"), Pair("25", "One shot"),
Pair("35", "Slice of life"), Pair("26", "Psychological"),
Pair("36", "Smut"), Pair("27", "Romance"),
Pair("37", "Sports"), Pair("28", "School life"),
Pair("38", "Supernatural"), Pair("29", "Sci fi"),
Pair("39", "Tragedy"), Pair("30", "Seinen"),
Pair("40", "Webtoons"), Pair("31", "Shoujo"),
Pair("41", "Yaoi"), Pair("32", "Shoujo ai"),
Pair("42", "Yuri") Pair("33", "Shounen"),
)) Pair("34", "Shounen ai"),
Pair("35", "Slice of life"),
Pair("36", "Smut"),
Pair("37", "Sports"),
Pair("38", "Supernatural"),
Pair("39", "Tragedy"),
Pair("40", "Webtoons"),
Pair("41", "Yaoi"),
Pair("42", "Yuri")
)
open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {

View File

@ -1,19 +1,14 @@
package eu.kanade.tachiyomi.extension.all.mangabox package eu.kanade.tachiyomi.extension.all.mangabox
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request import okhttp3.Request
import okhttp3.RequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
@ -22,8 +17,8 @@ class MangaBoxFactory : SourceFactory {
Mangakakalot(), Mangakakalot(),
Manganelo(), Manganelo(),
Mangabat(), Mangabat(),
MangaOnl() MangaOnl(),
//ChapterManga() OtherMangakakalot()
) )
} }
@ -45,7 +40,7 @@ abstract class MangaBoxPathedGenres(
) )
class GenreFilter(genrePairs: Array<Pair<String, String>>) : UriPartFilter("Category", genrePairs) class GenreFilter(genrePairs: Array<Pair<String, String>>) : UriPartFilter("Category", genrePairs)
// Pair("path_segment/", "display name") // Pair("path_segment/", "display name")
abstract fun getGenrePairs(): Array<Pair<String, String>> abstract override fun getGenrePairs(): Array<Pair<String, String>>
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return if (query.isNotBlank()) { return if (query.isNotBlank()) {
GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers) GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers)
@ -180,54 +175,63 @@ class MangaOnl : MangaBoxPathedGenres("MangaOnl", "https://mangaonl.com", "en")
) )
} }
class ChapterManga : MangaBox("ChapterManga", "https://chaptermanga.com", "en", SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH)) { class OtherMangakakalot : MangaBox("Mangakakalots (unoriginal)", "https://mangakakalots.com", "en") {
override val popularUrlPath = "hot-manga-page-" override fun searchMangaSelector(): String = "${super.searchMangaSelector()}, div.list-truyen-item-wrap"
override val latestUrlPath = "read-latest-manga-page-"
override fun chapterListRequest(manga: SManga): Request {
val response = client.newCall(GET(baseUrl + manga.url, headers)).execute()
val cookie = response.headers("set-cookie")
.filter{ it.contains("laravel_session") }
.map{ it.substringAfter("=").substringBefore(";") }
val document = response.asJsoup()
val token = document.select("meta[name=\"csrf-token\"]").attr("content")
val script = document.select("script:containsData(manga_slug)").first()
val mangaSlug = script.data().substringAfter("manga_slug : \'").substringBefore("\'")
val mangaId = script.data().substringAfter("manga_id : \'").substringBefore("\'")
val tokenHeaders = headers.newBuilder()
.add("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8")
.add("X-CSRF-Token", token)
.add("Cookie", cookie.toString())
.build()
val body = RequestBody.create(null, "manga_slug=$mangaSlug&manga_id=$mangaId")
return POST("$baseUrl/get-chapter-list", tokenHeaders, body)
}
override fun chapterListSelector() = "div.row"
override fun chapterFromElement(element: Element): SChapter = super.chapterFromElement(element).apply {
chapter_number = Regex("""[Cc]hapter\s\d*""").find(name)?.value?.substringAfter(" ")?.toFloatOrNull() ?: 0F
}
// TODO chapterlistparse -- default chapter order could be better
override fun getFilterList() = FilterList()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val site = baseUrl.substringAfter("//")
val searchHeaders = headers.newBuilder().add("Content-Type", "application/x-www-form-urlencoded").build()
val body = RequestBody.create(null, "q=site%3A$site+inurl%3A$site%2Fread-manga+${query.replace(" ", "+")}&b=&kl=us-en")
return POST("https://duckduckgo.com/html/", searchHeaders, body)
}
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val mangas = response.asJsoup().select(searchMangaSelector()) val document = response.asJsoup()
.filter{ it.text().startsWith("Read") } val mangas = document.select(searchMangaSelector()).map { searchMangaFromElement(it) }
.map{ searchMangaFromElement(it) } val hasNextPage = !response.request().url().toString()
.contains(document.select(searchMangaNextPageSelector()).attr("href"))
return MangasPage(mangas, false) return MangasPage(mangas, hasNextPage)
}
override fun searchMangaSelector() = "div.result h2 a"
override fun searchMangaFromElement(element: Element): SManga {
return SManga.create().apply {
title = element.text().substringAfter("Read").substringBeforeLast("online").trim()
setUrlWithoutDomain(element.attr("href"))
}
} }
override fun searchMangaNextPageSelector() = "div.group_page a:last-of-type"
override fun getStatusPairs() = arrayOf(
Pair("all", "ALL"),
Pair("Completed", "Completed"),
Pair("Ongoing", "Ongoing")
)
override fun getGenrePairs() = arrayOf(
Pair("all", "ALL"),
Pair("Action", "Action"),
Pair("Adult", "Adult"),
Pair("Adventure", "Adventure"),
Pair("Comedy", "Comedy"),
Pair("Cooking", "Cooking"),
Pair("Doujinshi", "Doujinshi"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gender bender", "Gender bender"),
Pair("Harem", "Harem"),
Pair("Historical", "Historical"),
Pair("Horror", "Horror"),
Pair("Isekai", "Isekai"),
Pair("Josei", "Josei"),
Pair("Manhua", "Manhua"),
Pair("Manhwa", "Manhwa"),
Pair("Martial arts", "Martial arts"),
Pair("Mature", "Mature"),
Pair("Mecha", "Mecha"),
Pair("Medical", "Medical"),
Pair("Mystery", "Mystery"),
Pair("One shot", "One shot"),
Pair("Psychological", "Psychological"),
Pair("Romance", "Romance"),
Pair("School life", "School life"),
Pair("Sci fi", "Sci fi"),
Pair("Seinen", "Seinen"),
Pair("Shoujo", "Shoujo"),
Pair("Shoujo ai", "Shoujo ai"),
Pair("Shounen", "Shounen"),
Pair("Shounen ai", "Shounen ai"),
Pair("Slice of life", "Slice of life"),
Pair("Smut", "Smut"),
Pair("Sports", "Sports"),
Pair("Supernatural", "Supernatural"),
Pair("Tragedy", "Tragedy"),
Pair("Webtoons", "Webtoons"),
Pair("Yaoi", "Yaoi"),
Pair("Yuri", "Yuri")
)
} }