diff --git a/src/pt/yushukemangas/AndroidManifest.xml b/src/pt/yushukemangas/AndroidManifest.xml new file mode 100644 index 000000000..eb9cc45dc --- /dev/null +++ b/src/pt/yushukemangas/AndroidManifest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + diff --git a/src/pt/yushukemangas/build.gradle b/src/pt/yushukemangas/build.gradle new file mode 100644 index 000000000..684e0aa57 --- /dev/null +++ b/src/pt/yushukemangas/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'Yushuke Mangas' + extClass = '.YushukeMangas' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/pt/yushukemangas/res/mipmap-hdpi/ic_launcher.png b/src/pt/yushukemangas/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..b6f813e7c Binary files /dev/null and b/src/pt/yushukemangas/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/pt/yushukemangas/res/mipmap-mdpi/ic_launcher.png b/src/pt/yushukemangas/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..3130a6845 Binary files /dev/null and b/src/pt/yushukemangas/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/pt/yushukemangas/res/mipmap-xhdpi/ic_launcher.png b/src/pt/yushukemangas/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..139bd9c9d Binary files /dev/null and b/src/pt/yushukemangas/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/pt/yushukemangas/res/mipmap-xxhdpi/ic_launcher.png b/src/pt/yushukemangas/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..f78162715 Binary files /dev/null and b/src/pt/yushukemangas/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/pt/yushukemangas/res/mipmap-xxxhdpi/ic_launcher.png b/src/pt/yushukemangas/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..df201e6a2 Binary files /dev/null and b/src/pt/yushukemangas/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangas.kt b/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangas.kt new file mode 100644 index 000000000..1895fe4f1 --- /dev/null +++ b/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangas.kt @@ -0,0 +1,178 @@ +package eu.kanade.tachiyomi.extension.pt.yushukemangas + +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.ParsedHttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +class YushukeMangas : ParsedHttpSource() { + + override val name = "Yushuke Mangas" + + override val baseUrl = "https://yushukemangas.com" + + override val lang = "pt-BR" + + override val supportsLatest = true + + override val client = network.cloudflareClient.newBuilder() + .rateLimit(3) + .build() + + // ============================== Popular =============================== + + override fun popularMangaRequest(page: Int) = GET(baseUrl, headers) + + override fun popularMangaSelector() = ".popular-manga-widget .popular-manga-item" + + override fun popularMangaFromElement(element: Element) = SManga.create().apply { + title = element.selectFirst("h3")!!.text() + thumbnail_url = element.selectFirst("img")?.absUrl("src") + setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) + } + + override fun popularMangaNextPageSelector() = null + + // ============================== Latest =============================== + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/?page=$page", headers) + + override fun latestUpdatesSelector() = ".manga-list .manga-item" + + override fun latestUpdatesFromElement(element: Element) = SManga.create().apply { + title = element.selectFirst("h2")!!.text() + thumbnail_url = element.selectFirst("img")?.absUrl("src") + setUrlWithoutDomain(element.selectFirst("a")!!.absUrl("href")) + } + + override fun latestUpdatesNextPageSelector() = ".pagination-next" + + // ============================== Search =============================== + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + var url = "$baseUrl/search".toHttpUrl().newBuilder() + .addQueryParameter("q", query) + .addQueryParameter("page", "1") + .build() + filters.forEach { filter -> + when (filter) { + is GenreFilter -> { + val selected = filter.selected() + if (selected == all) return@forEach + url = "$baseUrl/generos.php".toHttpUrl().newBuilder() + .addQueryParameter("genre", selected) + .addQueryParameter("search", query) + .build() + } + else -> {} + } + } + return GET(url, headers) + } + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (query.startsWith(PREFIX_SEARCH)) { + val id = query.substringAfter(PREFIX_SEARCH) + return client.newCall(GET("$baseUrl/manga?id=$id", headers)) + .asObservableSuccess() + .map { + val manga = mangaDetailsParse(it.asJsoup()) + MangasPage(listOf(manga), false) + } + } + return super.fetchSearchManga(page, query, filters) + } + + override fun searchMangaSelector() = "${latestUpdatesSelector()}, a.search-item" + + override fun searchMangaFromElement(element: Element) = SManga.create().apply { + title = element.selectFirst("h3, .search-title")!!.text() + thumbnail_url = element.selectFirst("img")?.absUrl("src") + setUrlWithoutDomain((element.selectFirst("a") ?: element).absUrl("href")) + } + + override fun searchMangaNextPageSelector() = null + + // ============================== Manga Details ========================= + + override fun mangaDetailsParse(document: Document) = SManga.create().apply { + val details = document.selectFirst(".manga-header")!! + title = details.selectFirst("h1")!!.text() + thumbnail_url = details.selectFirst(".manga-image img")?.absUrl("src") + genre = details.select(".manga-generos .genre-button").joinToString { it.text() } + description = details.selectFirst("p.manga-sinopse")?.text() + setUrlWithoutDomain(document.location()) + } + + // ============================== Chapters =============================== + + override fun chapterListSelector() = ".chapter-list .chapter-item a" + + override fun chapterFromElement(element: Element) = SChapter.create().apply { + name = element.selectFirst(".chapter-number")!!.text() + setUrlWithoutDomain(element.absUrl("href")) + } + + private fun chapterListNextPageSelector() = latestUpdatesNextPageSelector() + + override fun fetchChapterList(manga: SManga): Observable> { + val chapters = mutableListOf() + var page = 1 + do { + val document = fetchChapterListPage(manga, page++) + chapters += document.select(chapterListSelector()).map(::chapterFromElement) + } while (document.selectFirst(chapterListNextPageSelector()) != null) + return Observable.just(chapters) + } + + private fun fetchChapterListPage(manga: SManga, page: Int): Document { + return client + .newCall(GET("$baseUrl${manga.url}&page=$page", headers)) + .execute().asJsoup() + } + + // ============================== Pages =============================== + + override fun pageListParse(document: Document): List { + return document.select(".chapter-images img").mapIndexed { index, imageUrl -> + Page(index, imageUrl = imageUrl.absUrl("src")) + } + } + + override fun imageUrlParse(document: Document) = "" + + // ============================== Filters =============================== + + open class GenreFilter(displayName: String, private val vals: Array, state: Int = 0) : + Filter.Select(displayName, vals, state) { + fun selected() = vals[state] + } + + override fun getFilterList() = FilterList(GenreFilter("Gêneros", genresList)) + + private val all = "Todos" + + private val genresList = arrayOf( + all, "+18", "Abuso", "Adulto", "Amor Puro", "Artes Marciais", + "Aventura", "Ação", "Comédia", "Crime", "Cultivação", "Drama", + "Fantasia", "Gap Girls", "Gore", "Harém", "Histórico", "Horror", + "Isekai", "Mistério", "Overpowered", "Psicológico", "Reencarnação", + "Romance", "Sistema", "Tragédia", "Viagem no Tempo", "Violência", + ) + + companion object { + const val PREFIX_SEARCH = "id:" + } +} diff --git a/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangasUrlActivity.kt b/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangasUrlActivity.kt new file mode 100644 index 000000000..78e58dab1 --- /dev/null +++ b/src/pt/yushukemangas/src/eu/kanade/tachiyomi/extension/pt/yushukemangas/YushukeMangasUrlActivity.kt @@ -0,0 +1,36 @@ +package eu.kanade.tachiyomi.extension.pt.yushukemangas + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Bundle +import android.util.Log +import kotlin.system.exitProcess + +class YushukeMangasUrlActivity : Activity() { + + private val tag = javaClass.simpleName + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val id = intent?.data?.getQueryParameter("id") + if (id != null) { + val mainIntent = Intent().apply { + action = "eu.kanade.tachiyomi.SEARCH" + putExtra("query", "${YushukeMangas.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) + } +}