Delegate 8Muses, please manually migrate over your comics to the extension, as the old version of the 8Muses comics cannot support the new comics format

This commit is contained in:
Jobobby04 2020-08-10 21:15:08 -04:00
parent eee2c34abf
commit aae23f5ef3
7 changed files with 20 additions and 330 deletions

View File

@ -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
}

View File

@ -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 }

View File

@ -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<Unit>
class EightMuses(val context: Context) :
HttpSource(),
class EightMuses(delegate: HttpSource, val context: Context) :
DelegatedHttpSource(delegate),
LewdSource<EightMusesSearchMetadata, Document>,
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<SiteMap>(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<Unit>()
parsed.getElementsByTag("loc").forEach { item ->
seen[item.text().substring(22)] = Unit
// Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> =
urlImportFetchSearchManga(context, query) {
super.fetchSearchManga(page, query, filters)
}
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()
}
urlBuilder.addQueryParameter("page", page.toString())
filters.filterIsInstance<SortFilter>().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<MangasPage> {
return urlImportFetchSearchManga(context, query) {
fetchListing(searchMangaRequest(page, query, filters), false)
}
}
private fun fetchListing(request: Request, dig: Boolean): Observable<MangasPage> {
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<SManga> {
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<SChapter> {
throw UnsupportedOperationException("Should not be called!")
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return RxJavaInterop.toV1Single(
GlobalScope.async(Dispatchers.IO) {
fetchAndParseChapterList("", manga.url)
}.asSingle(GlobalScope.coroutineContext)
).toObservable()
}
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
// Request
val req = eightMusesGet(baseUrl + url)
return client.newCall(req).asObservableSuccess().toSingle().toBlocking().value().use { response ->
val contents = parseSelf(response.asJsoup())
val out = mutableListOf<SChapter>()
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<Element>, val images: List<Element>)
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<Page> {
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<String>(
"Sort",
SORT_OPTIONS.map { it.second }.toTypedArray()
) {
fun addToUri(url: HttpUrl.Builder) {
url.addQueryParameter("sort", SORT_OPTIONS[state].first)
}
companion object {
// <Internal, Display>
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"
)

View File

@ -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<MainActivityBinding>() {
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 -->

View File

@ -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
}

View File

@ -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<HentaiCafe>()
val PURURIN_SOURCE_ID = delegatedSourceId<Pururin>()
val TSUMINO_SOURCE_ID = delegatedSourceId<Tsumino>()
const val HITOMI_SOURCE_ID = LEWD_SOURCE_SERIES + 10
const val EIGHTMUSES_SOURCE_ID = LEWD_SOURCE_SERIES + 11
val EIGHTMUSES_SOURCE_ID = delegatedSourceId<EightMuses>()
val HBROWSE_SOURCE_ID = delegatedSourceId<HBrowse>()
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(

View File

@ -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"
}