Added Manga-raw.club (#7242)

* [EN] manga-raw.club extension implementation

* Added nsfw tag
This commit is contained in:
NMBLM 2021-05-27 14:43:28 +01:00 committed by GitHub
parent ddbcdcedea
commit bee381f0e6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 348 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,13 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'manga-raw.club'
pkgNameSuffix = 'en.mangarawclub'
extClass = '.MangaRawClub'
extVersionCode = 1
libVersion = '1.2'
containsNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

View File

@ -0,0 +1,333 @@
package eu.kanade.tachiyomi.extension.en.mangarawclub
import eu.kanade.tachiyomi.annotations.Nsfw
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess
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.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Locale
import java.util.concurrent.TimeUnit
@Nsfw
class MangaRawClub : ParsedHttpSource() {
override val name = "manga-raw.club"
override val baseUrl = "https://www.manga-raw.club"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build()
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/browse/?results=$page&filter=views", headers)
}
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/listy/manga/?results=$page", headers)
}
override fun popularMangaSelector() = "ul.novel-list.grid.col.col2 > li"
// override fun popularMangaSelector() = "li.novel-item"
override fun latestUpdatesSelector() = popularMangaSelector()
override fun searchMangaSelector() = "ul.novel-list.grid.col.col1 > li"
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
val coverElement = element.getElementsByClass("novel-cover").first()
var titleElement = element.getElementsByClass("novel-title text1row").first()
if (titleElement == null) {
titleElement = element.getElementsByClass("novel-title text2row").first()
}
manga.thumbnail_url = coverElement.select("img").attr("data-src")
manga.setUrlWithoutDomain(element.select("a").first().attr("href"))
manga.title = titleElement.text()
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "ul.pagination > li:last-child > a"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create()
val authorElement = document.getElementsByClass("author").first()
manga.author = authorElement.select("a").attr("title")
manga.artist = ""
val genres = mutableListOf<String>()
document.select("a[href*=genre]").forEach { element ->
val genre = element.text()
genres.add(genre)
}
manga.genre = genres.joinToString(", ")
manga.status = parseStatus(document.select("h5:contains(Status) + div").text())
manga.description = document.getElementsByClass("description").first().text()
val coverElement = document.getElementsByClass("cover")
manga.thumbnail_url = baseUrl + coverElement.select("img").attr("data-src")
return manga
}
private fun parseStatus(element: String): Int = when {
element.toLowerCase(Locale.getDefault()).contains("publishing") -> SManga.ONGOING
element.toLowerCase(Locale.getDefault()).contains("finished") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
override fun chapterListSelector() = "ul.chapter-list > li"
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.getElementsByTag("a").first()
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = element.getElementsByClass("chapter-title").first().text()
val date = element.getElementsByTag("time").first().attr("datetime")
chapter.date_upload = parseChapterDate(date)
return chapter
}
private fun parseChapterDate(date: String): Long {
// "April 21, 2021, 4:05 p.m."
val fdate = date.replace(".", "").replace("Sept", "Sep")
val format = "MMMMM dd, yyyy, h:mm a"
val format2 = "MMMMM dd, yyyy, h a" // because sometimes if it is exact hour it wont have minutes because why not
val sdf = SimpleDateFormat(format)
return try {
val value = sdf.parse(fdate)
value!!.time
} catch (e: ParseException) {
val sdfF = SimpleDateFormat(format2)
val value = sdfF.parse(fdate)
value!!.time
}
}
override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>()
document.select("section.page-in.content-wrap > div > center > img").forEachIndexed { i, it ->
pages.add(Page(i, "", it.attr("src")))
}
return pages
}
override fun imageUrlParse(document: Document) = ""
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val request = searchMangaRequest(page, query, filters)
return client.newCall(request).asObservableSuccess().map {
response ->
queryParse(response)
}
}
private fun queryParse(response: Response): MangasPage {
val mangas = mutableListOf<SManga>()
val document = response.asJsoup()
document.select(latestUpdatesSelector()).forEach { element ->
mangas.add(latestUpdatesFromElement(element))
}
val nextPage = document.select(latestUpdatesNextPageSelector()).first() != null
return MangasPage(mangas, nextPage)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
if (query.length > 0) {
return GET("$baseUrl/search/?search=$query", headers)
}
val url = "$baseUrl/browse/".toHttpUrlOrNull()!!.newBuilder()
.addQueryParameter("results", page.toString())
val requestBody = FormBody.Builder()
filters.forEach { filter ->
when (filter) {
is GenreList -> {
val genreInclude = mutableListOf<String>()
filter.state.forEach {
if (it.state == 1) {
genreInclude.add(it.name)
}
}
if (genreInclude.isNotEmpty()) {
genreInclude.forEach { genre ->
requestBody.add("options[]", genre)
}
}
}
is GenrePairList -> url.addQueryParameter("genre", filter.toUriPart())
is Order -> url.addQueryParameter("filter", filter.toUriPart())
is Status -> requestBody.add("status", filter.toUriPart())
is Action -> requestBody.add("action", filter.toUriPart())
}
}
return GET(url.toString(), headers)
val built = requestBody.build()
return POST("$baseUrl/search", headers, requestBody.build())
// url: String,
// headers: Headers = DEFAULT_HEADERS,
// body: RequestBody = DEFAULT_BODY,
// cache: CacheControl = DEFAULT_CACHE_CONTROL)
// return GET(url.toString(), headers)
}
private class Action : UriPartFilter(
"Action",
arrayOf(
Pair("All", ""),
Pair("Include", "include"),
Pair("Exclude", "exclude")
)
)
private class Order : UriPartFilter(
"Order",
arrayOf(
Pair("Random", "Random"),
Pair("Updated", "Updated"),
Pair("New", "New"),
Pair("Views", "views"),
)
)
private class Genre(name: String, val id: String = name) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
private class Status : UriPartFilter(
"Status",
arrayOf(
Pair("All", ""),
Pair("Completed", "Completed"),
Pair("Ongoing", "Ongoing")
)
)
private class GenrePairList : UriPartFilter(
"Genres",
arrayOf(
Pair("All", ""),
Pair("Action", "Action"),
Pair("Adult", "Adult"),
Pair("Adventure", "Adventure"),
Pair("Comedy", "Comedy"),
Pair("Cooking", "Cooking"),
Pair("Doujinshi", "Doujinshi"),
Pair("Drama", "Drama"),
Pair("Ecchi", "Ecchi"),
Pair("Fantasy", "Fantasy"),
Pair("Gender bender", "Gender bender"),
Pair("Harem", "Harem"),
Pair("Historical", "Historical"),
Pair("Horror", "Horror"),
Pair("Isekai", "Isekai"),
Pair("Josei", "Josei"),
Pair("Manhua", "Manhua"),
Pair("Manhwa", "Manhwa"),
Pair("Martial arts", "Martial arts"),
Pair("Mature", "Mature"),
Pair("Mecha", "Mecha"),
Pair("Medical", "Medical"),
Pair("Mystery", "Mystery"),
Pair("One shot", "One shot"),
Pair("Psychological", "Psychological"),
Pair("Romance", "Romance"),
Pair("School life", "School life"),
Pair("Sci fi", "Sci fi"),
Pair("Seinen", "Seinen"),
Pair("Shoujo", "Shoujo"),
Pair("Shounen", "Shounen"),
Pair("Slice of life", "Slice of life"),
Pair("Sports", "Sports"),
Pair("Supernatural", "Supernatural"),
Pair("Tragedy", "Tragedy"),
Pair("Webtoons", "Webtoons"),
Pair("ladies", "ladies")
)
)
override fun getFilterList() = FilterList(
Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(),
// Status(),
// Action(),
Order(),
GenrePairList(),
// GenreList(getGenreList())
)
private fun getGenreList() = listOf(
Genre("Action"),
Genre("Adult"),
Genre("Adventure"),
Genre("Comedy"),
Genre("Cooking"),
Genre("Doujinshi"),
Genre("Drama"),
Genre("Ecchi"),
Genre("Fantasy"),
Genre("Gender bender"),
Genre("Harem"),
Genre("Historical"),
Genre("Horror"),
Genre("Isekai"),
Genre("Josei"),
Genre("Manhua"),
Genre("Manhwa"),
Genre("Martial arts"),
Genre("Mature"),
Genre("Mecha"),
Genre("Medical"),
Genre("Mystery"),
Genre("One shot"),
Genre("Psychological"),
Genre("Romance"),
Genre("School life"),
Genre("Sci fi"),
Genre("Seinen"),
Genre("Shoujo"),
Genre("Shounen"),
Genre("Slice of life"),
Genre("Sports"),
Genre("Supernatural"),
Genre("Tragedy"),
Genre("Webtoons"),
Genre("ladies")
)
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
}