diff --git a/src/id/mangacan/build.gradle b/src/id/mangacan/build.gradle new file mode 100644 index 000000000..e0af1a66d --- /dev/null +++ b/src/id/mangacan/build.gradle @@ -0,0 +1,10 @@ +ext { + extName = 'Manga Can' + extClass = '.MangaCan' + themePkg = 'mangathemesia' + baseUrl = 'https://mangacanblog.com' + overrideVersionCode = 0 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/id/mangacan/res/mipmap-hdpi/ic_launcher.png b/src/id/mangacan/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..078246a94 Binary files /dev/null and b/src/id/mangacan/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/id/mangacan/res/mipmap-mdpi/ic_launcher.png b/src/id/mangacan/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..1a3098583 Binary files /dev/null and b/src/id/mangacan/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/id/mangacan/res/mipmap-xhdpi/ic_launcher.png b/src/id/mangacan/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..521f509ec Binary files /dev/null and b/src/id/mangacan/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/id/mangacan/res/mipmap-xxhdpi/ic_launcher.png b/src/id/mangacan/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..e78255484 Binary files /dev/null and b/src/id/mangacan/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/id/mangacan/res/mipmap-xxxhdpi/ic_launcher.png b/src/id/mangacan/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..1026fc136 Binary files /dev/null and b/src/id/mangacan/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/id/mangacan/src/eu/kanade/tachiyomi/extension/id/mangacan/MangaCan.kt b/src/id/mangacan/src/eu/kanade/tachiyomi/extension/id/mangacan/MangaCan.kt new file mode 100644 index 000000000..03aece8a2 --- /dev/null +++ b/src/id/mangacan/src/eu/kanade/tachiyomi/extension/id/mangacan/MangaCan.kt @@ -0,0 +1,122 @@ +package eu.kanade.tachiyomi.extension.id.mangacan + +import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia +import eu.kanade.tachiyomi.network.GET +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.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import rx.Observable + +class MangaCan : MangaThemesia( + "Manga Can", + "https://mangacanblog.com", + "id", + "/", +) { + override val client = super.client.newBuilder() + .rateLimit(3) + .build() + + override val supportsLatest = false + + override val seriesGenreSelector = ".seriestugenre a[href*=genre]" + + override val pageSelector = "div.images img" + + private var genreList: Array> = emptyArray() + + override fun imageRequest(page: Page): Request { + return super.imageRequest(page).newBuilder() + .removeHeader("Referer") + .addHeader("Referer", "$baseUrl/") + .build() + } + + override fun searchMangaParse(response: Response): MangasPage { + if (genreList.isEmpty()) { + genreList += "All" to "" + genreList += parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string())) + } + + return super.searchMangaParse(response) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (query.startsWith(URL_SEARCH_PREFIX).not()) return super.fetchSearchManga(page, query, filters) + val url = query.substringAfter(URL_SEARCH_PREFIX) + return fetchMangaDetails(SManga.create().apply { setUrlWithoutDomain(url) }) + .map { + it.apply { setUrlWithoutDomain(url) } + MangasPage(listOf(it), false) + } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + var selected = "" + with(filters.filterIsInstance()) { + selected = when { + isNotEmpty() -> firstOrNull { it.selectedValue().isNotBlank() }?.selectedValue() ?: "" + else -> "" + } + } + + if (query.isBlank() && selected.isBlank()) { + return super.searchMangaRequest(page, query, filters) + } + + val url = if (query.isNotBlank()) { + baseUrl.toHttpUrl().newBuilder() + .addPathSegment("cari") + .addPathSegment(query.trim().replace(SPACES_REGEX, "-").lowercase()) + .addPathSegment("$page.html") + .build() + } else { + "$baseUrl$selected".toHttpUrl() + } + + return GET(url, headers) + } + + override fun getFilterList(): FilterList { + val filters = mutableListOf>() + if (genreList.isNotEmpty()) { + filters.addAll( + listOf( + Filter.Header(intl["genre_exclusion_warning"]), + GenreFilter(intl["genre_filter_title"], genreList), + ), + ) + } else { + filters.add( + Filter.Header(intl["genre_missing_warning"]), + ) + } + return FilterList(filters) + } + + private fun parseGenres(document: Document): Array> { + return document.select(".textwidget.custom-html-widget a").map { element -> + element.text() to element.attr("href") + }.toTypedArray() + } + + private class GenreFilter( + name: String, + options: Array>, + ) : SelectFilter( + name, + options, + ) + + companion object { + val SPACES_REGEX = "\\s+".toRegex() + } +}