Add Source MimiHentai (#9726)
* Add Mimihentai * Delete PageDTO.kt * Delete ChapterDTO & use getMangaUrl * Use Page parseAs * delete client * used getChapterUrl as instructed stevenyomi * use toManga * imageUrlParse throw UnsupportedOperationException() Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com> * Fix various issues --------- Co-authored-by: stevenyomi <95685115+stevenyomi@users.noreply.github.com>
This commit is contained in:
parent
cb99f8ea64
commit
42a448d15f
7
src/vi/mimihentai/build.gradle
Normal file
7
src/vi/mimihentai/build.gradle
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
ext {
|
||||||
|
extName = "MiMiHentai"
|
||||||
|
extClass = ".MiMiHentai"
|
||||||
|
extVersionCode = 1
|
||||||
|
isNsfw = true
|
||||||
|
}
|
||||||
|
apply from: "$rootDir/common.gradle"
|
BIN
src/vi/mimihentai/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
src/vi/mimihentai/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 5.8 KiB |
BIN
src/vi/mimihentai/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
src/vi/mimihentai/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.0 KiB |
BIN
src/vi/mimihentai/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
src/vi/mimihentai/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.6 KiB |
BIN
src/vi/mimihentai/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
src/vi/mimihentai/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
BIN
src/vi/mimihentai/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
src/vi/mimihentai/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 26 KiB |
@ -0,0 +1,64 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.vi.mimihentai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import keiyoushi.utils.tryParse
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ListingDto(
|
||||||
|
val data: List<MangaDto>,
|
||||||
|
val totalPage: Long,
|
||||||
|
val currentPage: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class MangaDto(
|
||||||
|
private val id: Long,
|
||||||
|
private val title: String,
|
||||||
|
private val coverUrl: String,
|
||||||
|
private val description: String,
|
||||||
|
private val authors: List<Author>,
|
||||||
|
private val genres: List<Genre>,
|
||||||
|
) {
|
||||||
|
fun toSManga(): SManga = SManga.create().apply {
|
||||||
|
title = this@MangaDto.title
|
||||||
|
thumbnail_url = coverUrl
|
||||||
|
url = "$id"
|
||||||
|
description = this@MangaDto.description
|
||||||
|
author = authors.joinToString { i -> i.name }
|
||||||
|
genre = genres.joinToString { i -> i.name }
|
||||||
|
initialized = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Author(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class Genre(
|
||||||
|
val name: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class ChapterDto(
|
||||||
|
private val id: Long,
|
||||||
|
private val title: String,
|
||||||
|
private val createdAt: String,
|
||||||
|
) {
|
||||||
|
fun toSChapter(mangaId: String): SChapter = SChapter.create().apply {
|
||||||
|
name = title
|
||||||
|
date_upload = dateFormat.tryParse(createdAt)
|
||||||
|
url = "$mangaId/$id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS", Locale.US)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
class PageListDto(
|
||||||
|
val pages: List<String>,
|
||||||
|
)
|
@ -0,0 +1,182 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.vi.mimihentai
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
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 keiyoushi.utils.parseAs
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
import rx.Observable
|
||||||
|
|
||||||
|
class MiMiHentai : HttpSource() {
|
||||||
|
override val name: String = "MiMiHentai"
|
||||||
|
|
||||||
|
override val lang: String = "vi"
|
||||||
|
|
||||||
|
override val baseUrl: String = "https://mimihentai.com"
|
||||||
|
|
||||||
|
private val apiUrl: String = "$baseUrl/api/v1/manga"
|
||||||
|
|
||||||
|
override val supportsLatest: Boolean = true
|
||||||
|
|
||||||
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
|
.add("Referer", "$baseUrl/")
|
||||||
|
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
|
.rateLimit(3)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request = GET(
|
||||||
|
apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("tatcatruyen")
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
addQueryParameter("sort", "updated_at")
|
||||||
|
}.build(),
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
|
val res = response.parseAs<ListingDto>()
|
||||||
|
val hasNextPage = res.currentPage != res.totalPage
|
||||||
|
return MangasPage(res.data.map { it.toSManga() }, hasNextPage)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getMangaUrl(manga: SManga): String = "$baseUrl/g/${manga.url}"
|
||||||
|
|
||||||
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
|
val id = manga.url.substringAfterLast("/")
|
||||||
|
val url = apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment("gallery")
|
||||||
|
.addPathSegment(id)
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val segments = response.request.url.pathSegments
|
||||||
|
val mangaId = segments.last()
|
||||||
|
val res = response.parseAs<List<ChapterDto>>()
|
||||||
|
return res.map { it.toSChapter(mangaId) }
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getChapterUrl(chapter: SChapter): String {
|
||||||
|
val mangaId = chapter.url.substringBefore('/')
|
||||||
|
val chapterId = chapter.url.substringAfter('/')
|
||||||
|
return "$baseUrl/g/$mangaId/chapter/$chapterId"
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
|
val id = manga.url.substringAfterLast("/")
|
||||||
|
val url = apiUrl.toHttpUrl().newBuilder()
|
||||||
|
.addPathSegment("info")
|
||||||
|
.addPathSegment(id)
|
||||||
|
.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
val res = response.parseAs<MangaDto>()
|
||||||
|
return res.toSManga()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListRequest(chapter: SChapter): Request {
|
||||||
|
return GET("$apiUrl/chapter?id=${chapter.url.substringAfterLast("/")}", headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun pageListParse(response: Response): List<Page> {
|
||||||
|
val res = response.parseAs<PageListDto>()
|
||||||
|
return res.pages.mapIndexed { index, url ->
|
||||||
|
Page(index, imageUrl = url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaParse(response: Response): MangasPage = latestUpdatesParse(response)
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request = GET(
|
||||||
|
apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("tatcatruyen")
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
addQueryParameter("sort", "views")
|
||||||
|
}.build(),
|
||||||
|
headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||||
|
return when {
|
||||||
|
query.startsWith(PREFIX_ID_SEARCH) -> {
|
||||||
|
val id = query.removePrefix(PREFIX_ID_SEARCH)
|
||||||
|
client.newCall(searchMangaByIdRequest(id))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response -> searchMangaByIdParse(response) }
|
||||||
|
}
|
||||||
|
query.toIntOrNull() != null -> {
|
||||||
|
client.newCall(searchMangaByIdRequest(query))
|
||||||
|
.asObservableSuccess()
|
||||||
|
.map { response -> searchMangaByIdParse(response) }
|
||||||
|
}
|
||||||
|
else -> super.fetchSearchManga(page, query, filters)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private fun searchMangaByIdRequest(id: String) = GET("$apiUrl/info/$id", headers)
|
||||||
|
|
||||||
|
override fun searchMangaParse(response: Response): MangasPage = popularMangaParse(response)
|
||||||
|
|
||||||
|
private fun searchMangaByIdParse(response: Response): MangasPage {
|
||||||
|
val details = mangaDetailsParse(response)
|
||||||
|
return MangasPage(listOf(details), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val url = apiUrl.toHttpUrl().newBuilder().apply {
|
||||||
|
addPathSegments("search")
|
||||||
|
addQueryParameter("page", page.toString())
|
||||||
|
addQueryParameter("name", query)
|
||||||
|
|
||||||
|
(if (filters.isEmpty()) getFilterList() else filters).forEach { filters ->
|
||||||
|
when (filters) {
|
||||||
|
is SortByList ->
|
||||||
|
{
|
||||||
|
val sort = getSortByList()[filters.state]
|
||||||
|
addQueryParameter("sort", sort.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
return GET(url, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class SortByList(sort: Array<SortBy>) : Filter.Select<SortBy>("Sắp xếp", sort)
|
||||||
|
private class SortBy(name: String, val id: String) : Filter.CheckBox(name) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getFilterList() = FilterList(
|
||||||
|
SortByList(getSortByList()),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun getSortByList() = arrayOf(
|
||||||
|
SortBy("Mới", "updated_at"),
|
||||||
|
SortBy("Likes", "likes"),
|
||||||
|
SortBy("Views", "views"),
|
||||||
|
SortBy("Lưu", "follows"),
|
||||||
|
SortBy("Tên", "title"),
|
||||||
|
)
|
||||||
|
companion object {
|
||||||
|
private const val PREFIX_ID_SEARCH = "id:"
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user