Manta Comics: new extension (#9635)

This commit is contained in:
ObserverOfTime 2021-10-26 18:59:02 +03:00 committed by GitHub
parent 7ec5b66189
commit 6a5bad1f42
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 292 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

12
src/en/manta/build.gradle Normal file
View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Manta Comics'
pkgNameSuffix = 'en.manta'
extClass = '.MantaComics'
extVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

View File

@ -0,0 +1,127 @@
package eu.kanade.tachiyomi.extension.en.manta
import kotlinx.serialization.Serializable
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit.MILLISECONDS as MS
private val isoDate by lazy {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.ROOT)
}
private inline val String?.timestamp: Long
get() = this?.substringBefore('.')?.let(isoDate::parse)?.time ?: 0L
@Serializable
data class Series<T : Any>(
val data: T,
val id: Int,
val image: Cover,
val episodes: List<Episode>? = null
) {
override fun toString() = data.toString()
}
@Serializable
data class Title(private val title: Name) {
override fun toString() = title.toString()
}
@Serializable
data class Details(
val tags: List<Tag>,
val isCompleted: Boolean? = null,
private val description: Description,
private val creators: List<Creator>
) {
val artists by lazy {
creators.filter { it.role == "Illustration" }
}
val authors by lazy {
creators.filter { it.role != "Illustration" }.ifEmpty { creators }
}
override fun toString() = description.toString()
}
@Serializable
data class Episode(
val id: Int,
val ord: Int,
val data: Data?,
private val createdAt: String,
val cutImages: List<Image>? = null
) {
val timestamp: Long
get() = createdAt.timestamp
val isLocked: Boolean
get() = timeTillFree > 0
val waitingTime: String
get() = when (val days = MS.toDays(timeTillFree)) {
0L -> "later today"
1L -> "tomorrow"
else -> "in $days days"
}
private val timeTillFree by lazy {
data?.freeAt.timestamp - System.currentTimeMillis()
}
override fun toString() = buildString {
append(data?.title ?: "Episode $ord")
if (isLocked) append(" \uD83D\uDD12")
}
}
@Serializable
data class Data(
val title: String? = null,
val freeAt: String? = null
)
@Serializable
data class Creator(
private val name: String,
val role: String
) {
override fun toString() = name
}
@Serializable
data class Description(
private val long: String,
private val short: String
) {
override fun toString() = "$short\n\n$long"
}
@Serializable
data class Cover(private val `1280x1840_480`: Image) {
override fun toString() = `1280x1840_480`.toString()
}
@Serializable
data class Image(private val downloadUrl: String) {
override fun toString() = downloadUrl
}
@Serializable
data class Tag(private val name: Name) {
override fun toString() = name.toString()
}
@Serializable
data class Name(private val en: String) {
override fun toString() = en
}
@Serializable
data class Status(
private val description: String,
private val message: String
) {
override fun toString() = "$description: $message"
}

View File

@ -0,0 +1,128 @@
package eu.kanade.tachiyomi.extension.en.manta
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable
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 kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromJsonElement
import kotlinx.serialization.json.jsonObject
import okhttp3.Request
import okhttp3.Response
import uy.kohesive.injekt.injectLazy
class MantaComics : HttpSource() {
override val name = "Manta"
override val lang = "en"
override val baseUrl = "https://manta.net"
override val supportsLatest = false
private val json by injectLazy<Json>()
override fun headersBuilder() = super.headersBuilder()
.set("User-Agent", "Manta/167").set("Origin", baseUrl)
override fun latestUpdatesRequest(page: Int) =
GET("$baseUrl/manta/v1/search/series?cat=New", headers)
override fun fetchPopularManga(page: Int) =
latestUpdatesRequest(page).fetch(::searchMangaParse)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
filters.category.ifEmpty { if (query.isEmpty()) "New" else "" }.let {
GET("$baseUrl/manta/v1/search/series?cat=$it&q=$query", headers)
}
override fun searchMangaParse(response: Response) =
response.parse<List<Series<Title>>>().map {
SManga.create().apply {
title = it.toString()
url = it.id.toString()
thumbnail_url = it.image.toString()
}
}.let { MangasPage(it, false) }
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
searchMangaRequest(page, query, filters).fetch(::searchMangaParse)
// Request the actual manga URL for the webview
override fun mangaDetailsRequest(manga: SManga) =
GET("$baseUrl/series/${manga.url}")
override fun mangaDetailsParse(response: Response) =
SManga.create().apply {
val data = response.parse<Series<Details>>().data
description = data.toString()
genre = data.tags.joinToString()
artist = data.artists.joinToString()
author = data.authors.joinToString()
status = when (data.isCompleted) {
true -> SManga.COMPLETED
else -> SManga.ONGOING
}
initialized = true
}
override fun fetchMangaDetails(manga: SManga) =
chapterListRequest(manga).fetch(::mangaDetailsParse)
override fun chapterListRequest(manga: SManga) =
GET("$baseUrl/front/v1/series/${manga.url}", headers)
override fun chapterListParse(response: Response) =
response.parse<Series<Title>>().episodes!!.map {
SChapter.create().apply {
name = it.toString()
url = it.id.toString()
date_upload = it.timestamp
chapter_number = it.ord.toFloat()
}
}
override fun fetchChapterList(manga: SManga) =
chapterListRequest(manga).fetch(::chapterListParse)
override fun pageListRequest(chapter: SChapter) =
GET("$baseUrl/front/v1/episodes/${chapter.url}", headers)
override fun pageListParse(response: Response) =
response.parse<Episode>().run {
if (!isLocked) return@run cutImages!!
error("This episode will be available $waitingTime.")
}.mapIndexed { idx, img -> Page(idx, "", img.toString()) }
override fun fetchPageList(chapter: SChapter) =
pageListRequest(chapter).fetch(::pageListParse)
override fun getFilterList() = FilterList(Category())
override fun latestUpdatesParse(response: Response) =
throw UnsupportedOperationException("Not used")
override fun popularMangaRequest(page: Int) =
throw UnsupportedOperationException("Not used")
override fun popularMangaParse(response: Response) =
throw UnsupportedOperationException("Not used")
override fun imageUrlParse(response: Response) =
throw UnsupportedOperationException("Not used")
private fun <R> Request.fetch(parse: (Response) -> R) =
client.newCall(this).asObservable().map { res ->
if (res.isSuccessful) return@map parse(res)
error(res.parse<Status>("status").toString())
}!!
private inline fun <reified T> Response.parse(key: String = "data") =
json.decodeFromJsonElement<T>(
json.parseToJsonElement(body!!.string()).jsonObject[key]!!
)
}

View File

@ -0,0 +1,23 @@
package eu.kanade.tachiyomi.extension.en.manta
import eu.kanade.tachiyomi.source.model.Filter
private val categories = arrayOf(
"",
"New",
"Exclusive",
"Completed",
"Romance",
"BL / GL",
"Drama",
"Fantasy",
"Thriller",
"Slice of life"
)
class Category(
values: Array<String> = categories
) : Filter.Select<String>("Category", values)
inline val List<Filter<*>>.category: String
get() = (firstOrNull() as? Category)?.run { values[state] } ?: ""