Skip to content

Commit 71c089f

Browse files
Mangakuri (ID): update structure (#13957)
* Mangakuri (ID): update structure * icon * use jsoup for cleanup html in synopsis * Update Mangakuri.kt * Update MangakuriDto.kt
1 parent 45eaa94 commit 71c089f

9 files changed

Lines changed: 387 additions & 13 deletions

File tree

src/id/mangakuri/build.gradle

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,7 @@
11
ext {
22
extName = 'Mangakuri'
33
extClass = '.Mangakuri'
4-
themePkg = 'mangathemesia'
5-
baseUrl = 'https://mangakuri.org'
6-
overrideVersionCode = 0
4+
extVersionCode = 31
75
isNsfw = true
86
}
97

-567 Bytes
Loading
-402 Bytes
Loading
-742 Bytes
Loading
870 Bytes
Loading
2.73 KB
Loading
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
package eu.kanade.tachiyomi.extension.id.mangakuri
2+
3+
import eu.kanade.tachiyomi.source.model.Filter
4+
5+
abstract class SelectFilter(
6+
displayName: String,
7+
private val vals: Array<Pair<String, String>>,
8+
) : Filter.Select<String>(
9+
displayName,
10+
vals.map { it.first }.toTypedArray(),
11+
) {
12+
fun selectedValue() = vals[state].second
13+
}
14+
15+
class OrderFilter :
16+
SelectFilter(
17+
"Order",
18+
arrayOf(
19+
Pair("DESC", "desc"),
20+
Pair("ASC", "asc"),
21+
),
22+
)
23+
24+
class SortFilter :
25+
SelectFilter(
26+
"Sort By",
27+
arrayOf(
28+
Pair("New", "new"),
29+
Pair("Top Views", "views"),
30+
Pair("Top Rate", "rate"),
31+
Pair("Top Bookmark", "bookmark"),
32+
Pair("Title A-Z", "az"),
33+
Pair("Title Z-A", "za"),
34+
),
35+
)
36+
37+
class StatusFilter :
38+
SelectFilter(
39+
"Status",
40+
arrayOf(
41+
Pair("All", ""),
42+
Pair("Ongoing", "ONGOING"),
43+
Pair("Completed", "COMPLETED"),
44+
Pair("Hiatus", "HIATUS"),
45+
),
46+
)
47+
48+
class TypeFilter :
49+
SelectFilter(
50+
"Type",
51+
arrayOf(
52+
Pair("All", ""),
53+
Pair("Manga", "MANGA"),
54+
Pair("Manhwa", "MANHWA"),
55+
Pair("Manhua", "MANHUA"),
56+
),
57+
)
58+
59+
class ColorFilter :
60+
SelectFilter(
61+
"Color",
62+
arrayOf(
63+
Pair("All", ""),
64+
Pair("Full Color", "FULL_COLOR"),
65+
Pair("B&W", "BW"),
66+
),
67+
)
68+
69+
class ReadingFilter :
70+
SelectFilter(
71+
"Reading",
72+
arrayOf(
73+
Pair("All", ""),
74+
Pair("Vertical Scroll", "VERTICAL_SCROLL"),
75+
Pair("Page", "PAGE"),
76+
),
77+
)
78+
79+
class GenreFilter :
80+
SelectFilter(
81+
"Genre",
82+
arrayOf(
83+
Pair("All", ""),
84+
Pair("Action", "action"),
85+
Pair("Adult", "adult"),
86+
Pair("Adventure", "adventure"),
87+
Pair("Aksi", "aksi"),
88+
Pair("Arts", "arts"),
89+
Pair("Bl", "bl"),
90+
Pair("Boys Love", "boys-love"),
91+
Pair("Boyslove", "boyslove"),
92+
Pair("Comedy", "comedy"),
93+
Pair("Crybaby Seme", "crybaby-seme"),
94+
Pair("Dll", "dll"),
95+
Pair("Drama", "drama"),
96+
Pair("Fantasi Modern", "fantasi-modern"),
97+
Pair("Fantasy", "fantasy"),
98+
Pair("Gender Bender", "gender-bender"),
99+
Pair("Gong Bucin", "gong-bucin"),
100+
Pair("Gong Lebih Tua", "gong-lebih-tua"),
101+
Pair("Historical", "historical"),
102+
Pair("Horror", "horror"),
103+
Pair("Investigasi Kasus", "investigasi-kasus"),
104+
Pair("Investigasikasus", "investigasikasus"),
105+
Pair("Isekai", "isekai"),
106+
Pair("Martial", "martial"),
107+
Pair("Martial Arts", "martial-arts"),
108+
Pair("Mature", "mature"),
109+
Pair("Mystery", "mystery"),
110+
Pair("Napolitana Ghost Story", "napolitana-ghost-story"),
111+
Pair("Office", "office"),
112+
Pair("Okultisme", "okultisme"),
113+
Pair("Psychological", "psychological"),
114+
Pair("Reincarnation", "reincarnation"),
115+
Pair("Revenge", "revenge"),
116+
Pair("Romance", "romance"),
117+
Pair("Royalty", "royalty"),
118+
Pair("Salvation", "salvation"),
119+
Pair("School Life", "school-life"),
120+
Pair("Sci-Fi", "sci-fi"),
121+
Pair("Shounen", "shounen"),
122+
Pair("Shounen Ai", "shounen-ai"),
123+
Pair("Slice Of Life", "slice-of-life"),
124+
Pair("Smut", "smut"),
125+
Pair("Smut Supernatural Yaoi", "smut-supernatural-yaoi"),
126+
Pair("Su Aktif", "su-aktif"),
127+
Pair("Su Cinta Bertepuk Sebelah Tangan", "su-cinta-bertepuk-sebelah-tangan"),
128+
Pair("Su Luka Masa Lalu", "su-luka-masa-lalu"),
129+
Pair("Su Menggemaskan", "su-menggemaskan"),
130+
Pair("Supernatural", "supernatural"),
131+
Pair("Xianxia", "xianxia"),
132+
Pair("Yaoi", "yaoi"),
133+
Pair("Yaoi Bl", "yaoi-bl"),
134+
),
135+
)
136+
137+
class TextFilter(name: String, val queryKey: String) : Filter.Text(name)
Lines changed: 192 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,201 @@
11
package eu.kanade.tachiyomi.extension.id.mangakuri
22

3-
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
3+
import eu.kanade.tachiyomi.network.GET
4+
import eu.kanade.tachiyomi.source.model.FilterList
5+
import eu.kanade.tachiyomi.source.model.MangasPage
6+
import eu.kanade.tachiyomi.source.model.Page
7+
import eu.kanade.tachiyomi.source.model.SChapter
8+
import eu.kanade.tachiyomi.source.model.SManga
9+
import eu.kanade.tachiyomi.source.online.HttpSource
10+
import keiyoushi.utils.parseAs
11+
import keiyoushi.utils.tryParse
12+
import okhttp3.HttpUrl.Companion.toHttpUrl
13+
import okhttp3.Request
14+
import okhttp3.Response
15+
import org.jsoup.Jsoup
416
import java.text.SimpleDateFormat
517
import java.util.Locale
18+
import java.util.TimeZone
619

7-
class Mangakuri : MangaThemesia("Mangakuri", "https://mangakuri.org", "id", dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id"))) {
20+
class Mangakuri : HttpSource() {
821

9-
override val hasProjectPage = true
22+
override val name = "Mangakuri"
1023

11-
override val typeFilterOptions = arrayOf(
12-
Pair("Default", ""),
13-
Pair("Manga", "Manga"),
14-
Pair("Manhwa", "Manhwa"),
15-
Pair("Manhua", "Manhua"),
16-
Pair("Comic", "Comic"),
17-
Pair("Novel", "Novel"),
24+
override val baseUrl = "https://mangakuri.org"
25+
26+
override val lang = "id"
27+
28+
override val supportsLatest = true
29+
30+
override val client = network.cloudflareClient
31+
32+
override val versionId = 2
33+
34+
private val apiUrl = "https://api.mangakuri.org/api"
35+
36+
override fun headersBuilder() = super.headersBuilder()
37+
.add("Referer", "$baseUrl/")
38+
.add("Origin", baseUrl)
39+
40+
// ================= Popular =================
41+
override fun popularMangaRequest(page: Int): Request {
42+
val url = "$apiUrl/search".toHttpUrl().newBuilder()
43+
.addQueryParameter("type", "COMIC")
44+
.addQueryParameter("sort", "views")
45+
.addQueryParameter("order", "desc")
46+
.addQueryParameter("limit", "20")
47+
.addQueryParameter("page", page.toString())
48+
.build()
49+
return GET(url, headers)
50+
}
51+
52+
override fun popularMangaParse(response: Response): MangasPage {
53+
val dto = response.parseAs<SearchResponseDto>()
54+
val mangas = dto.data.map { manga ->
55+
SManga.create().apply {
56+
url = "/comic/${manga.slug}"
57+
title = manga.title
58+
thumbnail_url = manga.posterImageUrl
59+
}
60+
}
61+
return MangasPage(mangas, response.request.url.queryParameter("page")!!.toInt() < dto.totalPages)
62+
}
63+
64+
// ================= Latest =================
65+
override fun latestUpdatesRequest(page: Int): Request {
66+
val url = "$apiUrl/search".toHttpUrl().newBuilder()
67+
.addQueryParameter("type", "COMIC")
68+
.addQueryParameter("sort", "new")
69+
.addQueryParameter("order", "desc")
70+
.addQueryParameter("limit", "20")
71+
.addQueryParameter("page", page.toString())
72+
.build()
73+
return GET(url, headers)
74+
}
75+
76+
override fun latestUpdatesParse(response: Response) = popularMangaParse(response)
77+
78+
// ================= Search =================
79+
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
80+
val url = "$apiUrl/search".toHttpUrl().newBuilder()
81+
.addQueryParameter("type", "COMIC")
82+
.addQueryParameter("limit", "20")
83+
.addQueryParameter("page", page.toString())
84+
85+
if (query.isNotEmpty()) {
86+
url.addQueryParameter("q", query)
87+
}
88+
89+
filters.forEach { filter ->
90+
when (filter) {
91+
is SortFilter -> url.addQueryParameter("sort", filter.selectedValue())
92+
is OrderFilter -> url.addQueryParameter("order", filter.selectedValue())
93+
is StatusFilter -> {
94+
val status = filter.selectedValue()
95+
if (status.isNotEmpty()) url.addQueryParameter("status", status)
96+
}
97+
is GenreFilter -> {
98+
val genre = filter.selectedValue()
99+
if (genre.isNotEmpty()) url.addQueryParameter("genre", genre)
100+
}
101+
is TypeFilter -> {
102+
val type = filter.selectedValue()
103+
if (type.isNotEmpty()) url.addQueryParameter("comic_type", type)
104+
}
105+
is ColorFilter -> {
106+
val color = filter.selectedValue()
107+
if (color.isNotEmpty()) url.addQueryParameter("color_format", color)
108+
}
109+
is ReadingFilter -> {
110+
val reading = filter.selectedValue()
111+
if (reading.isNotEmpty()) url.addQueryParameter("reading_format", reading)
112+
}
113+
is TextFilter -> {
114+
if (filter.state.isNotEmpty()) url.addQueryParameter(filter.queryKey, filter.state)
115+
}
116+
else -> {}
117+
}
118+
}
119+
120+
return GET(url.build(), headers)
121+
}
122+
123+
override fun searchMangaParse(response: Response) = popularMangaParse(response)
124+
125+
// ================= Details =================
126+
override fun mangaDetailsRequest(manga: SManga): Request = GET("$apiUrl/series${manga.url}", headers)
127+
128+
override fun mangaDetailsParse(response: Response): SManga {
129+
val dto = response.parseAs<SeriesDetailDto>()
130+
return SManga.create().apply {
131+
url = "/comic/${dto.slug}"
132+
title = dto.title
133+
thumbnail_url = dto.posterImageUrl
134+
author = dto.authorName
135+
artist = dto.artistName
136+
description = dto.synopsis?.let { Jsoup.parse(it).text() }
137+
genre = dto.genres.joinToString { it.name }
138+
status = when (dto.comicStatus?.uppercase()) {
139+
"ONGOING" -> SManga.ONGOING
140+
"COMPLETED" -> SManga.COMPLETED
141+
"HIATUS" -> SManga.ON_HIATUS
142+
else -> SManga.UNKNOWN
143+
}
144+
initialized = true
145+
}
146+
}
147+
148+
override fun getMangaUrl(manga: SManga): String = "$baseUrl${manga.url}"
149+
150+
// ================= Chapters =================
151+
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
152+
153+
override fun chapterListParse(response: Response): List<SChapter> {
154+
val dto = response.parseAs<SeriesDetailDto>()
155+
val comicSlug = dto.slug
156+
return dto.units.map { chapter ->
157+
SChapter.create().apply {
158+
url = "/comic/$comicSlug/chapter/${chapter.slug}"
159+
name = "Chapter ${formatChapterNumber(chapter.number)}"
160+
chapter_number = chapter.number.toFloatOrNull() ?: -1f
161+
date_upload = dateFormat.tryParse(chapter.createdAt)
162+
}
163+
}
164+
}
165+
166+
private fun formatChapterNumber(number: String): String = number.removeSuffix(".00")
167+
168+
override fun getChapterUrl(chapter: SChapter): String = "$baseUrl${chapter.url}"
169+
170+
// ================= Pages =================
171+
override fun pageListRequest(chapter: SChapter): Request = GET("$apiUrl/series${chapter.url}", headers)
172+
173+
override fun pageListParse(response: Response): List<Page> {
174+
val dto = response.parseAs<ChapterDetailDto>()
175+
return dto.chapter.pages.mapIndexed { i, page ->
176+
Page(i, imageUrl = page.imageUrl)
177+
}
178+
}
179+
180+
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
181+
182+
// ================= Filters =================
183+
override fun getFilterList() = FilterList(
184+
SortFilter(),
185+
OrderFilter(),
186+
StatusFilter(),
187+
GenreFilter(),
188+
TypeFilter(),
189+
ColorFilter(),
190+
ReadingFilter(),
191+
TextFilter("Author", "author"),
192+
TextFilter("Artist", "artist"),
193+
TextFilter("Publisher", "publisher"),
18194
)
195+
196+
private val dateFormat by lazy {
197+
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US).apply {
198+
timeZone = TimeZone.getTimeZone("UTC")
199+
}
200+
}
19201
}

0 commit comments

Comments
 (0)