add Rmanga.app (#16131)

* new source Rmanga.app

* cleanup

* add mirror
This commit is contained in:
mobi2002 2023-04-25 22:18:37 +05:00 committed by GitHub
parent a8773131b8
commit 5dbf037c79
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 294 additions and 0 deletions

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -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

View File

@ -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"
}
}

View File

@ -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),
)