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