Update [TH] Nekopost extension (#6818)

* 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

* Fix next page not loaded when previous page has no new title

* Fix offset not reset

* Change most of extension to API-based

* Fix bug where some chapter uses legacy url format

* Update build.gradle

* Update to use with new UI and API
This commit is contained in:
Sittikorn Hirunpongthawat 2021-05-05 09:15:56 +07:00 committed by GitHub
parent 5845c336b5
commit 60693e465c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 340 additions and 470 deletions

View File

@ -0,0 +1,153 @@
package eu.kanade.tachiyomi.extension.th.nekopost
data class RawMangaData(
val no_new_chapter: String,
val nc_chapter_id: String,
val np_project_id: String,
val np_name: String,
val np_name_link: String,
val nc_chapter_no: String,
val nc_chapter_name: String,
val nc_chapter_cover: String,
val nc_provider: String,
val np_group_dir: String,
val nc_created_date: String,
)
data class RawMangaDataList(
val code: String,
val listItem: Array<RawMangaData>?
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RawMangaDataList
if (code != other.code) return false
if (listItem != null) {
if (other.listItem == null) return false
if (!listItem.contentEquals(other.listItem)) return false
} else if (other.listItem != null) return false
return true
}
override fun hashCode(): Int {
var result = code.hashCode()
result = 31 * result + (listItem?.contentHashCode() ?: 0)
return result
}
}
data class RawProjectData(
val np_status: String,
val np_project_id: String,
val np_type: String,
val np_name: String,
val np_name_link: String,
val np_flag_mature: String,
val np_info: String,
val np_view: String,
val np_comment: String,
val np_created_date: String,
val np_updated_date: String,
val author_name: String,
val artist_name: String,
val np_web: String,
val np_licenced_by: String,
)
data class RawProjectGenre(
val npc_name: String,
val npc_name_link: String,
)
data class RawChapterData(
val nc_chapter_id: String,
val nc_chapter_no: String,
val nc_chapter_name: String,
val nc_provider: String,
val cu_displayname: String,
val nc_created_date: String,
val nc_data_file: String,
val nc_owner_id: String,
)
data class RawMangaDetailedData(
val code: String,
val projectInfo: RawProjectData,
val projectCategoryUsed: Array<RawProjectGenre>?,
val projectChapterList: Array<RawChapterData>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RawMangaDetailedData
if (code != other.code) return false
if (projectInfo != other.projectInfo) return false
if (projectCategoryUsed != null) {
if (other.projectCategoryUsed == null) return false
if (!projectCategoryUsed.contentEquals(other.projectCategoryUsed)) return false
} else if (other.projectCategoryUsed != null) return false
if (!projectChapterList.contentEquals(other.projectChapterList)) return false
return true
}
override fun hashCode(): Int {
var result = code.hashCode()
result = 31 * result + projectInfo.hashCode()
result = 31 * result + (projectCategoryUsed?.contentHashCode() ?: 0)
result = 31 * result + projectChapterList.contentHashCode()
return result
}
}
data class RawPageData(
val pageNo: Int,
val fileName: String,
val width: Int,
val height: Int,
val pageCount: Int
)
data class RawChapterDetailedData(
val projectId: String,
val chapterId: Int,
val chapterNo: String,
val pageItem: Array<RawPageData>,
) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RawChapterDetailedData
if (projectId != other.projectId) return false
if (chapterId != other.chapterId) return false
if (chapterNo != other.chapterNo) return false
if (!pageItem.contentEquals(other.pageItem)) return false
return true
}
override fun hashCode(): Int {
var result = projectId.hashCode()
result = 31 * result + chapterId
result = 31 * result + chapterNo.hashCode()
result = 31 * result + pageItem.contentHashCode()
return result
}
}
data class MangaNameList(
val np_project_id: String,
val np_name: String,
val np_name_link: String,
val np_type: String,
val np_status: String,
val np_no_chapter: String,
)

View File

@ -1,79 +0,0 @@
package eu.kanade.tachiyomi.extension.th.nekopost
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.ArrayList
import java.util.Locale
class NPArrayList<E>(c: Collection<E>, val mangaList: List<Element>) : ArrayList<E>(c) {
override fun isEmpty(): Boolean = mangaList.isEmpty()
fun isNotEmpty(): Boolean = mangaList.isNotEmpty()
fun isListEmpty(): Boolean = super.isEmpty()
fun isListNotEmpty(): Boolean = !isListEmpty()
}
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 ?: 0L
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: List<String> = listOf("JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC")
}

View File

@ -1,36 +1,30 @@
package eu.kanade.tachiyomi.extension.th.nekopost package eu.kanade.tachiyomi.extension.th.nekopost
import com.google.gson.Gson
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
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.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.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.json.JSONArray
import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.net.URL import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale import java.util.Locale
import kotlin.collections.set
class Nekopost : ParsedHttpSource() { class Nekopost : ParsedHttpSource() {
override val baseUrl: String = "https://www.nekopost.net/manga/" override val baseUrl: String = "https://www.nekopost.net/manga/"
private val mangaListUrl: String = "https://www.nekopost.net/project/ajax_load_update/m/" private val mangaListUrl: String = "https://tuner.nekopost.net/ApiTest/getLatestChapterOffset/m/"
private val baseFileUrl: String = "https://fs.nekopost.net/" private val projectDataUrl: String = "https://tuner.nekopost.net/ApiTest/getProjectDetailFull/"
private val legacyChapterDataUrl: String = "https://www.nekopost.net/reader/loadChapterContent/" private val fileUrl: String = "https://fs.nekopost.net/"
private val searchUrl: String = "https://www.nekopost.net/search/"
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
@ -43,230 +37,83 @@ class Nekopost : ParsedHttpSource() {
override val supportsLatest: Boolean = true override val supportsLatest: Boolean = true
private var latestMangaList: HashSet<String> = HashSet() private data class MangaListTracker(
private var popularMangaList: HashSet<String> = HashSet() var offset: Int = 0,
val list: HashSet<String> = HashSet()
)
private val projectList: HashMap<Int, ProjectParser.ProjectData> = HashMap() private var latestMangaTracker = MangaListTracker()
private val projectParser: ProjectParser = ProjectParser() private var popularMangaTracker = MangaListTracker()
object NP { data class ProjectRecord(
class Chapter : SChapter { val project: SManga,
override lateinit var url: String val project_id: String,
val chapter_list: HashSet<String> = HashSet(),
)
override lateinit var name: String data class ChapterRecord(
val chapter: SChapter,
val chapter_id: String,
val project: ProjectRecord,
val pages_data: String,
)
override var date_upload: Long = 0 private var projectUrlMap = HashMap<String, ProjectRecord>()
private var chapterList = HashMap<String, ChapterRecord>()
override var chapter_number: Float = -1f private fun getStatus(status: String) = when (status) {
"1" -> SManga.ONGOING
override var scanlator: String? = null "2" -> SManga.COMPLETED
"3" -> SManga.LICENSED
lateinit var chapterData: ProjectParser.ProjectData.ChapterInfo else -> SManga.UNKNOWN
lateinit var projectData: ProjectParser.ProjectData
}
class Manga : SManga {
override lateinit var url: String
override lateinit var title: String
override var artist: String? = null
override var author: String? = null
override var description: String? = null
override var genre: String? = null
override var status: Int = 0
override var thumbnail_url: String? = null
override var initialized: Boolean = false
lateinit var projectData: ProjectParser.ProjectData
}
} }
inner class ProjectParser { private fun fetchMangas(page: Int, tracker: MangaListTracker): Observable<MangasPage> {
if (page == 1) {
tracker.list.clear()
tracker.offset = 0
}
inner class ProjectData { return client.newCall(latestUpdatesRequest(page + tracker.offset))
inner class ProjectInfo { .asObservableSuccess()
var np_project_id: Int = 0 .concatMap { response ->
var np_name: String = "" latestUpdatesParse(response).let {
var np_info: String? = null if (it.mangas.isEmpty() && it.hasNextPage) {
var np_view: Int = 0 tracker.offset++
var np_no_chapter: Int = 0 fetchLatestUpdates(page)
var np_created_date: Long? = null } else {
var np_updated_date: Long? = null Observable.just(it)
var np_status: Int = 0 }
var np_author: String? = null
var np_artist: String? = null
}
inner class ChapterInfo {
var nc_chapter_id: Int = 0
var nc_chapter_no: Float = 0f
var nc_chapter_name: String = ""
var nc_provider: String? = null
var nc_created_date: Long? = null
var nc_owner_id: Int? = null
var nc_data_file: String = ""
var legacy_data_file: Boolean = false
fun getChapterJsonFolder(): String = "${baseFileUrl}collectManga/${info.np_project_id}/$nc_chapter_id/"
val sChapter: NP.Chapter
get() = NP.Chapter().apply {
if (nc_chapter_no - nc_chapter_no.toInt() == 0f) setUrlWithoutDomain("${info.np_project_id}/${nc_chapter_no.toInt()}")
else setUrlWithoutDomain("${info.np_project_id}/$nc_chapter_no")
name = nc_chapter_name
if (nc_created_date != null) date_upload = nc_created_date!!
chapter_number = nc_chapter_no
scanlator = nc_provider
chapterData = this@ChapterInfo
projectData = this@ProjectData
}.also { chapterListMap[nc_chapter_no] = this }
}
inner class ProjectCate {
var npc_id: Int = 0
var npc_name: String = ""
var npc_name_link: String = ""
}
val sManga: NP.Manga
get() = NP.Manga().apply {
setUrlWithoutDomain("${info.np_project_id}")
title = info.np_name
artist = info.np_artist
author = info.np_author
description = info.np_info
genre = projectCate.joinToString(", ") { it.npc_name }
status = info.np_status
thumbnail_url = getCoverUrl(this@ProjectData)
projectData = this@ProjectData
} }
fun getChapterData(chapterNo: Float): ChapterInfo? =
if (chapterListMap.contains(chapterNo)) chapterListMap[chapterNo]
else chapterList.find { it.nc_chapter_no == chapterNo }
var info: ProjectInfo = ProjectInfo()
var chapterList: List<ChapterInfo> = emptyList()
private val chapterListMap: HashMap<Float, ProjectParser.ProjectData.ChapterInfo> = HashMap()
var projectCate: List<ProjectCate> = emptyList()
}
private fun getProjectJsonFolder(projectID: Int): String = projectID.toDouble().let {
(it / 1000.0 - (it % 1000.0) / 1000.0).let { _tmp ->
var tmp = _tmp
if (projectID % 1000 != 0) tmp += 1
tmp *= 1000
tmp.toInt().toString().padStart(6, '0')
} }
}
private fun getProjectDataUrl(projectID: Int): String = "${baseFileUrl}collectJson/${getProjectJsonFolder(projectID)}/$projectID/${projectID}dtl.json"
private fun getStatus(status: String) = when (status) {
"1" -> SManga.ONGOING
"2" -> SManga.COMPLETED
"3" -> SManga.LICENSED
else -> SManga.UNKNOWN
}
fun getCoverUrl(projectData: ProjectData): String = "${baseFileUrl}collectManga/${projectData.info.np_project_id}/${projectData.info.np_project_id}_cover.jpg"
fun getProjectData(projectID: Int): ProjectData {
return if (projectList.contains(projectID)) projectList[projectID]!!
else JSONObject(URL(getProjectDataUrl(projectID)).readText())
.let {
ProjectData().apply {
info = it.getJSONObject("info").let { pInfo ->
ProjectInfo().apply {
np_project_id = pInfo.getString("np_project_id").toInt()
np_name = pInfo.getString("np_name")
np_info = pInfo.getString("np_info")
np_view = pInfo.getString("np_view").toInt()
np_no_chapter = pInfo.getString("np_no_chapter").toInt()
np_created_date = NPUtils.convertDateStringToEpoch(pInfo.getString("np_created_date"), "yyyy-MM-dd hh:mm:ss")
np_updated_date = NPUtils.convertDateStringToEpoch(pInfo.getString("np_updated_date"), "yyyy-MM-dd hh:mm:ss")
np_status = getStatus(pInfo.getString("np_status"))
np_author = pInfo.getString("np_author")
np_artist = pInfo.getString("np_artist")
}
}
chapterList = it.getJSONArray("chapterList").let { chListData ->
val chList = ArrayList<ProjectData.ChapterInfo>()
for (chIndex in 0 until chListData.length()) {
val chInfo = chListData.getJSONObject(chIndex)
chList.add(
ChapterInfo().apply {
nc_chapter_id = chInfo.getString("nc_chapter_id").toInt()
nc_chapter_no = chInfo.getString("nc_chapter_no").toFloat()
nc_chapter_name = chInfo.getString("nc_chapter_name")
nc_provider = chInfo.getString("nc_provider")
nc_created_date = chInfo.getString("nc_created_date").let {
it.split("-").toTypedArray().apply {
this[1] = (NPUtils.monthList.indexOf(this[1].toUpperCase(Locale.ROOT)) + 1).toString().padStart(2, '0')
}
}.joinToString("-").let { NPUtils.convertDateStringToEpoch(it) }
nc_owner_id = chInfo.getString("nc_owner_id").toInt()
nc_data_file = chInfo.getString("nc_data_file").let {
if (it.isNullOrBlank()) {
legacy_data_file = true
if (nc_chapter_no - nc_chapter_no.toInt() == 0f)
nc_chapter_no.toInt().toString()
else
nc_chapter_no.toString()
} else {
it
}
}
}
)
}
chList
}
projectCate = it.getJSONArray("projectCate").let { cateListData ->
val cateList = ArrayList<ProjectData.ProjectCate>()
for (cateIndex in 0 until cateListData.length()) {
val cateInfo = cateListData.getJSONObject(cateIndex)
if (cateInfo.getString("project_id") != "null") {
cateList.add(
ProjectCate().apply {
npc_id = cateInfo.getString("npc_id").toInt()
npc_name = cateInfo.getString("npc_name")
npc_name_link = cateInfo.getString("npc_name_link")
}
)
}
}
cateList
}
}.also { projectList[projectID] = it }
}
}
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { private fun mangasRequest(page: Int): Request = GET("$mangaListUrl${page - 1}")
return if (manga.status != SManga.LICENSED) {
Observable.just( private fun mangasParse(response: Response, tracker: MangaListTracker): MangasPage {
projectParser.getProjectData(manga.url.toInt()).chapterList.map { it.sChapter } val mangaData = Gson().fromJson(response.body!!.string(), RawMangaDataList::class.java)
)
return if (mangaData.listItem != null) {
val mangas: List<SManga> = mangaData.listItem.filter {
!tracker.list.contains(it.np_project_id)
}.map {
tracker.list.add(it.np_project_id)
SManga.create().apply {
url = it.np_project_id
title = it.np_name
thumbnail_url = "${fileUrl}collectManga/${it.np_project_id}/${it.np_project_id}_cover.jpg"
initialized = false
projectUrlMap[it.np_project_id] = ProjectRecord(
project = this,
project_id = it.np_project_id
)
}
}
MangasPage(mangas, true)
} else { } else {
Observable.error(Exception("Licensed - No chapters to show")) MangasPage(emptyList(), true)
} }
} }
@ -278,200 +125,149 @@ class Nekopost : ParsedHttpSource() {
override fun imageUrlParse(document: Document): String = throw NotImplementedError("Unused") override fun imageUrlParse(document: Document): String = throw NotImplementedError("Unused")
private var latestUpdatePageOffset: Int = 0 override fun fetchLatestUpdates(page: Int): Observable<MangasPage> = fetchMangas(page, latestMangaTracker)
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { override fun latestUpdatesParse(response: Response): MangasPage = mangasParse(response, latestMangaTracker)
if (page == 1) {
latestMangaList = HashSet()
latestUpdatePageOffset = 0
}
return client.newCall(latestUpdatesRequest(page + latestUpdatePageOffset)) override fun latestUpdatesFromElement(element: Element): SManga = throw Exception("Unused")
.asObservableSuccess()
.concatMap { response ->
latestUpdatesParse(response).let {
if ((it.mangas as NPArrayList<SManga>).isListEmpty() && it.mangas.isNotEmpty()) {
latestUpdatePageOffset++
fetchLatestUpdates(page)
} else Observable.just(it)
}
}
}
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesNextPageSelector(): String = throw Exception("Unused")
val document = response.asJsoup()
val mangaList = document.select(latestUpdatesSelector()).filter { element -> override fun latestUpdatesRequest(page: Int): Request = mangasRequest(page)
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)]) override fun latestUpdatesSelector(): String = throw Exception("Unused")
}
val mangas = NPArrayList(
mangaList.map { element -> latestUpdatesFromElement(element) }.filter { manga ->
if (!latestMangaList.contains(manga.url)) {
latestMangaList.add(manga.url)
true
} else false
},
mangaList
)
val hasNextPage = mangaList.isNotEmpty()
return MangasPage(mangas, hasNextPage)
}
override fun latestUpdatesFromElement(element: Element): SManga {
val projectID = NPUtils.getMangaOrChapterAlias(element.select("a").attr("href")).toInt()
return projectParser.getProjectData(projectID).sManga
}
override fun latestUpdatesNextPageSelector(): String? = throw Exception("Unused")
override fun latestUpdatesRequest(page: Int): Request = GET("$mangaListUrl/${page - 1}")
override fun latestUpdatesSelector(): String = "a[href]"
override fun fetchMangaDetails(manga: SManga): Observable<SManga> = Observable.just(projectParser.getProjectData(manga.url.toInt()).sManga)
override fun mangaDetailsParse(document: Document): SManga = throw NotImplementedError("Unused") override fun mangaDetailsParse(document: Document): SManga = throw NotImplementedError("Unused")
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { override fun fetchMangaDetails(sManga: SManga): Observable<SManga> {
val chData = chapter.url.split("/") val manga = projectUrlMap[sManga.url]!!
val pj = projectParser.getProjectData(chData[0].toInt())
val ch = pj.getChapterData(chData[1].toFloat())!!
val pageList: ArrayList<Page> = ArrayList()
if (ch.legacy_data_file) { return client.newCall(GET("$projectDataUrl${manga.project_id}"))
JSONArray(URL("${legacyChapterDataUrl}${pj.info.np_project_id}/${ch.nc_data_file}").readText()).getJSONArray(3).let { pageItem -> .asObservableSuccess()
for (pageIndex in 0 until pageItem.length()) { .concatMap {
pageList.add( val mangaData = Gson().fromJson(it.body!!.string(), RawMangaDetailedData::class.java)
pageItem.getJSONObject(pageIndex).let { pageData ->
Page( Observable.just(
pageData.getString("page_no").toInt() - 1, manga.project.apply {
"", mangaData.projectInfo.also { projectData ->
"${ch.getChapterJsonFolder()}${pageData.getString("value_url")}" artist = projectData.artist_name
) author = projectData.author_name
description = projectData.np_info
status = getStatus(projectData.np_status)
initialized = true
} }
) genre = mangaData.projectCategoryUsed?.joinToString(", ") { cat -> cat.npc_name }
} ?: ""
}
)
} }
}
override fun fetchChapterList(sManga: SManga): Observable<List<SChapter>> {
val manga = projectUrlMap[sManga.url]!!
return if (manga.project.status != SManga.LICENSED) {
client.newCall(GET("$projectDataUrl${manga.project_id}"))
.asObservableSuccess()
.map {
val mangaData = Gson().fromJson(it.body!!.string(), RawMangaDetailedData::class.java)
mangaData.projectChapterList.map { chapter ->
val chapterUrl = "$baseUrl${manga.project_id}/${chapter.nc_chapter_no}"
manga.chapter_list.add(chapterUrl)
val createdChapter = SChapter.create().apply {
url = chapterUrl
name = chapter.nc_chapter_name
date_upload = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale("th")).parse(chapter.nc_created_date)?.time
?: 0L
chapter_number = chapter.nc_chapter_no.toFloat()
scanlator = chapter.cu_displayname
}
chapterList[chapterUrl] = ChapterRecord(
chapter = createdChapter,
project = manga,
chapter_id = chapter.nc_chapter_id,
pages_data = chapter.nc_data_file,
)
createdChapter
}
}
} else { } else {
JSONObject(URL("${ch.getChapterJsonFolder()}${ch.nc_data_file}").readText()).getJSONArray("pageItem").let { pageItem -> Observable.error(Exception("Licensed - No chapter to show"))
for (pageIndex in 0 until pageItem.length()) { }
pageList.add( }
pageItem.getJSONObject(pageIndex).let { pageData ->
Page( override fun fetchPageList(sChapter: SChapter): Observable<List<Page>> {
pageData.getInt("pageNo") - 1, val chapter = chapterList[sChapter.url]!!
"",
"${ch.getChapterJsonFolder()}${pageData.getString("fileName")}" return client.newCall(GET("${fileUrl}collectManga/${chapter.project.project_id}/${chapter.chapter_id}/${chapter.pages_data}"))
) .asObservableSuccess()
} .map {
val chapterData = Gson().fromJson(it.body!!.string(), RawChapterDetailedData::class.java)
chapterData.pageItem.map { pageData ->
Page(
index = pageData.pageNo,
imageUrl = "${fileUrl}collectManga/${chapter.project.project_id}/${chapter.chapter_id}/${pageData.fileName}",
) )
} }
} }
}
return Observable.just(pageList)
} }
override fun pageListParse(document: Document): List<Page> = throw NotImplementedError("Unused") override fun pageListParse(document: Document): List<Page> = throw NotImplementedError("Unused")
private var popularMangaPageOffset: Int = 0 override fun fetchPopularManga(page: Int): Observable<MangasPage> = fetchMangas(page, popularMangaTracker)
override fun fetchPopularManga(page: Int): Observable<MangasPage> { override fun popularMangaParse(response: Response): MangasPage = mangasParse(response, popularMangaTracker)
if (page == 1) {
popularMangaList = HashSet()
popularMangaPageOffset = 0
}
return client.newCall(popularMangaRequest(page + popularMangaPageOffset)) override fun popularMangaFromElement(element: Element): SManga = throw NotImplementedError("Unused")
override fun popularMangaNextPageSelector(): String = throw Exception("Unused")
override fun popularMangaRequest(page: Int): Request = mangasRequest(page)
override fun popularMangaSelector(): String = throw Exception("Unused")
override fun searchMangaFromElement(element: Element): SManga = throw Exception("Unused")
override fun searchMangaNextPageSelector(): String = throw Exception("Unused")
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(GET("${fileUrl}dataJson/dataProjectName.json"))
.asObservableSuccess() .asObservableSuccess()
.concatMap { response -> .map {
popularMangaParse(response).let { val nameData = Gson().fromJson(it.body!!.string(), Array<MangaNameList>::class.java)
if ((it.mangas as NPArrayList<SManga>).isListEmpty() && it.mangas.isNotEmpty()) {
popularMangaPageOffset++ val mangas: List<SManga> = nameData.filter { d -> Regex(query, setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)).find(d.np_name) != null }
fetchPopularManga(page) .map { matchedManga ->
} else Observable.just(it) if (!projectUrlMap.containsKey(matchedManga.np_project_id)) {
} SManga.create().apply {
url = matchedManga.np_project_id
title = matchedManga.np_name
thumbnail_url = "${fileUrl}collectManga/${matchedManga.np_project_id}/${matchedManga.np_project_id}_cover.jpg"
initialized = false
projectUrlMap[matchedManga.np_project_id] = ProjectRecord(
project = this,
project_id = matchedManga.np_project_id
)
}
} else {
projectUrlMap[matchedManga.np_project_id]!!.project
}
}
MangasPage(mangas, true)
} }
} }
override fun popularMangaParse(response: Response): MangasPage { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Unused")
val document = response.asJsoup()
val mangaList = document.select(popularMangaSelector()) override fun searchMangaParse(response: Response): MangasPage = throw Exception("Unused")
val mangas = NPArrayList( override fun searchMangaSelector(): String = throw Exception("Unused")
mangaList.map { element -> popularMangaFromElement(element) }.filter { manga ->
if (!popularMangaList.contains(manga.url)) {
popularMangaList.add(manga.url)
true
} else false
},
mangaList
)
val hasNextPage = mangaList.isNotEmpty()
return MangasPage(mangas, hasNextPage)
}
override fun popularMangaFromElement(element: Element): SManga = latestUpdatesFromElement(element)
override fun popularMangaNextPageSelector(): String? = latestUpdatesNextPageSelector()
override fun popularMangaRequest(page: Int): Request = latestUpdatesRequest(page)
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 = projectParser.getProjectData(NPUtils.getMangaOrChapterAlias(element.attr("href")).toInt()).sManga
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 .project_info a"
} }