parent
a8773131b8
commit
5dbf037c79
|
@ -0,0 +1,2 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="eu.kanade.tachiyomi.extension" />
|
|
@ -0,0 +1,11 @@
|
|||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'kotlin-android'
|
||||
|
||||
ext {
|
||||
extName = 'Rmanga.app'
|
||||
pkgNameSuffix = 'en.rmanga'
|
||||
extClass = '.Rmanga'
|
||||
extVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 11 KiB |
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
Binary file not shown.
After Width: | Height: | Size: 97 KiB |
|
@ -0,0 +1,186 @@
|
|||
package eu.kanade.tachiyomi.extension.en.rmanga
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
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 okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class Rmanga : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
override val name = "Rmanga.app"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(4)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
override val baseUrl = preferences.getString(DOMAIN_PREF, "https://rmanga.app")!!
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", baseUrl)
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/ranking/most-viewed/$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.category-items > ul > li"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("div.category-name a").let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a.pagination__item:contains(»)"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/latest-updates/$page", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
return super.latestUpdatesParse(response).apply {
|
||||
this.mangas.distinctBy { it.url }
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = "div.latest-updates > ul > li"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("div.latest-updates-name a").let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val payload = FormBody.Builder().apply {
|
||||
add("manga-name", query.trim())
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is TypeFilter -> {
|
||||
add("type", filter.getValue())
|
||||
}
|
||||
is AuthorFilter -> {
|
||||
add("author-name", filter.state.trim())
|
||||
}
|
||||
is ArtistFilter -> {
|
||||
add("artist-name", filter.state.trim())
|
||||
}
|
||||
is StatusFilter -> {
|
||||
add("status", filter.getValue())
|
||||
}
|
||||
is GenreFilter -> {
|
||||
filter.state.forEach { genreState ->
|
||||
when (genreState.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> add("include[]", genreState.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> add("exclude[]", genreState.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
return POST("$baseUrl/detailed-search", headers, payload)
|
||||
}
|
||||
|
||||
override fun getFilterList() = filters
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
title = document.select("div.section-header-title").first()!!.text()
|
||||
description = document.select("div.empty-box").eachText().joinToString("\n\n", postfix = "\n\n")
|
||||
thumbnail_url = document.select("div.novels-detail-left img").attr("abs:src")
|
||||
document.select("div.novels-detail-right > ul").let { element ->
|
||||
author = element.select("li:contains(author)").text().substringAfter(":").trim().takeUnless { it == "N/A" }
|
||||
artist = element.select("li:contains(artist)").text().substringAfter(":").trim().takeUnless { it == "N/A" }
|
||||
genre = element.select("li:contains(genres) a").joinToString { it.text() }
|
||||
status = element.select("li:contains(status)").text().parseStatus()
|
||||
description += element.select("li:contains(alternative)").text()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseStatus(): Int {
|
||||
return when {
|
||||
this.contains("ongoing", true) -> SManga.ONGOING
|
||||
this.contains("completed", true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.novels-detail-chapters a"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
return SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
name = element.ownText()
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select("div.chapter-detail-novel-big-image img").mapIndexed { index, img ->
|
||||
Page(index = index, imageUrl = img.attr("abs:src"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException("Not Used")
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = DOMAIN_PREF
|
||||
title = "Preferred domain"
|
||||
entries = arrayOf("rmanga.app", "readmanga.app")
|
||||
entryValues = arrayOf("https://rmanga.app", "https://readmanga.app")
|
||||
setDefaultValue("https://rmanga.app")
|
||||
summary = "Requires App Restart"
|
||||
}.let { screen.addPreference(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DOMAIN_PREF = "pref_domain"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
package eu.kanade.tachiyomi.extension.en.rmanga
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
class Genre(
|
||||
name: String,
|
||||
val id: String,
|
||||
) : Filter.TriState(name)
|
||||
|
||||
internal class GenreFilter(name: String, private val genres: List<Genre>) :
|
||||
Filter.Group<Genre>(name, genres)
|
||||
|
||||
private val genreList = listOf(
|
||||
Genre("Action", "1"),
|
||||
Genre("Adventure", "23"),
|
||||
Genre("All Categories", "42"),
|
||||
Genre("Comedy", "12"),
|
||||
Genre("Cooking", "51"),
|
||||
Genre("Doujinshi", "26"),
|
||||
Genre("Drama", "9"),
|
||||
Genre("Ecchi", "2"),
|
||||
Genre("Fantasy", "3"),
|
||||
Genre("Gender Bender", "30"),
|
||||
Genre("Harem", "4"),
|
||||
Genre("Historical", "36"),
|
||||
Genre("Horror", "34"),
|
||||
Genre("Isekai", "44"),
|
||||
Genre("Josei", "17"),
|
||||
Genre("Lolicon", "39"),
|
||||
Genre("Magic", "48"),
|
||||
Genre("Manga", "5"),
|
||||
Genre("Manhua", "31"),
|
||||
Genre("Manhwa", "32"),
|
||||
Genre("Martial Arts", "22"),
|
||||
Genre("Mature", "50"),
|
||||
Genre("Mecha", "33"),
|
||||
Genre("Mind Game", "52"),
|
||||
Genre("Mystery", "13"),
|
||||
Genre("None", "41"),
|
||||
Genre("One shot", "16"),
|
||||
Genre("Psychological", "14"),
|
||||
Genre("Recarnation", "49"),
|
||||
Genre("Romance", "6"),
|
||||
Genre("School Life", "10"),
|
||||
Genre("Sci fi", "19"),
|
||||
Genre("Seinen", "24"),
|
||||
Genre("Shotacon", "38"),
|
||||
Genre("Shoujo", "8"),
|
||||
Genre("Shoujo Ai", "37"),
|
||||
Genre("Shounen", "7"),
|
||||
Genre("Shounen Ai", "35"),
|
||||
Genre("Slice of Life", "21"),
|
||||
Genre("Sports", "29"),
|
||||
Genre("Supernatural", "11"),
|
||||
Genre("Time Travel", "45"),
|
||||
Genre("Tragedy", "15"),
|
||||
Genre("Uncategorized", "43"),
|
||||
Genre("Yaoi", "28"),
|
||||
Genre("Yuri", "20"),
|
||||
)
|
||||
|
||||
internal class TypeFilter(name: String, private val types: Array<String>) :
|
||||
Filter.Select<String>(name, types) {
|
||||
fun getValue() = types[state]
|
||||
}
|
||||
|
||||
private val typeFilter: Array<String> = arrayOf(
|
||||
"All",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Chinese",
|
||||
)
|
||||
|
||||
internal class AuthorFilter(title: String) : Filter.Text(title)
|
||||
internal class ArtistFilter(title: String) : Filter.Text(title)
|
||||
|
||||
internal class StatusFilter(name: String, private val status: Array<String>) :
|
||||
Filter.Select<String>(name, status) {
|
||||
fun getValue() = status[state]
|
||||
}
|
||||
|
||||
private val statusFilter: Array<String> = arrayOf(
|
||||
"Both",
|
||||
"Ongoing",
|
||||
"Completed",
|
||||
)
|
||||
|
||||
val filters = FilterList(
|
||||
TypeFilter("Type", typeFilter),
|
||||
AuthorFilter("Author"),
|
||||
ArtistFilter("Artist"),
|
||||
StatusFilter("Status", statusFilter),
|
||||
GenreFilter("Genres", genreList),
|
||||
)
|
Loading…
Reference in New Issue