Add TakeComic / Remove Web Comic Ganma Plus & Web Comic Ganma & multisrc (#11183)
* add storiadash * remove and add takecomic * api and refactor * apiUrl
@ -1,9 +0,0 @@
|
|||||||
plugins {
|
|
||||||
id("lib-multisrc")
|
|
||||||
}
|
|
||||||
|
|
||||||
baseVersionCode = 9
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
api(project(":lib:speedbinb"))
|
|
||||||
}
|
|
||||||
@ -1,129 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.multisrc.comicgamma
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.lib.speedbinb.SpeedBinbInterceptor
|
|
||||||
import eu.kanade.tachiyomi.lib.speedbinb.SpeedBinbReader
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
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.online.ParsedHttpSource
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import org.jsoup.select.Evaluator
|
|
||||||
import rx.Observable
|
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
open class ComicGamma(
|
|
||||||
override val name: String,
|
|
||||||
override val baseUrl: String,
|
|
||||||
override val lang: String = "ja",
|
|
||||||
) : ParsedHttpSource() {
|
|
||||||
override val supportsLatest = false
|
|
||||||
|
|
||||||
private val json = Injekt.get<Json>()
|
|
||||||
|
|
||||||
override val client = network.cloudflareClient.newBuilder()
|
|
||||||
.addInterceptor(SpeedBinbInterceptor(json))
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/", headers)
|
|
||||||
override fun popularMangaNextPageSelector(): String? = null
|
|
||||||
override fun popularMangaSelector() = ".tab_panel.active .manga_item"
|
|
||||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
|
||||||
url = element.selectFirst(Evaluator.Tag("a"))!!.attr("href")
|
|
||||||
title = element.selectFirst(Evaluator.Class("manga_title"))!!.text()
|
|
||||||
author = element.selectFirst(Evaluator.Class("manga_author"))!!.text()
|
|
||||||
val genreList = element.select(Evaluator.Tag("li")).map { it.text() }
|
|
||||||
genre = genreList.joinToString()
|
|
||||||
status = when {
|
|
||||||
genreList.contains("完結") && !genreList.contains("リピート配信") -> SManga.COMPLETED
|
|
||||||
else -> SManga.ONGOING
|
|
||||||
}
|
|
||||||
thumbnail_url = element.selectFirst(Evaluator.Tag("img"))!!.absUrl("src")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
|
|
||||||
fetchPopularManga(page).map { p -> MangasPage(p.mangas.filter { it.title.contains(query) }, false) }
|
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun searchMangaSelector() = throw UnsupportedOperationException()
|
|
||||||
override fun searchMangaFromElement(element: Element) = throw UnsupportedOperationException()
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
|
||||||
throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
private val reader by lazy { SpeedBinbReader(client, headers, json) }
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document) = reader.pageListParse(document)
|
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
|
||||||
val titleElement = document.selectFirst(Evaluator.Class("manga__title"))!!
|
|
||||||
val titleName = titleElement.child(0).text()
|
|
||||||
val desc = document.selectFirst(".detail__item > p:not(:empty)")?.run {
|
|
||||||
select(Evaluator.Tag("br")).prepend("\\n")
|
|
||||||
this.text().replace("\\n", "\n").replace("\n ", "\n")
|
|
||||||
}
|
|
||||||
val listResponse = client.newCall(popularMangaRequest(0)).execute()
|
|
||||||
val manga = popularMangaParse(listResponse).mangas.find { it.title == titleName }
|
|
||||||
return manga?.apply { description = desc } ?: SManga.create().apply {
|
|
||||||
author = titleElement.child(1).text()
|
|
||||||
description = desc
|
|
||||||
status = SManga.UNKNOWN
|
|
||||||
val slug = document.location().removeSuffix("/").substringAfterLast("/")
|
|
||||||
thumbnail_url = "$baseUrl/img/manga_thumb/${slug}_list.jpg"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector() = ".read__area .read__outer > a:not([href=#comics])"
|
|
||||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
|
||||||
url = element.attr("href").toOldChapterUrl()
|
|
||||||
val number = url.removeSuffix("/").substringAfterLast('/').replace('_', '.')
|
|
||||||
val list = element.selectFirst(Evaluator.Class("read__contents"))!!.children()
|
|
||||||
name = "[$number] ${list[0].text()}"
|
|
||||||
if (list.size >= 3) {
|
|
||||||
date_upload = dateFormat.parseJST(list[2].text())?.time ?: 0L
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListRequest(chapter: SChapter) =
|
|
||||||
GET(baseUrl + chapter.url.toNewChapterUrl(), headers)
|
|
||||||
|
|
||||||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
internal fun SimpleDateFormat.parseJST(date: String) = parse(date)?.apply {
|
|
||||||
time += 12 * 3600 * 1000 // updates at 12 noon
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getJSTFormat(datePattern: String) =
|
|
||||||
SimpleDateFormat(datePattern, Locale.JAPANESE).apply {
|
|
||||||
timeZone = TimeZone.getTimeZone("GMT+09:00")
|
|
||||||
}
|
|
||||||
|
|
||||||
private val dateFormat by lazy { getJSTFormat("yyyy年M月dd日") }
|
|
||||||
|
|
||||||
private fun String.toOldChapterUrl(): String {
|
|
||||||
// ../../../_files/madeinabyss/063_2/
|
|
||||||
val segments = split('/')
|
|
||||||
val size = segments.size
|
|
||||||
val slug = segments[size - 3]
|
|
||||||
val number = segments[size - 2]
|
|
||||||
return "/manga/$slug/_files/$number/"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.toNewChapterUrl(): String {
|
|
||||||
val segments = split('/')
|
|
||||||
return "/_files/${segments[2]}/${segments[4]}/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
10
src/ja/takecomic/build.gradle
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'TakeComic'
|
||||||
|
extClass = '.TakeComic'
|
||||||
|
themePkg = 'comiciviewer'
|
||||||
|
baseUrl = 'https://takecomic.jp'
|
||||||
|
overrideVersionCode = 0
|
||||||
|
isNsfw = false
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
BIN
src/ja/takecomic/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
src/ja/takecomic/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
src/ja/takecomic/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
src/ja/takecomic/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
src/ja/takecomic/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
@ -0,0 +1,180 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.ja.takecomic
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import keiyoushi.utils.parseAs
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ApiResponse(
|
||||||
|
val series: SeriesData,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SeriesData(
|
||||||
|
val summary: SeriesSummary,
|
||||||
|
private val episodes: List<Episode> = emptyList(),
|
||||||
|
) {
|
||||||
|
fun toSChapter(accessMap: Map<String, EpisodeAccess>, showLocked: Boolean, showCampaignLocked: Boolean): List<SChapter> {
|
||||||
|
return this.episodes.mapNotNull { episode ->
|
||||||
|
val accessInfo = accessMap[episode.id]
|
||||||
|
val hasAccess = accessInfo?.hasAccess
|
||||||
|
val isCampaign = accessInfo?.isCampaign
|
||||||
|
val isLocked = !hasAccess!!
|
||||||
|
val isCampaignLocked = isLocked && isCampaign!!
|
||||||
|
|
||||||
|
if (isCampaignLocked && !showCampaignLocked) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
if (isLocked && !isCampaignLocked && !showLocked) {
|
||||||
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
|
SChapter.create().apply {
|
||||||
|
name = episode.title
|
||||||
|
date_upload = episode.datePublished * 1000L
|
||||||
|
when {
|
||||||
|
isCampaignLocked -> {
|
||||||
|
name = "➡\uFE0F $name"
|
||||||
|
url = "/episodes/${episode.id}#${TakeComic.LOGIN_SUFFIX}"
|
||||||
|
}
|
||||||
|
isLocked -> {
|
||||||
|
name = "🔒 $name"
|
||||||
|
url = "/episodes/${episode.id}"
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
url = "/episodes/${episode.id}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SeriesSummary(
|
||||||
|
private val name: String,
|
||||||
|
private val description: String,
|
||||||
|
private val author: List<Author>,
|
||||||
|
private val images: List<SeriesImage>,
|
||||||
|
private val tag: List<Tag>,
|
||||||
|
) {
|
||||||
|
fun toSManga(seriesHash: String): SManga = SManga.create().apply {
|
||||||
|
url = "/series/$seriesHash"
|
||||||
|
title = this@SeriesSummary.name
|
||||||
|
author = this@SeriesSummary.author.joinToString { it.name }
|
||||||
|
artist = author
|
||||||
|
description = try {
|
||||||
|
this@SeriesSummary.description.parseAs<List<DescriptionNode>>()
|
||||||
|
.joinToString("\n") { node -> node.children.joinToString { it.text } }
|
||||||
|
} catch (e: Exception) {
|
||||||
|
this@SeriesSummary.description
|
||||||
|
}
|
||||||
|
genre = this@SeriesSummary.tag.joinToString { it.name }
|
||||||
|
thumbnail_url = this@SeriesSummary.images.joinToString { it.url }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Author(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SeriesImage(
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Tag(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Episode(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val datePublished: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DescriptionNode(
|
||||||
|
val children: List<DescriptionChild>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class DescriptionChild(
|
||||||
|
val text: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class AccessApiResponse(
|
||||||
|
val seriesAccess: SeriesAccess,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SeriesAccess(
|
||||||
|
val episodeAccesses: List<EpisodeAccess>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class EpisodeAccess(
|
||||||
|
val episodeId: String,
|
||||||
|
val hasAccess: Boolean,
|
||||||
|
val isCampaign: Boolean,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchApiResponse(
|
||||||
|
val searchResult: SearchResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchResult(
|
||||||
|
val series: SeriesResult,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SeriesResult(
|
||||||
|
val total: Int,
|
||||||
|
val series: List<SearchSeries>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchSeries(
|
||||||
|
private val id: String,
|
||||||
|
private val name: String,
|
||||||
|
private val images: List<SeriesImage>,
|
||||||
|
) {
|
||||||
|
fun toSManga(): SManga = SManga.create().apply {
|
||||||
|
url = "/series/$id"
|
||||||
|
title = name
|
||||||
|
thumbnail_url = images.joinToString { it.url }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class UserInfoApiResponse(
|
||||||
|
val user: UserData?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class UserData(
|
||||||
|
val id: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class EpisodeDetailsApiResponse(
|
||||||
|
val episode: EpisodeDetails,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class EpisodeDetails(
|
||||||
|
val content: List<EpisodeContent> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class EpisodeContent(
|
||||||
|
val type: String,
|
||||||
|
val viewerId: String? = null,
|
||||||
|
)
|
||||||
@ -0,0 +1,227 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.ja.takecomic
|
||||||
|
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
|
import androidx.preference.SwitchPreferenceCompat
|
||||||
|
import eu.kanade.tachiyomi.multisrc.comiciviewer.ComiciViewer
|
||||||
|
import eu.kanade.tachiyomi.multisrc.comiciviewer.ViewerResponse
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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.util.asJsoup
|
||||||
|
import keiyoushi.utils.firstInstance
|
||||||
|
import keiyoushi.utils.getPreferencesLazy
|
||||||
|
import keiyoushi.utils.parseAs
|
||||||
|
import okhttp3.CacheControl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import kotlin.getValue
|
||||||
|
|
||||||
|
class TakeComic : ComiciViewer(
|
||||||
|
"TakeComic",
|
||||||
|
"https://takecomic.jp",
|
||||||
|
"ja",
|
||||||
|
) {
|
||||||
|
private val apiUrl = "$baseUrl/api"
|
||||||
|
private val preferences: SharedPreferences by getPreferencesLazy()
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
return latestUpdatesParse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/series/list/up/$page", headers)
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val mangas = document.select("div.series-list-item").map { element ->
|
||||||
|
SManga.create().apply {
|
||||||
|
setUrlWithoutDomain(element.selectFirst("a.series-list-item-link")!!.attr("href"))
|
||||||
|
title = element.selectFirst("div.series-list-item-h span")!!.text()
|
||||||
|
thumbnail_url = element.selectFirst("img.series-list-item-img")?.attr("src")?.let { baseUrl.toHttpUrlOrNull()?.newBuilder(it)?.build()?.queryParameter("url") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val hasNextPage = document.selectFirst("a.g-pager-link.mode-active + a.g-pager-link") != null
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
val url = "$apiUrl/search".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("q", query)
|
||||||
|
.addQueryParameter("page", page.toString())
|
||||||
|
.addQueryParameter("size", SEARCH_PAGE_SIZE.toString())
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
|
val browseFilter = filterList.firstInstance<BrowseFilter>()
|
||||||
|
val path = getFilterOptions()[browseFilter.state].second
|
||||||
|
|
||||||
|
val url = if (path == "/ranking/manga") {
|
||||||
|
"$baseUrl$path"
|
||||||
|
} else {
|
||||||
|
"$baseUrl$path/$page"
|
||||||
|
}
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val url = response.request.url.toString()
|
||||||
|
if (url.contains("/api/search")) {
|
||||||
|
val result = response.parseAs<SearchApiResponse>().searchResult.series
|
||||||
|
val mangas = result.series.map { it.toSManga() }
|
||||||
|
val page = response.request.url.queryParameter("page")!!.toInt()
|
||||||
|
val hasNextPage = result.total > page * SEARCH_PAGE_SIZE
|
||||||
|
return MangasPage(mangas, hasNextPage)
|
||||||
|
}
|
||||||
|
return latestUpdatesParse(response)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val seriesHash = response.request.url.pathSegments.last()
|
||||||
|
val apiUrl = "$apiUrl/episodes".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("seriesHash", seriesHash)
|
||||||
|
.build()
|
||||||
|
val apiRequest = GET(apiUrl, headers)
|
||||||
|
val apiResponse = client.newCall(apiRequest).execute()
|
||||||
|
return apiResponse.parseAs<ApiResponse>().series.summary.toSManga(seriesHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val seriesHash = manga.url.substringAfterLast("/")
|
||||||
|
val apiUrl = "$apiUrl/episodes".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("seriesHash", seriesHash)
|
||||||
|
.addQueryParameter("episodeFrom", "1")
|
||||||
|
.addQueryParameter("episodeTo", "9999")
|
||||||
|
.build()
|
||||||
|
return GET(apiUrl, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val apiResponse = response.parseAs<ApiResponse>()
|
||||||
|
val seriesHash = response.request.url.queryParameter("seriesHash")!!
|
||||||
|
|
||||||
|
val accessUrl = "$apiUrl/series/access".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("seriesHash", seriesHash)
|
||||||
|
.addQueryParameter("episodeFrom", "1")
|
||||||
|
.addQueryParameter("episodeTo", "9999")
|
||||||
|
.build()
|
||||||
|
val accessRequest = GET(accessUrl, headers, CacheControl.FORCE_NETWORK)
|
||||||
|
val accessResponse = client.newCall(accessRequest).execute()
|
||||||
|
val accessMap = accessResponse.parseAs<AccessApiResponse>().seriesAccess.episodeAccesses
|
||||||
|
.associateBy { it.episodeId }
|
||||||
|
|
||||||
|
val showLocked = preferences.getBoolean(SHOW_LOCKED_PREF_KEY, true)
|
||||||
|
val showCampaignLocked = preferences.getBoolean(SHOW_CAMPAIGN_LOCKED_PREF_KEY, true)
|
||||||
|
|
||||||
|
return apiResponse.series.toSChapter(accessMap, showLocked, showCampaignLocked).reversed()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
if (chapter.url.endsWith(LOGIN_SUFFIX)) {
|
||||||
|
throw Exception("This chapter is free but you need to log in via WebView and refresh the entry")
|
||||||
|
}
|
||||||
|
return super.pageListRequest(chapter)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val episodeId = response.request.url.pathSegments.last()
|
||||||
|
|
||||||
|
var comiciViewerId: String? = null
|
||||||
|
var memberJwt: String? = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
val apiUrl = "$apiUrl/episodes/$episodeId"
|
||||||
|
val accessRequest = GET(apiUrl, headers, CacheControl.FORCE_NETWORK)
|
||||||
|
val apiResponse = client.newCall(accessRequest).execute()
|
||||||
|
if (apiResponse.isSuccessful) {
|
||||||
|
comiciViewerId = apiResponse.parseAs<EpisodeDetailsApiResponse>()
|
||||||
|
.episode.content
|
||||||
|
.firstOrNull { it.type == "viewer" }?.viewerId
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { comiciViewerId = null }
|
||||||
|
|
||||||
|
if (comiciViewerId == null) {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val viewer = document.selectFirst("#comici-viewer") ?: throw Exception("Log in via WebView and purchase this chapter to read")
|
||||||
|
comiciViewerId = viewer.attr("data-comici-viewer-id")
|
||||||
|
memberJwt = viewer.attr("data-member-jwt")
|
||||||
|
}
|
||||||
|
|
||||||
|
val userId = try {
|
||||||
|
val userInfoResponse = client.newCall(GET("$apiUrl/user/info", headers)).execute()
|
||||||
|
userInfoResponse.parseAs<UserInfoApiResponse>().user?.id
|
||||||
|
} catch (e: Exception) {
|
||||||
|
memberJwt
|
||||||
|
}
|
||||||
|
|
||||||
|
val requestUrl = "$apiUrl/book/contentsInfo".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("comici-viewer-id", comiciViewerId)
|
||||||
|
.addQueryParameter("user-id", userId)
|
||||||
|
.addQueryParameter("page-from", "0")
|
||||||
|
|
||||||
|
val pageTo = client.newCall(GET(requestUrl.addQueryParameter("page-to", "1").build(), headers))
|
||||||
|
.execute().use { initialResponse ->
|
||||||
|
if (!initialResponse.isSuccessful) {
|
||||||
|
throw Exception("Failed to get page list. HTTP ${initialResponse.code}")
|
||||||
|
}
|
||||||
|
initialResponse.parseAs<ViewerResponse>().totalPages.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val getAllPagesUrl = requestUrl.setQueryParameter("page-to", pageTo).build()
|
||||||
|
return client.newCall(GET(getAllPagesUrl, headers)).execute().use { allPagesResponse ->
|
||||||
|
if (allPagesResponse.isSuccessful) {
|
||||||
|
allPagesResponse.parseAs<ViewerResponse>().result.map { resultItem ->
|
||||||
|
val urlBuilder = resultItem.imageUrl.toHttpUrl().newBuilder()
|
||||||
|
if (resultItem.scramble.isNotEmpty()) {
|
||||||
|
urlBuilder.addQueryParameter("scramble", resultItem.scramble)
|
||||||
|
}
|
||||||
|
Page(
|
||||||
|
index = resultItem.sort,
|
||||||
|
imageUrl = urlBuilder.build().toString(),
|
||||||
|
)
|
||||||
|
}.sortedBy { it.index }
|
||||||
|
} else {
|
||||||
|
throw Exception("Failed to get full page list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
super.setupPreferenceScreen(screen)
|
||||||
|
SwitchPreferenceCompat(screen.context).apply {
|
||||||
|
key = SHOW_CAMPAIGN_LOCKED_PREF_KEY
|
||||||
|
title = "Show 'Require Login' chapters"
|
||||||
|
summary = "Shows chapters that are free but require login"
|
||||||
|
setDefaultValue(true)
|
||||||
|
}.also(screen::addPreference)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterOptions(): List<Pair<String, String>> = listOf(
|
||||||
|
Pair("ランキング", "/ranking/manga"),
|
||||||
|
Pair("更新順", "/series/list/up"),
|
||||||
|
Pair("新作順", "/series/list/new"),
|
||||||
|
Pair("完結", "/category/manga/complete"),
|
||||||
|
Pair("月曜日", "/category/manga/day/1"),
|
||||||
|
Pair("火曜日", "/category/manga/day/2"),
|
||||||
|
Pair("水曜日", "/category/manga/day/3"),
|
||||||
|
Pair("木曜日", "/category/manga/day/4"),
|
||||||
|
Pair("金曜日", "/category/manga/day/5"),
|
||||||
|
Pair("土曜日", "/category/manga/day/6"),
|
||||||
|
Pair("日曜日", "/category/manga/day/7"),
|
||||||
|
Pair("その他", "/category/manga/day/8"),
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val SEARCH_PAGE_SIZE = 24
|
||||||
|
private const val SHOW_LOCKED_PREF_KEY = "pref_show_locked_chapters"
|
||||||
|
private const val SHOW_CAMPAIGN_LOCKED_PREF_KEY = "pref_show_campaign_locked_chapters"
|
||||||
|
const val LOGIN_SUFFIX = "#LOGIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,10 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Web Comic Gamma'
|
|
||||||
extClass = '.WebComicGamma'
|
|
||||||
themePkg = 'comicgamma'
|
|
||||||
baseUrl = 'https://webcomicgamma.takeshobo.co.jp'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = false
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB |
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 9.3 KiB |
|
Before Width: | Height: | Size: 13 KiB |
@ -1,5 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.ja.webcomicgamma
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.comicgamma.ComicGamma
|
|
||||||
|
|
||||||
class WebComicGamma : ComicGamma("Web Comic Gamma", "https://webcomicgamma.takeshobo.co.jp", "ja")
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
ext {
|
|
||||||
extName = 'Web Comic Gamma Plus'
|
|
||||||
extClass = '.WebComicGammaPlus'
|
|
||||||
themePkg = 'comicgamma'
|
|
||||||
baseUrl = 'https://gammaplus.takeshobo.co.jp'
|
|
||||||
overrideVersionCode = 0
|
|
||||||
isNsfw = true
|
|
||||||
}
|
|
||||||
|
|
||||||
apply from: "$rootDir/common.gradle"
|
|
||||||
|
Before Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 6.3 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
@ -1,5 +0,0 @@
|
|||||||
package eu.kanade.tachiyomi.extension.ja.webcomicgammaplus
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.comicgamma.ComicGamma
|
|
||||||
|
|
||||||
class WebComicGammaPlus : ComicGamma("Web Comic Gamma Plus", "https://gammaplus.takeshobo.co.jp", "ja")
|
|
||||||