diff --git a/src/en/kiutaku/AndroidManifest.xml b/src/en/kiutaku/AndroidManifest.xml new file mode 100644 index 000000000..0b523e5bb --- /dev/null +++ b/src/en/kiutaku/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/en/kiutaku/build.gradle b/src/en/kiutaku/build.gradle new file mode 100644 index 000000000..74e905bbd --- /dev/null +++ b/src/en/kiutaku/build.gradle @@ -0,0 +1,12 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'Kiutaku' + pkgNameSuffix = 'en.kiutaku' + extClass = '.Kiutaku' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/en/kiutaku/res/mipmap-hdpi/ic_launcher.png b/src/en/kiutaku/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..3deba530c Binary files /dev/null and b/src/en/kiutaku/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/en/kiutaku/res/mipmap-mdpi/ic_launcher.png b/src/en/kiutaku/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..ccd6e5124 Binary files /dev/null and b/src/en/kiutaku/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/en/kiutaku/res/mipmap-xhdpi/ic_launcher.png b/src/en/kiutaku/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..813477043 Binary files /dev/null and b/src/en/kiutaku/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/en/kiutaku/res/mipmap-xxhdpi/ic_launcher.png b/src/en/kiutaku/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..3e6b139c8 Binary files /dev/null and b/src/en/kiutaku/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/en/kiutaku/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/kiutaku/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..271d1c307 Binary files /dev/null and b/src/en/kiutaku/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/Kiutaku.kt b/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/Kiutaku.kt new file mode 100644 index 000000000..8a8d0fd04 --- /dev/null +++ b/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/Kiutaku.kt @@ -0,0 +1,127 @@ +package eu.kanade.tachiyomi.extension.en.kiutaku + +import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.network.interceptor.rateLimitHost +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.model.UpdateStrategy +import eu.kanade.tachiyomi.source.online.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +class Kiutaku : ParsedHttpSource() { + + override val name = "Kiutaku" + + override val baseUrl = "https://kiutaku.com" + + override val lang = "en" + + override val supportsLatest = true + + override val client by lazy { + network.client.newBuilder() + .rateLimitHost(baseUrl.toHttpUrl(), 2) + .build() + } + + // ============================== Popular =============================== + private fun getPage(page: Int) = (page - 1) * 20 + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/hot?start=${getPage(page)}", headers) + + override fun popularMangaSelector() = "div.blog > div.items-row" + + override fun popularMangaFromElement(element: Element) = SManga.create().apply { + setUrlWithoutDomain(element.selectFirst("a.item-link")!!.attr("href")) + thumbnail_url = element.selectFirst("img")?.attr("src") + title = element.selectFirst("h2")?.text() ?: "Cosplay" + } + + override fun popularMangaNextPageSelector() = "nav > a.pagination-next:not([disabled])" + + // =============================== Latest =============================== + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?start=${getPage(page)}", headers) + + override fun latestUpdatesSelector() = popularMangaSelector() + + override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element) + + override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() + + // =============================== Search =============================== + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + return if (query.startsWith(PREFIX_SEARCH)) { // URL intent handler + val id = query.removePrefix(PREFIX_SEARCH) + client.newCall(GET("$baseUrl/$id")) + .asObservableSuccess() + .map(::searchMangaByIdParse) + } else { + super.fetchSearchManga(page, query, filters) + } + } + + private fun searchMangaByIdParse(response: Response): MangasPage { + val details = mangaDetailsParse(response.use { it.asJsoup() }) + return MangasPage(listOf(details), false) + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + return GET("$baseUrl/?search=$query&start=${getPage(page)}", headers) + } + + override fun searchMangaSelector() = popularMangaSelector() + + override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element) + + override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() + + // =========================== Manga Details ============================ + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + status = SManga.COMPLETED + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + title = document.selectFirst("div.article-header")?.text() ?: "Cosplay" + genre = document.selectFirst("div.article-tags") + ?.select("a.tag > span") + ?.eachText() + ?.joinToString { it.trimStart('#') } + } + + // ============================== Chapters ============================== + // Fix chapter order + override fun chapterListParse(response: Response) = + super.chapterListParse(response).reversed() + + override fun chapterListSelector() = "nav.pagination:first-of-type a" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + setUrlWithoutDomain(element.attr("href")) + val text = element.text() + name = "Page $text" + chapter_number = text.toFloatOrNull() ?: 1F + } + + // =============================== Pages ================================ + override fun pageListParse(document: Document): List { + return document.select("div.article-fulltext img[src]").mapIndexed { index, item -> + Page(index, "", item.attr("src")) + } + } + + override fun imageUrlParse(document: Document): String { + throw UnsupportedOperationException("Not used.") + } + + companion object { + const val PREFIX_SEARCH = "id:" + } +} diff --git a/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/KiutakuUrlActivity.kt b/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/KiutakuUrlActivity.kt new file mode 100644 index 000000000..efd630127 --- /dev/null +++ b/src/en/kiutaku/src/eu/kanade/tachiyomi/extension/en/kiutaku/KiutakuUrlActivity.kt @@ -0,0 +1,41 @@ +package eu.kanade.tachiyomi.extension.en.kiutaku + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +/** + * Springboard that accepts https://kiutaku.com/ intents + * and redirects them to the main Tachiyomi process. + */ +class KiutakuUrlActivity : Activity() { + + private val tag = javaClass.simpleName + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val pathSegments = intent?.data?.pathSegments + if (pathSegments != null && pathSegments.isNotEmpty()) { + val id = pathSegments.first() + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${Kiutaku.PREFIX_SEARCH}$id") + putExtra("filter", packageName) + } + + try { + startActivity(mainIntent) + } catch (e: ActivityNotFoundException) { + Log.e(tag, e.toString()) + } + } else { + Log.e(tag, "could not parse uri from intent $intent") + } + + finish() + exitProcess(0) + } +}