diff --git a/src/all/eromuse/build.gradle b/src/all/eromuse/build.gradle new file mode 100644 index 000000000..904f0b549 --- /dev/null +++ b/src/all/eromuse/build.gradle @@ -0,0 +1,13 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +ext { + extName = 'EroMuse (8muses and Erofus)' + pkgNameSuffix = 'all.eromuse' + extClass = '.EroMuseFactory' + extVersionCode = 1 + libVersion = '1.2' + containsNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/eromuse/res/mipmap-hdpi/ic_launcher.png b/src/all/eromuse/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..ed7255f26 Binary files /dev/null and b/src/all/eromuse/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/eromuse/res/mipmap-mdpi/ic_launcher.png b/src/all/eromuse/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..93b95f52e Binary files /dev/null and b/src/all/eromuse/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/eromuse/res/mipmap-xhdpi/ic_launcher.png b/src/all/eromuse/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..61a27645f Binary files /dev/null and b/src/all/eromuse/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/eromuse/res/mipmap-xxhdpi/ic_launcher.png b/src/all/eromuse/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..ec70641b2 Binary files /dev/null and b/src/all/eromuse/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/eromuse/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/eromuse/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..91ea439c7 Binary files /dev/null and b/src/all/eromuse/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/eromuse/res/web_hi_res_512.png b/src/all/eromuse/res/web_hi_res_512.png new file mode 100644 index 000000000..7be23d2f2 Binary files /dev/null and b/src/all/eromuse/res/web_hi_res_512.png differ diff --git a/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuse.kt b/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuse.kt new file mode 100644 index 000000000..8ff8ba023 --- /dev/null +++ b/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuse.kt @@ -0,0 +1,349 @@ +package eu.kanade.tachiyomi.extension.all.eromuse + +import eu.kanade.tachiyomi.network.GET +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.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import okhttp3.Request +import okhttp3.Response +import org.jsoup.nodes.Document +import org.jsoup.nodes.Element +import rx.Observable + +@ExperimentalStdlibApi +open class EroMuse(override val name: String, override val baseUrl: String) : HttpSource() { + + override val lang = "en" + + override val supportsLatest = true + + override val client: OkHttpClient = network.cloudflareClient + + /** + * Browse, search, and latest all run through an ArrayDeque of requests that acts as a stack we push and pop to/from + * For the fetch functions, we only need to worry about pushing the first page to the stack because subsequent pages + * get pushed to the stack during parseManga(). Page 1's URL must include page=1 if the next page would be page=2, + * if page 2 is path_to/2, nothing special needs to be done. + */ + + // the stack - shouldn't need to touch these except for visibility + protected data class StackItem(val url: String, val pageType: Int) + private lateinit var stackItem: StackItem + protected val pageStack = ArrayDeque() + companion object { + const val VARIOUS_AUTHORS = 0 + const val AUTHOR = 1 + const val SEARCH_RESULTS_OR_BASE = 2 + } + protected lateinit var currentSortingMode: String + + // might need to override for new sources + private val nextPageSelector = ".pagination span.current + span a" + protected open val albumSelector = "a.c-tile:has(img):not(:has(.members-only))" + protected open val topLevelPathSegments = listOf("album", "Various-Authors") + private val pageQueryRegex = Regex("""page=\d+""") + + private fun Document.addNextPageToStack() { + this.select(nextPageSelector).firstOrNull()?.text()?.toIntOrNull()?.let { int -> + val nextPage = if (stackItem.url.contains(pageQueryRegex)) { + stackItem.url.replace(pageQueryRegex, "page=$int") + } else { + val httpUrl = HttpUrl.parse(stackItem.url)!! + val builder = if (httpUrl.pathSegments().last().toIntOrNull() is Int) { + httpUrl.newBuilder().removePathSegment(httpUrl.pathSegments().lastIndex) + } else { + httpUrl.newBuilder() + } + builder.addPathSegment(int.toString()).toString() + } + pageStack.add(StackItem(nextPage, stackItem.pageType)) + } + } + + protected fun Element.imgAttr(): String = if (this.hasAttr("data-src")) this.attr("abs:data-src") else this.attr("abs:src") + + private fun mangaFromElement(element: Element): SManga { + return SManga.create().apply { + setUrlWithoutDomain(element.attr("href")) + title = element.text() + thumbnail_url = element.select("img").firstOrNull()?.imgAttr() + } + } + + protected fun parseManga(document: Document): MangasPage { + fun internalParse(document: Document): List { + val authorDocument = if (stackItem.pageType == VARIOUS_AUTHORS) { + client.newCall(stackRequest()).execute().asJsoup() + } else { + document + } + authorDocument.addNextPageToStack() + return authorDocument.select(albumSelector).map { mangaFromElement(it) } + } + + if (stackItem.pageType in listOf(VARIOUS_AUTHORS, SEARCH_RESULTS_OR_BASE)) document.addNextPageToStack() + val mangas = when (stackItem.pageType) { + VARIOUS_AUTHORS -> { + document.select(albumSelector)?.let { elements -> elements.reversed().map { pageStack.addLast(StackItem(it.attr("abs:href"), AUTHOR)) } } + internalParse(document) + } + AUTHOR -> { + internalParse(document) + } + SEARCH_RESULTS_OR_BASE -> { + val searchMangas = mutableListOf() + document.select(albumSelector) + .map { element -> + val url = element.attr("abs:href") + when (HttpUrl.parse(url)!!.pathSegments().takeLastWhile { it !in topLevelPathSegments }.count()) { + // manga album + 2 -> { + searchMangas.add(mangaFromElement(element)) + } + // author album + 1 -> { + pageStack.addLast(StackItem(url, AUTHOR)) + if (searchMangas.isEmpty()) searchMangas += internalParse(client.newCall(stackRequest()).execute().asJsoup()) else null + } + // 0 doesn't need to be parsed; >= 3 probably doesn't need to be parsed but in some instances maybe we could do something + else -> null + } + } + searchMangas + } + else -> emptyList() + } + return MangasPage(mangas, pageStack.isNotEmpty()) + } + + protected fun stackRequest(): Request { + stackItem = pageStack.removeLast() + val url = if (stackItem.pageType == AUTHOR && currentSortingMode.isNotEmpty() && !stackItem.url.contains("sort")) { + HttpUrl.parse(stackItem.url)!!.newBuilder().addQueryParameter("sort", currentSortingMode).toString() + } else { + stackItem.url + } + return GET(url, headers) + } + + // Popular + + protected fun fetchManga(url: String, page: Int, sortingMode: String): Observable { + if (page == 1) { + pageStack.clear() + pageStack.addLast(StackItem(url, VARIOUS_AUTHORS)) + currentSortingMode = sortingMode + } + + return client.newCall(stackRequest()) + .asObservableSuccess() + .map { response -> parseManga(response.asJsoup()) } + } + + override fun fetchPopularManga(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors", page, "") + + override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException("Not used") + override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") + + // Latest + + override fun fetchLatestUpdates(page: Int): Observable = fetchManga("$baseUrl/comics/album/Various-Authors?sort=date", page, "date") + + override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException("Not used") + override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") + + // Search + + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (page == 1) { + pageStack.clear() + + val filterList = if (filters.isEmpty()) getFilterList() else filters + currentSortingMode = filterList.filterIsInstance().first().toQueryValue() + + if (query.isNotBlank()) { + val url = HttpUrl.parse("$baseUrl/search?q=$query")!!.newBuilder().apply { + if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) + addQueryParameter("page", "1") + } + pageStack.addLast(StackItem(url.toString(), SEARCH_RESULTS_OR_BASE)) + } else { + val albumFilter = filterList.filterIsInstance().first().selection() + val url = HttpUrl.parse("$baseUrl/comics/${albumFilter.pathSegments}")!!.newBuilder().apply { + if (currentSortingMode.isNotEmpty()) addQueryParameter("sort", currentSortingMode) + if (albumFilter.pageType != AUTHOR) addQueryParameter("page", "1") + } + pageStack.addLast(StackItem(url.toString(), albumFilter.pageType)) + } + } + + return client.newCall(stackRequest()) + .asObservableSuccess() + .map { response -> parseManga(response.asJsoup()) } + } + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException("Not used") + override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException("Not used") + + // Details + + override fun mangaDetailsParse(response: Response): SManga { + return SManga.create().apply { + with(response.asJsoup()) { + thumbnail_url = select("$albumSelector img").firstOrNull()?.imgAttr() + author = select("div.top-menu-breadcrumb li:nth-last-child(2)").text() + } + } + } + + // Chapters + + protected open val linkedChapterSelector = "a.c-tile:has(img)[href*=/comics/album/]" + protected open val pageThumbnailSelector = "a.c-tile:has(img)[href*=/comics/picture/] img" + + override fun chapterListParse(response: Response): List { + val document = response.asJsoup() + // Linked albums + val chapters = document.select(linkedChapterSelector) + .mapNotNull { + SChapter.create().apply { + name = it.text() + setUrlWithoutDomain(it.attr("href")) + } + } + .reversed() + .toMutableList() + // Self + document.select(pageThumbnailSelector).firstOrNull()?.let { + chapters.add( + SChapter.create().apply { + name = "Chapter" + setUrlWithoutDomain(response.request().url().toString()) + } + ) + } + return chapters + } + + // Pages + + protected open val pageThumbnailPathSegment = "/th/" + protected open val pageFullsizePathSegment = "/fl/" + + override fun pageListParse(response: Response): List { + return response.asJsoup().select(pageThumbnailSelector).mapIndexed { i, img -> + Page(i, "", img.imgAttr().replace(pageThumbnailPathSegment, pageFullsizePathSegment)) + } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Not used") + + // Filters + + override fun getFilterList(): FilterList { + return FilterList( + Filter.Header("Text search only combines with sort!"), + Filter.Separator(), + AlbumFilter(getAlbumList()), + SortFilter(getSortList()) + ) + } + + protected class AlbumFilter(private val vals: Array>) : Filter.Select("Album", vals.map { it.first }.toTypedArray()) { + fun selection() = AlbumFilterData(vals[state].second, vals[state].third) + data class AlbumFilterData(val pathSegments: String, val pageType: Int) + } + protected open fun getAlbumList() = arrayOf( + Triple("Various Authors", "album/Various-Authors", VARIOUS_AUTHORS), + Triple("All Authors", "", SEARCH_RESULTS_OR_BASE), + Triple("MilfToon Comics", "album/MilfToon-Comics", AUTHOR), + Triple("Fakku Comics", "album/Fakku-Comics", AUTHOR), + Triple("BE Story Club Comics", "album/BE-Story-Club-Comics", AUTHOR), + Triple("ShadBase Comics", "album/ShadBase-Comics", AUTHOR), + Triple("ZZZ Comics", "album/ZZZ-Comics", AUTHOR), + Triple("PalComix Comics", "album/PalComix-Comics", AUTHOR), + Triple("Hentai and Manga English", "album/Hentai-and-Manga-English", AUTHOR), + Triple("MCC Comics", "album/MCC-Comics", AUTHOR), + Triple("Expansionfan Comics", "album/Expansionfan-Comics", AUTHOR), + Triple("JAB Comics", "album/JAB-Comics", AUTHOR), + Triple("Giantess Fan Comics", "album/Giantess-Fan-Comics", AUTHOR), + Triple("Renderotica Comics", "album/Renderotica-Comics", AUTHOR), + Triple("IllustratedInterracial.com Comics", "album/IllustratedInterracial_com-Comics", AUTHOR), + Triple("Giantess Club Comics", "album/Giantess-Club-Comics", AUTHOR), + Triple("Innocent Dickgirls Comics", "album/Innocent-Dickgirls-Comics", AUTHOR), + Triple("Locofuria Comics", "album/Locofuria-Comics", AUTHOR), + Triple("PigKing - CrazyDad Comics", "album/PigKing-CrazyDad-Comics", AUTHOR), + Triple("Cartoon Reality Comics", "album/Cartoon-Reality-Comics", AUTHOR), + Triple("Affect3D Comics", "album/Affect3D-Comics", AUTHOR), + Triple("TG Comics", "album/TG-Comics", AUTHOR), + Triple("Melkormancin.com Comics", "album/Melkormancin_com-Comics", AUTHOR), + Triple("Seiren.com.br Comics", "album/Seiren_com_br-Comics", AUTHOR), + Triple("Tracy Scops Comics", "album/Tracy-Scops-Comics", AUTHOR), + Triple("Fake Celebrities Sex Pictures", "album/Fake-Celebrities-Sex-Pictures", AUTHOR), + Triple("Fred Perry Comics", "album/Fred-Perry-Comics", AUTHOR), + Triple("Witchking00 Comics", "album/Witchking00-Comics", AUTHOR), + Triple("8muses Comics", "album/8muses-Comics", AUTHOR), + Triple("KAOS Comics", "album/KAOS-Comics", AUTHOR), + Triple("Vaesark Comics", "album/Vaesark-Comics", AUTHOR), + Triple("Fansadox Comics", "album/Fansadox-Comics", AUTHOR), + Triple("DreamTales Comics", "album/DreamTales-Comics", AUTHOR), + Triple("Croc Comics", "album/Croc-Comics", AUTHOR), + Triple("Jay Marvel Comics", "album/Jay-Marvel-Comics", AUTHOR), + Triple("JohnPersons.com Comics", "album/JohnPersons_com-Comics", AUTHOR), + Triple("MuscleFan Comics", "album/MuscleFan-Comics", AUTHOR), + Triple("Taboolicious.xxx Comics", "album/Taboolicious_xxx-Comics", AUTHOR), + Triple("MongoBongo Comics", "album/MongoBongo-Comics", AUTHOR), + Triple("Slipshine Comics", "album/Slipshine-Comics", AUTHOR), + Triple("Everfire Comics", "album/Everfire-Comics", AUTHOR), + Triple("PrismGirls Comics", "album/PrismGirls-Comics", AUTHOR), + Triple("Abimboleb Comics", "album/Abimboleb-Comics", AUTHOR), + Triple("Y3DF - Your3DFantasy.com Comics", "album/Y3DF-Your3DFantasy_com-Comics", AUTHOR), + Triple("Grow Comics", "album/Grow-Comics", AUTHOR), + Triple("OkayOkayOKOk Comics", "album/OkayOkayOKOk-Comics", AUTHOR), + Triple("Tufos Comics", "album/Tufos-Comics", AUTHOR), + Triple("Cartoon Valley", "album/Cartoon-Valley", AUTHOR), + Triple("3DMonsterStories.com Comics", "album/3DMonsterStories_com-Comics", AUTHOR), + Triple("Kogeikun Comics", "album/Kogeikun-Comics", AUTHOR), + Triple("The Foxxx Comics", "album/The-Foxxx-Comics", AUTHOR), + Triple("Theme Collections", "album/Theme-Collections", AUTHOR), + Triple("Interracial-Comics", "album/Interracial-Comics", AUTHOR), + Triple("Expansion Comics", "album/Expansion-Comics", AUTHOR), + Triple("Moiarte Comics", "album/Moiarte-Comics", AUTHOR), + Triple("Incognitymous Comics", "album/Incognitymous-Comics", AUTHOR), + Triple("DizzyDills Comics", "album/DizzyDills-Comics", AUTHOR), + Triple("DukesHardcoreHoneys.com Comics", "album/DukesHardcoreHoneys_com-Comics", AUTHOR), + Triple("Stormfeder Comics", "album/Stormfeder-Comics", AUTHOR), + Triple("Bimbo Story Club Comics", "album/Bimbo-Story-Club-Comics", AUTHOR), + Triple("Smudge Comics", "album/Smudge-Comics", AUTHOR), + Triple("Dollproject Comics", "album/Dollproject-Comics", AUTHOR), + Triple("SuperHeroineComixxx", "album/SuperHeroineComixxx", AUTHOR), + Triple("Karmagik Comics", "album/Karmagik-Comics", AUTHOR), + Triple("Blacknwhite.com Comics", "album/Blacknwhite_com-Comics", AUTHOR), + Triple("ArtOfJaguar Comics", "album/ArtOfJaguar-Comics", AUTHOR), + Triple("Kirtu.com Comics", "album/Kirtu_com-Comics", AUTHOR), + Triple("UberMonkey Comics", "album/UberMonkey-Comics", AUTHOR), + Triple("DarkSoul3D Comics", "album/DarkSoul3D-Comics", AUTHOR), + Triple("Markydaysaid Comics", "album/Markydaysaid-Comics", AUTHOR), + Triple("Central Comics", "album/Central-Comics", AUTHOR), + Triple("Frozen Parody Comics", "album/Frozen-Parody-Comics", AUTHOR), + Triple("Blacknwhitecomics.com Comix", "album/Blacknwhitecomics_com-Comix", AUTHOR) + ) + + protected class SortFilter(private val vals: Array>) : Filter.Select("Sort Order", vals.map { it.first }.toTypedArray()) { + fun toQueryValue() = vals[state].second + } + protected open fun getSortList() = arrayOf( + Pair("Views", ""), + Pair("Likes", "like"), + Pair("Date", "date"), + Pair("A-Z", "az") + ) +} diff --git a/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuseFactory.kt b/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuseFactory.kt new file mode 100644 index 000000000..51639c733 --- /dev/null +++ b/src/all/eromuse/src/eu/kanade/tachiyomi/extension/all/eromuse/EroMuseFactory.kt @@ -0,0 +1,162 @@ +package eu.kanade.tachiyomi.extension.all.eromuse + +import eu.kanade.tachiyomi.network.asObservableSuccess +import eu.kanade.tachiyomi.source.Source +import eu.kanade.tachiyomi.source.SourceFactory +import eu.kanade.tachiyomi.source.model.FilterList +import eu.kanade.tachiyomi.source.model.MangasPage +import eu.kanade.tachiyomi.source.model.SManga +import eu.kanade.tachiyomi.util.asJsoup +import okhttp3.HttpUrl +import okhttp3.Response +import rx.Observable + +@ExperimentalStdlibApi +class EroMuseFactory : SourceFactory { + override fun createSources(): List = listOf( + EroMuse("8Muses", "https://comics.8muses.com"), + Erofus() + ) +} + +@ExperimentalStdlibApi +class Erofus : EroMuse("Erofus", "https://www.erofus.com") { + + override val albumSelector = "a.a-click" + override val topLevelPathSegments = listOf("various-authors", "comics") + + override fun fetchPopularManga(page: Int): Observable = fetchManga("$baseUrl/comics/various-authors?sort=viewed&page=1", page, "viewed") + override fun fetchLatestUpdates(page: Int): Observable = fetchManga("$baseUrl/comics/various-authors?sort=recent&page=1", page, "recent") + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { + if (page == 1) { + pageStack.clear() + + val filterList = if (filters.isEmpty()) getFilterList() else filters + currentSortingMode = filterList.filterIsInstance().first().toQueryValue() + + if (query.isNotBlank()) { + // TODO possibly add genre search if a decent list of them can be built + pageStack.addLast(StackItem("$baseUrl/?search=$query&sort=$currentSortingMode&page=1", SEARCH_RESULTS_OR_BASE)) + } else { + val albumFilter = filterList.filterIsInstance().first().selection() + val url = HttpUrl.parse(baseUrl + albumFilter.pathSegments)!!.newBuilder() + .addQueryParameter("sort", currentSortingMode) + .addQueryParameter("page", "1") + + pageStack.addLast(StackItem(url.toString(), albumFilter.pageType)) + } + } + + return client.newCall(stackRequest()) + .asObservableSuccess() + .map { response -> parseManga(response.asJsoup()) } + } + + override fun mangaDetailsParse(response: Response): SManga { + return SManga.create().apply { + with(response.asJsoup()) { + thumbnail_url = select("$albumSelector img").firstOrNull()?.imgAttr() + author = select("div.navigation-breadcrumb li:nth-last-child(3)").text() + genre = select("div.album-tag-container a").joinToString { it.text() } + } + } + } + + override val linkedChapterSelector = "a.a-click:has(img)[href^=/comics/]" + override val pageThumbnailSelector = "a.a-click:has(img)[href*=/pic/] img" + + override val pageThumbnailPathSegment = "/thumb/" + override val pageFullsizePathSegment = "/medium/" + + override fun getAlbumList() = arrayOf( + Triple("Various Authors", "/comics/various-authors", VARIOUS_AUTHORS), + Triple("All Authors", "", SEARCH_RESULTS_OR_BASE), + Triple("Various Authors", "/comics/various-authors", AUTHOR), + Triple("TabooLicious.xxx Comics", "/comics/taboolicious_xxx-comics", AUTHOR), + Triple("Hentai and Manga English", "/comics/hentai-and-manga-english", AUTHOR), + Triple("IllustratedInterracial.com Comics", "/comics/illustratedinterracial_com-comics", AUTHOR), + Triple("ZZZ Comics", "/comics/zzz-comics", AUTHOR), + Triple("JohnPersons.com Comics", "/comics/johnpersons_com-comics", AUTHOR), + Triple("For members only", "/", AUTHOR), + Triple("PalComix Comics", "/comics/palcomix-comics", AUTHOR), + Triple("Melkormancin.com Comics", "/comics/melkormancin_com-comics", AUTHOR), + Triple("TG Comics", "/comics/tg-comics", AUTHOR), + Triple("ShadBase Comics", "/comics/shadbase-comics", AUTHOR), + Triple("Filthy Figments Comics", "/comics/filthy-figments-comics", AUTHOR), + Triple("Witchking00 Comics", "/comics/witchking00-comics", AUTHOR), + Triple("Tease Comix", "/comics/tease-comix", AUTHOR), + Triple("PrismGirls Comics", "/comics/prismgirls-comics", AUTHOR), + Triple("Croc Comics", "/comics/croc-comics", AUTHOR), + Triple("CRAZYXXX3DWORLD Comics", "/comics/crazyxxx3dworld-comics", AUTHOR), + Triple("Moiarte Comics", "/comics/moiarte-comics", AUTHOR), + Triple("Nicole Heat Comics", "/comics/nicole-heat-comics", AUTHOR), + Triple("Expansion Comics", "/comics/expansion-comics", AUTHOR), + Triple("DizzyDills Comics", "/comics/dizzydills-comics", AUTHOR), + Triple("Hustler Cartoons", "/comics/hustler-cartoons", AUTHOR), + Triple("ArtOfJaguar Comics", "/comics/artofjaguar-comics", AUTHOR), + Triple("Grow Comics", "/comics/grow-comics", AUTHOR), + Triple("Bimbo Story Club Comics", "/comics/bimbo-story-club-comics", AUTHOR), + Triple("HentaiTNA.com Comics", "/comics/hentaitna_com-comics", AUTHOR), + Triple("ZZomp Comics", "/comics/zzomp-comics", AUTHOR), + Triple("Seiren.com.br Comics", "/comics/seiren_com_br-comics", AUTHOR), + Triple("DukesHardcoreHoneys.com Comics", "/comics/dukeshardcorehoneys_com-comics", AUTHOR), + Triple("Frozen Parody Comics", "/comics/frozen-parody-comics", AUTHOR), + Triple("Giantess Club Comics", "/comics/giantess-club-comics", AUTHOR), + Triple("Ultimate3DPorn Comics", "/comics/ultimate3dporn-comics", AUTHOR), + Triple("Sean Harrington Comics", "/comics/sean-harrington-comics", AUTHOR), + Triple("Central Comics", "/comics/central-comics", AUTHOR), + Triple("Mana World Comics", "/comics/mana-world-comics", AUTHOR), + Triple("The Foxxx Comics", "/comics/the-foxxx-comics", AUTHOR), + Triple("Bloody Sugar Comics", "/comics/bloody-sugar-comics", AUTHOR), + Triple("Deuce Comics", "/comics/deuce-comics", AUTHOR), + Triple("Adult Empire Comics", "/comics/adult-empire-comics", AUTHOR), + Triple("SuperHeroineComixxx", "/comics/superheroinecomixxx", AUTHOR), + Triple("Sluttish Comics", "/comics/sluttish-comics", AUTHOR), + Triple("Damn3D Comics", "/comics/damn3d-comics", AUTHOR), + Triple("Fake Celebrities Sex Pictures", "/comics/fake-celebrities-sex-pictures", AUTHOR), + Triple("Secret Chest Comics", "/comics/secret-chest-comics", AUTHOR), + Triple("Project Bellerophon Comics", "/comics/project-bellerophon-comics", AUTHOR), + Triple("Smudge Comics", "/comics/smudge-comics", AUTHOR), + Triple("Superheroine Central Comics", "/comics/superheroine-central-comics", AUTHOR), + Triple("Jay Marvel Comics", "/comics/jay-marvel-comics", AUTHOR), + Triple("Fred Perry Comics", "/comics/fred-perry-comics", AUTHOR), + Triple("Seduced Amanda Comics", "/comics/seduced-amanda-comics", AUTHOR), + Triple("VGBabes Comics", "/comics/vgbabes-comics", AUTHOR), + Triple("SodomSluts.com Comics", "/comics/sodomsluts_com-comics", AUTHOR), + Triple("AKABUR Comics", "/comics/akabur-comics", AUTHOR), + Triple("eBluberry Comics", "/comics/ebluberry-comics", AUTHOR), + Triple("InterracialComicPorn.com Comics", "/comics/interracialcomicporn_com-comics", AUTHOR), + Triple("Dubh3d-Dubhgilla Comics", "/comics/dubh3d-dubhgilla-comics", AUTHOR), + Triple("Gush Bomb Comix", "/comics/gush-bomb-comix", AUTHOR), + Triple("Chiyoji Tomo Comics", "/comics/chiyoji-tomo-comics", AUTHOR), + Triple("Mangrowing Comics", "/comics/mangrowing-comics", AUTHOR), + Triple("eAdultComics Collection", "/comics/eadultcomics-collection", AUTHOR), + Triple("Skulltitti Comics", "/comics/skulltitti-comics", AUTHOR), + Triple("James Lemay Comics", "/comics/james-lemay-comics", AUTHOR), + Triple("TalesOfPleasure.com Comics", "/comics/talesofpleasure_com-comics", AUTHOR), + Triple("Eden Comics", "/comics/eden-comics", AUTHOR), + Triple("WorldOfPeach Comics", "/comics/worldofpeach-comics", AUTHOR), + Triple("Daniel40 Comics", "/comics/daniel40-comics", AUTHOR), + Triple("DontFapGirl Comics", "/comics/dontfapgirl-comics", AUTHOR), + Triple("Wingbird Comics", "/comics/wingbird-comics", AUTHOR), + Triple("Intrigue3d.com Comics", "/comics/intrigue3d_com-comics", AUTHOR), + Triple("Hentaikey Comics", "/comics/hentaikey-comics", AUTHOR), + Triple("Kamina1978 Comics", "/comics/kamina1978-comics", AUTHOR), + Triple("3DPerils Comics", "/comics/3dperils-comics", AUTHOR), + Triple("Tracy Scops Comics", "/comics/tracy-scops-comics", AUTHOR), + Triple("Shemale3D Comics", "/comics/shemale3d-comics", AUTHOR), + Triple("InterracialSex3D.com Comics", "/comics/Interracialsex3d-Com-Comix", AUTHOR), + Triple("MyHentaiGrid Comics", "/comics/myhentaigrid-comics", AUTHOR), + Triple("Magnifire Comics", "/comics/magnifire-comics", AUTHOR), + Triple("Reptileye Comics", "/comics/reptileye-comics", AUTHOR), + Triple("ProjectPinkXXX.com Comics", "/comics/projectpinkxxx_com-comics", AUTHOR), + Triple("CallMePlisskin Comics", "/comics/callmeplisskin-comics", AUTHOR) + ) + + override fun getSortList() = arrayOf( + Pair("Viewed", "viewed"), + Pair("Liked", "liked"), + Pair("Date", "recent"), + Pair("A-Z", "az") + ) +}