Add MayoTune (#9106)
* Add MayoTune * Use fallback for text contents * Use DTO and refactor * Use `selectFirst` for status * Update fallback thumbnail URL * Lint code * Implement as single source * Encapsulate `ChapterDto` * Use relative URL * Correctly handle request endpoints
This commit is contained in:
parent
5406227f0f
commit
ef9d26cfe8
9
src/en/mayotune/build.gradle
Normal file
9
src/en/mayotune/build.gradle
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
ext {
|
||||||
|
extName = 'MayoTune'
|
||||||
|
extClass = '.MayoTune'
|
||||||
|
extVersionCode = 1
|
||||||
|
baseUrl = 'https://mayotune.xyz'
|
||||||
|
isNsfw = false
|
||||||
|
}
|
||||||
|
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/en/mayotune/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/en/mayotune/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.1 KiB |
BIN
src/en/mayotune/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/en/mayotune/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.7 KiB |
BIN
src/en/mayotune/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/en/mayotune/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.3 KiB |
BIN
src/en/mayotune/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/en/mayotune/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.4 KiB |
BIN
src/en/mayotune/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/en/mayotune/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,35 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.mayotune
|
||||||
|
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import kotlinx.serialization.Contextual
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ChapterDto(
|
||||||
|
val id: String,
|
||||||
|
val title: String,
|
||||||
|
val number: Float,
|
||||||
|
val pageCount: Int,
|
||||||
|
val date: String,
|
||||||
|
) {
|
||||||
|
@Contextual
|
||||||
|
private val sdf = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||||
|
|
||||||
|
fun getChapterURL(): String = "/chapter/${this.id}"
|
||||||
|
|
||||||
|
fun getNumberStr(): String = if (this.number % 1 == 0f) {
|
||||||
|
this.number.toInt().toString()
|
||||||
|
} else {
|
||||||
|
this.number.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getChapterTitle(): String = if (!this.title.isEmpty()) {
|
||||||
|
"Chapter ${this.getNumberStr()}: ${this.title}"
|
||||||
|
} else {
|
||||||
|
"Chapter ${this.getNumberStr()}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDateTimestamp(): Long = this.sdf.tryParse(this.date)
|
||||||
|
}
|
@ -0,0 +1,124 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.en.mayotune
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
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.parseAs
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
class MayoTune() : HttpSource() {
|
||||||
|
override val name: String = "MayoTune"
|
||||||
|
override val baseUrl: String = "https://mayotune.xyz"
|
||||||
|
override val lang: String = "en"
|
||||||
|
override val versionId: Int = 1
|
||||||
|
|
||||||
|
private val source = SManga.create().apply {
|
||||||
|
title = "Mayonaka Heart Tune"
|
||||||
|
url = "/"
|
||||||
|
thumbnail_url = "$baseUrl/img/cover.jpg"
|
||||||
|
author = "Masakuni Igarashi"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Popular
|
||||||
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
|
return Observable.just(MangasPage(listOf(source), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Latest
|
||||||
|
override val supportsLatest: Boolean = true
|
||||||
|
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||||
|
return Observable.just(MangasPage(listOf(source), false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Search
|
||||||
|
override fun fetchSearchManga(
|
||||||
|
page: Int,
|
||||||
|
query: String,
|
||||||
|
filters: FilterList,
|
||||||
|
): Observable<MangasPage> {
|
||||||
|
val mangas = mutableListOf<SManga>()
|
||||||
|
|
||||||
|
if (source.title.lowercase().contains(query.lowercase()) ||
|
||||||
|
source.author?.lowercase()?.contains(query.lowercase()) == true
|
||||||
|
) {
|
||||||
|
mangas.add(source)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Observable.just(MangasPage(mangas, false))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage =
|
||||||
|
throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
// Get Override
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
return GET("$baseUrl/api/chapters", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Details
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
val statusText =
|
||||||
|
document.selectFirst("div.text-center:contains(Status)")?.text()
|
||||||
|
?.substringBefore("Status")
|
||||||
|
?.trim()
|
||||||
|
|
||||||
|
url = source.url
|
||||||
|
title = source.title
|
||||||
|
artist = source.artist
|
||||||
|
author = source.author
|
||||||
|
description = document.selectFirst(".text-lg")?.text()
|
||||||
|
genre = document.selectFirst("span.text-sm:nth-child(2)")?.text()?.replace("•", ",")
|
||||||
|
status = when (statusText) {
|
||||||
|
"Ongoing" -> SManga.ONGOING
|
||||||
|
"Completed" -> SManga.COMPLETED
|
||||||
|
"Cancelled" -> SManga.CANCELLED
|
||||||
|
"Hiatus" -> SManga.ON_HIATUS
|
||||||
|
"Finished" -> SManga.PUBLISHING_FINISHED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
thumbnail_url = document.selectFirst("img.object-contain")?.absUrl("src")
|
||||||
|
?.ifEmpty { source.thumbnail_url }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val chapters = response.parseAs<List<ChapterDto>>()
|
||||||
|
return chapters.sortedByDescending { it.number }.map { chapter ->
|
||||||
|
SChapter.create().apply {
|
||||||
|
url = chapter.getChapterURL()
|
||||||
|
name = chapter.getChapterTitle()
|
||||||
|
chapter_number = chapter.number
|
||||||
|
date_upload = chapter.getDateTimestamp()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Pages
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
return document.select("div.w-full > img").mapIndexed { index, img ->
|
||||||
|
Page(index, imageUrl = img.absUrl("src"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user