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