Nekopost - Search Error "HTTP 404" Fix (#10833)
* fiex url and request model
* fix request model
* it's working so i made a check point
* everything working fine I've tested it
* rename PagingRequest to PagingInfo, remove default data from model
* use @SerialName for ProjectDate
* use filterNot instead of filter {!...}
* refactor: optimize code
- Use lazy initialization for SimpleDateFormat to avoid repeated instantiation
- Simplify property declarations with type inference
- Replace verbose null checks with Elvis operators and safe calls
- Eliminate unnecessary intermediate variables
- Combine existingProject.add() with map operation for better efficiency
- Inline object creation in SearchRequest constructor
- Use run block for cleaner null handling in popularMangaParse
- Reduce string concatenation by caching basePath in pageListParse
- Remove redundant status checks and variable assignments
tested BUILD SUCCESSFUL
tested fonctionality in mihon
This commit is contained in:
parent
2928fc45a6
commit
50ae4f3f06
@ -1,7 +1,7 @@
|
|||||||
ext {
|
ext {
|
||||||
extName = 'Nekopost'
|
extName = 'Nekopost'
|
||||||
extClass = '.Nekopost'
|
extClass = '.Nekopost'
|
||||||
extVersionCode = 12
|
extVersionCode = 13
|
||||||
isNsfw = true
|
isNsfw = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
package eu.kanade.tachiyomi.extension.th.nekopost
|
package eu.kanade.tachiyomi.extension.th.nekopost
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.PagingInfo
|
||||||
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawChapterInfo
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawChapterInfo
|
||||||
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectInfo
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectInfo
|
||||||
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSearchSummaryList
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSearchSummaryList
|
||||||
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSummaryList
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.RawProjectSummaryList
|
||||||
import eu.kanade.tachiyomi.extension.th.nekopost.model.SearchRequest
|
import eu.kanade.tachiyomi.extension.th.nekopost.model.SearchRequest
|
||||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.source.model.FilterList
|
import eu.kanade.tachiyomi.source.model.FilterList
|
||||||
@ -28,26 +28,25 @@ import java.util.Locale
|
|||||||
|
|
||||||
class Nekopost : HttpSource() {
|
class Nekopost : HttpSource() {
|
||||||
private val json: Json by injectLazy()
|
private val json: Json by injectLazy()
|
||||||
override val baseUrl: String = "https://www.nekopost.net"
|
|
||||||
|
|
||||||
private val latestMangaEndpoint: String = "https://api.osemocphoto.com/frontAPI/getLatestChapter/m"
|
|
||||||
private val projectDataEndpoint: String = "https://api.osemocphoto.com/frontAPI/getProjectInfo"
|
|
||||||
private val fileHost: String = "https://www.osemocphoto.com"
|
|
||||||
|
|
||||||
|
override val baseUrl = "https://www.nekopost.net"
|
||||||
|
override val lang = "th"
|
||||||
|
override val name = "Nekopost"
|
||||||
|
override val supportsLatest = false
|
||||||
override val client: OkHttpClient = network.cloudflareClient
|
override val client: OkHttpClient = network.cloudflareClient
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
private val latestMangaEndpoint = "https://api.osemocphoto.com/frontAPI/getLatestChapter/m"
|
||||||
return super.headersBuilder().add("Referer", "$baseUrl/")
|
private val projectDataEndpoint = "https://api.osemocphoto.com/frontAPI/getProjectInfo"
|
||||||
|
private val fileHost = "https://www.osemocphoto.com"
|
||||||
|
|
||||||
|
private val existingProject = HashSet<String>()
|
||||||
|
private var firstPageNulled = false
|
||||||
|
|
||||||
|
private val dateFormat by lazy {
|
||||||
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale("th"))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val existingProject: HashSet<String> = HashSet()
|
override fun headersBuilder() = super.headersBuilder().add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
private var firstPageNulled: Boolean = false
|
|
||||||
|
|
||||||
override val lang: String = "th"
|
|
||||||
override val name: String = "Nekopost"
|
|
||||||
|
|
||||||
override val supportsLatest: Boolean = false
|
|
||||||
|
|
||||||
private fun getStatus(status: String) = when (status) {
|
private fun getStatus(status: String) = when (status) {
|
||||||
"1" -> SManga.ONGOING
|
"1" -> SManga.ONGOING
|
||||||
@ -57,24 +56,17 @@ class Nekopost : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException()
|
override fun imageUrlRequest(page: Page): Request = throw UnsupportedOperationException()
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga) = GET("$projectDataEndpoint/${manga.url}", headers)
|
||||||
return GET("$projectDataEndpoint/${manga.url}", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga) = "$baseUrl/manga/${manga.url}"
|
override fun getMangaUrl(manga: SManga) = "$baseUrl/manga/${manga.url}"
|
||||||
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga {
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
val responseBody = response.body
|
val projectInfo: RawProjectInfo = json.decodeFromString(response.body.string())
|
||||||
val projectInfo: RawProjectInfo = json.decodeFromString(responseBody.string())
|
|
||||||
val manga = SManga.create()
|
return SManga.create().apply {
|
||||||
manga.apply {
|
|
||||||
projectInfo.projectInfo.let {
|
projectInfo.projectInfo.let {
|
||||||
url = it.projectId
|
url = it.projectId
|
||||||
title = it.projectName
|
title = it.projectName
|
||||||
@ -85,14 +77,8 @@ class Nekopost : HttpSource() {
|
|||||||
thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg"
|
thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg"
|
||||||
initialized = true
|
initialized = true
|
||||||
}
|
}
|
||||||
|
genre = projectInfo.projectCategoryUsed?.joinToString(", ") { it.categoryName }.orEmpty()
|
||||||
genre = if (projectInfo.projectCategoryUsed != null) {
|
|
||||||
projectInfo.projectCategoryUsed.joinToString(", ") { it.categoryName }
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return manga
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
@ -101,101 +87,96 @@ class Nekopost : HttpSource() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
val responseBody = response.body.string()
|
val projectInfo: RawProjectInfo = json.decodeFromString(response.body.string())
|
||||||
val projectInfo: RawProjectInfo = json.decodeFromString(responseBody)
|
|
||||||
val manga = SManga.create()
|
|
||||||
manga.status = getStatus(projectInfo.projectInfo.status)
|
|
||||||
|
|
||||||
if (manga.status == SManga.LICENSED) {
|
if (getStatus(projectInfo.projectInfo.status) == SManga.LICENSED) {
|
||||||
throw Exception("Licensed - No chapter to show")
|
throw Exception("Licensed - No chapter to show")
|
||||||
}
|
}
|
||||||
|
|
||||||
return projectInfo.projectChapterList!!.map { chapter ->
|
val projectId = projectInfo.projectInfo.projectId.toInt()
|
||||||
|
|
||||||
|
return projectInfo.projectChapterList?.map { chapter ->
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = "${projectInfo.projectInfo.projectId.toInt()}/${chapter.chapterId}/${projectInfo.projectInfo.projectId.toInt()}_${chapter.chapterId}.json"
|
url = "$projectId/${chapter.chapterId}/${projectId}_${chapter.chapterId}.json"
|
||||||
name = chapter.chapterName
|
name = chapter.chapterName
|
||||||
date_upload = SimpleDateFormat(
|
date_upload = dateFormat.parse(chapter.createDate)?.time ?: 0L
|
||||||
"yyyy-MM-dd HH:mm:ss",
|
|
||||||
Locale("th"),
|
|
||||||
).parse(chapter.createDate)?.time ?: 0L
|
|
||||||
chapter_number = chapter.chapterNo.toFloat()
|
chapter_number = chapter.chapterNo.toFloat()
|
||||||
scanlator = chapter.providerName
|
scanlator = chapter.providerName
|
||||||
}
|
}
|
||||||
}
|
} ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter): Request {
|
override fun pageListRequest(chapter: SChapter) = GET("$fileHost/collectManga/${chapter.url}", headers)
|
||||||
return GET("$fileHost/collectManga/${chapter.url}", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getChapterUrl(chapter: SChapter) =
|
override fun getChapterUrl(chapter: SChapter) =
|
||||||
"$baseUrl/manga/${chapter.url.substringBefore("/")}/${chapter.chapter_number.toString().removeSuffix(".0")}"
|
"$baseUrl/manga/${chapter.url.substringBefore("/")}/${chapter.chapter_number.toString().removeSuffix(".0")}"
|
||||||
|
|
||||||
override fun pageListParse(response: Response): List<Page> {
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
val responseBody = response.body
|
val chapterInfo: RawChapterInfo = json.decodeFromString(response.body.string())
|
||||||
val chapterInfo: RawChapterInfo = json.decodeFromString(responseBody.string())
|
val basePath = "$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}"
|
||||||
|
|
||||||
return chapterInfo.pageItem.map { page ->
|
return chapterInfo.pageItem.map { page ->
|
||||||
val imgUrl: String = if (page.pageName != null) {
|
|
||||||
"$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.pageName}"
|
|
||||||
} else {
|
|
||||||
"$fileHost/collectManga/${chapterInfo.projectId}/${chapterInfo.chapterId}/${page.fileName}"
|
|
||||||
}
|
|
||||||
Page(
|
Page(
|
||||||
index = page.pageNo,
|
index = page.pageNo,
|
||||||
imageUrl = imgUrl,
|
imageUrl = "$basePath/${page.pageName ?: page.fileName}",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
if (page <= 1) existingProject.clear()
|
if (page <= 1) existingProject.clear()
|
||||||
// API has a bug that sometime it returns null on first page
|
|
||||||
return GET("$latestMangaEndpoint/${if (firstPageNulled) page else page - 1}", headers)
|
return GET("$latestMangaEndpoint/${if (firstPageNulled) page else page - 1}", headers)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
val responseBody = response.body
|
val projectList: RawProjectSummaryList = json.decodeFromString(response.body.string())
|
||||||
val projectList: RawProjectSummaryList = json.decodeFromString(responseBody.string())
|
|
||||||
|
|
||||||
val mangaList: List<SManga> = if (projectList.listChapter != null) {
|
projectList.listChapter ?: run {
|
||||||
projectList.listChapter.filter { !existingProject.contains(it.projectId) }.map {
|
firstPageNulled = true
|
||||||
|
return MangasPage(emptyList(), hasNextPage = false)
|
||||||
|
}
|
||||||
|
|
||||||
|
val mangaList = projectList.listChapter
|
||||||
|
.filterNot { it.projectId in existingProject }
|
||||||
|
.map {
|
||||||
|
existingProject.add(it.projectId)
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = it.projectId
|
url = it.projectId
|
||||||
title = it.projectName
|
title = it.projectName
|
||||||
thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg"
|
thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg"
|
||||||
initialized = false
|
initialized = false
|
||||||
status = 0
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
firstPageNulled = true // API has a bug that sometime it returns null on first page
|
|
||||||
return MangasPage(emptyList(), hasNextPage = false)
|
|
||||||
}
|
|
||||||
|
|
||||||
mangaList.forEach { existingProject.add(it.url) }
|
|
||||||
|
|
||||||
return MangasPage(mangaList, hasNextPage = true)
|
return MangasPage(mangaList, hasNextPage = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val headers = Headers.headersOf("accept", "*/*", "content-type", "text/plain;charset=UTF-8", "origin", baseUrl)
|
val searchHeaders = headersBuilder()
|
||||||
val requestBody = Json.encodeToString(SearchRequest(query, page)).toRequestBody()
|
.set("Accept", "*/*")
|
||||||
return POST("$baseUrl/api/explore/search", headers, requestBody)
|
.set("Content-Type", "application/json")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val searchData = SearchRequest(
|
||||||
|
keyword = query,
|
||||||
|
status = 0,
|
||||||
|
paging = PagingInfo(pageNo = page, pageSize = 100),
|
||||||
|
)
|
||||||
|
|
||||||
|
return POST("$baseUrl/api/project/search", searchHeaders, Json.encodeToString(searchData).toRequestBody())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
val responseBody = response.body.string()
|
val projectList: RawProjectSearchSummaryList = json.decodeFromString(response.body.string())
|
||||||
val decrypted = CryptoAES.decrypt(responseBody, "AeyTest")
|
|
||||||
|
|
||||||
val projectList: RawProjectSearchSummaryList = json.decodeFromString(decrypted)
|
val mangaList = projectList.listProject
|
||||||
val mangaList: List<SManga> = projectList.listProject
|
|
||||||
.filter { it.projectType == "m" }
|
.filter { it.projectType == "m" }
|
||||||
.map {
|
.map {
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = it.projectId.toString()
|
url = it.pid.toString()
|
||||||
title = it.projectName
|
title = it.projectName
|
||||||
status = it.status
|
status = it.status
|
||||||
thumbnail_url = "$fileHost/collectManga/${it.projectId}/${it.projectId}_cover.jpg?ver=${it.coverVersion}"
|
thumbnail_url = "$fileHost/collectManga/${it.pid}/${it.pid}_cover.jpg?ver=${it.coverVersion}"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,15 +5,39 @@ import kotlinx.serialization.Serializable
|
|||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class RawProjectSearchSummary(
|
data class RawProjectSearchSummary(
|
||||||
val projectId: Int,
|
val pid: Int,
|
||||||
val projectName: String,
|
val projectName: String,
|
||||||
val projectType: String,
|
val aliasName: String,
|
||||||
@SerialName("STATUS")
|
val website: String,
|
||||||
|
val authorId: Int,
|
||||||
|
val authorName: String,
|
||||||
|
val artistId: Int,
|
||||||
|
val artistName: String,
|
||||||
|
val info: String,
|
||||||
val status: Int,
|
val status: Int,
|
||||||
|
val flgMature: String,
|
||||||
|
val flgIntense: String,
|
||||||
|
val flgViolent: String,
|
||||||
|
val flgGlue: String,
|
||||||
|
val flgReligion: String,
|
||||||
|
val flgHidemeta: String,
|
||||||
|
val mainCategory: String,
|
||||||
|
val goingType: String,
|
||||||
|
val projectType: String,
|
||||||
|
val readerGroup: String,
|
||||||
|
val releaseDate: ProjectDate = ProjectDate(),
|
||||||
|
val updateDate: ProjectDate = ProjectDate(),
|
||||||
|
val views: Int,
|
||||||
|
val imageVersion: Int,
|
||||||
val noChapter: Int,
|
val noChapter: Int,
|
||||||
val coverVersion: Int,
|
val coverVersion: Int,
|
||||||
val info: String,
|
val concatCate: String,
|
||||||
val views: Int,
|
val editorId: Int,
|
||||||
@SerialName("lastUpdate")
|
val editorName: String,
|
||||||
val lastUpdateDate: String,
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ProjectDate(
|
||||||
|
@SerialName("String") val string: String = "",
|
||||||
|
@SerialName("Valid") val valid: Boolean = false,
|
||||||
)
|
)
|
||||||
|
|||||||
@ -5,5 +5,4 @@ import kotlinx.serialization.Serializable
|
|||||||
@Serializable
|
@Serializable
|
||||||
data class RawProjectSearchSummaryList(
|
data class RawProjectSearchSummaryList(
|
||||||
val listProject: List<RawProjectSearchSummary>,
|
val listProject: List<RawProjectSearchSummary>,
|
||||||
val totalRecord: String,
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -2,8 +2,15 @@ package eu.kanade.tachiyomi.extension.th.nekopost.model
|
|||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PagingInfo(
|
||||||
|
val pageNo: Int,
|
||||||
|
val pageSize: Int,
|
||||||
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class SearchRequest(
|
data class SearchRequest(
|
||||||
val keyword: String,
|
val keyword: String,
|
||||||
val pageNo: Int,
|
val status: Int,
|
||||||
|
val paging: PagingInfo,
|
||||||
)
|
)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user