parent
							
								
									c64cf2948e
								
							
						
					
					
						commit
						9ad04a5b01
					
				| @ -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' | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -1,56 +1,54 @@ | |||||||
| 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 | ||||||
| @ -73,61 +71,95 @@ 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 searchMangaSelector() = "${popularMangaSelector()}, .search-item-wrap" | ||||||
|  |     override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply { | ||||||
|  |         url = element.select(".item__thumb a, .title-section .title a").attr("href") | ||||||
|  |         val browseTitle = element.select(".item__thumb img") | ||||||
|  |         title = if (browseTitle != null) { | ||||||
|  |             browseTitle.attr("alt") | ||||||
|  |         } else { | ||||||
|  |             element.select(".title-section .title a").text() | ||||||
|  |         } | ||||||
|  |         thumbnail_url = element.select(".item__thumb img, .thumb-wrap img").attr("src") | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     //Details | ||||||
| 
 | 
 | ||||||
|     override fun mangaDetailsParse(document: Document) = SManga.create().apply { |     override fun mangaDetailsParse(document: Document) = SManga.create().apply { | ||||||
|         title = document.getElementsByClass("series-header-title").text().trim() |         title = document.select(".desc__title").text().trim() | ||||||
| 
 |         author = document.select(".tag__author").text().trim() | ||||||
|         author = document.getElementsByClass("name").text().trim() |  | ||||||
|         artist = author |         artist = author | ||||||
| 
 |         description = document.select(".js-series-description").text().trim() | ||||||
|         description = document.getElementById("series-desc-body").text().trim() |         genre = document.select("div.info__genre a, div.item__genre a") | ||||||
| 
 |             .joinToString(", ") { it.text() } | ||||||
|         genre = document.getElementsByClass("genre").joinToString { it.text() } |  | ||||||
| 
 |  | ||||||
|         status = SManga.UNKNOWN |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/comics?pageNumber=$page&browse=FRESH") |     //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)) } | ||||||
|  |         var nextPage = document.select(".paging__button--next:not(.disabled)") | ||||||
|  |         while (!nextPage.isNullOrEmpty()) { | ||||||
|  |             document = client.newCall(GET(baseUri + nextPage.attr("href"))).execute().asJsoup() | ||||||
|  |             document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) } | ||||||
|  |             nextPage = document.select(".paging__button--next:not(.disabled)") | ||||||
|  |         } | ||||||
|  |         return chapters | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     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 { |         } else { | ||||||
|                 gson.fromJson<JsonArray>(script.substringAfter("episodeList : ").substringBefore(",\n")) |             "" | ||||||
|                     //Ensure that the chapter is published (source allows scheduling chapters) |         } + element.select(".info__title").text().trim() | ||||||
|                     .filter { it["orgScene"].int != 0 } |  | ||||||
|                     .map { json -> |  | ||||||
|                     SChapter.create().apply { |  | ||||||
|                         url = "/episode/${json["id"].string}" |  | ||||||
| 
 | 
 | ||||||
|                         name = (if (json["locked"].asBoolean) "\uD83D\uDD12" else "") + json["title"].string |         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() | ||||||
| 
 | 
 | ||||||
|                         date_upload = json["publishDate"].long |         date_upload = | ||||||
| 
 |             parseDate(element.select(".info__tag").text().substringAfter(":").substringBefore("•").trim()) | ||||||
|                         chapter_number = json["scene"].float |  | ||||||
|                     } |  | ||||||
|                 }.reversed() |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     override fun chapterListSelector() |     private fun parseDate(date: String): Long { | ||||||
|             = throw UnsupportedOperationException("This method should not be called!") |         return SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(date)?.time ?: 0 | ||||||
| 
 |  | ||||||
|     override fun chapterFromElement(element: Element) |  | ||||||
|             = 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 |     //Pages | ||||||
|     override fun imageUrlParse(document: Document) | 
 | ||||||
|             = throw UnsupportedOperationException("This method should not be called!") |     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 imageUrlParse(document: Document) = | ||||||
|  |         throw UnsupportedOperationException("This method should not be called!") | ||||||
|  | 
 | ||||||
|  |     //Filters | ||||||
| 
 | 
 | ||||||
|     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 | ||||||
| @ -135,42 +167,57 @@ class Tapastic : ParsedHttpSource() { | |||||||
|         Filter.Separator(), |         Filter.Separator(), | ||||||
|         FilterFilter(), |         FilterFilter(), | ||||||
|         GenreFilter(), |         GenreFilter(), | ||||||
|  |         StatusFilter(), | ||||||
|         Filter.Separator(), |         Filter.Separator(), | ||||||
|         Filter.Header("Sort is ignored when filter is active!"), |         Filter.Header("Sort is ignored when filter is active!"), | ||||||
|         SortFilter() |         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( | ||||||
|  |         displayName: String, val uriParam: String, val vals: Array<Pair<String, String>>, | ||||||
|         val firstIsUnspecified: Boolean = true, |         val firstIsUnspecified: Boolean = true, | ||||||
|                                        defaultValue: Int = 0) : |         defaultValue: Int = 0 | ||||||
|             Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter { |     ) : | ||||||
|  |         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) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user
	 happywillow0
						happywillow0