diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 0bf25899f..4aecea4e5 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -19,7 +19,6 @@ import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.util.lang.launchNow import eu.kanade.tachiyomi.util.system.toast import exh.EH_SOURCE_ID -import exh.EIGHTMUSES_SOURCE_ID import exh.EXH_SOURCE_ID import exh.HITOMI_SOURCE_ID import exh.MERGED_SOURCE_ID @@ -87,7 +86,6 @@ class ExtensionManager( PERV_EDEN_IT_SOURCE_ID -> context.getDrawable(R.mipmap.ic_perveden_source) NHENTAI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_nhentai_source) HITOMI_SOURCE_ID -> context.getDrawable(R.mipmap.ic_hitomi_source) - EIGHTMUSES_SOURCE_ID -> context.getDrawable(R.mipmap.ic_8muses_source) MERGED_SOURCE_ID -> context.getDrawable(R.mipmap.ic_merged_source) else -> null } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt index 4c59fd1ac..f2fbc122f 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/SourceManager.kt @@ -140,7 +140,6 @@ open class SourceManager(private val context: Context) { exSrcs += PervEden(PERV_EDEN_IT_SOURCE_ID, PervEdenLang.it, context) exSrcs += NHentai(context) exSrcs += Hitomi(context) - exSrcs += EightMuses(context) return exSrcs } // SY <-- @@ -173,7 +172,7 @@ open class SourceManager(private val context: Context) { // SY --> companion object { - private const val fillInSourceId = 9999L + private const val fillInSourceId = Long.MAX_VALUE val DELEGATED_SOURCES = listOf( DelegatedSource( "Hentai Cafe", @@ -205,6 +204,12 @@ open class SourceManager(private val context: Context) { 1401584337232758222, "eu.kanade.tachiyomi.extension.en.hbrowse.HBrowse", HBrowse::class + ), + DelegatedSource( + "8Muses", + 1802675169972965535, + "eu.kanade.tachiyomi.extension.all.eromuse.EroMuse", + EightMuses::class ) ).associateBy { it.originalSourceQualifiedClassName } diff --git a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt index 8a2af529f..ae76ad105 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/source/online/english/EightMuses.kt @@ -2,252 +2,37 @@ package eu.kanade.tachiyomi.source.online.english import android.content.Context import android.net.Uri -import com.kizitonwose.time.hours -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.source.online.LewdSource import eu.kanade.tachiyomi.source.online.UrlImportableSource import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.util.asJsoup -import exh.EIGHTMUSES_SOURCE_ID import exh.metadata.metadata.EightMusesSearchMetadata import exh.metadata.metadata.base.RaisedTag +import exh.source.DelegatedHttpSource import exh.ui.metadata.adapters.EightMusesDescriptionAdapter -import exh.util.CachedField -import exh.util.NakedTrie -import exh.util.await import exh.util.urlImportFetchSearchManga -import hu.akarnokd.rxjava.interop.RxJavaInterop -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.GlobalScope -import kotlinx.coroutines.async -import kotlinx.coroutines.rx2.asSingle -import kotlinx.coroutines.withContext -import okhttp3.Headers -import okhttp3.HttpUrl -import okhttp3.HttpUrl.Companion.toHttpUrlOrNull -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.Response -import org.jsoup.Jsoup import org.jsoup.nodes.Document import org.jsoup.nodes.Element import rx.Observable -import rx.schedulers.Schedulers -typealias SiteMap = NakedTrie - -class EightMuses(val context: Context) : - HttpSource(), +class EightMuses(delegate: HttpSource, val context: Context) : + DelegatedHttpSource(delegate), LewdSource, UrlImportableSource { - override val id = EIGHTMUSES_SOURCE_ID - - /** - * Name of the source. - */ - override val name = "8muses" - /** - * Whether the source has support for latest updates. - */ - override val supportsLatest = true - /** - * An ISO 639-1 compliant language code (two letters in lower case). - */ - override val lang: String = "en" - override val metaClass = EightMusesSearchMetadata::class + override val lang = "en" - /** - * Base url of the website without the trailing slash, like: http://mysite.com - */ - override val baseUrl = EightMusesSearchMetadata.BASE_URL - - private val siteMapCache = CachedField(1.hours.inMilliseconds.longValue) - - override val client: OkHttpClient - get() = network.cloudflareClient - - private suspend fun obtainSiteMap() = siteMapCache.obtain { - withContext(Dispatchers.IO) { - val result = client.newCall(eightMusesGet("$baseUrl/sitemap/1.xml")) - .asObservableSuccess() - .toSingle() - .await(Schedulers.io()) - .body!!.string() - - val parsed = Jsoup.parse(result) - - val seen = NakedTrie() - - parsed.getElementsByTag("loc").forEach { item -> - seen[item.text().substring(22)] = Unit - } - - seen - } - } - - override fun headersBuilder(): Headers.Builder { - return Headers.Builder() - .add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;") - .add("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8") - .add("Referer", "https://www.8muses.com") - .add("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") - } - - private fun eightMusesGet(url: String): Request { - return GET(url, headers = headersBuilder().build()) - } - - /** - * Returns the request for the popular manga given the page. - * - * @param page the page number to retrieve. - */ - override fun popularMangaRequest(page: Int) = eightMusesGet("$baseUrl/comics/$page") - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun popularMangaParse(response: Response): MangasPage { - throw UnsupportedOperationException("Should not be called!") - } - - /** - * Returns the request for the search manga given the page. - * - * @param page the page number to retrieve. - * @param query the search query. - * @param filters the list of filters to apply. - */ - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - val urlBuilder = if (!query.isBlank()) { - "$baseUrl/search".toHttpUrlOrNull()!! - .newBuilder() - .addQueryParameter("q", query) - } else { - "$baseUrl/comics".toHttpUrlOrNull()!! - .newBuilder() + // Support direct URL importing + override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable = + urlImportFetchSearchManga(context, query) { + super.fetchSearchManga(page, query, filters) } - urlBuilder.addQueryParameter("page", page.toString()) - - filters.filterIsInstance().map { - it.addToUri(urlBuilder) - } - - return eightMusesGet(urlBuilder.toString()) - } - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun searchMangaParse(response: Response): MangasPage { - throw UnsupportedOperationException("Should not be called!") - } - - /** - * Returns the request for latest manga given the page. - * - * @param page the page number to retrieve. - */ - override fun latestUpdatesRequest(page: Int) = eightMusesGet("$baseUrl/comics/lastupdate?page=$page") - - /** - * Parses the response from the site and returns a [MangasPage] object. - * - * @param response the response from the site. - */ - override fun latestUpdatesParse(response: Response): MangasPage { - throw UnsupportedOperationException("Should not be called!") - } - -// override fun fetchLatestUpdates(page: Int) = fetchListing(latestUpdatesRequest(page), false) - override fun fetchLatestUpdates(page: Int) = fetchListing(popularMangaRequest(page), false) - - override fun fetchPopularManga(page: Int) = fetchListing(popularMangaRequest(page), false) // TODO Dig - - override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable { - return urlImportFetchSearchManga(context, query) { - fetchListing(searchMangaRequest(page, query, filters), false) - } - } - - private fun fetchListing(request: Request, dig: Boolean): Observable { - return client.newCall(request) - .asObservableSuccess() - .flatMapSingle { response -> - RxJavaInterop.toV1Single( - GlobalScope.async(Dispatchers.IO) { - parseResultsPage(response, dig) - }.asSingle(GlobalScope.coroutineContext) - ) - } - } - - private suspend fun parseResultsPage(response: Response, dig: Boolean): MangasPage { - val doc = response.asJsoup() - val contents = parseSelf(doc) - - val onLastPage = doc.selectFirst(".current:nth-last-child(2)") != null - - return MangasPage( - if (dig) { - contents.albums.flatMap { - val href = it.attr("href") - val splitHref = href.split('/') - obtainSiteMap().subMap(href).filter { - it.key.split('/').size - splitHref.size == 1 - }.map { (key, _) -> - SManga.create().apply { - url = key - - title = key.substringAfterLast('/').replace('-', ' ') - } - } - } - } else { - contents.albums.map { - SManga.create().apply { - url = it.attr("href") - - title = it.select(".title-text").text() - - thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src") - } - } - }, - !onLastPage - ) - } - - /** - * Parses the response from the site and returns the details of a manga. - * - * @param response the response from the site. - */ - override fun mangaDetailsParse(response: Response): SManga { - throw UnsupportedOperationException("Should not be called!") - } - - /** - * Returns an observable with the updated details for a manga. Normally it's not needed to - * override this method. - * - * @param manga the manga to be updated. - */ override fun fetchMangaDetails(manga: SManga): Observable { return client.newCall(mangaDetailsRequest(manga)) .asObservableSuccess() @@ -256,46 +41,6 @@ class EightMuses(val context: Context) : } } - /** - * Parses the response from the site and returns a list of chapters. - * - * @param response the response from the site. - */ - override fun chapterListParse(response: Response): List { - throw UnsupportedOperationException("Should not be called!") - } - - override fun fetchChapterList(manga: SManga): Observable> { - return RxJavaInterop.toV1Single( - GlobalScope.async(Dispatchers.IO) { - fetchAndParseChapterList("", manga.url) - }.asSingle(GlobalScope.coroutineContext) - ).toObservable() - } - - private suspend fun fetchAndParseChapterList(prefix: String, url: String): List { - // Request - val req = eightMusesGet(baseUrl + url) - - return client.newCall(req).asObservableSuccess().toSingle().toBlocking().value().use { response -> - val contents = parseSelf(response.asJsoup()) - - val out = mutableListOf() - if (contents.images.isNotEmpty()) { - out += SChapter.create().apply { - this.url = url - this.name = if (prefix.isBlank()) ">" else prefix - } - } - - val builtPrefix = if (prefix.isBlank()) "> " else "$prefix > " - - out + contents.albums.flatMap { ele -> - fetchAndParseChapterList(builtPrefix + ele.selectFirst(".title-text").text(), ele.attr("href")) - } - } - } - data class SelfContents(val albums: List, val images: List) private fun parseSelf(doc: Document): SelfContents { @@ -309,22 +54,6 @@ class EightMuses(val context: Context) : return SelfContents(selfAlbums, selfImages) } - /** - * Parses the response from the site and returns a list of pages. - * - * @param response the response from the site. - */ - override fun pageListParse(response: Response): List { - val contents = parseSelf(response.asJsoup()) - return contents.images.mapIndexed { index, element -> - Page( - index, - element.attr("href"), - "$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9) - ) - } - } - override fun parseIntoMetadata(metadata: EightMusesSearchMetadata, input: Document) { with(metadata) { path = Uri.parse(input.location()).pathSegments @@ -355,40 +84,9 @@ class EightMuses(val context: Context) : } } - class SortFilter : Filter.Select( - "Sort", - SORT_OPTIONS.map { it.second }.toTypedArray() - ) { - fun addToUri(url: HttpUrl.Builder) { - url.addQueryParameter("sort", SORT_OPTIONS[state].first) - } - - companion object { - // - private val SORT_OPTIONS = listOf( - "" to "Views", - "like" to "Likes", - "date" to "Date", - "az" to "A-Z" - ) - } - } - - override fun getFilterList() = FilterList( - SortFilter() - ) - - /** - * Parses the response from the site and returns the absolute url to the source image. - * - * @param response the response from the site. - */ - override fun imageUrlParse(response: Response): String { - throw UnsupportedOperationException("Should not be called!") - } - override val matchingHosts = listOf( "www.8muses.com", + "comics.8muses.com", "8muses.com" ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index abb8afee8..18ffc7874 100755 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -42,7 +42,6 @@ import eu.kanade.tachiyomi.ui.recent.updates.UpdatesController import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchUI import exh.EH_SOURCE_ID -import exh.EIGHTMUSES_SOURCE_ID import exh.EXHMigrations import exh.EXH_SOURCE_ID import exh.HITOMI_SOURCE_ID @@ -225,9 +224,6 @@ class MainActivity : BaseActivity() { if (HITOMI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { BlacklistedSources.HIDDEN_SOURCES += HITOMI_SOURCE_ID } - if (EIGHTMUSES_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES += EIGHTMUSES_SOURCE_ID - } } // SY --> diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt index 03638c131..d5cc6c415 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsAdvancedController.kt @@ -37,7 +37,6 @@ import eu.kanade.tachiyomi.util.preference.titleRes import eu.kanade.tachiyomi.util.system.powerManager import eu.kanade.tachiyomi.util.system.toast import exh.EH_SOURCE_ID -import exh.EIGHTMUSES_SOURCE_ID import exh.EXH_SOURCE_ID import exh.HITOMI_SOURCE_ID import exh.NHENTAI_SOURCE_ID @@ -193,9 +192,6 @@ class SettingsAdvancedController : SettingsController() { if (HITOMI_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { BlacklistedSources.HIDDEN_SOURCES += HITOMI_SOURCE_ID } - if (EIGHTMUSES_SOURCE_ID !in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES += EIGHTMUSES_SOURCE_ID - } } else { if (EH_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { BlacklistedSources.HIDDEN_SOURCES -= EH_SOURCE_ID @@ -215,9 +211,6 @@ class SettingsAdvancedController : SettingsController() { if (HITOMI_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { BlacklistedSources.HIDDEN_SOURCES -= HITOMI_SOURCE_ID } - if (EIGHTMUSES_SOURCE_ID in BlacklistedSources.HIDDEN_SOURCES) { - BlacklistedSources.HIDDEN_SOURCES -= EIGHTMUSES_SOURCE_ID - } } true } diff --git a/app/src/main/java/exh/EHSourceHelpers.kt b/app/src/main/java/exh/EHSourceHelpers.kt index 380b5ccdd..d80bbc11f 100755 --- a/app/src/main/java/exh/EHSourceHelpers.kt +++ b/app/src/main/java/exh/EHSourceHelpers.kt @@ -2,6 +2,7 @@ package exh import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.SourceManager +import eu.kanade.tachiyomi.source.online.english.EightMuses import eu.kanade.tachiyomi.source.online.english.HBrowse import eu.kanade.tachiyomi.source.online.english.HentaiCafe import eu.kanade.tachiyomi.source.online.english.Pururin @@ -22,7 +23,7 @@ val HENTAI_CAFE_SOURCE_ID = delegatedSourceId() val PURURIN_SOURCE_ID = delegatedSourceId() val TSUMINO_SOURCE_ID = delegatedSourceId() const val HITOMI_SOURCE_ID = LEWD_SOURCE_SERIES + 10 -const val EIGHTMUSES_SOURCE_ID = LEWD_SOURCE_SERIES + 11 +val EIGHTMUSES_SOURCE_ID = delegatedSourceId() val HBROWSE_SOURCE_ID = delegatedSourceId() const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69 @@ -30,7 +31,8 @@ private val DELEGATED_LEWD_SOURCES = listOf( HentaiCafe::class, Pururin::class, Tsumino::class, - HBrowse::class + HBrowse::class, + EightMuses::class ) val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf( diff --git a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt index f5595ae0b..8696db23f 100644 --- a/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt +++ b/app/src/main/java/exh/metadata/metadata/EightMusesSearchMetadata.kt @@ -53,8 +53,6 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() { const val TAG_TYPE_DEFAULT = 0 - const val BASE_URL = "https://www.8muses.com" - const val TAGS_NAMESPACE = "tags" const val ARTIST_NAMESPACE = "artist" }