Add Nekopost (#2647)

* Add Nekopost V.1.2.1

* Fix genre not shown

* Change Icons

* Rename monthMap -> monthList

* Change from getter to normal declaration

* Rename getUrlWithoutDomainFromFullUrl to getMangaOrChapterAlias

* Fix duplicate title

* Fix unable to search by Genre tag

* Change Genre and Status from Enum to Pairs

* Minor changes
This commit is contained in:
Sittikorn Hirunpongthawat 2020-04-14 04:23:45 +07:00 committed by GitHub
parent 8b120ef5d6
commit b220477525
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 328 additions and 0 deletions

View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: Nekopost'
pkgNameSuffix = 'th.nekopost'
extClass = '.Nekopost'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 190 KiB

View File

@ -0,0 +1,68 @@
package eu.kanade.tachiyomi.extension.th.nekopost
import java.text.SimpleDateFormat
import java.util.*
object NPUtils {
private val urlWithoutDomainFromFullUrlRegex: Regex = Regex("^https://www\\.nekopost\\.net/manga/(.*)$")
fun getMangaOrChapterAlias(url: String): String {
val (urlWithoutDomain) = urlWithoutDomainFromFullUrlRegex.find(url)!!.destructured
return urlWithoutDomain
}
fun convertDateStringToEpoch(dateStr: String, format: String = "yyyy-MM-dd"): Long = SimpleDateFormat(format, Locale("th")).parse(dateStr).time
fun getSearchQuery(keyword: String = "", genreList: Array<String>, statusList: Array<String>): String {
val keywordQuery = "ip_keyword=$keyword"
val genreQuery = genreList.joinToString("&") { genre -> "ip_genre[]=${getValueOf(Genre, genre)}" }
val statusQuery = statusList.let {
if (it.isNotEmpty()) it.map { status -> getValueOf(Status, status) }
else Status.map { status -> status.second }
}.joinToString("&") { status -> "ip_status[]=$status" }
val typeQuery = "ip_type[]=m"
return "$keywordQuery&$genreQuery&$statusQuery&$typeQuery"
}
val Genre = arrayOf(
Pair("Fantasy", 1),
Pair("Action", 2),
Pair("Drama", 3),
Pair("Sport", 5),
Pair("Sci-fi", 7),
Pair("Comedy", 8),
Pair("Slice of Life", 9),
Pair("Romance", 10),
Pair("Adventure", 13),
Pair("Yaoi", 23),
Pair("Yuri", 24),
Pair("Trap", 25),
Pair("Gender Bender", 26),
Pair("Mystery", 32),
Pair("Doujinshi", 37),
Pair("Grume", 41),
Pair("Shoujo", 42),
Pair("School Life", 43),
Pair("Isekai", 44),
Pair("Shounen", 46),
Pair("Second Life", 45),
Pair("Horror", 47),
Pair("One short", 48),
Pair("Seinen", 49)
).sortedWith(compareBy { it.first }).toTypedArray()
val Status = arrayOf(
Pair("Ongoing", 1),
Pair("Completed", 2),
Pair("Licensed", 3)
)
fun <T, F, S> getValueOf(array: Array<T>, name: F): S? where T : Pair<F, S> = array.find { genre -> genre.first == name }?.second
val monthList: Array<String> = arrayOf("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")
}

View File

@ -0,0 +1,248 @@
package eu.kanade.tachiyomi.extension.th.nekopost
import android.util.Log
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request
import okhttp3.Response
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.net.URL
import java.util.*
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
class Nekopost() : ParsedHttpSource() {
override val baseUrl: String = "https://www.nekopost.net/manga/"
private val mangaListUrl: String = "https://www.nekopost.net/project/ajax_load_update/m/"
private val chapterContentUrl: String = "https://www.nekopost.net/reader/loadChapterContent/"
private val chapterImageUrl: String = "https://www.nekopost.net/file_server/collectManga/"
private val searchUrl: String = "https://www.nekopost.net/search/"
private val fallbackImageUrl: String = "https://www.nekopost.net/images/no_image.jpg"
override val lang: String = "th"
override val name: String = "Nekopost"
override val supportsLatest: Boolean = true
private var latestMangaList: HashSet<String> = HashSet()
private var popularMangaList: HashSet<String> = HashSet()
override fun chapterListSelector(): String = ".bg-card.card.pb-2 tr"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
element.select("a").first().let {
setUrlWithoutDomain(NPUtils.getMangaOrChapterAlias(it.attr("href")))
name = it.text()
}
date_upload = NPUtils.convertDateStringToEpoch(element.select("b").last().nextSibling().toString().trim())
scanlator = element.select("a").last().text()
}
override fun imageUrlParse(document: Document): String = ".bg-card.card .p-3.text-white img"
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector()).filter { element ->
val dateText = element.select(".date").text().trim()
val currentDate = Calendar.getInstance(Locale("th"))
dateText.contains(currentDate.get(Calendar.DATE).toString()) && dateText.contains(NPUtils.monthList[currentDate.get(Calendar.MONTH)])
}.map { element -> latestUpdatesFromElement(element) }.filter { manga ->
if (!latestMangaList.contains(manga.url)) {
latestMangaList.add(manga.url)
true
} else false
}
val hasNextPage = mangas.isNotEmpty()
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
setUrlWithoutDomain(NPUtils.getMangaOrChapterAlias(element.select("a").attr("href")))
title = element.select(".info > b").text().trim()
thumbnail_url = element.select(".img img").first().attr("src").replace("preview", "cover").let { url ->
if (url === "") fallbackImageUrl
else url
}
}
override fun latestUpdatesNextPageSelector(): String? = throw Exception("Unused")
override fun latestUpdatesRequest(page: Int): Request {
if (page == 1) latestMangaList = HashSet()
return GET("$mangaListUrl/${page - 1}")
}
override fun latestUpdatesSelector(): String = "a[href]"
@ExperimentalStdlibApi
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
document.select(".bg-card.card").first().let {
title = it.select(".card-title.text-silver").text()
thumbnail_url = it.select(".bg-card.card").select(".p-3.text-white").select("img").first().attr("src").let { url ->
if (url === "") fallbackImageUrl
else url
}
it.select("table.mt-1").select("tr").let { tr ->
author = tr[0].select("td").last().text()
artist = tr[1].select("td").last().text()
status = when (tr[3].select("td").last().text()) {
"Active" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
"Licensed" -> SManga.LICENSED
else -> SManga.UNKNOWN
}
}
description = it.select(".bg-secondary").text().trim()
genre = it.select("td[colspan='2'][valign='top']").first().text()
.replace("Category:", "")
.split(",").joinToString(", ") { genre ->
genre.trim().split("_").joinToString(" ") { str ->
if (str.toLowerCase(Locale.getDefault()) == "of") str else str.capitalize(Locale.getDefault())
}
}
}
}
override fun pageListParse(document: Document): List<Page> {
return JSONArray(URL("$chapterContentUrl${NPUtils.getMangaOrChapterAlias(document.location())}").readText()).let { chapterContentJSON ->
try {
val pageListJSON = chapterContentJSON.getJSONArray(3)
val chapterDataJson = chapterContentJSON.getJSONObject(1)
val pageList: ArrayList<Page> = ArrayList()
for (i in 0 until pageListJSON.length()) {
pageList.add(
Page(i, "", pageListJSON.getJSONObject(i).let { pageJSON ->
"$chapterImageUrl${chapterDataJson.getString("nc_project_id")}/${pageJSON.getString("chapter_id")}/${pageJSON.getString("value_url")}"
})
)
}
pageList
} catch (e: JSONException) {
val pageListNameJSON = chapterContentJSON.getString(3)
val chapterDataJson = chapterContentJSON.getJSONObject(1)
val pageListDataJSON = JSONObject(URL("$chapterImageUrl${chapterDataJson.getString("nc_project_id")}/${chapterDataJson.getString("nc_chapter_id")}/$pageListNameJSON").readText())
val pageListJSON = pageListDataJSON.getJSONArray("pageItem")
val pageList: ArrayList<Page> = ArrayList()
for (i in 0 until pageListJSON.length()) {
pageList.add(
Page(i, "", pageListJSON.getJSONObject(i).let { pageJSON ->
"$chapterImageUrl${chapterDataJson.getString("nc_project_id")}/${chapterDataJson.getString("nc_chapter_id")}/${pageJSON.getString("fileName")}"
})
)
}
pageList
}
}
}
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(popularMangaSelector()).map { element -> popularMangaFromElement(element) }.filter { manga ->
if (!popularMangaList.contains(manga.url)) {
popularMangaList.add(manga.url)
true
} else false
}
val hasNextPage = mangas.isNotEmpty()
return MangasPage(mangas, hasNextPage)
}
override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element)
override fun popularMangaNextPageSelector(): String? = latestUpdatesNextPageSelector()
override fun popularMangaRequest(page: Int): Request {
if (page == 1) popularMangaList = HashSet()
return GET("$mangaListUrl/${page - 1}")
}
override fun popularMangaSelector(): String = latestUpdatesSelector()
override fun getFilterList(): FilterList = FilterList(
GenreFilter(),
StatusFilter()
)
private class GenreFilter : Filter.Group<GenreCheckbox>("Genre", NPUtils.Genre.map { genre -> GenreCheckbox(genre.first) })
private class GenreCheckbox(genre: String) : Filter.CheckBox(genre, false)
private class StatusFilter : Filter.Group<StatusCheckbox>("Status", NPUtils.Status.map { status -> StatusCheckbox(status.first) })
private class StatusCheckbox(status: String) : Filter.CheckBox(status, false)
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
element.select(".project_info").select("a").let {
title = it.text()
setUrlWithoutDomain(NPUtils.getMangaOrChapterAlias(it.attr("href")))
}
thumbnail_url = element.select("img").attr("data-original").let { url ->
if (url === "") fallbackImageUrl
else url
}
status = when (element.select(".status").text()) {
"On Going" -> SManga.ONGOING
"Completed" -> SManga.COMPLETED
"Licensed" -> SManga.LICENSED
else -> SManga.UNKNOWN
}
}
override fun searchMangaNextPageSelector(): String? = null
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (page > 1) throw Error("No more page")
var queryString = query
val genreList: Array<String> = try {
(filters.find { filter -> filter is GenreFilter } as GenreFilter).state.filter { checkbox -> checkbox.state }.map { checkbox -> checkbox.name }.toTypedArray()
} catch (e: Exception) {
emptyArray<String>()
}.let {
when {
it.isNotEmpty() -> it
NPUtils.getValueOf(NPUtils.Genre, query) == null -> it
else ->{
queryString = ""
arrayOf(query)
}
}
}
val statusList: Array<String> = try {
(filters.find { filter -> filter is StatusFilter } as StatusFilter).state.filter { checkbox -> checkbox.state }.map { checkbox -> checkbox.name }.toTypedArray()
} catch (e: Exception) {
emptyArray()
}
return GET("$searchUrl?${NPUtils.getSearchQuery(queryString, genreList, statusList)}")
}
override fun searchMangaSelector(): String = ".list_project .item"
}