-
Notifications
You must be signed in to change notification settings - Fork 543
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
ReaperScans Unoriginal: Moved to ParsedHttpSource (#7002)
* move to ParsedHttpSource * remove old code * clean up
- Loading branch information
Showing
3 changed files
with
274 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,7 @@ | ||
ext { | ||
extName = 'Reaper Scans (unoriginal)' | ||
extClass = '.ReaperScansUnoriginal' | ||
themePkg = 'mangathemesia' | ||
baseUrl = 'https://reaper-scans.com' | ||
overrideVersionCode = 0 | ||
extVersionCode = 31 | ||
} | ||
|
||
apply from: "$rootDir/common.gradle" |
107 changes: 107 additions & 0 deletions
107
...aperscansunoriginal/src/eu/kanade/tachiyomi/extension/en/reaperscansunoriginal/Filters.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal | ||
|
||
import eu.kanade.tachiyomi.source.model.Filter | ||
import eu.kanade.tachiyomi.source.model.FilterList | ||
import okhttp3.HttpUrl | ||
|
||
interface UrlPartFilter { | ||
fun addUrlParameter(url: HttpUrl.Builder) | ||
} | ||
|
||
abstract class SelectFilter( | ||
name: String, | ||
private val urlParameter: String, | ||
private val options: List<Pair<String, String>>, | ||
defaultValue: String? = null, | ||
) : UrlPartFilter, Filter.Select<String>( | ||
name, | ||
options.map { it.first }.toTypedArray(), | ||
options.indexOfFirst { it.second == defaultValue }.coerceAtLeast(0), | ||
) { | ||
override fun addUrlParameter(url: HttpUrl.Builder) { | ||
url.addQueryParameter(urlParameter, options[state].second) | ||
} | ||
} | ||
|
||
class CheckBoxFilter(name: String, val value: String) : Filter.CheckBox(name) | ||
|
||
open class CheckBoxGroup( | ||
name: String, | ||
private val urlParameter: String, | ||
options: List<Pair<String, String>>, | ||
) : UrlPartFilter, Filter.Group<CheckBoxFilter>( | ||
name, | ||
options.map { CheckBoxFilter(it.first, it.second) }, | ||
) { | ||
override fun addUrlParameter(url: HttpUrl.Builder) { | ||
val checked = state.filter { it.state }.map { it.value } | ||
|
||
if (checked.isNotEmpty()) { | ||
checked.forEach { genre -> | ||
url.addQueryParameter(urlParameter, genre) | ||
} | ||
} | ||
} | ||
} | ||
|
||
class TypeFilter : CheckBoxGroup( | ||
"Status", | ||
"type[]", | ||
listOf( | ||
Pair("Action", "action"), | ||
Pair("Adventure", "adventure"), | ||
Pair("Fantasy", "fantasy"), | ||
Pair("Manga", "manga"), | ||
Pair("Manhua", "manhua"), | ||
Pair("Manhwa", "manhwa"), | ||
Pair("Seinen", "seinen"), | ||
), | ||
) | ||
|
||
class GenreFilter(genres: List<Pair<String, String>>) : CheckBoxGroup( | ||
"Genres", | ||
"genre[]", | ||
genres, | ||
) | ||
|
||
class YearFilter : CheckBoxGroup( | ||
"Status", | ||
"release[]", | ||
listOf( | ||
Pair("2024", "2024"), | ||
Pair("2023", "2023"), | ||
Pair("2022", "2022"), | ||
Pair("2021", "2021"), | ||
Pair("2020", "2020"), | ||
Pair("2019", "2019"), | ||
Pair("2018", "2018"), | ||
Pair("2017", "2017"), | ||
Pair("2016", "2016"), | ||
Pair("2015", "2015"), | ||
), | ||
) | ||
|
||
class StatusFilter : CheckBoxGroup( | ||
"Status", | ||
"status[]", | ||
listOf( | ||
Pair("Releasing", "on-going"), | ||
Pair("Completed", "end"), | ||
), | ||
) | ||
|
||
class OrderFilter(default: String? = null) : SelectFilter( | ||
"Sort by", | ||
"sort", | ||
listOf( | ||
Pair("", ""), | ||
Pair("Popular", "most_viewed"), | ||
Pair("Latest", "recently_added"), | ||
), | ||
default, | ||
) { | ||
companion object { | ||
val POPULAR = FilterList(OrderFilter("most_viewed")) | ||
val LATEST = FilterList(OrderFilter("recently_added")) | ||
} | ||
} |
172 changes: 166 additions & 6 deletions
172
...ginal/src/eu/kanade/tachiyomi/extension/en/reaperscansunoriginal/ReaperScansUnoriginal.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,14 +1,174 @@ | ||
package eu.kanade.tachiyomi.extension.en.reaperscansunoriginal | ||
|
||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia | ||
import eu.kanade.tachiyomi.network.GET | ||
import eu.kanade.tachiyomi.network.interceptor.rateLimit | ||
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.HttpUrl.Companion.toHttpUrl | ||
import okhttp3.Request | ||
import okhttp3.Response | ||
import org.jsoup.nodes.Document | ||
import org.jsoup.nodes.Element | ||
import java.util.Calendar | ||
|
||
class ReaperScansUnoriginal : ParsedHttpSource() { | ||
override val baseUrl = "https://reaper-scans.com" | ||
|
||
override val name = "Reaper Scans (unoriginal)" | ||
|
||
override val lang = "en" | ||
|
||
override val supportsLatest = true | ||
|
||
class ReaperScansUnoriginal : MangaThemesia( | ||
"Reaper Scans (unoriginal)", | ||
"https://reaper-scans.com", | ||
"en", | ||
) { | ||
override val client = super.client.newBuilder() | ||
.rateLimit(3) | ||
.build() | ||
|
||
// Popular | ||
override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException() | ||
|
||
override fun popularMangaNextPageSelector(): String = throw UnsupportedOperationException() | ||
|
||
override fun popularMangaRequest(page: Int) = | ||
searchMangaRequest(page, "", OrderFilter.POPULAR) | ||
|
||
override fun popularMangaSelector() = throw UnsupportedOperationException() | ||
|
||
override fun popularMangaParse(response: Response) = searchMangaParse(response) | ||
|
||
// Latest | ||
override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException() | ||
|
||
override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException() | ||
|
||
override fun latestUpdatesRequest(page: Int) = | ||
searchMangaRequest(page, "", OrderFilter.LATEST) | ||
|
||
override fun latestUpdatesSelector() = throw UnsupportedOperationException() | ||
|
||
override fun latestUpdatesParse(response: Response) = searchMangaParse(response) | ||
|
||
// Search | ||
override fun searchMangaFromElement(element: Element) = SManga.create().apply { | ||
thumbnail_url = element.select(".poster-image-wrapper > img").attr("src") | ||
title = element.select(".info > a").text() | ||
setUrlWithoutDomain(element.selectFirst(".info a")!!.attr("href")) | ||
} | ||
|
||
override fun searchMangaNextPageSelector() = "a[rel=\"next\"]" | ||
|
||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||
val url = baseUrl.toHttpUrl().newBuilder().apply { | ||
addQueryParameter("post_type", "wp-manga") | ||
addQueryParameter("s", query) | ||
filters.filterIsInstance<UrlPartFilter>().forEach { | ||
it.addUrlParameter(this) | ||
} | ||
if (page > 1) { | ||
addPathSegment("page") | ||
addPathSegment(page.toString()) | ||
} | ||
}.build() | ||
|
||
return GET(url, headers) | ||
} | ||
|
||
override fun searchMangaSelector() = ".inner" | ||
|
||
override fun searchMangaParse(response: Response): MangasPage { | ||
if (genres.isEmpty()) { | ||
genres = parseGenres(response.asJsoup(response.peekBody(Long.MAX_VALUE).string())) | ||
} | ||
|
||
return super.searchMangaParse(response) | ||
} | ||
|
||
// Chapter | ||
override fun chapterFromElement(element: Element) = SChapter.create().apply { | ||
setUrlWithoutDomain(element.attr("href")) | ||
name = element.attr("title") | ||
date_upload = parseRelativeDate(element.selectFirst("span + span")?.text()) | ||
} | ||
|
||
private fun parseRelativeDate(date: String?): Long { | ||
if (date == null) { | ||
return 0L | ||
} | ||
|
||
val trimmedDate = date.split(" ") | ||
if (trimmedDate.size != 3 && trimmedDate[2] != "ago") return 0L | ||
val number = trimmedDate[0].toIntOrNull() ?: return 0L | ||
val unit = trimmedDate[1].removeSuffix("s") // Remove 's' suffix | ||
val now = Calendar.getInstance() | ||
|
||
val javaUnit = when (unit) { | ||
"year", "yr" -> Calendar.YEAR | ||
"month" -> Calendar.MONTH | ||
"week", "wk" -> Calendar.WEEK_OF_MONTH | ||
"day" -> Calendar.DAY_OF_MONTH | ||
"hour", "hr" -> Calendar.HOUR | ||
"minute", "min" -> Calendar.MINUTE | ||
"second", "sec" -> Calendar.SECOND | ||
else -> return 0L | ||
} | ||
|
||
now.add(javaUnit, -number) | ||
|
||
return now.timeInMillis | ||
} | ||
|
||
override fun chapterListSelector() = "a.cairo" | ||
|
||
// Details | ||
override fun mangaDetailsParse(document: Document) = SManga.create().apply { | ||
document.selectFirst("div.serie-info")?.let { info -> | ||
description = info.selectFirst("div.description-content")?.text() | ||
author = info.selectFirst("span:containsOwn(Author) + span")?.text() | ||
artist = info.selectFirst("span:containsOwn(Artist) + span")?.text() | ||
status = info.selectFirst("span:containsOwn(Status) + span")?.text().toStatus() | ||
genre = info.select("div.genre-link").joinToString { it.text() } | ||
} | ||
} | ||
|
||
private fun String?.toStatus() = when { | ||
this == null -> SManga.UNKNOWN | ||
this.contains("Ongoing") -> SManga.ONGOING | ||
this.contains("Completed") -> SManga.COMPLETED | ||
else -> SManga.UNKNOWN | ||
} | ||
|
||
// Pages | ||
override fun pageListParse(document: Document): List<Page> { | ||
val chapterUrl = document.location() | ||
return document.select("div.image-skeleton img") | ||
.filterNot { it.attr("data-src").isEmpty() } | ||
.mapIndexed { i, img -> Page(i, chapterUrl, img.attr("data-src")) } | ||
} | ||
|
||
override fun imageUrlParse(document: Document) = throw UnsupportedOperationException() | ||
|
||
// Filter | ||
override fun getFilterList() = FilterList( | ||
TypeFilter(), | ||
Filter.Header("Press \"Reset\" to attempt to load genres"), | ||
GenreFilter(genres), | ||
YearFilter(), | ||
StatusFilter(), | ||
OrderFilter(), | ||
) | ||
|
||
private var genres = emptyList<Pair<String, String>>() | ||
|
||
private fun parseGenres(document: Document): List<Pair<String, String>> { | ||
return document.select("li:has(input[name=\"genre[]\"])") | ||
.map { | ||
Pair(it.selectFirst("label")!!.text(), it.selectFirst("input")!!.attr("value")) | ||
} | ||
} | ||
} |