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:
parent
5845c336b5
commit
60693e465c
|
@ -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,
|
||||
)
|
|
@ -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")
|
||||
}
|
|
@ -1,36 +1,30 @@
|
|||
package eu.kanade.tachiyomi.extension.th.nekopost
|
||||
|
||||
import com.google.gson.Gson
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
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.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.ParsedHttpSource
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Headers
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONObject
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
import java.net.URL
|
||||
import java.util.Calendar
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import kotlin.collections.set
|
||||
|
||||
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 baseFileUrl: String = "https://fs.nekopost.net/"
|
||||
private val legacyChapterDataUrl: String = "https://www.nekopost.net/reader/loadChapterContent/"
|
||||
private val searchUrl: String = "https://www.nekopost.net/search/"
|
||||
private val mangaListUrl: String = "https://tuner.nekopost.net/ApiTest/getLatestChapterOffset/m/"
|
||||
private val projectDataUrl: String = "https://tuner.nekopost.net/ApiTest/getProjectDetailFull/"
|
||||
private val fileUrl: String = "https://fs.nekopost.net/"
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient
|
||||
|
||||
|
@ -43,230 +37,83 @@ class Nekopost : ParsedHttpSource() {
|
|||
|
||||
override val supportsLatest: Boolean = true
|
||||
|
||||
private var latestMangaList: HashSet<String> = HashSet()
|
||||
private var popularMangaList: HashSet<String> = HashSet()
|
||||
private data class MangaListTracker(
|
||||
var offset: Int = 0,
|
||||
val list: HashSet<String> = HashSet()
|
||||
)
|
||||
|
||||
private val projectList: HashMap<Int, ProjectParser.ProjectData> = HashMap()
|
||||
private val projectParser: ProjectParser = ProjectParser()
|
||||
private var latestMangaTracker = MangaListTracker()
|
||||
private var popularMangaTracker = MangaListTracker()
|
||||
|
||||
object NP {
|
||||
class Chapter : SChapter {
|
||||
override lateinit var url: String
|
||||
data class ProjectRecord(
|
||||
val project: SManga,
|
||||
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
|
||||
|
||||
override var scanlator: String? = null
|
||||
|
||||
lateinit var chapterData: ProjectParser.ProjectData.ChapterInfo
|
||||
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
|
||||
}
|
||||
private fun getStatus(status: String) = when (status) {
|
||||
"1" -> SManga.ONGOING
|
||||
"2" -> SManga.COMPLETED
|
||||
"3" -> SManga.LICENSED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
inner class ProjectParser {
|
||||
private fun fetchMangas(page: Int, tracker: MangaListTracker): Observable<MangasPage> {
|
||||
if (page == 1) {
|
||||
tracker.list.clear()
|
||||
tracker.offset = 0
|
||||
}
|
||||
|
||||
inner class ProjectData {
|
||||
inner class ProjectInfo {
|
||||
var np_project_id: Int = 0
|
||||
var np_name: String = ""
|
||||
var np_info: String? = null
|
||||
var np_view: Int = 0
|
||||
var np_no_chapter: Int = 0
|
||||
var np_created_date: Long? = null
|
||||
var np_updated_date: Long? = null
|
||||
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
|
||||
return client.newCall(latestUpdatesRequest(page + tracker.offset))
|
||||
.asObservableSuccess()
|
||||
.concatMap { response ->
|
||||
latestUpdatesParse(response).let {
|
||||
if (it.mangas.isEmpty() && it.hasNextPage) {
|
||||
tracker.offset++
|
||||
fetchLatestUpdates(page)
|
||||
} else {
|
||||
Observable.just(it)
|
||||
}
|
||||
}
|
||||
|
||||
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>> {
|
||||
return if (manga.status != SManga.LICENSED) {
|
||||
Observable.just(
|
||||
projectParser.getProjectData(manga.url.toInt()).chapterList.map { it.sChapter }
|
||||
)
|
||||
private fun mangasRequest(page: Int): Request = GET("$mangaListUrl${page - 1}")
|
||||
|
||||
private fun mangasParse(response: Response, tracker: MangaListTracker): MangasPage {
|
||||
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 {
|
||||
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")
|
||||
|
||||
private var latestUpdatePageOffset: Int = 0
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> = fetchMangas(page, latestMangaTracker)
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
if (page == 1) {
|
||||
latestMangaList = HashSet()
|
||||
latestUpdatePageOffset = 0
|
||||
}
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = mangasParse(response, latestMangaTracker)
|
||||
|
||||
return client.newCall(latestUpdatesRequest(page + latestUpdatePageOffset))
|
||||
.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 latestUpdatesFromElement(element: Element): SManga = throw Exception("Unused")
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
val document = response.asJsoup()
|
||||
override fun latestUpdatesNextPageSelector(): String = throw Exception("Unused")
|
||||
|
||||
val mangaList = document.select(latestUpdatesSelector()).filter { element ->
|
||||
val dateText = element.select(".date").text().trim()
|
||||
val currentDate = Calendar.getInstance(Locale("th"))
|
||||
override fun latestUpdatesRequest(page: Int): Request = mangasRequest(page)
|
||||
|
||||
dateText.contains(currentDate.get(Calendar.DATE).toString()) && dateText.contains(NPUtils.monthList[currentDate.get(Calendar.MONTH)])
|
||||
}
|
||||
|
||||
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 latestUpdatesSelector(): String = throw Exception("Unused")
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga = throw NotImplementedError("Unused")
|
||||
|
||||
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
|
||||
val chData = chapter.url.split("/")
|
||||
val pj = projectParser.getProjectData(chData[0].toInt())
|
||||
val ch = pj.getChapterData(chData[1].toFloat())!!
|
||||
val pageList: ArrayList<Page> = ArrayList()
|
||||
override fun fetchMangaDetails(sManga: SManga): Observable<SManga> {
|
||||
val manga = projectUrlMap[sManga.url]!!
|
||||
|
||||
if (ch.legacy_data_file) {
|
||||
JSONArray(URL("${legacyChapterDataUrl}${pj.info.np_project_id}/${ch.nc_data_file}").readText()).getJSONArray(3).let { pageItem ->
|
||||
for (pageIndex in 0 until pageItem.length()) {
|
||||
pageList.add(
|
||||
pageItem.getJSONObject(pageIndex).let { pageData ->
|
||||
Page(
|
||||
pageData.getString("page_no").toInt() - 1,
|
||||
"",
|
||||
"${ch.getChapterJsonFolder()}${pageData.getString("value_url")}"
|
||||
)
|
||||
return client.newCall(GET("$projectDataUrl${manga.project_id}"))
|
||||
.asObservableSuccess()
|
||||
.concatMap {
|
||||
val mangaData = Gson().fromJson(it.body!!.string(), RawMangaDetailedData::class.java)
|
||||
|
||||
Observable.just(
|
||||
manga.project.apply {
|
||||
mangaData.projectInfo.also { projectData ->
|
||||
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 {
|
||||
JSONObject(URL("${ch.getChapterJsonFolder()}${ch.nc_data_file}").readText()).getJSONArray("pageItem").let { pageItem ->
|
||||
for (pageIndex in 0 until pageItem.length()) {
|
||||
pageList.add(
|
||||
pageItem.getJSONObject(pageIndex).let { pageData ->
|
||||
Page(
|
||||
pageData.getInt("pageNo") - 1,
|
||||
"",
|
||||
"${ch.getChapterJsonFolder()}${pageData.getString("fileName")}"
|
||||
)
|
||||
}
|
||||
Observable.error(Exception("Licensed - No chapter to show"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchPageList(sChapter: SChapter): Observable<List<Page>> {
|
||||
val chapter = chapterList[sChapter.url]!!
|
||||
|
||||
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")
|
||||
|
||||
private var popularMangaPageOffset: Int = 0
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> = fetchMangas(page, popularMangaTracker)
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
if (page == 1) {
|
||||
popularMangaList = HashSet()
|
||||
popularMangaPageOffset = 0
|
||||
}
|
||||
override fun popularMangaParse(response: Response): MangasPage = mangasParse(response, popularMangaTracker)
|
||||
|
||||
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()
|
||||
.concatMap { response ->
|
||||
popularMangaParse(response).let {
|
||||
if ((it.mangas as NPArrayList<SManga>).isListEmpty() && it.mangas.isNotEmpty()) {
|
||||
popularMangaPageOffset++
|
||||
fetchPopularManga(page)
|
||||
} else Observable.just(it)
|
||||
}
|
||||
.map {
|
||||
val nameData = Gson().fromJson(it.body!!.string(), Array<MangaNameList>::class.java)
|
||||
|
||||
val mangas: List<SManga> = nameData.filter { d -> Regex(query, setOf(RegexOption.IGNORE_CASE, RegexOption.MULTILINE)).find(d.np_name) != null }
|
||||
.map { matchedManga ->
|
||||
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 {
|
||||
val document = response.asJsoup()
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw Exception("Unused")
|
||||
|
||||
val mangaList = document.select(popularMangaSelector())
|
||||
override fun searchMangaParse(response: Response): MangasPage = throw Exception("Unused")
|
||||
|
||||
val mangas = NPArrayList(
|
||||
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"
|
||||
override fun searchMangaSelector(): String = throw Exception("Unused")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue