MangaBox update (#1888)

MangaBox update
This commit is contained in:
Mike 2019-12-20 07:18:17 -05:00 committed by arkon
parent abb3f69a33
commit f399772ecb
3 changed files with 135 additions and 138 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 = 5 extVersionCode = 6
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -3,16 +3,13 @@ package eu.kanade.tachiyomi.extension.all.mangabox
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
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.Page 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.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.text.ParseException import java.text.ParseException
@ -27,7 +24,7 @@ abstract class MangaBox (
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String, override val lang: String,
val dateformat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yy", Locale.ENGLISH) private val dateformat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yy", Locale.ENGLISH)
) : ParsedHttpSource() { ) : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
@ -37,62 +34,61 @@ abstract class MangaBox (
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.build() .build()
open val popularUrlPath = "manga_list" open val popularUrlPath = "manga_list?type=topview&category=all&state=all&page="
open val latestUrlPath = "manga_list" open val latestUrlPath = "manga_list?type=latest&category=all&state=all&page="
open val simpleQueryPath = "search/" open val simpleQueryPath = "search/"
override fun popularMangaSelector() = "div.truyen-list > div.list-truyen-item-wrap" override fun popularMangaSelector() = "div.truyen-list > div.list-truyen-item-wrap"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/$popularUrlPath?type=topview&category=all&state=all&page=$page", headers) return GET("$baseUrl/$popularUrlPath$page", headers)
} }
override fun latestUpdatesSelector() = popularMangaSelector() override fun latestUpdatesSelector() = popularMangaSelector()
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/$latestUrlPath?type=latest&category=all&state=all&page=$page", headers) return GET("$baseUrl/$latestUrlPath$page", headers)
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() return SManga.create().apply {
element.select("h3 a").first().let { element.select("h3 a").first().let {
manga.url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
manga.title = it.text() title = it.text()
}
thumbnail_url = element.select("img").first().attr("abs:src")
} }
manga.thumbnail_url = element.select("img").first().attr("abs:src")
return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element) override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "a.page_select + a:not(.page_last)" override fun popularMangaNextPageSelector() = "div.group_page, div.group-page a:not([href]) + a:not(:contains(Last))"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/manga_list")!!.newBuilder()
url.addQueryParameter("page", page.toString())
filters.forEach { filter ->
when (filter) {
is SortFilter -> {
url.addQueryParameter("type", filter.toUriPart())
}
is StatusFilter -> {
url.addQueryParameter("state", filter.toUriPart())
}
is GenreFilter -> {
url.addQueryParameter("category", filter.toUriPart())
}
}
}
return if (query.isNotBlank()) { return if (query.isNotBlank()) {
GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers) GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers)
} else { } else {
GET(url.build().toString(), headers) val url = HttpUrl.parse("$baseUrl/manga_list")!!.newBuilder()
url.addQueryParameter("page", page.toString())
filters.forEach { filter ->
when (filter) {
is SortFilter -> {
url.addQueryParameter("type", filter.toUriPart())
}
is StatusFilter -> {
url.addQueryParameter("state", filter.toUriPart())
}
is GenreFilter -> {
url.addQueryParameter("category", filter.toUriPart())
}
}
}
GET(url.toString(), headers)
} }
} }
@ -102,27 +98,11 @@ abstract class MangaBox (
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaParse(response: Response): MangasPage { open val mangaDetailsMainSelector = "div.manga-info-top, div.panel-story-info"
val document = response.asJsoup()
val mangaSelector = if (document.select(searchMangaSelector()).isNotEmpty()) {
searchMangaSelector()
} else {
popularMangaSelector()
}
val mangas = document.select(mangaSelector).map { element ->
searchMangaFromElement(element)
}
val hasNextPage = searchMangaNextPageSelector().let { selector ->
document.select(selector).first()
} != null
return MangasPage(mangas, hasNextPage)
}
open val mangaDetailsMainSelector = "div.manga-info-top" open val thumbnailSelector = "div.manga-info-pic img, span.info-image img"
open val thumbnailSelector = "div.manga-info-pic img" open val descriptionSelector = "div#noidungm, div#panel-story-info-description"
open val descriptionSelector = "div#noidungm"
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga): Request {
if (manga.url.startsWith("http")) { if (manga.url.startsWith("http")) {
@ -132,18 +112,23 @@ abstract class MangaBox (
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val infoElement = document.select(mangaDetailsMainSelector)
val infoElement = document.select(mangaDetailsMainSelector).first()
manga.title = infoElement.select("h1, h2").first().text() return SManga.create().apply {
manga.author = infoElement.select("li:contains(author) a").text() title = infoElement.select("h1, h2").text()
val status = infoElement.select("li:contains(status)").text() author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td").text()
manga.status = parseStatus(status) status = parseStatus(infoElement.select("li:contains(status), td:containsOwn(status) + td").text())
manga.genre = infoElement.select("div.manga-info-top li:contains(genres)").text().substringAfter(": ") genre = infoElement.select("div.manga-info-top li:contains(genres)").let { kakalotE ->
manga.description = document.select(descriptionSelector).first().ownText() if (kakalotE.isNotEmpty()) {
manga.thumbnail_url = document.select(thumbnailSelector).attr("abs:src") kakalotE.text().substringAfter(": ")
} else {
return manga // Nelo
infoElement.select("td:containsOwn(genres) + td a").joinToString { it.text() }
}
}
description = document.select(descriptionSelector)?.first()?.ownText()
thumbnail_url = document.select(thumbnailSelector).attr("abs:src")
}
} }
private fun parseStatus(status: String?) = when { private fun parseStatus(status: String?) = when {
@ -160,49 +145,40 @@ abstract class MangaBox (
return super.chapterListRequest(manga) return super.chapterListRequest(manga)
} }
override fun chapterListSelector() = "div.chapter-list div.row" override fun chapterListSelector() = "div.chapter-list div.row, ul.row-content-chapter li"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create() return SChapter.create().apply {
element.select("a").let {
element.select("a").let { url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain
chapter.url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain name = it.text()
chapter.name = it.text() }
date_upload = parseChapterDate(element.select("span").last().text(), element.ownerDocument().location()) ?: 0
} }
chapter.date_upload = parseChapterDate(element.select("span").last().text())
return chapter
} }
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String, url: String): Long? {
if ("ago" in date) { return if ("ago" in date) {
val value = date.split(' ')[0].toInt() val value = date.split(' ')[0].toIntOrNull()
val cal = Calendar.getInstance()
if ("min" in date) { when {
return Calendar.getInstance().apply { value != null && "min" in date -> cal.apply { add(Calendar.MINUTE, value * -1) }
add(Calendar.MINUTE, value * -1) value != null && "hour" in date -> cal.apply { add(Calendar.HOUR_OF_DAY, value * -1) }
}.timeInMillis value != null && "day" in date -> cal.apply { add(Calendar.DATE, value * -1) }
} else -> null
}?.timeInMillis
if ("hour" in date) { } else {
return Calendar.getInstance().apply { try {
add(Calendar.HOUR_OF_DAY, value * -1) if (url.contains("manganelo")) {
}.timeInMillis // Nelo's date format
} SimpleDateFormat("MMM dd,yy", Locale.ENGLISH).parse(date)
} else {
if ("day" in date) { dateformat.parse(date)
return Calendar.getInstance().apply { }
add(Calendar.DATE, value * -1) } catch (e: ParseException) {
}.timeInMillis null
} }?.time
} }
try {
return dateformat.parse(date).time
} catch (e: ParseException) {
}
return 0L
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
@ -212,28 +188,22 @@ abstract class MangaBox (
return super.pageListRequest(chapter) return super.pageListRequest(chapter)
} }
open val pageListSelector = "div#vungdoc img" open val pageListSelector = "div#vungdoc img, div.container-chapter-reader img"
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() return document.select(pageListSelector).mapIndexed { i, element ->
val url = element.attr("abs:src").let { src ->
document.select(pageListSelector).forEach { if (src.startsWith("https://convert_image_digi.mgicdn.com")) {
pages.add(Page(pages.size, "", changecdn(it.attr("abs:src")))) "https://images.weserv.nl/?url=" + src.substringAfter("//")
} } else {
src
return pages }
} }
Page(i, "", url)
private fun changecdn(url: String): String {
if (url.startsWith("https://convert_image_digi.mgicdn.com")) {
val newurl = "https://images.weserv.nl/?url=" + url.removePrefix("https://")
return newurl
} else {
return url
} }
} }
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("No used") override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
// Based on change_alias JS function from Mangakakalot's website // Based on change_alias JS function from Mangakakalot's website
open fun normalizeSearchQuery(query: String): String { open fun normalizeSearchQuery(query: String): String {

View File

@ -6,6 +6,7 @@ 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.SManga
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request import okhttp3.Request
@ -13,7 +14,7 @@ import okhttp3.RequestBody
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Locale
class MangaBoxFactory : SourceFactory { class MangaBoxFactory : SourceFactory {
override fun createSources(): List<Source> = listOf( override fun createSources(): List<Source> = listOf(
@ -31,11 +32,31 @@ class MangaBoxFactory : SourceFactory {
class Mangakakalot : MangaBox("Mangakakalot", "http://mangakakalot.com", "en") class Mangakakalot : MangaBox("Mangakakalot", "http://mangakakalot.com", "en")
class Manganelo : MangaBox("Manganelo", "https://manganelo.com", "en") class Manganelo : MangaBox("Manganelo", "https://manganelo.com", "en") {
// Nelo's date format is part of the base class
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/genre-all/$page?type=topview", headers)
override fun popularMangaSelector() = "div.content-genres-item"
override val latestUrlPath = "genre-all/"
override fun searchMangaSelector() = "div.search-story-item"
override fun getFilterList() = FilterList()
}
class Mangafree : MangaBox("Mangafree", "http://mangafree.online", "en") { class Mangafree : MangaBox("Mangafree", "http://mangafree.online", "en") {
override val popularUrlPath = "hotmanga" override val popularUrlPath = "hotmanga/"
override val latestUrlPath = "latest" override val latestUrlPath = "latest/"
override fun popularMangaParse(response: Response): MangasPage {
return response.asJsoup().let { document ->
val mangas = document.select(popularMangaSelector()).map { popularMangaFromElement(it) }
val hasNextPage = document.select("script:containsData(setpagination)").last().data()
.substringAfter("setPagination(").substringBefore(")").split(",").let {
it[0] != it[1]
}
MangasPage(mangas, hasNextPage)
}
}
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
override fun chapterListSelector() = "div#ContentPlaceHolderLeft_list_chapter_comic div.row" override fun chapterListSelector() = "div#ContentPlaceHolderLeft_list_chapter_comic div.row"
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
} }
@ -51,11 +72,18 @@ class Mangabat : MangaBox("Mangabat", "https://mangabat.com", "en") {
override val pageListSelector = "div.vung_doc img" override val pageListSelector = "div.vung_doc img"
} }
class KonoBasho : MangaBox("Kono-Basho", "https://kono-basho.com", "en") class KonoBasho : MangaBox("Kono-Basho", "https://kono-basho.com", "en", SimpleDateFormat("MMM dd,yy", Locale.ENGLISH)) {
// Basically a dupe of Manganelo
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/genre-all/$page?type=topview", headers)
override fun popularMangaSelector() = "div.content-genres-item"
override val latestUrlPath = "genre-all/"
override fun searchMangaSelector() = "div.search-story-item"
override fun getFilterList() = FilterList()
}
class MangaOnl : MangaBox("MangaOnl", "https://mangaonl.com", "en") { class MangaOnl : MangaBox("MangaOnl", "https://mangaonl.com", "en") {
override val popularUrlPath = "story-list-ty-topview-st-all-ca-all-1" override val popularUrlPath = "story-list-ty-topview-st-all-ca-all-"
override val latestUrlPath = "story-list-ty-latest-st-all-ca-all-1" override val latestUrlPath = "story-list-ty-latest-st-all-ca-all-"
override fun popularMangaSelector() = "div.story_item" override fun popularMangaSelector() = "div.story_item"
override val mangaDetailsMainSelector = "div.panel_story_info" override val mangaDetailsMainSelector = "div.panel_story_info"
override val thumbnailSelector = "img.story_avatar" override val thumbnailSelector = "img.story_avatar"
@ -66,8 +94,8 @@ class MangaOnl : MangaBox("MangaOnl", "https://mangaonl.com", "en") {
} }
class ChapterManga : MangaBox("ChapterManga", "https://chaptermanga.com", "en", SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH)) { class ChapterManga : MangaBox("ChapterManga", "https://chaptermanga.com", "en", SimpleDateFormat("dd-MM-yyyy", Locale.ENGLISH)) {
override val popularUrlPath = "hot-manga" override val popularUrlPath = "hot-manga-page-"
override val latestUrlPath = "read-latest-manga" override val latestUrlPath = "read-latest-manga-page-"
override fun chapterListRequest(manga: SManga): Request { override fun chapterListRequest(manga: SManga): Request {
val response = client.newCall(GET(baseUrl + manga.url, headers)).execute() val response = client.newCall(GET(baseUrl + manga.url, headers)).execute()
val cookie = response.headers("set-cookie") val cookie = response.headers("set-cookie")
@ -88,6 +116,10 @@ class ChapterManga : MangaBox("ChapterManga", "https://chaptermanga.com", "en",
return POST("$baseUrl/get-chapter-list", tokenHeaders, body) return POST("$baseUrl/get-chapter-list", tokenHeaders, body)
} }
override fun chapterListSelector() = "div.row" 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 getFilterList() = FilterList()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val site = baseUrl.substringAfter("//") val site = baseUrl.substringAfter("//")
@ -97,23 +129,18 @@ class ChapterManga : MangaBox("ChapterManga", "https://chaptermanga.com", "en",
return POST("https://duckduckgo.com/html/", searchHeaders, body) return POST("https://duckduckgo.com/html/", searchHeaders, body)
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val mangas = response.asJsoup().select(searchMangaSelector())
val mangas = mutableListOf<SManga>()
document.select(searchMangaSelector())
.filter{ it.text().startsWith("Read") } .filter{ it.text().startsWith("Read") }
.map{ mangas.add(searchMangaFromElement(it)) } .map{ searchMangaFromElement(it) }
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
override fun searchMangaSelector() = "div.result h2 a" override fun searchMangaSelector() = "div.result h2 a"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() return SManga.create().apply {
title = element.text().substringAfter("Read").substringBeforeLast("online").trim()
manga.title = element.text().substringAfter("Read").substringBeforeLast("online").trim() setUrlWithoutDomain(element.attr("href"))
manga.setUrlWithoutDomain(element.attr("href")) }
return manga
} }
} }