Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib-multisrc/clipstudioreader/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@ plugins {
}

keiyoushi {
baseVersionCode = 1
libVersion = "1.4"
baseVersionCode = 2
libVersion = "1.6"
}
Original file line number Diff line number Diff line change
@@ -1,53 +1,57 @@
package eu.kanade.tachiyomi.multisrc.clipstudioreader

import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.network.get
import keiyoushi.source.KeiSource
import keiyoushi.utils.parseAs
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Response
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.parser.Parser

abstract class ClipStudioReader : HttpSource() {
abstract class ClipStudioReader : KeiSource() {

override val client = network.client.newBuilder()
override fun OkHttpClient.Builder.configureClient() = this
.addInterceptor(Deobfuscator())
.addInterceptor(ImageInterceptor())
.build()

override fun headersBuilder() = super.headersBuilder()
.set("Referer", "$baseUrl/")
override fun Headers.Builder.configureHeaders() = add("Referer", "$baseUrl/")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

referer is added by default


override fun pageListParse(response: Response): List<Page> {
abstract suspend fun getViewerResponse(chapter: SChapter): Response

override suspend fun getPageList(chapter: SChapter): List<Page> {
val response = getViewerResponse(chapter)
val requestUrl = response.request.url
val contentId = requestUrl.queryParameter("c")

if (contentId != null) {
// EPUB-based path
val tokenUrl = "$baseUrl/api/tokens/viewer?content_id=$contentId".toHttpUrl()
val tokenResponse = client.newCall(GET(tokenUrl, headers)).execute()
val tokenResponse = client.get(tokenUrl, headers)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

headers are passed implicitly, no need to pass them yourself

val viewerToken = tokenResponse.parseAs<TokenResponse>().token

val metaUrl = "$baseUrl/api/contents/$contentId/meta".toHttpUrl()
val apiHeaders = headersBuilder().add("Authorization", "Bearer $viewerToken").build()
val metaResponse = client.newCall(GET(metaUrl, apiHeaders)).execute()
val apiHeaders = headers.newBuilder().add("Authorization", "Bearer $viewerToken").build()
val metaResponse = client.get(metaUrl, apiHeaders)
val contentBaseUrl = metaResponse.parseAs<MetaResponse>().content.baseUrl

val preprocessUrl = "$contentBaseUrl/preprocess-settings.json"
val obfuscationResponse = client.newCall(GET(preprocessUrl, headers)).execute()
val obfuscationResponse = client.get(preprocessUrl, headers)
val obfuscationKey = obfuscationResponse.parseAs<PreprocessSettings>().obfuscateImageKey

val containerUrl = "$contentBaseUrl/META-INF/container.xml"
val containerResponse = client.newCall(GET(containerUrl, headers)).execute()
val containerResponse = client.get(containerUrl, headers)
val containerDoc = Jsoup.parse(containerResponse.body.string(), containerUrl, Parser.xmlParser())
val opfPath = containerDoc.selectFirst("*|rootfile")?.attr("full-path")
?: throw Exception("Failed to find rootfile in container.xml")

val opfUrl = (contentBaseUrl.removeSuffix("/") + "/" + opfPath).toHttpUrl()
val opfResponse = client.newCall(GET(opfUrl, headers)).execute()
val opfResponse = client.get(opfUrl, headers)
val opfDoc = opfResponse.asJsoup()

val imageManifestItems = opfDoc.select("*|item[media-type^=image/]")
Expand Down Expand Up @@ -89,7 +93,7 @@ abstract class ClipStudioReader : HttpSource() {
addQueryParameter("param", authkey)
}.build()

val faceResponse = client.newCall(GET(faceUrl, headers)).execute()
val faceResponse = client.get(faceUrl, headers)
if (!faceResponse.isSuccessful) throw Exception("HTTP error ${faceResponse.code} while fetching face.xml")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

client.get ensures http success

val faceData = faceResponse.use { parseFaceData(it.asJsoup()) }

Expand All @@ -106,7 +110,8 @@ abstract class ClipStudioReader : HttpSource() {
}
}

override fun imageUrlParse(response: Response): String {
override suspend fun getImageUrl(page: Page): String {
val response = client.get(page.url, headers)
val requestUrl = response.request.url
val document = response.asJsoup()

Expand Down
2 changes: 1 addition & 1 deletion src/ja/comicfesta/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ keiyoushi {
name = "Comic Festa"
versionCode = 1
contentWarning = ContentWarning.MIXED
libVersion = "1.4"
libVersion = "1.6"
theme = "clipstudioreader"

source {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@ package eu.kanade.tachiyomi.extension.ja.comicfesta
import androidx.preference.PreferenceScreen
import androidx.preference.SwitchPreferenceCompat
import eu.kanade.tachiyomi.multisrc.clipstudioreader.ClipStudioReader
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.multisrc.clipstudioreader.Deobfuscator
import eu.kanade.tachiyomi.multisrc.clipstudioreader.ImageInterceptor
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.SMangaUpdate
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.annotation.Source
import keiyoushi.lib.cookieinterceptor.CookieInterceptor
import keiyoushi.network.get
import keiyoushi.utils.extractNextJs
import keiyoushi.utils.getPreferencesLazy
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Request
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Response
import java.io.IOException

Expand All @@ -24,62 +28,65 @@ abstract class ComicFesta :
ClipStudioReader(),
ConfigurableSource {

override val supportsLatest = true

private val domain = baseUrl.toHttpUrl().host

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private val domain = baseUrl.toHttpUrl().host
private val domain get() = baseUrl.toHttpUrl().host

private val preferences by getPreferencesLazy()
private val rscHeaders = headersBuilder()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
private val rscHeaders = headersBuilder()
private val rscHeaders get() = headersBuilder()

.add("rsc", "1")
.build()

override val client = super.client.newBuilder()
.addNetworkInterceptor(CookieInterceptor(domain, listOf("checked_age" to "1", "sp_display" to "1", "cf_checked_age_guest" to "1", "cf_checked_age" to "1")))
.addInterceptor {
val request = it.request()
val response = it.proceed(request)
if (request.url.pathSegments.first() == "volumes" && (response.request.url.pathSegments.first() == "entry" || response.request.url.pathSegments.first() == "error")) {
throw IOException("Log in via WebView and purchase this product to read.")
}
response
}
.build()
override fun OkHttpClient.Builder.configureClient() = apply {
addInterceptor(Deobfuscator())
addInterceptor(ImageInterceptor())
Comment on lines +38 to +39

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

call super.configureClient instead

addNetworkInterceptor(CookieInterceptor(domain, listOf("checked_age" to "1", "sp_display" to "1", "cf_checked_age_guest" to "1", "cf_checked_age" to "1")))
addInterceptor(
Interceptor { chain ->
val request = chain.request()
val response = chain.proceed(request)
if (request.url.pathSegments.first() == "volumes" && (response.request.url.pathSegments.first() == "entry" || response.request.url.pathSegments.first() == "error")) {
throw IOException("Log in via WebView and purchase this product to read.")
}
response
},
)
}

override fun popularMangaRequest(page: Int): Request {
override fun getMangaUrl(manga: SManga): String = "$baseUrl/titles/${manga.url}"

override fun getChapterUrl(chapter: SChapter): String = "$baseUrl/volumes/${chapter.url}"

override suspend fun getPopularManga(page: Int): MangasPage {
val url = "$baseUrl/sales_rankings/monthly_general".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.build()
return GET(url, rscHeaders)
}

override fun popularMangaParse(response: Response): MangasPage {
val response = client.get(url, rscHeaders)
val result = response.extractNextJs<RankingResponse>()
val mangas = result?.titles.orEmpty().map { it.toSManga() }
val hasNextPage = response.request.url.queryParameter("page") != "2"
return MangasPage(mangas, hasNextPage)
}

override fun latestUpdatesRequest(page: Int): Request {
override suspend fun getLatestUpdates(page: Int): MangasPage {
val url = "$baseUrl/titles".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("sort", "release")
.addQueryParameter("search_form[other_item][]", "new")
.build()
return GET(url, headers)
val response = client.get(url, headers)
return parseSearchResponse(response)
}

override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)

override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
override suspend fun getSearchMangaList(page: Int, query: String, filters: FilterList): MangasPage {
val url = "$baseUrl/titles".toHttpUrl().newBuilder()
.addQueryParameter("page", page.toString())
.addQueryParameter("search_form[keyword]", query)
.addQueryParameter("search", query)
.addQueryParameter("commit", "search")
.build()
return GET(url, headers)
val response = client.get(url, headers)
return parseSearchResponse(response)
}

override fun searchMangaParse(response: Response): MangasPage {
private fun parseSearchResponse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select("div.list-detail-box").map {
SManga.create().apply {
Expand All @@ -92,30 +99,41 @@ abstract class ComicFesta :
return MangasPage(mangas, hasNextPage)
}

override fun mangaDetailsRequest(manga: SManga): Request = GET("$baseUrl/titles/${manga.url}", headers)

override fun mangaDetailsParse(response: Response): SManga {
override suspend fun fetchMangaUpdate(
manga: SManga,
chapters: List<SChapter>,
fetchDetails: Boolean,
fetchChapters: Boolean,
): SMangaUpdate {
val response = client.get("$baseUrl/titles/${manga.url}", headers)
val document = response.asJsoup()
return SManga.create().apply {
title = document.selectFirst("h1[class*='titleName']")!!.text()
author = document.select("a[href*='/authors/']").joinToString { it.text() }
description = document.selectFirst("div[class*='description']")?.text()
genre = document.select("[class*='outlineTag'] a").joinToString { it.text() }
thumbnail_url = document.selectFirst("img[class*='thumbnail']")?.absUrl("src")
val statusText = document.select("[class*='latest-package-num-display']").text()
status = if (statusText.contains("完結")) SManga.COMPLETED else SManga.ONGOING
val hideLocked = preferences.getBoolean(HIDE_LOCKED_PREF_KEY, false)

val sManga = if (fetchDetails) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you have all the info already, return both unconditionally

SManga.create().apply {
title = document.selectFirst("h1[class*='titleName']")!!.text()
author = document.select("a[href*='/authors/']").joinToString { it.text() }
description = document.selectFirst("div[class*='description']")?.text()
genre = document.select("[class*='outlineTag'] a").joinToString { it.text() }
thumbnail_url = document.selectFirst("img[class*='thumbnail']")?.absUrl("src")
val statusText = document.select("[class*='latest-package-num-display']").text()
status = if (statusText.contains("完結")) SManga.COMPLETED else SManga.ONGOING
}
} else {
manga
}
}

override fun chapterListRequest(manga: SManga): Request = mangaDetailsRequest(manga) // no redirect for r18 content with rsc
val sChapters = if (fetchChapters) {
val result = document.extractNextJs<ChapterListResponse>()
result?.toSChapterList(hideLocked).orEmpty().reversed()
} else {
chapters
}

override fun chapterListParse(response: Response): List<SChapter> {
val hideLocked = preferences.getBoolean(HIDE_LOCKED_PREF_KEY, false)
val result = response.extractNextJs<ChapterListResponse>()
return result?.toSChapterList(hideLocked).orEmpty().reversed()
return SMangaUpdate(sManga, sChapters)
}

override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/volumes/${chapter.url}", headers)
override suspend fun getViewerResponse(chapter: SChapter): Response = client.get("$baseUrl/volumes/${chapter.url}", headers)

override fun setupPreferenceScreen(screen: PreferenceScreen) {
SwitchPreferenceCompat(screen.context).apply {
Expand Down
2 changes: 1 addition & 1 deletion src/ja/firecross/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ keiyoushi {
name = "FireCross"
versionCode = 2
contentWarning = ContentWarning.SAFE
libVersion = "1.4"
libVersion = "1.6"
theme = "clipstudioreader"

source {
Expand Down
Loading