diff --git a/src/en/mayotune/build.gradle b/src/en/mayotune/build.gradle new file mode 100644 index 000000000..e3353ffa2 --- /dev/null +++ b/src/en/mayotune/build.gradle @@ -0,0 +1,9 @@ +ext { + extName = 'MayoTune' + extClass = '.MayoTune' + extVersionCode = 1 + baseUrl = 'https://mayotune.xyz' + isNsfw = false +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/mayotune/res/mipmap-hdpi/ic_launcher.png b/src/en/mayotune/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..76d3f6035 Binary files /dev/null and b/src/en/mayotune/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/mayotune/res/mipmap-mdpi/ic_launcher.png b/src/en/mayotune/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..b2f3f77e9 Binary files /dev/null and b/src/en/mayotune/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/mayotune/res/mipmap-xhdpi/ic_launcher.png b/src/en/mayotune/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..0e41703a7 Binary files /dev/null and b/src/en/mayotune/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/mayotune/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mayotune/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..9a9e89155 Binary files /dev/null and b/src/en/mayotune/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/mayotune/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mayotune/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..6e53909d6 Binary files /dev/null and b/src/en/mayotune/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/ChapterDto.kt b/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/ChapterDto.kt new file mode 100644 index 000000000..9504fc646 --- /dev/null +++ b/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/ChapterDto.kt @@ -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) +} diff --git a/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/MayoTune.kt b/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/MayoTune.kt new file mode 100644 index 000000000..38fb713bc --- /dev/null +++ b/src/en/mayotune/src/eu/kanade/tachiyomi/extension/en/mayotune/MayoTune.kt @@ -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 { + 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 { + 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 { + val mangas = mutableListOf() + + 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 { + val chapters = response.parseAs>() + 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 { + 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() +}