parent
c6cd3bfb62
commit
17e9e56d79
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,12 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply plugin: 'kotlinx-serialization'
|
||||||
|
|
||||||
|
ext {
|
||||||
|
extName = 'Comic Newtype'
|
||||||
|
pkgNameSuffix = 'ja.comicnewtype'
|
||||||
|
extClass = '.ComicNewtype'
|
||||||
|
extVersionCode = 1
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 1.6 KiB |
Binary file not shown.
After Width: | Height: | Size: 986 B |
Binary file not shown.
After Width: | Height: | Size: 1.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 5.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
|
@ -0,0 +1,128 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicnewtype
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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 eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.jsonObject
|
||||||
|
import kotlinx.serialization.json.jsonPrimitive
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import org.jsoup.Jsoup
|
||||||
|
import org.jsoup.select.Evaluator
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class ComicNewtype : HttpSource() {
|
||||||
|
override val name = "Comic Newtype"
|
||||||
|
override val lang = "ja"
|
||||||
|
override val baseUrl = "https://comic.webnewtype.com"
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
// Latest is disabled because manga list is sorted by update time by default.
|
||||||
|
// Ranking page has multiple rankings thus hard to do.
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int) = GET("$baseUrl/contents/?refind_search=all", headers)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup().also { it.parseGenres() }
|
||||||
|
if (document.selectFirst(Evaluator.Class("section__txt--serch")) != null)
|
||||||
|
return MangasPage(emptyList(), false)
|
||||||
|
|
||||||
|
val list = document.selectFirst(Evaluator.Class("content__col-list--common"))
|
||||||
|
val mangas = list.children().map {
|
||||||
|
val eyeCatcher = it.selectFirst(Evaluator.Class("catch__txt")).ownText()
|
||||||
|
val root = it.selectFirst(Evaluator.Tag("a"))
|
||||||
|
SManga.create().apply {
|
||||||
|
url = root.attr("href")
|
||||||
|
title = root.selectFirst(Evaluator.Class("detail__txt--ttl")).text()
|
||||||
|
author = root.selectFirst(Evaluator.Class("detail__txt--info")).ownText()
|
||||||
|
thumbnail_url = baseUrl + root.selectFirst(Evaluator.Tag("img"))
|
||||||
|
.attr("src").removeSuffix("/w250/")
|
||||||
|
val genreText = root.selectFirst(Evaluator.Class("detail__txt--label")).ownText()
|
||||||
|
if (genreText.isNotEmpty()) {
|
||||||
|
genre = genreText.substring(1).replace("#", ", ")
|
||||||
|
}
|
||||||
|
description = eyeCatcher
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return MangasPage(mangas, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response) = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val path = when {
|
||||||
|
query.isNotBlank() -> "/search/$query/"
|
||||||
|
else -> filters.genrePath ?: "/contents/"
|
||||||
|
}
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder(path)!!.addQueries(filters).build()
|
||||||
|
return Request.Builder().url(url).headers(headers).build()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response) = popularMangaParse(response)
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response) = SManga.create().apply {
|
||||||
|
val root = response.asJsoup().selectFirst(Evaluator.Class("pc__list--contents"))
|
||||||
|
title = root.selectFirst(Evaluator.Tag("h1")).ownText()
|
||||||
|
author = root.selectFirst(Evaluator.Class("contents__info")).ownText()
|
||||||
|
// This one is horizontal. Prefer the square one from manga list.
|
||||||
|
// thumbnail_url = baseUrl + root.selectFirst(Evaluator.Class("contents__thumb-comic"))
|
||||||
|
// .child(0).attr("src").removeSuffix("/w500/")
|
||||||
|
genre = root.selectFirst(Evaluator.Class("container__link-list--genre-btn"))
|
||||||
|
?.run { children().joinToString { it.text() } }
|
||||||
|
|
||||||
|
val updates = root.selectFirst(Evaluator.Class("contents__date--info-comic"))
|
||||||
|
.textNodes().filterNot { it.isBlank }.joinToString(" || ") { it.text() }
|
||||||
|
val isCompleted = (updates == "連載終了")
|
||||||
|
status = if (isCompleted) SManga.COMPLETED else SManga.ONGOING
|
||||||
|
description = buildString {
|
||||||
|
if (!isCompleted) append(updates).append("\n\n")
|
||||||
|
append(root.selectFirst(Evaluator.Class("contents__txt-catch")).ownText()).append("\n\n")
|
||||||
|
append(root.selectFirst(Evaluator.Class("contents__txt--desc")).ownText())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga) = GET(baseUrl + manga.url + "more/1/", headers)
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val jsonObject = Json.parseToJsonElement(response.body!!.string()).jsonObject
|
||||||
|
val html = jsonObject["html"]!!.jsonPrimitive.content // asserting ["next"] is 0
|
||||||
|
return Jsoup.parseBodyFragment(html).body().children().mapNotNull { element ->
|
||||||
|
val url = element.child(0).attr("href")
|
||||||
|
if (url[0] != '/') return@mapNotNull null
|
||||||
|
|
||||||
|
val dateEl = element.selectFirst(Evaluator.Class("detail__txt--date"))
|
||||||
|
val title = element.selectFirst(Evaluator.Tag("h2")).ownText().halfwidthDigits()
|
||||||
|
val noteEl = element.selectFirst(Evaluator.Class("detail__txt--caution"))
|
||||||
|
SChapter.create().apply {
|
||||||
|
this.url = url
|
||||||
|
name = if (noteEl == null) title else "$title(${noteEl.ownText()})"
|
||||||
|
dateEl?.let { dateFormat.parse(it.ownText()) }?.let { date_upload = it.time }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter) = GET(baseUrl + chapter.url + "json/", headers)
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> =
|
||||||
|
Json.decodeFromString<List<String>>(response.body!!.string()).mapIndexed { index, path ->
|
||||||
|
val newPath = path.removeSuffix("/h1200q75nc/")
|
||||||
|
Page(index, imageUrl = baseUrl + newPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("Not used.")
|
||||||
|
|
||||||
|
override fun getFilterList() = filterList
|
||||||
|
|
||||||
|
private val dateFormat by lazy { SimpleDateFormat("yyyy/M/d", Locale.ENGLISH) }
|
||||||
|
}
|
|
@ -0,0 +1,82 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicnewtype
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
|
import okhttp3.HttpUrl
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.select.Evaluator
|
||||||
|
|
||||||
|
var genreList: List<Genre> = emptyList()
|
||||||
|
|
||||||
|
fun Document.parseGenres() {
|
||||||
|
if (genreList.isNotEmpty()) return
|
||||||
|
val container = select(Evaluator.Class("container__link-list--genre-btn")).lastOrNull() ?: return
|
||||||
|
val items = container.children().ifEmpty { return }
|
||||||
|
val list = ArrayList<Genre>(items.size + 1).apply { add(Genre("全て", null)) }
|
||||||
|
genreList = items.mapTo(list) {
|
||||||
|
val link = it.child(0)
|
||||||
|
Genre(link.text(), link.attr("href"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val filterList: FilterList
|
||||||
|
get() {
|
||||||
|
val list = buildList(5) {
|
||||||
|
if (genreList.isEmpty()) {
|
||||||
|
add(Filter.Header("Press 'Reset' to attempt to show the genres"))
|
||||||
|
} else {
|
||||||
|
add(Filter.Header("Genre (ignored for text search)"))
|
||||||
|
add(GenreFilter(genreList))
|
||||||
|
}
|
||||||
|
add(Filter.Separator())
|
||||||
|
add(StatusFilter())
|
||||||
|
add(SortFilter())
|
||||||
|
}
|
||||||
|
return FilterList(list)
|
||||||
|
}
|
||||||
|
|
||||||
|
val FilterList.genrePath: String?
|
||||||
|
get() {
|
||||||
|
for (filter in this) {
|
||||||
|
if (filter is GenreFilter) return filter.path
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun HttpUrl.Builder.addQueries(filters: FilterList): HttpUrl.Builder {
|
||||||
|
for (filter in filters) {
|
||||||
|
if (filter is QueryFilter) filter.addQueryTo(this)
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
class Genre(val name: String, val path: String?)
|
||||||
|
|
||||||
|
class GenreFilter(private val list: List<Genre>) :
|
||||||
|
Filter.Select<String>("Genre", list.map { it.name }.toTypedArray()) {
|
||||||
|
val path get() = list[state].path
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class QueryFilter(name: String, values: Array<String>) :
|
||||||
|
Filter.Select<String>(name, values) {
|
||||||
|
abstract fun addQueryTo(builder: HttpUrl.Builder)
|
||||||
|
}
|
||||||
|
|
||||||
|
class StatusFilter : QueryFilter("Status", STATUS_VALUES) {
|
||||||
|
override fun addQueryTo(builder: HttpUrl.Builder) {
|
||||||
|
builder.addQueryParameter("refind_search", STATUS_QUERIES[state])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val STATUS_VALUES = arrayOf("全て", "連載中", "完結")
|
||||||
|
private val STATUS_QUERIES = arrayOf("all", "now", "fin")
|
||||||
|
|
||||||
|
class SortFilter : QueryFilter("Sort by", SORT_VALUES) {
|
||||||
|
override fun addQueryTo(builder: HttpUrl.Builder) {
|
||||||
|
if (state == 0) return
|
||||||
|
builder.addQueryParameter("btn_sort", SORT_QUERIES[state])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val SORT_VALUES = arrayOf("更新順", "五十音順")
|
||||||
|
private val SORT_QUERIES = arrayOf("opendate", "alphabetical")
|
|
@ -0,0 +1,7 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.ja.comicnewtype
|
||||||
|
|
||||||
|
fun String.halfwidthDigits() = buildString(length) {
|
||||||
|
for (char in this@halfwidthDigits) {
|
||||||
|
append(if (char in '0'..'9') char - ('0'.code - '0'.code) else char)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue