Add viewer_data download

This commit is contained in:
2026-03-07 23:58:53 +09:00
parent 58f7a22aef
commit f602523d02
2 changed files with 41 additions and 19 deletions

View File

@@ -11,12 +11,18 @@ import io.ktor.client.request.request
import io.ktor.client.request.setBody import io.ktor.client.request.setBody
import io.ktor.client.statement.bodyAsText import io.ktor.client.statement.bodyAsText
import io.ktor.http.HttpHeaders import io.ktor.http.HttpHeaders
import io.ktor.http.contentLength
import io.ktor.http.isSuccess import io.ktor.http.isSuccess
import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone import kotlinx.datetime.TimeZone
import kotlinx.datetime.number import kotlinx.datetime.number
import kotlinx.datetime.todayIn import kotlinx.datetime.todayIn
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import kotlin.time.Clock import kotlin.time.Clock
class NpiaDownloader { class NpiaDownloader {
@@ -31,12 +37,13 @@ class NpiaDownloader {
} }
object Endpoints { object Endpoints {
private const val BASE_URL = "https://novelpia.com" const val BASE_URL = "https://novelpia.com"
const val PROC_URL = "$BASE_URL/proc" const val PROC_URL = "$BASE_URL/proc"
const val LOGIN_URL = "$PROC_URL/login" const val LOGIN_URL = "$PROC_URL/login"
const val LOGOUT_URL = "$PROC_URL/logout" const val LOGOUT_URL = "$PROC_URL/logout"
const val EPISODE_LIST_URL = "$PROC_URL/episode_list" const val EPISODE_LIST_URL = "$PROC_URL/episode_list"
const val NOVEL_URL = "$BASE_URL/novel" const val NOVEL_URL = "$BASE_URL/novel"
const val SCRIPT_URL = "$PROC_URL/viewer_data"
} }
val client = HttpClient { val client = HttpClient {
@@ -52,7 +59,11 @@ class NpiaDownloader {
} }
install(ContentNegotiation) { install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
} }
} }
@@ -185,26 +196,38 @@ class NpiaDownloader {
} }
suspend fun downloadEpisode(episodeId: Int): List<ScriptLineData> { suspend fun downloadEpisode(episodeId: Int): List<ScriptLineData> {
val res = client.request("https://novelpia.com/novel/viewer/$episodeId") { val data = "size=14"
val res = client.post("${Endpoints.SCRIPT_URL}/$episodeId") {
header(HttpHeaders.Cookie, "LOGINKEY=$loginkey") header(HttpHeaders.Cookie, "LOGINKEY=$loginkey")
header(HttpHeaders.Origin, Endpoints.BASE_URL)
header(HttpHeaders.ContentType, "application/x-www-form-urlencoded; charset=UTF-8")
header(HttpHeaders.Referrer, "${Endpoints.BASE_URL}/viewer/$episodeId")
setBody(data)
} }
if (!res.status.isSuccess()) { if (!res.status.isSuccess()) {
throw Exception("Failed to fetch episode content: ${res.status}") throw Exception("Failed to fetch episode content: ${res.status}")
} }
println(res.bodyAsText())
return listOf() val json = Json.parseToJsonElement(res.bodyAsText()).jsonObject
val document = Ksoup.parse(res.bodyAsText())
val draw = document.select("#novel_drawing").first()!!; json["s"]!!.let { scriptJson ->
val lines: List<ScriptLineData> = draw.select(".line").map { val lines = mutableListOf<ScriptLineData>()
val imgsel = it.select("img") for (line in scriptJson.jsonArray) {
if (imgsel.isNotEmpty()) { val lineText = line.jsonObject["text"]!!.jsonPrimitive.content.replace("&nbsp;", " ").trimEnd().replace(Regex("<p style=\'height: 0px; width: 0px; overflow: hidden; opacity: 0; display: inline-block;\'>.*?</p>"), "")
val imageUrl = imgsel[0].attr("abs:src").ifEmpty { imgsel[0].attr("src") } if (lineText.startsWith("<img")) {
ScriptLineData.Image(imageUrl) val src = Ksoup.parse(lineText).selectFirst("img")?.attr("src") ?: continue
lines.add(ScriptLineData.Image(src))
} else { } else {
ScriptLineData.Html(it.html())
lines.add(ScriptLineData.Html(lineText))
} }
} }
return lines return lines
} }
}
} }

View File

@@ -41,9 +41,8 @@ class NpiaTest {
*/ */
val scripts = downloader.downloadEpisode(5221545) val scripts = downloader.downloadEpisode(5221545)
for (line in scripts) { for (line in scripts) {
println(line) println(line.toString())
} }
downloader.logout() downloader.logout()