[New] ReadM (#2542)

* Upload ReadM

* Add Icons

* Update ReadM.kt

- Extend default time out
- Simplify pageListParse
This commit is contained in:
happywillow0 2020-03-31 09:26:02 -04:00 committed by GitHub
parent 9c0b1f252d
commit 18206ed744
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 238 additions and 0 deletions

12
src/en/readm/build.gradle Normal file
View File

@ -0,0 +1,12 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
appName = 'Tachiyomi: ReadM'
pkgNameSuffix = 'en.readm'
extClass = '.ReadM'
extVersionCode = 1
libVersion = '1.2'
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -0,0 +1,226 @@
package eu.kanade.tachiyomi.extension.en.readm
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
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 org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.util.Calendar
import java.util.concurrent.TimeUnit
class ReadM : ParsedHttpSource() {
//Info
override val name: String = "ReadM"
override val baseUrl: String = "https://readm.org"
override val lang: String = "en"
override val supportsLatest: Boolean = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(1, TimeUnit.MINUTES)
.readTimeout(1, TimeUnit.MINUTES)
.retryOnConnectionFailure(true)
.followRedirects(true)
.build()
//Popular
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/popular-manga/$page", headers)
override fun popularMangaNextPageSelector(): String? = "div.pagination a:contains(»)"
override fun popularMangaSelector(): String = "div#discover-response li"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.select("img").attr("abs:src")
element.select("div.subject-title a").first().apply {
title = this.text().trim()
url = this.attr("href")
}
}
//Latest
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest-releases/$page", headers)
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesSelector(): String = "ul.latest-updates li"
override fun latestUpdatesFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.select("img").attr("data-src")
element.select("a").first().apply {
title = this.text().trim()
url = this.attr("href")
}
}
//Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val formBody = FormBody.Builder()
.add("manga-name", query)
filters.forEach { filter ->
when (filter) {
is TypeFilter -> formBody.add("type", typeArray[filter.state].second)
is AuthorName -> formBody.add("author-name", filter.state)
is ArtistName -> formBody.add("artist-name", filter.state)
is StatusFilter -> formBody.add("status", statusArray[filter.state].second)
is GenreFilter -> filter.state.forEach { genre ->
if (genre.isExcluded()) formBody.add("exclude[]", genre.id)
if (genre.isIncluded()) formBody.add("include[]", genre.id)
}
}
}
if (filters.isEmpty()) {
formBody
.add("type", "all")
.add("status", "both")
}
val searchHeaders = headers.newBuilder().add("X-Requested-With", "XMLHttpRequest").build()
return POST("$baseUrl/service/advanced_search", searchHeaders, formBody.build())
}
override fun searchMangaNextPageSelector(): String? = null
override fun searchMangaSelector(): String = "div.poster-with-subject"
override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
//Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
thumbnail_url = document.select("img.series-profile-thumb").attr("abs:src")
title = document.select("h1.page-title").text().trim()
author = document.select("span#first_episode a").text().trim()
artist = document.select("span#last_episode a").text().trim()
description = document.select("div.series-summary-wrapper p").text().trim()
genre = document.select("div.series-summary-wrapper div.item a").joinToString(", ") { it.text().trim() }
}
//Chapters
override fun chapterListSelector(): String = "div.season_start"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
name = element.select("a").text()
url = element.select("a").attr("href")
date_upload = parseChapterDate(element.select("td.episode-date").text().trim())
}
private fun parseChapterDate(date: String): Long {
val dateWords: List<String> = date.split(" ")
if (dateWords.size == 2) {
val timeAgo = Integer.parseInt(dateWords[0])
val calendar = Calendar.getInstance()
when {
dateWords[1].contains("Minute") -> {
calendar.add(Calendar.MINUTE, -timeAgo)
}
dateWords[1].contains("Hour") -> {
calendar.add(Calendar.HOUR_OF_DAY, -timeAgo)
}
dateWords[1].contains("Day") -> {
calendar.add(Calendar.DAY_OF_YEAR, -timeAgo)
}
dateWords[1].contains("Week") -> {
calendar.add(Calendar.WEEK_OF_YEAR, -timeAgo)
}
dateWords[1].contains("Month") -> {
calendar.add(Calendar.MONTH, -timeAgo)
}
dateWords[1].contains("Year") -> {
calendar.add(Calendar.YEAR, -timeAgo)
}
}
return calendar.timeInMillis
}
return 0L
}
//Pages
override fun imageUrlParse(document: Document): String = throw Exception("Not Used")
override fun pageListParse(document: Document): List<Page> = document.select("div.ch-images img").mapIndexed { index, element ->
Page(index, "", element.attr("abs:data-src"))
}
//Filters
override fun getFilterList(): FilterList = FilterList(
TypeFilter(typeArray),
AuthorName(),
ArtistName(),
StatusFilter(statusArray),
GenreFilter(genreArray())
)
private class TypeFilter(values: Array<Pair<String, String>>) : Filter.Select<String>("Type", values.map { it.first }.toTypedArray())
private class AuthorName : Filter.Text("Author Name")
private class ArtistName : Filter.Text("Artist Name")
private class StatusFilter(values: Array<Pair<String, String>>) : Filter.Select<String>("Status", values.map { it.first }.toTypedArray())
private class GenreFilter(state: List<Tag>) : Filter.Group<Tag>("Genres", state)
private class Tag(name: String, val id: String) : Filter.TriState(name)
private val typeArray = arrayOf(
Pair("All", "all"),
Pair("Japanese Manga", "japanese"),
Pair("Korean Manhwa", "korean"),
Pair("Chinese Manhua", "chinese")
)
private val statusArray = arrayOf(
Pair("Both", "both"),
Pair("Ongoing", "ongoing"),
Pair("Completed", "completed")
)
private fun genreArray() = listOf(
Tag("Action", "1"),
Tag("Adventure", "23"),
Tag("Comedy", "12"),
Tag("Doujinshi", "26"),
Tag("Drama", "9"),
Tag("Ecchi", "2"),
Tag("Fantasy", "3"),
Tag("Gender Bender", "30"),
Tag("Harem", "4"),
Tag("Historical", "36"),
Tag("Horror", "34"),
Tag("Josei", "17"),
Tag("Lolicon", "39"),
Tag("Manga", "5"),
Tag("Manhua", "31"),
Tag("Manhwa", "32"),
Tag("Martial Arts", "22"),
Tag("Mecha", "33"),
Tag("Mystery", "13"),
Tag("None", "41"),
Tag("One shot", "16"),
Tag("Psychological", "14"),
Tag("Romance", "6"),
Tag("School Life", "10"),
Tag("Sci fi", "19"),
Tag("Sci-fi", "40"),
Tag("Seinen", "24"),
Tag("Shotacon", "38"),
Tag("Shoujo", "8"),
Tag("Shoujo Ai", "37"),
Tag("Shounen", "7"),
Tag("Shounen Ai", "35"),
Tag("Slice of Life", "21"),
Tag("Sports", "29"),
Tag("Supernatural", "11"),
Tag("Tragedy", "15"),
Tag("Uncategorized", "43"),
Tag("Yaoi", "28"),
Tag("Yuri", "20")
).sortedWith(compareBy { it.name })
}