Tapas Re-write (#2401)

Tapas Re-write
This commit is contained in:
happywillow0 2020-03-10 23:19:09 -04:00 committed by GitHub
parent c64cf2948e
commit 9ad04a5b01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 150 additions and 100 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Tapas' appName = 'Tachiyomi: Tapas'
pkgNameSuffix = 'en.tapastic' pkgNameSuffix = 'en.tapastic'
extClass = '.Tapastic' extClass = '.Tapastic'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,64 +1,62 @@
package eu.kanade.tachiyomi.extension.en.tapastic package eu.kanade.tachiyomi.extension.en.tapastic
import android.net.Uri import android.net.Uri
import com.github.salomonbrys.kotson.* import android.util.Log
import com.google.gson.Gson
import com.google.gson.JsonArray
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.* 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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat
import java.util.Locale
class Tapastic : ParsedHttpSource() { class Tapastic : ParsedHttpSource() {
//Info
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override val name = "Tapastic" override val name = "Tapastic"
override val baseUrl = "https://tapas.io" override val baseUrl = "https://tapas.io"
private val browseMangaSelector = ".content-item" //Popular
private val nextPageSelector = "a.paging-btn.next"
private val gson by lazy { Gson() } override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/comics?b=POPULAR&g=&f=NONE&pageNumber=$page&pageSize=20&")
override fun popularMangaSelector() = browseMangaSelector override fun popularMangaNextPageSelector() = "div[data-has-next=true]"
override fun popularMangaSelector() = "li.js-list-item"
private fun mangaFromElement(element: Element) = SManga.create().apply { override fun popularMangaFromElement(element: Element) = SManga.create().apply {
val thumb = element.getElementsByClass("thumb-wrap") url = element.select(".item__thumb a").attr("href")
title = element.select(".item__thumb img").attr("alt")
url = thumb.attr("href") thumbnail_url = element.select(".item__thumb img").attr("src")
title = element.getElementsByClass("title").text().trim()
thumbnail_url = thumb.select("img").attr("src")
} }
override fun popularMangaFromElement(element: Element) = mangaFromElement(element) //Latest
override fun popularMangaNextPageSelector() = nextPageSelector override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/comics?b=FRESH&g=&f=NONE&pageNumber=$page&pageSize=20&")
override fun searchMangaSelector() = "$browseMangaSelector, .search-item-wrap" override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun searchMangaFromElement(element: Element) = mangaFromElement(element) //Search
override fun searchMangaNextPageSelector() = nextPageSelector
override fun popularMangaRequest(page: Int) = GET("$baseUrl/comics?pageNumber=$page&browse=POPULAR")
override fun latestUpdatesSelector() = browseMangaSelector
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
//If there is any search text, use text search, otherwise use filter search //If there is any search text, use text search, otherwise use filter search
val uri = if (query.isNotBlank()) { val uri = if (query.isNotBlank()) {
Uri.parse("$baseUrl/search") Uri.parse("$baseUrl/search")
.buildUpon() .buildUpon()
.appendQueryParameter("t", "COMICS") .appendQueryParameter("t", "COMICS")
.appendQueryParameter("q", query) .appendQueryParameter("q", query)
} else { } else {
val uri = Uri.parse("$baseUrl/comics").buildUpon() val uri = Uri.parse("$baseUrl/comics").buildUpon()
//Append uri filters //Append uri filters
@ -73,104 +71,153 @@ class Tapastic : ParsedHttpSource() {
return GET(uri.toString()) return GET(uri.toString())
} }
override fun latestUpdatesNextPageSelector() = nextPageSelector override fun searchMangaNextPageSelector() =
"${popularMangaNextPageSelector()}, a.paging__button--next"
override fun mangaDetailsParse(document: Document) = SManga.create().apply { override fun searchMangaSelector() = "${popularMangaSelector()}, .search-item-wrap"
title = document.getElementsByClass("series-header-title").text().trim() override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
url = element.select(".item__thumb a, .title-section .title a").attr("href")
author = document.getElementsByClass("name").text().trim() val browseTitle = element.select(".item__thumb img")
artist = author title = if (browseTitle != null) {
browseTitle.attr("alt")
description = document.getElementById("series-desc-body").text().trim() } else {
element.select(".title-section .title a").text()
genre = document.getElementsByClass("genre").joinToString { it.text() } }
thumbnail_url = element.select(".item__thumb img, .thumb-wrap img").attr("src")
status = SManga.UNKNOWN
} }
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comics?pageNumber=$page&browse=FRESH") //Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select(".desc__title").text().trim()
author = document.select(".tag__author").text().trim()
artist = author
description = document.select(".js-series-description").text().trim()
genre = document.select("div.info__genre a, div.item__genre a")
.joinToString(", ") { it.text() }
}
//Chapters
override fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url + "?sort_order=desc", headers)
}
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
//Chapters are stored in JavaScript as JSON! var document = response.asJsoup()
return response.asJsoup().select("script:containsData(_data)").first()?.data().let { script -> val baseUri = document.baseUri().substringBefore("?")
if (script.isNullOrEmpty() || !script.contains("episodeList : [")) { val chapters = mutableListOf<SChapter>()
emptyList() document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
} else { var nextPage = document.select(".paging__button--next:not(.disabled)")
gson.fromJson<JsonArray>(script.substringAfter("episodeList : ").substringBefore(",\n")) while (!nextPage.isNullOrEmpty()) {
//Ensure that the chapter is published (source allows scheduling chapters) document = client.newCall(GET(baseUri + nextPage.attr("href"))).execute().asJsoup()
.filter { it["orgScene"].int != 0 } document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
.map { json -> nextPage = document.select(".paging__button--next:not(.disabled)")
SChapter.create().apply { }
url = "/episode/${json["id"].string}" return chapters
}
name = (if (json["locked"].asBoolean) "\uD83D\uDD12" else "") + json["title"].string override fun chapterListSelector() = "li.content__item"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
val lock = !element.select(".sp-ico-episode-lock, .sp-ico-schedule-white").isNullOrEmpty()
name = if (lock) {
"\uD83D\uDD12 "
} else {
""
} + element.select(".info__title").text().trim()
date_upload = json["publishDate"].long url = if (lock) {
"locked"
} else {
element.select("a").first().attr("href")
}
chapter_number =
element.select(".info__header").text().substringAfter("Episode")
.substringBefore("Early access").trim().toFloat()
chapter_number = json["scene"].float date_upload =
} parseDate(element.select(".info__tag").text().substringAfter(":").substringBefore("").trim())
}.reversed() }
}
private fun parseDate(date: String): Long {
return SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0
}
//Pages
override fun pageListRequest(chapter: SChapter): Request {
if (chapter.url == "locked") throw Exception("Chapter Locked. If logged in, refresh chapter list.")
return GET(baseUrl + chapter.url, headers)
}
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
document.select("img.content__img").forEach {
add(Page(size, "", it.attr("src")))
} }
} }
override fun chapterListSelector() override fun imageUrlParse(document: Document) =
= throw UnsupportedOperationException("This method should not be called!") throw UnsupportedOperationException("This method should not be called!")
override fun chapterFromElement(element: Element) //Filters
= throw UnsupportedOperationException("This method should not be called!")
override fun pageListParse(document: Document)
= document.getElementsByClass("art-image").mapIndexed { index, element ->
Page(index, "", element.attr("src"))
}
//Unused, we can get image urls directly from the chapter page
override fun imageUrlParse(document: Document)
= throw UnsupportedOperationException("This method should not be called!")
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
//Tapastic does not support genre filtering and text search at the same time //Tapastic does not support genre filtering and text search at the same time
Filter.Header("NOTE: Ignored if using text search!"), Filter.Header("NOTE: Ignored if using text search!"),
Filter.Separator(), Filter.Separator(),
FilterFilter(), FilterFilter(),
GenreFilter(), GenreFilter(),
Filter.Separator(), StatusFilter(),
Filter.Header("Sort is ignored when filter is active!"), Filter.Separator(),
SortFilter() Filter.Header("Sort is ignored when filter is active!"),
SortFilter()
) )
private class FilterFilter : UriSelectFilter("Filter", "browse", arrayOf( private class FilterFilter : UriSelectFilter(
"Filter", "b", arrayOf(
Pair("ALL", "None"), Pair("ALL", "None"),
Pair("POPULAR", "Popular"), Pair("POPULAR", "Popular"),
Pair("TRENDING", "Trending"), Pair("TRENDING", "Trending"),
Pair("FRESH", "Fresh"), Pair("FRESH", "Fresh"),
Pair("TAPASTIC", "Staff Picks") Pair("BINGE", "Binge"),
), firstIsUnspecified = false, defaultValue = 1) Pair("ORIGINAL", "Tapas Originals")
), firstIsUnspecified = false, defaultValue = 1
)
private class GenreFilter : UriSelectFilter("Genre", "genreIds", arrayOf( private class GenreFilter : UriSelectFilter(
"Genre", "g", arrayOf(
Pair("", "Any"), Pair("", "Any"),
Pair("7", "Action"), Pair("7", "Action"),
Pair("22", "Boys Love"), Pair("22", "Boys Love"),
Pair("2", "Comedy"), Pair("2", "Comedy"),
Pair("8", "Drama"), Pair("8", "Drama"),
Pair("3", "Fantasy"), Pair("3", "Fantasy"),
Pair("24", "Girls Love"),
Pair("9", "Gaming"), Pair("9", "Gaming"),
Pair("6", "Horror"), Pair("6", "Horror"),
Pair("25", "LGBTQ+"),
Pair("10", "Mystery"), Pair("10", "Mystery"),
Pair("5", "Romance"), Pair("5", "Romance"),
Pair("4", "Science Fiction"), Pair("4", "Science Fiction"),
Pair("1", "Slice of Life") Pair("1", "Slice of Life")
)) )
)
private class SortFilter : UriSelectFilter("Sort", "sortType", arrayOf( private class StatusFilter : UriSelectFilter(
Pair("SUBSCRIBE", "Subscribers"), "Status", "f", arrayOf(
Pair("NONE", "All"),
Pair("F2R", "Free to read"),
Pair("PRM", "Premium")
)
)
private class SortFilter : UriSelectFilter(
"Sort", "s", arrayOf(
Pair("DATE", "Date"),
Pair("LIKE", "Likes"), Pair("LIKE", "Likes"),
Pair("VIEW", "Views"), Pair("SUBSCRIBE", "Subscribers")
Pair("COMMENT", "Comments"), )
Pair("CREATED", "Date"), )
Pair("TITLE", "Name")
))
/** /**
* Class that creates a select filter. Each entry in the dropdown has a name and a display name. * Class that creates a select filter. Each entry in the dropdown has a name and a display name.
@ -178,10 +225,13 @@ class Tapastic : ParsedHttpSource() {
* If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI. * If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI.
*/ */
//vals: <name, display> //vals: <name, display>
private open class UriSelectFilter(displayName: String, val uriParam: String, val vals: Array<Pair<String, String>>, private open class UriSelectFilter(
val firstIsUnspecified: Boolean = true, displayName: String, val uriParam: String, val vals: Array<Pair<String, String>>,
defaultValue: Int = 0) : val firstIsUnspecified: Boolean = true,
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { defaultValue: Int = 0
) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue),
UriFilter {
override fun addToUri(uri: Uri.Builder) { override fun addToUri(uri: Uri.Builder) {
if (state != 0 || !firstIsUnspecified) if (state != 0 || !firstIsUnspecified)
uri.appendQueryParameter(uriParam, vals[state].first) uri.appendQueryParameter(uriParam, vals[state].first)