parent
5f6e00499a
commit
f019c2a273
7
src/ja/ganganonline/build.gradle
Normal file
7
src/ja/ganganonline/build.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'Gangan Online'
|
||||||
|
extClass = '.GanganOnline'
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = false
|
||||||
|
}
|
||||||
|
apply from: "$rootDir/common.gradle"
|
||||||
BIN
src/ja/ganganonline/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/ja/ganganonline/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.8 KiB |
BIN
src/ja/ganganonline/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/ja/ganganonline/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.5 KiB |
BIN
src/ja/ganganonline/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/ja/ganganonline/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 10 KiB |
BIN
src/ja/ganganonline/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/ja/ganganonline/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
BIN
src/ja/ganganonline/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/ja/ganganonline/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
@ -0,0 +1,112 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.ja.ganganonline
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class NextData<T>(
|
||||||
|
val props: Props<T>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Props<T>(
|
||||||
|
val pageProps: PageProps<T>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageProps<T>(
|
||||||
|
val data: T,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaListDto(
|
||||||
|
val titleSections: List<MangaSectionDto>?, // Popular/Finished
|
||||||
|
val sections: List<SearchSectionDto>?, // Search
|
||||||
|
val ongoingTitleSection: MangaSectionDto?, // GA
|
||||||
|
val finishedTitleSection: MangaSectionDto?, // GA
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaSectionDto(
|
||||||
|
val titles: List<MangaDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class SearchSectionDto(
|
||||||
|
val titleLinks: List<MangaDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDto(
|
||||||
|
private val titleId: Int,
|
||||||
|
private val header: String?, // Popular/Finished
|
||||||
|
private val name: String?, // Search
|
||||||
|
private val imageUrl: String?,
|
||||||
|
val isNovel: Boolean?,
|
||||||
|
) {
|
||||||
|
fun toSManga(baseUrl: String): SManga = SManga.create().apply {
|
||||||
|
url = "/title/$titleId"
|
||||||
|
title = header ?: name!!
|
||||||
|
thumbnail_url = baseUrl + imageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PixivPageDto(
|
||||||
|
val ganganTitles: List<MangaDto>?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDetailDto(
|
||||||
|
val default: MangaDetailDefaultDto,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDetailDefaultDto(
|
||||||
|
private val titleName: String,
|
||||||
|
private val author: String,
|
||||||
|
private val description: String,
|
||||||
|
private val imageUrl: String,
|
||||||
|
val chapters: List<ChapterDto>,
|
||||||
|
) {
|
||||||
|
fun toSManga(baseUrl: String): SManga = SManga.create().apply {
|
||||||
|
title = titleName
|
||||||
|
author = this@MangaDetailDefaultDto.author
|
||||||
|
description = this@MangaDetailDefaultDto.description
|
||||||
|
thumbnail_url = baseUrl + imageUrl
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterDto(
|
||||||
|
private val id: Int,
|
||||||
|
val status: Int?,
|
||||||
|
private val mainText: String,
|
||||||
|
private val subText: String?,
|
||||||
|
private val publishingPeriod: String?,
|
||||||
|
) {
|
||||||
|
fun toSChapter(mangaUrl: String, dateFormat: SimpleDateFormat): SChapter = SChapter.create().apply {
|
||||||
|
url = "$mangaUrl/chapter/$id"
|
||||||
|
name = mainText + if (!subText.isNullOrEmpty()) " - $subText" else ""
|
||||||
|
date_upload = publishingPeriod?.substringBefore("〜").let { dateFormat.tryParse(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageListDto(
|
||||||
|
val pages: List<PageDto>,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageDto(
|
||||||
|
val image: PageImageUrlDto?,
|
||||||
|
val linkImage: PageImageUrlDto?,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageImageUrlDto(
|
||||||
|
val imageUrl: String,
|
||||||
|
)
|
||||||
@ -0,0 +1,127 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.ja.ganganonline
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
|
import keiyoushi.utils.firstInstance
|
||||||
|
import keiyoushi.utils.parseAs
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class GanganOnline : HttpSource() {
|
||||||
|
override val name = "Gangan Online"
|
||||||
|
override val baseUrl = "https://www.ganganonline.com"
|
||||||
|
override val lang = "ja"
|
||||||
|
override val supportsLatest = false
|
||||||
|
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy.MM.dd", Locale.JAPAN)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/rensai", headers)
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
if (query.isNotBlank()) {
|
||||||
|
val url = "$baseUrl/search/result".toHttpUrl().newBuilder()
|
||||||
|
.addQueryParameter("keyword", query)
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
val filter = filters.firstInstance<CategoryFilter>()
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegments(filter.toUriPart().removePrefix("/"))
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage {
|
||||||
|
val url = response.request.url.toString()
|
||||||
|
val mangas = when {
|
||||||
|
"/search/result" in url -> {
|
||||||
|
val data = response.parseAsNextData<MangaListDto>()
|
||||||
|
data.sections?.flatMap { it.titleLinks }
|
||||||
|
?.filter { it.isNovel != true }
|
||||||
|
?.map { it.toSManga(baseUrl) }
|
||||||
|
}
|
||||||
|
"/rensai" in url || "/finish" in url -> {
|
||||||
|
val data = response.parseAsNextData<MangaListDto>()
|
||||||
|
data.titleSections?.flatMap { it.titles }
|
||||||
|
?.filter { it.isNovel != true }
|
||||||
|
?.map { it.toSManga(baseUrl) }
|
||||||
|
}
|
||||||
|
"/ga" in url -> {
|
||||||
|
val data = response.parseAsNextData<MangaListDto>()
|
||||||
|
val ongoing = data.ongoingTitleSection?.titles!!
|
||||||
|
val finished = data.finishedTitleSection?.titles!!
|
||||||
|
(ongoing + finished)
|
||||||
|
.filter { it.isNovel != true }
|
||||||
|
.map { it.toSManga(baseUrl) }
|
||||||
|
}
|
||||||
|
"/pixiv" in url -> {
|
||||||
|
val data = response.parseAsNextData<PixivPageDto>()
|
||||||
|
data.ganganTitles?.map { it.toSManga(baseUrl) }
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
return MangasPage(mangas!!, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
return response.parseAsNextData<MangaDetailDto>().default.toSManga(baseUrl)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val mangaUrl = response.request.url.toString()
|
||||||
|
.substringBefore("/chapter")
|
||||||
|
.substringAfter(baseUrl)
|
||||||
|
val data = response.parseAsNextData<MangaDetailDto>().default
|
||||||
|
|
||||||
|
return data.chapters
|
||||||
|
.filter { it.status == null || it.status >= 4 }
|
||||||
|
.map { it.toSChapter(mangaUrl, dateFormat) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val data = response.parseAsNextData<PageListDto>()
|
||||||
|
return data.pages.mapIndexed { i, page ->
|
||||||
|
val imageUrl = (page.image ?: page.linkImage)!!.imageUrl
|
||||||
|
Page(i, imageUrl = baseUrl + imageUrl)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
CategoryFilter(getCategoryList()),
|
||||||
|
)
|
||||||
|
|
||||||
|
private class CategoryFilter(private val category: Array<Pair<String, String>>) :
|
||||||
|
Filter.Select<String>("Category", category.map { it.first }.toTypedArray()) {
|
||||||
|
fun toUriPart() = category[state].second
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getCategoryList() = arrayOf(
|
||||||
|
Pair("連載作品", "/rensai"),
|
||||||
|
Pair("連載終了作品", "/finish"),
|
||||||
|
Pair("ガンガンpixiv", "/pixiv"),
|
||||||
|
Pair("ガンガンGA", "/ga"),
|
||||||
|
)
|
||||||
|
|
||||||
|
private inline fun <reified T> Response.parseAsNextData(): T {
|
||||||
|
val script = this.asJsoup().selectFirst("script#__NEXT_DATA__")!!.data()
|
||||||
|
return script.parseAs<NextData<T>>().props.pageProps.data
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsupported
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user