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