ChampionCross/MangaCross: theme change (#11014)

fix championcross
This commit is contained in:
manti 2025-10-13 15:05:32 +02:00 committed by Draff
parent 9b874ae720
commit 83731ebae6
Signed by: Draff
GPG Key ID: E8A89F3211677653
8 changed files with 14 additions and 299 deletions

View File

@ -1,7 +1,10 @@
ext {
extName = 'Manga Cross'
extClass = '.MangaCross'
extVersionCode = 5
extName = "Champion Cross"
extClass = ".MangaCross"
themePkg = 'comiciviewer'
baseUrl = "https://championcross.jp"
overrideVersionCode = 5
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1,115 +1,12 @@
package eu.kanade.tachiyomi.extension.ja.mangacross
import android.util.Log
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.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
import kotlin.concurrent.thread
import eu.kanade.tachiyomi.multisrc.comiciviewer.ComiciViewer
class MangaCross : HttpSource() {
override val name = "Manga Cross"
override val lang = "ja"
override val baseUrl = "https://mangacross.jp"
override val supportsLatest = true
private val json: Json by injectLazy()
// Pagination does not work. 9999 is a dummy large number.
override fun popularMangaRequest(page: Int) = GET("$baseUrl/api/comics.json?count=9999", headers)
override fun popularMangaParse(response: Response) =
MangasPage(response.parseAs<MCComicList>().comics.map { it.toSManga() }, false)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/api/episodes.json?page=$page", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val result: MCEpisodeList = response.parseAs()
return MangasPage(result.episodes.map { it.comic!!.toSManga() }, result.current_page < result.total_pages)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
if (query.isNotEmpty()) {
GET("$baseUrl/api/comics/keywords/$query.json", headers)
} else {
when (val tag = filters.filterIsInstance<TagFilter>().firstOrNull()?.getTag()) {
null -> popularMangaRequest(page)
is MCComicCategory -> GET("$baseUrl/api/comics/categories/${tag.name}.json", headers)
is MCComicGenre -> GET("$baseUrl/api/comics/tags/${tag.name}.json", headers)
}
}
override fun searchMangaParse(response: Response) = popularMangaParse(response)
override fun fetchMangaDetails(manga: SManga): Observable<SManga> =
client.newCall(chapterListRequest(manga)).asObservableSuccess()
.map { mangaDetailsParse(it).apply { initialized = true } }
// mangaDetailsRequest untouched in order to let WebView open web page instead of json
override fun mangaDetailsParse(response: Response) = response.parseAs<MCComicDetails>().comic.toSManga()
override fun chapterListRequest(manga: SManga) = GET("$baseUrl/api${manga.url}.json", headers)
override fun chapterListParse(response: Response) = response.parseAs<MCComicDetails>().comic.toSChapterList()
override fun pageListParse(response: Response): List<Page> {
return try {
response.parseAs<MCViewer>().episode_pages.mapIndexed { i, it ->
Page(i, imageUrl = it.image.original_url)
}
} catch (e: SerializationException) {
throw Exception("Chapter is no longer available!")
}
}
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
private lateinit var tags: List<Pair<String, MCComicTag?>>
private var isFetchingTags = false
private fun fetchTags() {
if (isFetchingTags) return
isFetchingTags = true
thread {
try {
val response = client.newCall(GET("$baseUrl/api/menus.json", headers)).execute()
val filterList = response.parseAs<MCMenu>().toFilterList()
tags = listOf(Pair("All", null)) + filterList
} catch (e: Exception) {
Log.e("MangaCross", "Failed to fetch filters ($e)")
} finally {
isFetchingTags = false
}
}
}
override fun getFilterList() =
if (::tags.isInitialized) {
FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
TagFilter("Tag", tags),
)
} else {
fetchTags()
FilterList(
Filter.Header("Fetching tags..."),
Filter.Header("Go back to previous screen and retry."),
)
}
private class TagFilter(name: String, private val tags: List<Pair<String, MCComicTag?>>) :
Filter.Select<String>(name, tags.map { it.first }.toTypedArray()) {
fun getTag() = tags[state].second
}
private inline fun <reified T> Response.parseAs(): T = json.decodeFromStream(this.body.byteStream())
// MangaCross became ChampionCross
class MangaCross : ComiciViewer(
"Champion Cross",
"https://championcross.jp",
"ja",
) {
override val versionId = 2
}

View File

@ -1,185 +0,0 @@
package eu.kanade.tachiyomi.extension.ja.mangacross
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
import java.text.DateFormat.getDateTimeInstance
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
@Serializable
data class MCComicList(
val comics: List<MCComic>,
// Note: Pagination does not work. Pages after 1 return nothing.
// val current_count: Int,
// val current_page: Int?,
// val total_count: Int,
// val total_pages: Int,
)
// Useless fields are omitted while interesting ones are commented
@Serializable
data class MCComic(
val dir_name: String,
val title: String,
val author: String,
val comic_category: MCComicCategory? = null,
val comic_tags: List<MCComicGenre>,
val image_double_url: String, // horizontal
val list_image_double_url: String, // square
// val restricted: Boolean, // is NSFW
// Details below
val outline: String? = null,
val episodes: List<MCEpisode>? = null,
// val books: List<MCBook>? = null,
) {
fun toSManga() = SManga.create().apply {
url = "/comics/$dir_name"
title = this@MCComic.title
author = this@MCComic.author
description = getDescription()
genre = getGenre()
status = getStatus()
thumbnail_url = list_image_double_url
}
fun toSChapterList() = episodes!!
// .filter { it.status == "public" } // preserve private chapters in case user downloaded before
.map { it.toSChapter("/comics/$dir_name") }
private fun getDescription() = listOfNotNull(
episodes?.firstOrNull()?.getNextDatePrefix(),
outline?.stripHtml(),
).joinToString("\n")
private fun getGenre() = listOfNotNull(
comic_category?.display_name,
comic_tags.joinToString(", ") { it.name },
).joinToString(", ")
private fun getStatus() = when {
episodes?.firstOrNull()?.episode_next_date.isNullOrEmpty() -> SManga.UNKNOWN
else -> SManga.ONGOING
}
private fun String.stripHtml() = Jsoup.parseBodyFragment(this).text()
}
sealed class MCComicTag
@Serializable
data class MCComicCategory(val name: String, val display_name: String) : MCComicTag()
@Serializable
data class MCComicGenre(val name: String) : MCComicTag()
@Serializable
data class MCComicDetails(val comic: MCComic)
@Serializable
data class MCEpisodeList(
val episodes: List<MCEpisode>,
// Note: Pagination works.
val current_count: Int,
val current_page: Int,
val total_count: Int,
val total_pages: Int,
)
@Serializable
data class MCEpisode(
// val id: Long,
val volume: String,
val sort_volume: Int,
val title: String,
val publish_start: String, // all dates are in ISO time format
val publish_end: String?,
// Note: AFAIK these dates are always identical to those above.
// val member_publish_start: String,
// val member_publish_end: String?,
val status: String, // public or private
// val page_url: String,
// val list_image_double_url: String,
val episode_next_date: String?,
val next_date_customize_text: String?,
val comic: MCComic? = null, // in latest
) {
fun toSChapter(urlPrefix: String) = SChapter.create().apply {
url = "$urlPrefix/$sort_volume/viewer.json"
val prefix = if (status == "public") "" else "🔒 "
name = "$prefix$volume $title"
// milliseconds are always 000
date_upload = JST_FORMAT_LIST.parseJST(publish_start)!!.time
// show end date in scanlator field
scanlator = publish_end?.let { "~" + LOCAL_FORMAT_LIST.format(JST_FORMAT_LIST.parseJST(it)!!) }
}
fun getNextDatePrefix(): String? = when {
!episode_next_date.isNullOrEmpty() -> {
val date = JST_FORMAT_DESC.parseJST(episode_next_date)!!.apply {
time += 10 * 3600 * 1000 // 10 am JST
}
"【Next: ${LOCAL_FORMAT_DESC.format(date)}"
}
!next_date_customize_text.isNullOrEmpty() -> "$next_date_customize_text】"
else -> null
}
companion object {
// for thread-safety
private val JST_FORMAT_DESC = getJSTFormat()
private val JST_FORMAT_LIST = getJSTFormat()
private val LOCAL_FORMAT_DESC = getDateTimeInstance()
private val LOCAL_FORMAT_LIST = getDateTimeInstance()
private fun SimpleDateFormat.parseJST(date: String) = parse(date.removeSuffix("+09:00"))
private fun getJSTFormat() =
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS", Locale.ENGLISH).apply {
timeZone = TimeZone.getTimeZone("GMT+09:00")
}
}
}
@Serializable
data class MCBook(
val cover_url: String, // resolution is too low
)
@Serializable
data class MCViewer(
// val sort_volume: Int,
// val created_at: String,
// val updated_at: String,
// val volume: String,
// val title: String,
// val page_count: Int,
// episode_viewer_setting: { page_direction: "horizontal" }
val episode_pages: List<MCEpisodePage>,
)
@Serializable
data class MCEpisodePage(
// val order_index: Int,
val image: MCImage,
// val is_spread_start_page: Boolean,
)
@Serializable
data class MCImage(
// val pc_url: String, // has highest resolution but is upscaled from original, thus unnecessary
// val sp_url: String,
// val thumbnail_url: String,
val original_url: String,
// {pc,sp,thumbnail,original}_geometry: { width: Int, height: Int }
)
@Serializable
data class MCMenu(
val comic_categories: List<MCComicCategory>,
val comic_tags: List<MCComicGenre>,
) {
fun toFilterList(): List<Pair<String, MCComicTag>> =
comic_categories.map { Pair(it.display_name, it) } + comic_tags.map { Pair(it.name, it) }
}