Remove Manga-Flix and Manga Mutiny (#13581)

* Remove Manga-Flix and Manga Mutiny

* use URL for autocloser regex

* fix yaml escape

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>

Co-authored-by: Vetle Ledaal <vetle.ledaal@gmail.com>
This commit is contained in:
stevenyomi 2022-09-27 05:44:02 +08:00 committed by GitHub
parent 45b54599c4
commit 850c93aae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 1 additions and 735 deletions

View File

@ -43,7 +43,7 @@ jobs:
},
{
"type": "both",
"regex": ".*(team\\s*x|tqneplus|manga\\s*disk|komiktap|gourmet\\s*scans|manga\\s*crimson|mangawow|voidscans|hikari\\s*scans|mangagegecesi|piedpiperfb|knightnoscanlations|ahstudios|mangagecesi|nartag|xxx\\s*yaoi|yaoi\\s*fan\\s*clube|luminous|dragontea|manhwaid\\.org|hunters\\s*scan|mnhaestate|swat\\s*manga|manga\\s*swat|reset(?:\\s*|-)scan|aresmanga).*",
"regex": ".*(team\\s*x|tqneplus|manga\\s*disk|komiktap|gourmet\\s*scans|manga\\s*crimson|mangawow|voidscans|hikari\\s*scans|mangagegecesi|piedpiperfb|knightnoscanlations|ahstudios|mangagecesi|nartag|xxx\\s*yaoi|yaoi\\s*fan\\s*clube|luminous|dragontea|manhwaid\\.org|hunters\\s*scan|mnhaestate|swat\\s*manga|manga\\s*swat|reset(?:\\s*|-)scan|aresmanga|manga-flix\\.com).*",
"ignoreCase": true,
"message": "{match} will not be added back as the Scanlator team has requested it to be removed. Read #3475 for more information"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

View File

@ -1,31 +0,0 @@
package eu.kanade.tachiyomi.extension.ar.mangaflix
import eu.kanade.tachiyomi.multisrc.madara.Madara
import eu.kanade.tachiyomi.source.model.SChapter
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
class MangaFlix : Madara(
"مانجا فليكس",
"https://www.manga-flix.com",
"ar",
dateFormat = SimpleDateFormat("dd/MM/yyy", Locale.ROOT)
) {
override val useNewChapterEndpoint = true
override val mangaDetailsSelectorStatus = "div.summarys"
override fun chapterFromElement(element: Element): SChapter {
val chapter = super.chapterFromElement(element)
with(element) {
selectFirst(chapterUrlSelector)?.let { urlElement ->
// Using .text() on the <a> tag is too broad
chapter.name = urlElement.selectFirst("> p")!!.text()
}
}
return chapter
}
}

View File

@ -493,7 +493,6 @@ class MadaraGenerator : ThemeSourceGenerator {
SingleLang("مانجا العاشق", "https://3asq.org", "ar", className = "Manga3asq", overrideVersionCode = 2),
SingleLang("مانجا العرب Manga Alarab", "https://manga-alarab.com", "ar", className = "MangAlarab", overrideVersionCode = 1),
SingleLang("مانجا عرب تيم Manga Arab Team", "https://mangaarbteam.com", "ar", className = "MangaArabTeam", overrideVersionCode = 1),
SingleLang("مانجا فليكس", "https://www.manga-flix.com", "ar", className = "MangaFlix", overrideVersionCode = 1),
SingleLang("مانجا ليك", "https://mangalek.com", "ar", className = "Mangalek", overrideVersionCode = 1),
SingleLang("مانجا لينك", "https://mangalink.io", "ar", className = "MangaLinkio", overrideVersionCode = 2),
)

View File

@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="eu.kanade.tachiyomi.extension">
<application>
<activity
android:name=".en.mangamutiny.MangaMutinyUrlActivity"
android:excludeFromRecents="true"
android:exported="true"
android:theme="@android:style/Theme.NoDisplay">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="mangamutiny.org"
android:pathPattern="/title/..*"
android:scheme="https" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -1,13 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlinx-serialization'
ext {
extName = 'Manga Mutiny'
pkgNameSuffix = "en.mangamutiny"
extClass = '.MangaMutiny'
extVersionCode = 9
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 930 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

View File

@ -1,441 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangamutiny
import android.net.Uri
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservableSuccess
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 kotlinx.serialization.json.Json
import okhttp3.Headers
import okhttp3.Request
import okhttp3.Response
import rx.Observable
import uy.kohesive.injekt.injectLazy
class MangaMutiny : HttpSource() {
override val name = "Manga Mutiny"
override val baseUrl = "https://mangamutiny.org"
override val supportsLatest = true
override val lang = "en"
private val json: Json by injectLazy()
private val baseUrlAPI = "https://api.mangamutiny.org"
private val webViewSingleMangaPath = "title/"
private val webViewMultipleMangaPath = "titles/"
override fun headersBuilder(): Headers.Builder {
return super.headersBuilder().apply {
add("Accept", "application/json")
add("Origin", "https://mangamutiny.org")
}
}
private val apiMangaUrlPath = "v1/public/manga"
private val apiChapterUrlPath = "v1/public/chapter"
private val fetchAmount = 21
companion object {
const val PREFIX_ID_SEARCH = "slug:"
}
// Popular manga
override fun popularMangaRequest(page: Int): Request = mangaRequest(page)
override fun popularMangaParse(response: Response): MangasPage = mangaParse(response)
// Chapters
override fun chapterListRequest(manga: SManga): Request =
mangaDetailsRequestCommon(manga, false)
override fun chapterListParse(response: Response): List<SChapter> {
val responseBody = response.body
return responseBody?.use {
json.decodeFromString(ListChapterDS, it.string()).also {
responseBody.close()
}
} ?: listOf()
}
// latest
override fun latestUpdatesRequest(page: Int): Request =
mangaRequest(page, filters = FilterList(SortFilter().apply { this.state = 1 }))
override fun latestUpdatesParse(response: Response): MangasPage = mangaParse(response)
// browse + latest + search
override fun mangaDetailsRequest(manga: SManga): Request = mangaDetailsRequestCommon(manga)
private fun mangaDetailsRequestCommon(manga: SManga, lite: Boolean = true): Request {
val uri = if (isForWebView()) {
Uri.parse(baseUrl).buildUpon()
.appendEncodedPath(webViewSingleMangaPath)
.appendPath(manga.url)
} else {
Uri.parse(baseUrlAPI).buildUpon()
.appendEncodedPath(apiMangaUrlPath)
.appendPath(manga.url).let {
if (lite) it.appendQueryParameter("lite", "1") else it
}
}
return GET(uri.build().toString(), headers)
}
override fun mangaDetailsParse(response: Response): SManga {
val responseBody = response.body
if (responseBody != null) {
return responseBody.use {
json.decodeFromString(SMangaDS, it.string())
}
} else {
throw IllegalStateException("Response code ${response.code}")
}
}
override fun pageListRequest(chapter: SChapter): Request {
val uri = Uri.parse(baseUrlAPI).buildUpon()
.appendEncodedPath(apiChapterUrlPath)
.appendEncodedPath(chapter.url)
return GET(uri.build().toString(), headers)
}
override fun pageListParse(response: Response): List<Page> {
val responseBody = response.body
return responseBody?.use {
json.decodeFromString(ListPageDS, it.string())
} ?: listOf()
}
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request =
mangaRequest(page, query, filters)
override fun searchMangaParse(response: Response): MangasPage = mangaParse(response)
// commonly used functions
private fun mangaParse(response: Response): MangasPage {
val responseBody = response.body
return if (responseBody != null) {
val deserializationResult = json.decodeFromString(PageInfoDS, responseBody.string())
val totalObjects = deserializationResult.second
val skipped = response.request.url.queryParameter("skip")?.toInt() ?: 0
val moreElementsToSkip = skipped + fetchAmount < totalObjects
val pageSizeEqualsFetchAmount = deserializationResult.first.size == fetchAmount
val hasMorePages = pageSizeEqualsFetchAmount && moreElementsToSkip
MangasPage(deserializationResult.first, hasMorePages)
} else {
MangasPage(listOf(), false)
}
}
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return if (query.startsWith(PREFIX_ID_SEARCH)) {
val realQuery = query.removePrefix(PREFIX_ID_SEARCH)
val tempManga = SManga.create().apply {
url = realQuery
}
client.newCall(mangaDetailsRequestCommon(tempManga, true))
.asObservableSuccess()
.map { response ->
val details = mangaDetailsParse(response)
MangasPage(listOf(details), false)
}
} else {
client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess()
.map { response ->
searchMangaParse(response)
}
}
}
private fun mangaRequest(page: Int, query: String? = null, filters: FilterList? = null): Request {
val forWebView = isForWebView()
val uri = if (forWebView) {
Uri.parse(baseUrl).buildUpon().apply {
appendEncodedPath(webViewMultipleMangaPath)
}
} else {
Uri.parse(baseUrlAPI).buildUpon().apply {
appendEncodedPath(apiMangaUrlPath)
}
}
if (query?.isNotBlank() == true) {
uri.appendQueryParameter("text", query)
}
val applicableFilters = if (filters != null && filters.isNotEmpty()) {
filters
} else {
FilterList(SortFilter())
}
val uriParameterMap = mutableMapOf<String, String>()
for (singleFilter in applicableFilters) {
if (singleFilter is UriFilter) {
singleFilter.addParameter(uriParameterMap)
}
}
for (uriParameter in uriParameterMap) {
uri.appendQueryParameter(uriParameter.key, uriParameter.value)
}
if (!forWebView) {
uri.appendQueryParameter("limit", fetchAmount.toString())
if (page != 1) {
uri.appendQueryParameter("skip", ((page - 1) * fetchAmount).toString())
}
}
return GET(uri.build().toString(), headers)
}
// Filter
override fun getFilterList(): FilterList {
return FilterList(
StatusFilter(),
CategoryFilter(),
GenresFilter(),
FormatsFilter(),
SortFilter(),
AuthorFilter()
// ScanlatorFilter()
)
}
override fun imageUrlParse(response: Response): String {
throw Exception("Not used")
}
private interface UriFilter {
val uriParam: () -> String
val shouldAdd: () -> Boolean
val getParameter: () -> String
fun addParameter(parameterMap: MutableMap<String, String>) {
if (shouldAdd()) {
val newParameterValueBuilder = StringBuilder()
if (parameterMap[uriParam()] != null) {
newParameterValueBuilder.append(parameterMap[uriParam()] + " ")
}
newParameterValueBuilder.append(getParameter())
parameterMap[uriParam()] = newParameterValueBuilder.toString()
}
}
}
private abstract class UriSelectFilter(
displayName: String,
override val uriParam: () -> String,
val vals: Array<Pair<String, String>>,
val defaultValue: Int = 0
) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
override val shouldAdd = fun() =
this.state != defaultValue
override val getParameter = fun() = vals[state].first
}
private class StatusFilter : UriSelectFilter(
"Status",
fun() = "status",
arrayOf(
Pair("", "All"),
Pair("completed", "Completed"),
Pair("ongoing", "Ongoing")
)
)
private class CategoryFilter : UriSelectFilter(
"Category",
fun() = "tags",
arrayOf(
Pair("", "All"),
Pair("josei", "Josei"),
Pair("seinen", "Seinen"),
Pair("shoujo", "Shoujo"),
Pair("shounen", "Shounen")
)
)
// A single filter: either a genre or a format filter
private class GenreOrFormatFilter(val uriParam: String, displayName: String) :
Filter.CheckBox(displayName)
// A collection of genre or format filters
private abstract class GenreOrFormatFilterList(name: String, specificUriParam: String, elementList: List<GenreOrFormatFilter>) : Filter.Group<GenreOrFormatFilter>(name, elementList), UriFilter {
override val shouldAdd = fun() = state.any { it.state }
override val getParameter = fun() =
state.filter { it.state }.joinToString(" ") { it.uriParam }
override val uriParam = fun() = if (isForWebView()) specificUriParam else "tags"
}
// Generes filter list
private class GenresFilter : GenreOrFormatFilterList(
"Genres",
"genres",
listOf(
GenreOrFormatFilter("action", "action"),
GenreOrFormatFilter("adult", "adult"),
GenreOrFormatFilter("adventure", "adventure"),
GenreOrFormatFilter("aliens", "aliens"),
GenreOrFormatFilter("animals", "animals"),
GenreOrFormatFilter("comedy", "comedy"),
GenreOrFormatFilter("cooking", "cooking"),
GenreOrFormatFilter("crossdressing", "crossdressing"),
GenreOrFormatFilter("delinquents", "delinquents"),
GenreOrFormatFilter("demons", "demons"),
GenreOrFormatFilter("drama", "drama"),
GenreOrFormatFilter("ecchi", "ecchi"),
GenreOrFormatFilter("fantasy", "fantasy"),
GenreOrFormatFilter("gender_bender", "gender bender"),
GenreOrFormatFilter("genderswap", "genderswap"),
GenreOrFormatFilter("ghosts", "ghosts"),
GenreOrFormatFilter("gore", "gore"),
GenreOrFormatFilter("gyaru", "gyaru"),
GenreOrFormatFilter("harem", "harem"),
GenreOrFormatFilter("historical", "historical"),
GenreOrFormatFilter("horror", "horror"),
GenreOrFormatFilter("incest", "incest"),
GenreOrFormatFilter("isekai", "isekai"),
GenreOrFormatFilter("loli", "loli"),
GenreOrFormatFilter("magic", "magic"),
GenreOrFormatFilter("magical_girls", "magical girls"),
GenreOrFormatFilter("mangamutiny", "mangamutiny"),
GenreOrFormatFilter("martial_arts", "martial arts"),
GenreOrFormatFilter("mature", "mature"),
GenreOrFormatFilter("mecha", "mecha"),
GenreOrFormatFilter("medical", "medical"),
GenreOrFormatFilter("military", "military"),
GenreOrFormatFilter("monster_girls", "monster girls"),
GenreOrFormatFilter("monsters", "monsters"),
GenreOrFormatFilter("mystery", "mystery"),
GenreOrFormatFilter("ninja", "ninja"),
GenreOrFormatFilter("office_workers", "office workers"),
GenreOrFormatFilter("philosophical", "philosophical"),
GenreOrFormatFilter("psychological", "psychological"),
GenreOrFormatFilter("reincarnation", "reincarnation"),
GenreOrFormatFilter("reverse_harem", "reverse harem"),
GenreOrFormatFilter("romance", "romance"),
GenreOrFormatFilter("school_life", "school life"),
GenreOrFormatFilter("sci_fi", "sci fi"),
GenreOrFormatFilter("sci-fi", "sci-fi"),
GenreOrFormatFilter("sexual_violence", "sexual violence"),
GenreOrFormatFilter("shota", "shota"),
GenreOrFormatFilter("shoujo_ai", "shoujo ai"),
GenreOrFormatFilter("shounen_ai", "shounen ai"),
GenreOrFormatFilter("slice_of_life", "slice of life"),
GenreOrFormatFilter("smut", "smut"),
GenreOrFormatFilter("sports", "sports"),
GenreOrFormatFilter("superhero", "superhero"),
GenreOrFormatFilter("supernatural", "supernatural"),
GenreOrFormatFilter("survival", "survival"),
GenreOrFormatFilter("time_travel", "time travel"),
GenreOrFormatFilter("tragedy", "tragedy"),
GenreOrFormatFilter("video_games", "video games"),
GenreOrFormatFilter("virtual_reality", "virtual reality"),
GenreOrFormatFilter("webtoons", "webtoons"),
GenreOrFormatFilter("wuxia", "wuxia"),
GenreOrFormatFilter("zombies", "zombies")
)
)
// Actual format filter List
private class FormatsFilter : GenreOrFormatFilterList(
"Formats",
"formats",
listOf(
GenreOrFormatFilter("4-koma", "4-koma"),
GenreOrFormatFilter("adaptation", "adaptation"),
GenreOrFormatFilter("anthology", "anthology"),
GenreOrFormatFilter("award_winning", "award winning"),
GenreOrFormatFilter("doujinshi", "doujinshi"),
GenreOrFormatFilter("fan_colored", "fan colored"),
GenreOrFormatFilter("full_color", "full color"),
GenreOrFormatFilter("long_strip", "long strip"),
GenreOrFormatFilter("official_colored", "official colored"),
GenreOrFormatFilter("oneshot", "oneshot"),
GenreOrFormatFilter("web_comic", "web comic")
),
)
private class SortFilter : UriSelectFilter(
"Sort",
fun() = "sort",
arrayOf(
Pair("title", "Name"),
Pair("-lastReleasedAt", "Last update"),
Pair("-createdAt", "Newest"),
Pair("-rating -ratingCount", "Popular")
),
defaultValue = 3
) {
override val shouldAdd = fun() = if (isForWebView()) state != defaultValue else true
override val getParameter = fun(): String {
return if (isForWebView()) {
this.state.toString()
} else {
this.vals[this.state].first
}
}
}
private class AuthorFilter : Filter.Text("Manga Author & Artist"), UriFilter {
override val uriParam = fun() = "creator"
override val shouldAdd = fun() = state.isNotEmpty()
override val getParameter = fun(): String = state
}
/**The scanlator filter exists on the mangamutiny website, however it doesn't work.
This should stay disabled in the extension until it's properly implemented on the website,
otherwise users may be confused by searches that return no results.**/
/*
private class ScanlatorFilter : Filter.Text("Scanlator Name"), UriFilter {
override val uriParam = fun() = "scanlator"
override val shouldAdd = fun() = state.isNotEmpty()
override val getParameter = fun(): String = state
}
*/
}
private fun isForWebView(): Boolean =
Thread.currentThread().stackTrace.map { it.methodName }
.firstOrNull {
it.contains("WebView", true) && !it.contains("isForWebView")
} != null

View File

@ -1,183 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangamutiny
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import kotlinx.serialization.DeserializationStrategy
import kotlinx.serialization.builtins.MapSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.json.JsonArray
import kotlinx.serialization.json.JsonDecoder
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.JsonNull
import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.float
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonPrimitive
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.TimeZone
private fun JsonElement?.primitiveContent(): String? {
if (this is JsonNull) return null
return this?.jsonPrimitive?.content
}
private fun JsonElement?.primitiveInt(): Int? {
if (this is JsonNull) return null
return this?.jsonPrimitive?.int
}
private fun JsonElement?.primitiveFloat(): Float? {
if (this is JsonNull) return null
return this?.jsonPrimitive?.float
}
private val jsonObjectToMapSerializer = MapSerializer(String.serializer(), JsonElement.serializer())
object PageInfoDS : DeserializationStrategy<Pair<List<SManga>, Int>> {
override val descriptor: SerialDescriptor = jsonObjectToMapSerializer.descriptor
override fun deserialize(decoder: Decoder): Pair<List<SManga>, Int> {
require(decoder is JsonDecoder)
val json = decoder.json
val jsonElement = decoder.decodeJsonElement()
require(jsonElement is JsonObject)
val items = (jsonElement["items"] as JsonArray).map { json.decodeFromJsonElement(SMangaDS, it) }
val total = jsonElement["total"]?.jsonPrimitive?.int
require(total != null)
return Pair(items, total)
}
}
object SMangaDS : DeserializationStrategy<SManga> {
override val descriptor: SerialDescriptor = jsonObjectToMapSerializer.descriptor
override fun deserialize(decoder: Decoder): SManga {
require(decoder is JsonDecoder)
val jsonElement = decoder.decodeJsonElement()
require(jsonElement is JsonObject)
val title = jsonElement["title"].primitiveContent()
val slug = jsonElement["slug"].primitiveContent()
val thumbnail = jsonElement["thumbnail"].primitiveContent()
val status: Int = jsonElement["status"].primitiveContent()?.let {
when (it) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
} ?: SManga.UNKNOWN
val summary: String? = jsonElement["summary"].primitiveContent()
val artists: String? = jsonElement["artists"].primitiveContent()
val authors: String? = jsonElement["authors"].primitiveContent()
val tags: String? =
jsonElement["tags"]?.jsonArray?.mapNotNull { it.primitiveContent() }?.joinToString()
require(title != null && slug != null)
return SManga.create().apply {
this.title = title
this.url = slug
this.thumbnail_url = thumbnail
this.status = status
this.description = summary
this.artist = artists
this.author = authors
this.genre = tags
}
}
}
object ListChapterDS : DeserializationStrategy<List<SChapter>> {
override val descriptor: SerialDescriptor = jsonObjectToMapSerializer.descriptor
override fun deserialize(decoder: Decoder): List<SChapter> {
require(decoder is JsonDecoder)
val json = decoder.json
val jsonElement = decoder.decodeJsonElement()
require(jsonElement is JsonObject)
val jsonElementChapters = jsonElement["chapters"]?.jsonArray
require(jsonElementChapters != null)
return jsonElementChapters.map { chapter ->
json.decodeFromJsonElement(SChapterDS, chapter)
}.apply {
if (this.size == 1) this.first().chapter_number = 1F
}
}
}
private object SChapterDS : DeserializationStrategy<SChapter> {
override val descriptor: SerialDescriptor = jsonObjectToMapSerializer.descriptor
override fun deserialize(decoder: Decoder): SChapter {
require(decoder is JsonDecoder)
val jsonElement = decoder.decodeJsonElement()
require(jsonElement is JsonObject)
val volume: Int? = jsonElement["volume"].primitiveInt()
val chapter: Float? = jsonElement["chapter"].primitiveFloat()
val title: String? = jsonElement["title"].primitiveContent()
val slug: String? = jsonElement["slug"].primitiveContent()
val releasedAt: String? = jsonElement["releasedAt"].primitiveContent()
require(slug != null && releasedAt != null)
return SChapter.create().apply {
if (chapter != null) this.chapter_number = chapter
this.name = chapterTitleBuilder(volume, title, chapter)
this.url = slug
this.date_upload = dateFormatter.parse(releasedAt)?.time ?: 0
}
}
private val dateFormatter = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ENGLISH)
.apply { timeZone = TimeZone.getTimeZone("UTC") }
/**
* Converts this Float into a String, removing any trailing .0
*/
private fun Float.toStringWithoutDotZero(): String = when (this % 1) {
0F -> this.toInt().toString()
else -> this.toString()
}
private fun chapterTitleBuilder(volume: Int?, title: String?, chapter: Float?): String {
val chapterTitle = StringBuilder()
if (volume != null) {
chapterTitle.append("Vol. $volume ")
}
if (chapter != null) {
chapterTitle.append("Chapter ${chapter.toStringWithoutDotZero()}")
}
if (title != null && title != "") {
if (chapterTitle.isNotEmpty()) chapterTitle.append(": ")
chapterTitle.append(title)
}
return chapterTitle.toString()
}
}
object ListPageDS : DeserializationStrategy<List<Page>> {
override val descriptor: SerialDescriptor = jsonObjectToMapSerializer.descriptor
override fun deserialize(decoder: Decoder): List<Page> {
require(decoder is JsonDecoder)
val jsonElement = decoder.decodeJsonElement()
require(jsonElement is JsonObject)
val storage: String? = jsonElement["storage"].primitiveContent()
val manga: String? = jsonElement["manga"].primitiveContent()
val id: String? = jsonElement["id"].primitiveContent()
val images: List<String>? =
jsonElement["images"]?.jsonArray?.mapNotNull { it.primitiveContent() }
require(storage != null && manga != null && id != null && images != null)
val chapterUrl = "$storage/$manga/$id/"
return images.mapIndexed { index, pageSuffix -> Page(index, "", chapterUrl + pageSuffix) }
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangamutiny
import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.util.Log
import kotlin.system.exitProcess
/**
* Springboard that accepts https://mangamutiny.org/title/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 MangaMutinyUrlActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val pathSegments = intent?.data?.pathSegments
if (pathSegments != null && pathSegments.size > 1) {
val slug = pathSegments[1]
val mainIntent = Intent().apply {
action = "eu.kanade.tachiyomi.SEARCH"
putExtra("query", "${MangaMutiny.PREFIX_ID_SEARCH}$slug")
putExtra("filter", packageName)
}
try {
startActivity(mainIntent)
} catch (e: ActivityNotFoundException) {
Log.e("MangaMutinyUrlActivity", e.toString())
}
} else {
Log.e("MangaMutinyUrlActivity", "could not parse uri from intent $intent")
}
finish()
exitProcess(0)
}
}