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