feat: add GlobalComix (#8637)
* feat: add GlobalComix Closes #3726 * fix: parse comic URLs correctly * style: cleanup * refactor: rename PageListDataDto to PageDataDto * fix: sort search results by relevancy * fix: improve premium chapter detection * refactor: add chapter number to SChapter * refactor: remove unused fields and allow some fields to be nullable * refactor: minor cleanup * Update src/all/globalcomix/src/eu/kanade/tachiyomi/extension/all/globalcomix/GlobalComix.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * Update src/all/globalcomix/src/eu/kanade/tachiyomi/extension/all/globalcomix/GlobalComix.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * Update src/all/globalcomix/src/eu/kanade/tachiyomi/extension/all/globalcomix/GlobalComix.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * Update src/all/globalcomix/src/eu/kanade/tachiyomi/extension/all/globalcomix/dto/ChapterDto.kt Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com> * refactor: remove CacheControl * refactor: move constants of out object * refactor: add new imports & remove 204 check * refactor: remove chapter list 204 check --------- Co-authored-by: AwkwardPeak7 <48650614+AwkwardPeak7@users.noreply.github.com>
This commit is contained in:
parent
1a6774af59
commit
0da807ff70
27
src/all/globalcomix/AndroidManifest.xml
Normal file
27
src/all/globalcomix/AndroidManifest.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application>
|
||||
<activity
|
||||
android:name=".all.globalcomix.GlobalComixUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter
|
||||
android:autoVerify="false"
|
||||
tools:targetApi="23">
|
||||
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data android:host="globalcomix.com" />
|
||||
<data android:scheme="https" />
|
||||
|
||||
<data android:pathPattern="/c/..*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
5
src/all/globalcomix/assets/i18n/messages_en.properties
Normal file
5
src/all/globalcomix/assets/i18n/messages_en.properties
Normal file
@ -0,0 +1,5 @@
|
||||
data_saver=Data saver
|
||||
data_saver_summary=Enables smaller, more compressed images
|
||||
invalid_manga_id=Not a valid comic ID
|
||||
show_locked_chapters=Show chapters with pay-walled pages
|
||||
show_locked_chapters_summary=Display chapters that require an account with a premium subscription
|
12
src/all/globalcomix/build.gradle
Normal file
12
src/all/globalcomix/build.gradle
Normal file
@ -0,0 +1,12 @@
|
||||
ext {
|
||||
extName = 'GlobalComix'
|
||||
extClass = '.GlobalComixFactory'
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation(project(":lib:i18n"))
|
||||
}
|
BIN
src/all/globalcomix/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/all/globalcomix/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
src/all/globalcomix/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/all/globalcomix/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
BIN
src/all/globalcomix/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/all/globalcomix/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.3 KiB |
BIN
src/all/globalcomix/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/all/globalcomix/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.8 KiB |
BIN
src/all/globalcomix/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/all/globalcomix/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@ -0,0 +1,234 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.PreferenceScreen
|
||||
import androidx.preference.SwitchPreferenceCompat
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.ChapterDataDto.Companion.createChapter
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.ChapterDto
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.ChaptersDto
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.EntityDto
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.MangaDataDto.Companion.createManga
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.MangaDto
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.MangasDto
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.dto.UnknownEntity
|
||||
import eu.kanade.tachiyomi.lib.i18n.Intl
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
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.Page
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import keiyoushi.utils.getPreferencesLazy
|
||||
import keiyoushi.utils.parseAs
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.modules.SerializersModule
|
||||
import kotlinx.serialization.modules.plus
|
||||
import kotlinx.serialization.modules.polymorphic
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
abstract class GlobalComix(final override val lang: String, private val extLang: String = lang) :
|
||||
ConfigurableSource, HttpSource() {
|
||||
|
||||
override val name = "GlobalComix"
|
||||
override val baseUrl = webUrl
|
||||
override val supportsLatest = true
|
||||
|
||||
private val preferences: SharedPreferences by getPreferencesLazy()
|
||||
|
||||
private val json = Json {
|
||||
isLenient = true
|
||||
ignoreUnknownKeys = true
|
||||
serializersModule += SerializersModule {
|
||||
polymorphic(EntityDto::class) {
|
||||
defaultDeserializer { UnknownEntity.serializer() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val intl = Intl(
|
||||
language = lang,
|
||||
baseLanguage = english,
|
||||
availableLanguages = setOf(english),
|
||||
classLoader = this::class.java.classLoader!!,
|
||||
createMessageFileName = { lang -> Intl.createDefaultMessageFileName(lang) },
|
||||
)
|
||||
|
||||
final override fun headersBuilder() = super.headersBuilder().apply {
|
||||
set("Referer", "$baseUrl/")
|
||||
set("Origin", baseUrl)
|
||||
set("x-gc-client", clientId)
|
||||
set("x-gc-identmode", "cookie")
|
||||
}
|
||||
|
||||
override val client = network.client.newBuilder()
|
||||
.rateLimit(3)
|
||||
.build()
|
||||
|
||||
private fun simpleQueryRequest(page: Int, orderBy: String?, query: String?): Request {
|
||||
val url = apiSearchUrl.toHttpUrl().newBuilder()
|
||||
.addQueryParameter("lang_id[]", extLang)
|
||||
.addQueryParameter("p", page.toString())
|
||||
|
||||
orderBy?.let { url.addQueryParameter("sort", it) }
|
||||
query?.let { url.addQueryParameter("q", it) }
|
||||
|
||||
return GET(url.build(), headers)
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request =
|
||||
simpleQueryRequest(page, orderBy = null, query = null)
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage =
|
||||
mangaListParse(response)
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request =
|
||||
simpleQueryRequest(page, "recent", query = null)
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage =
|
||||
mangaListParse(response)
|
||||
|
||||
private fun mangaListParse(response: Response): MangasPage {
|
||||
val isSingleItemLookup = response.request.url.toString().startsWith(apiMangaUrl)
|
||||
return if (!isSingleItemLookup) {
|
||||
// Normally, the response is a paginated list of mangas
|
||||
// The results property will be a JSON array
|
||||
response.parseAs<MangasDto>().payload!!.let { dto ->
|
||||
MangasPage(
|
||||
dto.results.map { it -> it.createManga() },
|
||||
dto.pagination.hasNextPage,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
// However, when using the 'id:' query prefix (via the UrlActivity for example),
|
||||
// the response is a single manga and the results property will be a JSON object
|
||||
MangasPage(
|
||||
listOf(
|
||||
response.parseAs<MangaDto>().payload!!
|
||||
.results
|
||||
.createManga(),
|
||||
),
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
// If the query is a slug ID, return the manga directly
|
||||
if (query.startsWith(prefixIdSearch)) {
|
||||
val mangaSlugId = query.removePrefix(prefixIdSearch)
|
||||
|
||||
if (mangaSlugId.isEmpty()) {
|
||||
throw Exception(intl["invalid_manga_id"])
|
||||
}
|
||||
|
||||
val url = apiMangaUrl.toHttpUrl().newBuilder()
|
||||
.addPathSegment(mangaSlugId)
|
||||
.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
return simpleQueryRequest(page, orderBy = "relevance", query)
|
||||
}
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||
|
||||
override fun getMangaUrl(manga: SManga): String = "$webComicUrl/${titleToSlug(manga.title)}"
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
val url = apiMangaUrl.toHttpUrl().newBuilder()
|
||||
.addPathSegment(titleToSlug(manga.title))
|
||||
.build()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga =
|
||||
response.parseAs<MangaDto>().payload!!
|
||||
.results
|
||||
.createManga()
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
val url = apiSearchUrl.toHttpUrl().newBuilder()
|
||||
.addPathSegment(manga.url) // manga.url contains the the comic id
|
||||
.addPathSegment("releases")
|
||||
.addQueryParameter("lang_id", extLang)
|
||||
.addQueryParameter("all", "true")
|
||||
.toString()
|
||||
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> =
|
||||
response.parseAs<ChaptersDto>().payload!!.results.filterNot { dto ->
|
||||
dto.isPremium && !preferences.showLockedChapters
|
||||
}.map { it.createChapter() }
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter): String =
|
||||
"$baseUrl/read/${chapter.url}"
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
val chapterKey = chapter.url
|
||||
val url = "$apiChapterUrl/$chapterKey"
|
||||
return GET(url, headers)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val chapterKey = response.request.url.pathSegments.last()
|
||||
val chapterWebUrl = "$webChapterUrl/$chapterKey"
|
||||
|
||||
return response.parseAs<ChapterDto>()
|
||||
.payload!!
|
||||
.results
|
||||
.page_objects!!
|
||||
.map { dto -> if (preferences.useDataSaver) dto.mobile_image_url else dto.desktop_image_url }
|
||||
.mapIndexed { index, url -> Page(index, "$chapterWebUrl/$index", url) }
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response): String = ""
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val dataSaverPref = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = getDataSaverPreferenceKey(extLang)
|
||||
title = intl["data_saver"]
|
||||
summary = intl["data_saver_summary"]
|
||||
setDefaultValue(false)
|
||||
}
|
||||
|
||||
val showLockedChaptersPref = SwitchPreferenceCompat(screen.context).apply {
|
||||
key = getShowLockedChaptersPreferenceKey(extLang)
|
||||
title = intl["show_locked_chapters"]
|
||||
summary = intl["show_locked_chapters_summary"]
|
||||
setDefaultValue(true)
|
||||
}
|
||||
|
||||
screen.addPreference(dataSaverPref)
|
||||
screen.addPreference(showLockedChaptersPref)
|
||||
}
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T = parseAs(json)
|
||||
|
||||
private val SharedPreferences.useDataSaver
|
||||
get() = getBoolean(getDataSaverPreferenceKey(extLang), false)
|
||||
|
||||
private val SharedPreferences.showLockedChapters
|
||||
get() = getBoolean(getShowLockedChaptersPreferenceKey(extLang), true)
|
||||
|
||||
companion object {
|
||||
fun titleToSlug(title: String) = title.trim()
|
||||
.lowercase(Locale.US)
|
||||
.replace(titleSpecialCharactersRegex, "-")
|
||||
|
||||
val titleSpecialCharactersRegex = "[^a-z0-9]+".toRegex()
|
||||
val dateFormatter = SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.US)
|
||||
.apply { timeZone = TimeZone.getTimeZone("UTC") }
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix
|
||||
|
||||
const val lockSymbol = "🔒"
|
||||
|
||||
// Language codes used for translations
|
||||
const val english = "en"
|
||||
|
||||
// JSON discriminators
|
||||
const val release = "Release"
|
||||
const val comic = "Comic"
|
||||
const val artist = "Artist"
|
||||
const val releasePage = "ReleasePage"
|
||||
|
||||
// Web requests
|
||||
const val webUrl = "https://globalcomix.com"
|
||||
const val webComicUrl = "$webUrl/c"
|
||||
const val webChapterUrl = "$webUrl/read"
|
||||
const val apiUrl = "https://api.globalcomix.com/v1"
|
||||
const val apiMangaUrl = "$apiUrl/read"
|
||||
const val apiChapterUrl = "$apiUrl/readV2"
|
||||
const val apiSearchUrl = "$apiUrl/comics"
|
||||
|
||||
const val clientId = "gck_d0f170d5729446dcb3b55e6b3ebc7bf6"
|
||||
|
||||
// Search prefix for title ids
|
||||
const val prefixIdSearch = "id:"
|
||||
|
||||
// Preferences
|
||||
fun getDataSaverPreferenceKey(extLang: String): String = "dataSaver_$extLang"
|
||||
fun getShowLockedChaptersPreferenceKey(extLang: String): String = "showLockedChapters_$extLang"
|
@ -0,0 +1,92 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix
|
||||
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
|
||||
class GlobalComixFactory : SourceFactory {
|
||||
override fun createSources(): List<Source> = listOf(
|
||||
GlobalComixAlbanian(),
|
||||
GlobalComixArabic(),
|
||||
GlobalComixBulgarian(),
|
||||
GlobalComixBengali(),
|
||||
GlobalComixBrazilianPortuguese(),
|
||||
GlobalComixChineseMandarin(),
|
||||
GlobalComixCzech(),
|
||||
GlobalComixGerman(),
|
||||
GlobalComixDanish(),
|
||||
GlobalComixGreek(),
|
||||
GlobalComixEnglish(),
|
||||
GlobalComixSpanish(),
|
||||
GlobalComixPersian(),
|
||||
GlobalComixFinnish(),
|
||||
GlobalComixFilipino(),
|
||||
GlobalComixFrench(),
|
||||
GlobalComixHindi(),
|
||||
GlobalComixHungarian(),
|
||||
GlobalComixIndonesian(),
|
||||
GlobalComixItalian(),
|
||||
GlobalComixHebrew(),
|
||||
GlobalComixJapanese(),
|
||||
GlobalComixKorean(),
|
||||
GlobalComixLatvian(),
|
||||
GlobalComixMalay(),
|
||||
GlobalComixDutch(),
|
||||
GlobalComixNorwegian(),
|
||||
GlobalComixPolish(),
|
||||
GlobalComixPortugese(),
|
||||
GlobalComixRomanian(),
|
||||
GlobalComixRussian(),
|
||||
GlobalComixSwedish(),
|
||||
GlobalComixSlovak(),
|
||||
GlobalComixSlovenian(),
|
||||
GlobalComixTamil(),
|
||||
GlobalComixThai(),
|
||||
GlobalComixTurkish(),
|
||||
GlobalComixUkrainian(),
|
||||
GlobalComixUrdu(),
|
||||
GlobalComixVietnamese(),
|
||||
GlobalComixChineseCantonese(),
|
||||
)
|
||||
}
|
||||
|
||||
class GlobalComixAlbanian : GlobalComix("al")
|
||||
class GlobalComixArabic : GlobalComix("ar")
|
||||
class GlobalComixBulgarian : GlobalComix("bg")
|
||||
class GlobalComixBengali : GlobalComix("bn")
|
||||
class GlobalComixBrazilianPortuguese : GlobalComix("pt-BR", "br")
|
||||
class GlobalComixChineseMandarin : GlobalComix("zh-Hans", "cn")
|
||||
class GlobalComixCzech : GlobalComix("cs", "cz")
|
||||
class GlobalComixGerman : GlobalComix("de")
|
||||
class GlobalComixDanish : GlobalComix("dk")
|
||||
class GlobalComixGreek : GlobalComix("el")
|
||||
class GlobalComixEnglish : GlobalComix("en")
|
||||
class GlobalComixSpanish : GlobalComix("es")
|
||||
class GlobalComixPersian : GlobalComix("fa")
|
||||
class GlobalComixFinnish : GlobalComix("fi")
|
||||
class GlobalComixFilipino : GlobalComix("fil", "fo")
|
||||
class GlobalComixFrench : GlobalComix("fr")
|
||||
class GlobalComixHindi : GlobalComix("hi")
|
||||
class GlobalComixHungarian : GlobalComix("hu")
|
||||
class GlobalComixIndonesian : GlobalComix("id")
|
||||
class GlobalComixItalian : GlobalComix("it")
|
||||
class GlobalComixHebrew : GlobalComix("he", "iw")
|
||||
class GlobalComixJapanese : GlobalComix("ja", "jp")
|
||||
class GlobalComixKorean : GlobalComix("ko", "kr")
|
||||
class GlobalComixLatvian : GlobalComix("lv")
|
||||
class GlobalComixMalay : GlobalComix("ms", "my")
|
||||
class GlobalComixDutch : GlobalComix("nl")
|
||||
class GlobalComixNorwegian : GlobalComix("no")
|
||||
class GlobalComixPolish : GlobalComix("pl")
|
||||
class GlobalComixPortugese : GlobalComix("pt")
|
||||
class GlobalComixRomanian : GlobalComix("ro")
|
||||
class GlobalComixRussian : GlobalComix("ru")
|
||||
class GlobalComixSwedish : GlobalComix("sv", "se")
|
||||
class GlobalComixSlovak : GlobalComix("sk")
|
||||
class GlobalComixSlovenian : GlobalComix("sl")
|
||||
class GlobalComixTamil : GlobalComix("ta")
|
||||
class GlobalComixThai : GlobalComix("th")
|
||||
class GlobalComixTurkish : GlobalComix("tr")
|
||||
class GlobalComixUkrainian : GlobalComix("uk", "ua")
|
||||
class GlobalComixUrdu : GlobalComix("ur")
|
||||
class GlobalComixVietnamese : GlobalComix("vi")
|
||||
class GlobalComixChineseCantonese : GlobalComix("zh-Hant", "zh")
|
@ -0,0 +1,45 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
/**
|
||||
* Springboard that accepts https://globalcomix.com/c/xxx intents and redirects them to
|
||||
* the main tachiyomi process. The idea is to not install the intent filter unless
|
||||
* you have this extension installed, but still let the main tachiyomi app control
|
||||
* things.
|
||||
*/
|
||||
class GlobalComixUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
|
||||
// Supported path: /c/title-slug
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
val titleId = pathSegments[1]
|
||||
val mainIntent = Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", prefixIdSearch + titleId)
|
||||
putExtra("filter", packageName)
|
||||
}
|
||||
|
||||
try {
|
||||
startActivity(mainIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("GlobalComixUrlActivity", e.toString())
|
||||
}
|
||||
} else {
|
||||
Log.e("GlobalComixUrlActivity", "Received data URL is unsupported: ${intent?.data}")
|
||||
Toast.makeText(this, "This URL cannot be handled by the GlobalComix extension.", Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.artist
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
@SerialName(artist)
|
||||
class ArtistDto(
|
||||
val name: String, // Slug
|
||||
val roman_name: String?,
|
||||
) : EntityDto()
|
@ -0,0 +1,63 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.GlobalComix.Companion.dateFormatter
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.lockSymbol
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.release
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import keiyoushi.utils.tryParse
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias ChapterDto = ResponseDto<ChapterDataDto>
|
||||
typealias ChaptersDto = PaginatedResponseDto<ChapterDataDto>
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
@SerialName(release)
|
||||
class ChapterDataDto(
|
||||
val title: String,
|
||||
val chapter: String, // Stringified number
|
||||
val key: String, // UUID, required for /readV2 endpoint
|
||||
val premium_only: Int? = 0,
|
||||
val published_time: String,
|
||||
|
||||
// Only available when calling the /readV2 endpoint
|
||||
val page_objects: List<PageDataDto>?,
|
||||
) : EntityDto() {
|
||||
val isPremium: Boolean
|
||||
get() = premium_only == 1
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Create an [SChapter] instance from the JSON DTO element.
|
||||
*/
|
||||
fun ChapterDataDto.createChapter(): SChapter {
|
||||
val chapterName = mutableListOf<String>()
|
||||
if (isPremium) {
|
||||
chapterName.add(lockSymbol)
|
||||
}
|
||||
|
||||
chapter.let {
|
||||
if (it.isNotEmpty()) {
|
||||
chapterName.add("Ch.$it")
|
||||
}
|
||||
}
|
||||
|
||||
title.let {
|
||||
if (it.isNotEmpty()) {
|
||||
if (chapterName.isNotEmpty()) {
|
||||
chapterName.add("-")
|
||||
}
|
||||
chapterName.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
return SChapter.create().apply {
|
||||
url = key
|
||||
name = chapterName.joinToString(" ")
|
||||
chapter_number = chapter.toFloatOrNull() ?: 0f
|
||||
date_upload = dateFormatter.tryParse(published_time)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class EntityDto {
|
||||
val id: Long = -1
|
||||
}
|
||||
|
||||
@Serializable
|
||||
class UnknownEntity() : EntityDto()
|
@ -0,0 +1,49 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.comic
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias MangaDto = ResponseDto<MangaDataDto>
|
||||
typealias MangasDto = PaginatedResponseDto<MangaDataDto>
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
@SerialName(comic)
|
||||
class MangaDataDto(
|
||||
val name: String,
|
||||
val description: String?,
|
||||
val status_name: String?,
|
||||
val category_name: String?,
|
||||
val image_url: String?,
|
||||
val artist: ArtistDto,
|
||||
) : EntityDto() {
|
||||
companion object {
|
||||
/**
|
||||
* Create an [SManga] instance from the JSON DTO element.
|
||||
*/
|
||||
fun MangaDataDto.createManga(): SManga =
|
||||
SManga.create().also {
|
||||
it.initialized = true
|
||||
it.url = id.toString()
|
||||
it.description = description
|
||||
it.author = artist.let { it.roman_name ?: it.name }
|
||||
it.status = status_name?.let(::convertStatus) ?: SManga.UNKNOWN
|
||||
it.genre = category_name
|
||||
it.title = name
|
||||
it.thumbnail_url = image_url
|
||||
}
|
||||
|
||||
private fun convertStatus(status: String): Int {
|
||||
return when (status) {
|
||||
"Ongoing" -> SManga.ONGOING
|
||||
"Preview" -> SManga.ONGOING
|
||||
"Finished" -> SManga.COMPLETED
|
||||
"On hold" -> SManga.ON_HIATUS
|
||||
"Cancelled" -> SManga.CANCELLED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import eu.kanade.tachiyomi.extension.all.globalcomix.releasePage
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
@SerialName(releasePage)
|
||||
class PageDataDto(
|
||||
val is_page_paid: Boolean,
|
||||
val desktop_image_url: String,
|
||||
val mobile_image_url: String,
|
||||
) : EntityDto()
|
@ -0,0 +1,36 @@
|
||||
package eu.kanade.tachiyomi.extension.all.globalcomix.dto
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
class PaginatedResponseDto<T : EntityDto>(
|
||||
val payload: PaginatedPayloadDto<T>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class PaginatedPayloadDto<T : EntityDto>(
|
||||
val results: List<T> = emptyList(),
|
||||
val pagination: PaginationStateDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class ResponseDto<T : EntityDto>(
|
||||
val payload: PayloadDto<T>? = null,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
class PayloadDto<T : EntityDto>(
|
||||
val results: T,
|
||||
)
|
||||
|
||||
@Suppress("PropertyName")
|
||||
@Serializable
|
||||
class PaginationStateDto(
|
||||
val page: Int = 1,
|
||||
val per_page: Int = 0,
|
||||
val total_pages: Int = 0,
|
||||
val total_results: Int = 0,
|
||||
) {
|
||||
val hasNextPage: Boolean
|
||||
get() = page < total_pages
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user