Linting Fixes AZ
This commit is contained in:
@@ -83,10 +83,12 @@ class ChapterCache(private val context: Context) {
|
||||
// --> EH
|
||||
// Cache size is in MB
|
||||
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||
return DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
cacheSize * 1024 * 1024)
|
||||
return DiskLruCache.open(
|
||||
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||
PARAMETER_APP_VERSION,
|
||||
PARAMETER_VALUE_COUNT,
|
||||
cacheSize * 1024 * 1024
|
||||
)
|
||||
}
|
||||
// <-- EH
|
||||
|
||||
|
||||
@@ -19,18 +19,22 @@ interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||
.whereArgs(mangaId)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(RawQuery.builder()
|
||||
.withQuery(
|
||||
RawQuery.builder()
|
||||
.query(getMergedChaptersQuery(mangaId))
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
fun getRecentChapters(date: Date) = db.get()
|
||||
@@ -80,11 +84,13 @@ interface ChapterQueries : DbProvider {
|
||||
|
||||
fun getChapters(url: String) = db.get()
|
||||
.listOfObjects(Chapter::class.java)
|
||||
.withQuery(Query.builder()
|
||||
.withQuery(
|
||||
Query.builder()
|
||||
.table(ChapterTable.TABLE)
|
||||
.where("${ChapterTable.COL_URL} = ?")
|
||||
.whereArgs(url)
|
||||
.build())
|
||||
.build()
|
||||
)
|
||||
.prepare()
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,8 @@ import eu.kanade.tachiyomi.data.database.tables.MergedTable as Merged
|
||||
/**
|
||||
* Query to get the manga merged into a merged manga
|
||||
*/
|
||||
fun getMergedMangaQuery(id: Long) = """
|
||||
fun getMergedMangaQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Manga.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
@@ -22,7 +23,8 @@ fun getMergedMangaQuery(id: Long) = """
|
||||
/**
|
||||
* Query to get the chapters of all manga in a merged manga
|
||||
*/
|
||||
fun getMergedChaptersQuery(id: Long) = """
|
||||
fun getMergedChaptersQuery(id: Long) =
|
||||
"""
|
||||
SELECT ${Chapter.TABLE}.*
|
||||
FROM (
|
||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||
|
||||
+4
-4
@@ -21,10 +21,10 @@ class MangaUrlPutResolver : PutResolver<Manga>() {
|
||||
}
|
||||
|
||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
.table(MangaTable.TABLE)
|
||||
.where("${MangaTable.COL_ID} = ?")
|
||||
.whereArgs(manga.id)
|
||||
.build()
|
||||
|
||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||
put(MangaTable.COL_URL, manga.url)
|
||||
|
||||
@@ -9,7 +9,8 @@ object MergedTable {
|
||||
const val COL_MANGA_ID = "mangaID"
|
||||
|
||||
val createTableQuery: String
|
||||
get() = """CREATE TABLE $TABLE(
|
||||
get() =
|
||||
"""CREATE TABLE $TABLE(
|
||||
$COL_MERGE_ID INTEGER NOT NULL,
|
||||
$COL_MANGA_ID INTEGER NOT NULL
|
||||
)"""
|
||||
|
||||
@@ -147,7 +147,7 @@ class ExtensionManager(
|
||||
|
||||
fun Extension.isBlacklisted(
|
||||
blacklistEnabled: Boolean =
|
||||
preferences.eh_enableSourceBlacklist().get()
|
||||
preferences.eh_enableSourceBlacklist().get()
|
||||
): Boolean {
|
||||
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
||||
private fun newMetaInstance() = metaClass.constructors.find {
|
||||
it.parameters.isEmpty()
|
||||
}?.call()
|
||||
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
|
||||
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
|
||||
|
||||
/**
|
||||
* Parses metadata from the input and then copies it into the manga
|
||||
|
||||
@@ -19,10 +19,12 @@ interface UrlImportableSource : Source {
|
||||
return try {
|
||||
val uri = URI(url)
|
||||
var out = uri.path
|
||||
if (uri.query != null)
|
||||
if (uri.query != null) {
|
||||
out += "?" + uri.query
|
||||
if (uri.fragment != null)
|
||||
}
|
||||
if (uri.fragment != null) {
|
||||
out += "#" + uri.fragment
|
||||
}
|
||||
out
|
||||
} catch (e: URISyntaxException) {
|
||||
url
|
||||
|
||||
@@ -73,16 +73,18 @@ class EHentai(
|
||||
override val metaClass = EHentaiSearchMetadata::class
|
||||
|
||||
val schema: String
|
||||
get() = if (prefs.secureEXH().getOrDefault())
|
||||
get() = if (prefs.secureEXH().getOrDefault()) {
|
||||
"https"
|
||||
else
|
||||
} else {
|
||||
"http"
|
||||
}
|
||||
|
||||
val domain: String
|
||||
get() = if (exh)
|
||||
get() = if (exh) {
|
||||
"exhentai.org"
|
||||
else
|
||||
} else {
|
||||
"e-hentai.org"
|
||||
}
|
||||
|
||||
override val baseUrl: String
|
||||
get() = "$schema://$domain"
|
||||
@@ -111,25 +113,27 @@ class EHentai(
|
||||
val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
|
||||
|
||||
ParsedManga(
|
||||
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
|
||||
favElement?.attr("style")?.substring(14, 17)
|
||||
),
|
||||
manga = Manga.create(id).apply {
|
||||
// Get title
|
||||
title = thumbnailElement.attr("title")
|
||||
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
|
||||
// Get image
|
||||
thumbnail_url = thumbnailElement.attr("src")
|
||||
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
|
||||
favElement?.attr("style")?.substring(14, 17)
|
||||
),
|
||||
manga = Manga.create(id).apply {
|
||||
// Get title
|
||||
title = thumbnailElement.attr("title")
|
||||
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
|
||||
// Get image
|
||||
thumbnail_url = thumbnailElement.attr("src")
|
||||
|
||||
// TODO Parse genre + uploader + tags
|
||||
})
|
||||
// TODO Parse genre + uploader + tags
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val parsedLocation = doc.location().toHttpUrlOrNull()
|
||||
|
||||
// Add to page if required
|
||||
val hasNextPage = if (parsedLocation == null ||
|
||||
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) {
|
||||
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)
|
||||
) {
|
||||
select("a[onclick=return false]").last()?.let {
|
||||
it.text() == ">"
|
||||
} ?: false
|
||||
@@ -160,7 +164,7 @@ class EHentai(
|
||||
while (true) {
|
||||
val gid = EHentaiSearchMetadata.galleryId(url).toInt()
|
||||
val cachedParent = updateHelper.parentLookupTable.get(
|
||||
gid
|
||||
gid
|
||||
)
|
||||
if (cachedParent == null) {
|
||||
throttleFunc()
|
||||
@@ -175,19 +179,19 @@ class EHentai(
|
||||
|
||||
if (parentLink != null) {
|
||||
updateHelper.parentLookupTable.put(
|
||||
gid,
|
||||
GalleryEntry(
|
||||
EHentaiSearchMetadata.galleryId(parentLink),
|
||||
EHentaiSearchMetadata.galleryToken(parentLink)
|
||||
)
|
||||
gid,
|
||||
GalleryEntry(
|
||||
EHentaiSearchMetadata.galleryId(parentLink),
|
||||
EHentaiSearchMetadata.galleryToken(parentLink)
|
||||
)
|
||||
)
|
||||
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
|
||||
} else break
|
||||
} else {
|
||||
XLog.d("Parent cache hit: %s!", gid)
|
||||
url = EHentaiSearchMetadata.idAndTokenToUrl(
|
||||
cachedParent.gId,
|
||||
cachedParent.gToken
|
||||
cachedParent.gId,
|
||||
cachedParent.gToken
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -201,9 +205,11 @@ class EHentai(
|
||||
url = EHentaiSearchMetadata.normalizeUrl(d.location())
|
||||
name = "v1: " + d.selectFirst("#gn").text()
|
||||
chapter_number = 1f
|
||||
date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el ->
|
||||
el.text().toLowerCase() == "posted:"
|
||||
}!!.nextElementSibling().text()).time
|
||||
date_upload = EX_DATE_FORMAT.parse(
|
||||
d.select("#gdd .gdt1").find { el ->
|
||||
el.text().toLowerCase() == "posted:"
|
||||
}!!.nextElementSibling().text()
|
||||
).time
|
||||
}
|
||||
// Build and append the rest of the galleries
|
||||
if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self)
|
||||
@@ -253,27 +259,32 @@ class EHentai(
|
||||
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
||||
}
|
||||
|
||||
private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
|
||||
private fun chapterPageRequest(np: String) = exGet(np, null, headers)
|
||||
private fun chapterPageCall(np: String): Observable<Response> {
|
||||
return client.newCall(chapterPageRequest(np)).asObservableSuccess()
|
||||
}
|
||||
private fun chapterPageRequest(np: String): Request {
|
||||
return exGet(np, null, headers)
|
||||
}
|
||||
|
||||
private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let {
|
||||
return if (it.text() == ">") it.attr("href") else null
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int) = if (exh)
|
||||
override fun popularMangaRequest(page: Int) = if (exh) {
|
||||
latestUpdatesRequest(page)
|
||||
else
|
||||
} else {
|
||||
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
|
||||
}
|
||||
|
||||
// Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
urlImportFetchSearchManga(query) {
|
||||
searchMangaRequestObservable(page, query, filters).flatMap {
|
||||
client.newCall(it).asObservableSuccess()
|
||||
}.map { response ->
|
||||
searchMangaParse(response)
|
||||
}
|
||||
urlImportFetchSearchManga(query) {
|
||||
searchMangaRequestObservable(page, query, filters).flatMap {
|
||||
client.newCall(it).asObservableSuccess()
|
||||
}.map { response ->
|
||||
searchMangaParse(response)
|
||||
}
|
||||
}
|
||||
|
||||
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
|
||||
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
|
||||
@@ -287,20 +298,20 @@ class EHentai(
|
||||
// Reverse search results on filter
|
||||
if (filters.any { it is ReverseFilter && it.state }) {
|
||||
return client.newCall(request)
|
||||
.asObservableSuccess()
|
||||
.map {
|
||||
val doc = it.asJsoup()
|
||||
.asObservableSuccess()
|
||||
.map {
|
||||
val doc = it.asJsoup()
|
||||
|
||||
val elements = doc.select(".ptt > tbody > tr > td")
|
||||
val elements = doc.select(".ptt > tbody > tr > td")
|
||||
|
||||
val totalElement = elements[elements.size - 2]
|
||||
val totalElement = elements[elements.size - 2]
|
||||
|
||||
val thisPage = totalElement.text().toInt() - (page - 1)
|
||||
val thisPage = totalElement.text().toInt() - (page - 1)
|
||||
|
||||
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
||||
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
||||
|
||||
exGet(uri.toString(), thisPage)
|
||||
}
|
||||
exGet(uri.toString(), thisPage)
|
||||
}
|
||||
} else {
|
||||
return Observable.just(request)
|
||||
}
|
||||
@@ -314,22 +325,28 @@ class EHentai(
|
||||
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
||||
override fun latestUpdatesParse(response: Response) = genericMangaParse(response)
|
||||
|
||||
fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true) = GET(page?.let {
|
||||
addParam(url, "page", Integer.toString(page - 1))
|
||||
} ?: url, additionalHeaders?.let {
|
||||
val headers = headers.newBuilder()
|
||||
it.toMultimap().forEach { (t, u) ->
|
||||
u.forEach {
|
||||
headers.add(t, it)
|
||||
fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request {
|
||||
return GET(
|
||||
page?.let {
|
||||
addParam(url, "page", Integer.toString(page - 1))
|
||||
} ?: url,
|
||||
additionalHeaders?.let {
|
||||
val headers = headers.newBuilder()
|
||||
it.toMultimap().forEach { (t, u) ->
|
||||
u.forEach {
|
||||
headers.add(t, it)
|
||||
}
|
||||
}
|
||||
headers.build()
|
||||
} ?: headers
|
||||
).let {
|
||||
if (!cache) {
|
||||
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}
|
||||
headers.build()
|
||||
} ?: headers).let {
|
||||
if (!cache)
|
||||
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
|
||||
else
|
||||
it
|
||||
}!!
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an observable with the updated details for a manga. Normally it's not needed to
|
||||
@@ -339,33 +356,37 @@ class EHentai(
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableWithAsyncStacktrace()
|
||||
.flatMap { (stacktrace, response) ->
|
||||
if (response.isSuccessful) {
|
||||
// Pull to most recent
|
||||
val doc = response.asJsoup()
|
||||
val newerGallery = doc.select("#gnd a").lastOrNull()
|
||||
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
|
||||
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
|
||||
client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess().map { it.asJsoup() }
|
||||
} else Observable.just(doc)
|
||||
.asObservableWithAsyncStacktrace()
|
||||
.flatMap { (stacktrace, response) ->
|
||||
if (response.isSuccessful) {
|
||||
// Pull to most recent
|
||||
val doc = response.asJsoup()
|
||||
val newerGallery = doc.select("#gnd a").lastOrNull()
|
||||
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
|
||||
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
|
||||
client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess().map { it.asJsoup() }
|
||||
} else Observable.just(doc)
|
||||
|
||||
pre.flatMap {
|
||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
||||
initialized = true
|
||||
}))
|
||||
}
|
||||
pre.flatMap {
|
||||
parseToManga(manga, it).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
} else {
|
||||
response.close()
|
||||
|
||||
if (response.code == 404) {
|
||||
throw GalleryNotFoundException(stacktrace)
|
||||
} else {
|
||||
response.close()
|
||||
|
||||
if (response.code == 404) {
|
||||
throw GalleryNotFoundException(stacktrace)
|
||||
} else {
|
||||
throw Exception("HTTP error ${response.code}", stacktrace)
|
||||
}
|
||||
throw Exception("HTTP error ${response.code}", stacktrace)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -389,11 +410,11 @@ class EHentai(
|
||||
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
||||
}
|
||||
genre = select(".cs")
|
||||
.attr("onclick")
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.substringAfterLast('/')
|
||||
?.removeSuffix("'")
|
||||
.attr("onclick")
|
||||
.nullIfBlank()
|
||||
?.trim()
|
||||
?.substringAfterLast('/')
|
||||
?.removeSuffix("'")
|
||||
|
||||
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
||||
|
||||
@@ -404,8 +425,10 @@ class EHentai(
|
||||
val right = rightElement.text().nullIfBlank()?.trim()
|
||||
if (left != null && right != null) {
|
||||
ignore {
|
||||
when (left.removeSuffix(":")
|
||||
.toLowerCase()) {
|
||||
when (
|
||||
left.removeSuffix(":")
|
||||
.toLowerCase()
|
||||
) {
|
||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
|
||||
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
|
||||
@@ -428,7 +451,8 @@ class EHentai(
|
||||
|
||||
lastUpdateCheck = System.currentTimeMillis()
|
||||
if (datePosted != null &&
|
||||
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) {
|
||||
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME
|
||||
) {
|
||||
aged = true
|
||||
XLog.d("aged %s - too old", title)
|
||||
}
|
||||
@@ -436,32 +460,35 @@ class EHentai(
|
||||
// Parse ratings
|
||||
ignore {
|
||||
averageRating = select("#rating_label")
|
||||
.text()
|
||||
.removePrefix("Average:")
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toDouble()
|
||||
.text()
|
||||
.removePrefix("Average:")
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toDouble()
|
||||
ratingCount = select("#rating_count")
|
||||
.text()
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toInt()
|
||||
.text()
|
||||
.trim()
|
||||
.nullIfBlank()
|
||||
?.toInt()
|
||||
}
|
||||
|
||||
// Parse tags
|
||||
tags.clear()
|
||||
select("#taglist tr").forEach {
|
||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||
tags.addAll(it.select("div").map { element ->
|
||||
RaisedTag(
|
||||
tags.addAll(
|
||||
it.select("div").map { element ->
|
||||
RaisedTag(
|
||||
namespace,
|
||||
element.text().trim(),
|
||||
if (element.hasClass("gtl"))
|
||||
if (element.hasClass("gtl")) {
|
||||
TAG_TYPE_LIGHT
|
||||
else
|
||||
} else {
|
||||
TAG_TYPE_NORMAL
|
||||
)
|
||||
})
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
// Add genre as virtual tag
|
||||
@@ -478,8 +505,8 @@ class EHentai(
|
||||
|
||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||
return client.newCall(imageUrlRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { realImageUrlParse(it, page) }
|
||||
.asObservableSuccess()
|
||||
.map { realImageUrlParse(it, page) }
|
||||
}
|
||||
|
||||
fun realImageUrlParse(response: Response, page: Page): String {
|
||||
@@ -505,9 +532,13 @@ class EHentai(
|
||||
var favNames: List<String>? = null
|
||||
|
||||
do {
|
||||
val response2 = client.newCall(exGet(favoriteUrl,
|
||||
val response2 = client.newCall(
|
||||
exGet(
|
||||
favoriteUrl,
|
||||
page = page,
|
||||
cache = false)).execute()
|
||||
cache = false
|
||||
)
|
||||
).execute()
|
||||
val doc = response2.asJsoup()
|
||||
|
||||
// Parse favorites
|
||||
@@ -515,22 +546,24 @@ class EHentai(
|
||||
result += parsed.first
|
||||
|
||||
// Parse fav names
|
||||
if (favNames == null)
|
||||
if (favNames == null) {
|
||||
favNames = doc.select(".fp:not(.fps)").mapNotNull {
|
||||
it.child(2).text()
|
||||
}
|
||||
|
||||
}
|
||||
// Next page
|
||||
|
||||
page++
|
||||
} while (parsed.second)
|
||||
|
||||
return Pair(result as List<ParsedManga>, favNames!!)
|
||||
}
|
||||
|
||||
fun spPref() = if (exh)
|
||||
fun spPref() = if (exh) {
|
||||
prefs.eh_exhSettingsProfile()
|
||||
else
|
||||
} else {
|
||||
prefs.eh_ehSettingsProfile()
|
||||
}
|
||||
|
||||
fun rawCookies(sp: Int): Map<String, String> {
|
||||
val cookies: MutableMap<String, String> = mutableMapOf()
|
||||
@@ -541,16 +574,19 @@ class EHentai(
|
||||
cookies["sp"] = sp.toString()
|
||||
|
||||
val sessionKey = prefs.eh_settingsKey().getOrDefault()
|
||||
if (sessionKey != null)
|
||||
if (sessionKey != null) {
|
||||
cookies["sk"] = sessionKey
|
||||
}
|
||||
|
||||
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
|
||||
if (sessionCookie != null)
|
||||
if (sessionCookie != null) {
|
||||
cookies["s"] = sessionCookie
|
||||
}
|
||||
|
||||
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
|
||||
if (hathPerksCookie != null)
|
||||
if (hathPerksCookie != null) {
|
||||
cookies["hath_perks"] = hathPerksCookie
|
||||
}
|
||||
}
|
||||
|
||||
// Session-less extended display mode (for users without ExHentai)
|
||||
@@ -568,51 +604,57 @@ class EHentai(
|
||||
override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!!
|
||||
|
||||
fun addParam(url: String, param: String, value: String) = Uri.parse(url)
|
||||
.buildUpon()
|
||||
.appendQueryParameter(param, value)
|
||||
.toString()
|
||||
.buildUpon()
|
||||
.appendQueryParameter(param, value)
|
||||
.toString()
|
||||
|
||||
override val client = network.client.newBuilder()
|
||||
.cookieJar(CookieJar.NO_COOKIES)
|
||||
.addInterceptor { chain ->
|
||||
val newReq = chain
|
||||
.request()
|
||||
.newBuilder()
|
||||
.removeHeader("Cookie")
|
||||
.addHeader("Cookie", cookiesHeader())
|
||||
.build()
|
||||
.cookieJar(CookieJar.NO_COOKIES)
|
||||
.addInterceptor { chain ->
|
||||
val newReq = chain
|
||||
.request()
|
||||
.newBuilder()
|
||||
.removeHeader("Cookie")
|
||||
.addHeader("Cookie", cookiesHeader())
|
||||
.build()
|
||||
|
||||
chain.proceed(newReq)
|
||||
}.build()!!
|
||||
chain.proceed(newReq)
|
||||
}.build()!!
|
||||
|
||||
// Filters
|
||||
override fun getFilterList() = FilterList(
|
||||
Watched(),
|
||||
GenreGroup(),
|
||||
AdvancedGroup(),
|
||||
ReverseFilter()
|
||||
Watched(),
|
||||
GenreGroup(),
|
||||
AdvancedGroup(),
|
||||
ReverseFilter()
|
||||
)
|
||||
|
||||
class Watched : Filter.CheckBox("Watched List"), UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
if (state)
|
||||
if (state) {
|
||||
builder.appendPath("watched")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
|
||||
class GenreGroup : Filter.Group<GenreOption>("Genres", listOf(
|
||||
GenreOption("Dōjinshi", 2),
|
||||
GenreOption("Manga", 4),
|
||||
GenreOption("Artist CG", 8),
|
||||
GenreOption("Game CG", 16),
|
||||
GenreOption("Western", 512),
|
||||
GenreOption("Non-H", 256),
|
||||
GenreOption("Image Set", 32),
|
||||
GenreOption("Cosplay", 64),
|
||||
GenreOption("Asian Porn", 128),
|
||||
GenreOption("Misc", 1)
|
||||
)), UriFilter {
|
||||
class GenreGroup :
|
||||
Filter.Group<GenreOption>(
|
||||
"Genres",
|
||||
listOf(
|
||||
GenreOption("Dōjinshi", 2),
|
||||
GenreOption("Manga", 4),
|
||||
GenreOption("Artist CG", 8),
|
||||
GenreOption("Game CG", 16),
|
||||
GenreOption("Western", 512),
|
||||
GenreOption("Non-H", 256),
|
||||
GenreOption("Image Set", 32),
|
||||
GenreOption("Cosplay", 64),
|
||||
GenreOption("Asian Porn", 128),
|
||||
GenreOption("Misc", 1)
|
||||
)
|
||||
),
|
||||
UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
val bits = state.fold(0) { acc, genre ->
|
||||
if (!genre.state) acc + genre.genreId else acc
|
||||
@@ -623,8 +665,9 @@ class EHentai(
|
||||
|
||||
class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
if (state)
|
||||
if (state) {
|
||||
builder.appendQueryParameter(param, "on")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -643,13 +686,18 @@ class EHentai(
|
||||
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
||||
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
||||
|
||||
class RatingOption : Filter.Select<String>("Minimum Rating", arrayOf(
|
||||
"Any",
|
||||
"2 stars",
|
||||
"3 stars",
|
||||
"4 stars",
|
||||
"5 stars"
|
||||
)), UriFilter {
|
||||
class RatingOption :
|
||||
Filter.Select<String>(
|
||||
"Minimum Rating",
|
||||
arrayOf(
|
||||
"Any",
|
||||
"2 stars",
|
||||
"3 stars",
|
||||
"4 stars",
|
||||
"5 stars"
|
||||
)
|
||||
),
|
||||
UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
if (state > 0) {
|
||||
builder.appendQueryParameter("f_srdd", Integer.toString(state + 1))
|
||||
@@ -658,7 +706,9 @@ class EHentai(
|
||||
}
|
||||
}
|
||||
|
||||
class AdvancedGroup : UriGroup<Filter<*>>("Advanced Options", listOf(
|
||||
class AdvancedGroup : UriGroup<Filter<*>>(
|
||||
"Advanced Options",
|
||||
listOf(
|
||||
AdvancedOption("Search Gallery Name", "f_sname", true),
|
||||
AdvancedOption("Search Gallery Tags", "f_stags", true),
|
||||
AdvancedOption("Search Gallery Description", "f_sdesc"),
|
||||
@@ -670,24 +720,26 @@ class EHentai(
|
||||
RatingOption(),
|
||||
MinPagesOption(),
|
||||
MaxPagesOption()
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
class ReverseFilter : Filter.CheckBox("Reverse search results")
|
||||
|
||||
override val name = if (exh)
|
||||
override val name = if (exh) {
|
||||
"ExHentai"
|
||||
else
|
||||
} else {
|
||||
"E-Hentai"
|
||||
}
|
||||
|
||||
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
|
||||
|
||||
// === URL IMPORT STUFF
|
||||
|
||||
override val matchingHosts: List<String> = if (exh) listOf(
|
||||
"exhentai.org"
|
||||
"exhentai.org"
|
||||
) else listOf(
|
||||
"g.e-hentai.org",
|
||||
"e-hentai.org"
|
||||
"g.e-hentai.org",
|
||||
"e-hentai.org"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
@@ -717,17 +769,23 @@ class EHentai(
|
||||
val json = JsonObject()
|
||||
json["method"] = "gtoken"
|
||||
json["pagelist"] = JsonArray().apply {
|
||||
add(JsonArray().apply {
|
||||
add(gallery.toInt())
|
||||
add(pageToken)
|
||||
add(pageNum.toInt())
|
||||
})
|
||||
add(
|
||||
JsonArray().apply {
|
||||
add(gallery.toInt())
|
||||
add(pageToken)
|
||||
add(pageNum.toInt())
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
val outJson = JsonParser.parseString(client.newCall(Request.Builder()
|
||||
.url(EH_API_BASE)
|
||||
.post(RequestBody.create(JSON, json.toString()))
|
||||
.build()).execute().body!!.string()).obj
|
||||
val outJson = JsonParser.parseString(
|
||||
client.newCall(
|
||||
Request.Builder()
|
||||
.url(EH_API_BASE)
|
||||
.post(RequestBody.create(JSON, json.toString()))
|
||||
.build()
|
||||
).execute().body!!.string()
|
||||
).obj
|
||||
|
||||
val obj = outJson["tokenlist"].array.first()
|
||||
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
||||
@@ -742,16 +800,16 @@ class EHentai(
|
||||
private val JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
|
||||
|
||||
private val FAVORITES_BORDER_HEX_COLORS = listOf(
|
||||
"000",
|
||||
"f00",
|
||||
"fa0",
|
||||
"dd0",
|
||||
"080",
|
||||
"9f4",
|
||||
"4bf",
|
||||
"00f",
|
||||
"508",
|
||||
"e8e"
|
||||
"000",
|
||||
"f00",
|
||||
"fa0",
|
||||
"dd0",
|
||||
"080",
|
||||
"9f4",
|
||||
"4bf",
|
||||
"00f",
|
||||
"508",
|
||||
"e8e"
|
||||
)
|
||||
|
||||
fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") {
|
||||
|
||||
@@ -65,7 +65,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
private fun tagIndexVersion(): Single<Long> {
|
||||
val sCachedTagIndexVersion = cachedTagIndexVersion
|
||||
return if (sCachedTagIndexVersion == null ||
|
||||
tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) {
|
||||
tagIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
|
||||
) {
|
||||
HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||
cachedTagIndexVersion = it
|
||||
tagIndexVersionCacheTime = System.currentTimeMillis()
|
||||
@@ -80,7 +81,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
private fun galleryIndexVersion(): Single<Long> {
|
||||
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
||||
return if (sCachedGalleryIndexVersion == null ||
|
||||
galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()) {
|
||||
galleryIndexVersionCacheTime + INDEX_VERSION_CACHE_TIME_MS < System.currentTimeMillis()
|
||||
) {
|
||||
HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||
cachedGalleryIndexVersion = it
|
||||
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
||||
@@ -162,9 +164,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||
"$LTN_BASE_URL/popular-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
"$LTN_BASE_URL/popular-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -192,7 +194,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
|
||||
// TODO Cache the results coming out of HitomiNozomi
|
||||
val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv }
|
||||
.map { HitomiNozomi(client, it.first, it.second) }
|
||||
.map { HitomiNozomi(client, it.first, it.second) }
|
||||
|
||||
var base = if (positive.isEmpty()) {
|
||||
hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } }
|
||||
@@ -240,9 +242,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
* @param page the page number to retrieve.
|
||||
*/
|
||||
override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||
"$LTN_BASE_URL/index-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
"$LTN_BASE_URL/index-all.nozomi",
|
||||
100L * (page - 1),
|
||||
99L + 100 * (page - 1)
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -254,14 +256,14 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(popularMangaRequest(page))
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return client.newCall(latestUpdatesRequest(page))
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
.asObservableSuccess()
|
||||
.flatMap { responseToMangas(it) }
|
||||
}
|
||||
|
||||
fun responseToMangas(response: Response): Observable<MangasPage> {
|
||||
@@ -270,9 +272,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
val end = range.substringBefore('/').substringAfter('-').toLong()
|
||||
val body = response.body!!
|
||||
return parseNozomiPage(body.bytes())
|
||||
.map {
|
||||
MangasPage(it, end < total - 1)
|
||||
}
|
||||
.map {
|
||||
MangasPage(it, end < total - 1)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> {
|
||||
@@ -285,13 +287,15 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
}
|
||||
|
||||
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
|
||||
return Single.zip(ids.map {
|
||||
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
||||
return Single.zip(
|
||||
ids.map {
|
||||
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
||||
.asObservableSuccess()
|
||||
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
||||
.map { parseGalleryBlock(it) }
|
||||
.toSingle()
|
||||
}) { it.map { m -> m as SManga } }
|
||||
}
|
||||
) { it.map { m -> m as SManga } }
|
||||
}
|
||||
|
||||
private fun parseGalleryBlock(response: Response): SManga {
|
||||
@@ -318,23 +322,27 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
||||
initialized = true
|
||||
}))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return Observable.just(
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
chapter_number = 0.0f
|
||||
}
|
||||
)
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
chapter_number = 0.0f
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -372,9 +380,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
val hashPath1 = hash.takeLast(1)
|
||||
val hashPath2 = hash.takeLast(3).take(2)
|
||||
Page(
|
||||
index,
|
||||
"",
|
||||
"https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
|
||||
index,
|
||||
"",
|
||||
"https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -396,19 +404,20 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
it[it.lastIndex - 1]
|
||||
}
|
||||
return request.newBuilder()
|
||||
.header("Referer", "$BASE_URL/reader/$hlId.html")
|
||||
.build()
|
||||
.header("Referer", "$BASE_URL/reader/$hlId.html")
|
||||
.build()
|
||||
}
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"hitomi.la"
|
||||
"hitomi.la"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||
|
||||
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader")
|
||||
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") {
|
||||
return null
|
||||
}
|
||||
|
||||
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
|
||||
}
|
||||
@@ -419,10 +428,11 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
||||
private val NUMBER_OF_FRONTENDS = 2
|
||||
|
||||
private val DATE_FORMAT by lazy {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US)
|
||||
else
|
||||
} else {
|
||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
}
|
||||
|
||||
val sortFilter = filters.filterIsInstance<SortFilter>().firstOrNull()?.state
|
||||
?: defaultSortFilterSelection()
|
||||
?: defaultSortFilterSelection()
|
||||
|
||||
if (sortFilter.index == 1) {
|
||||
if (query.isBlank()) error("You must specify a search query if you wish to sort by popularity!")
|
||||
@@ -89,22 +89,22 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
|
||||
if (sortFilter.ascending) {
|
||||
return client.newCall(nhGet(uri.toString()))
|
||||
.asObservableSuccess()
|
||||
.map {
|
||||
val doc = it.asJsoup()
|
||||
.asObservableSuccess()
|
||||
.map {
|
||||
val doc = it.asJsoup()
|
||||
|
||||
val lastPage = doc.selectFirst(".last")
|
||||
?.attr("href")
|
||||
?.substringAfterLast('=')
|
||||
?.toIntOrNull() ?: 1
|
||||
val lastPage = doc.selectFirst(".last")
|
||||
?.attr("href")
|
||||
?.substringAfterLast('=')
|
||||
?.toIntOrNull() ?: 1
|
||||
|
||||
val thisPage = lastPage - (page - 1)
|
||||
val thisPage = lastPage - (page - 1)
|
||||
|
||||
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
||||
uri.appendQueryParameter("page", thisPage.toString())
|
||||
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
||||
uri.appendQueryParameter("page", thisPage.toString())
|
||||
|
||||
nhGet(uri.toString(), page)
|
||||
}
|
||||
nhGet(uri.toString(), page)
|
||||
}
|
||||
}
|
||||
|
||||
uri.appendQueryParameter("page", page.toString())
|
||||
@@ -134,12 +134,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
||||
initialized = true
|
||||
}))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url)
|
||||
@@ -208,31 +212,38 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
}?.apply {
|
||||
tags.clear()
|
||||
}?.forEach {
|
||||
if (it.first != null && it.second != null)
|
||||
if (it.first != null && it.second != null) {
|
||||
tags.add(RaisedTag(it.first!!, it.second!!, TAG_TYPE_DEFAULT))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
|
||||
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
|
||||
.asObservableSuccess()
|
||||
.toSingle()
|
||||
.asObservableSuccess()
|
||||
.toSingle()
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga) = Observable.just(listOf(SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
chapter_number = 1f
|
||||
}))
|
||||
override fun fetchChapterList(manga: SManga) = Observable.just(
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
url = manga.url
|
||||
name = "Chapter"
|
||||
chapter_number = 1f
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
|
||||
if (metadata.mediaId == null) emptyList()
|
||||
else
|
||||
if (metadata.mediaId == null) {
|
||||
emptyList()
|
||||
} else {
|
||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
||||
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
|
||||
Page(index, imageUrl!!, imageUrl)
|
||||
}
|
||||
}
|
||||
}.toObservable()
|
||||
|
||||
override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
|
||||
@@ -259,9 +270,9 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
private class filterLang : Filter.Select<String>("Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray())
|
||||
|
||||
class SortFilter : Filter.Sort(
|
||||
"Sort",
|
||||
arrayOf("Date", "Popular"),
|
||||
defaultSortFilterSelection()
|
||||
"Sort",
|
||||
arrayOf("Date", "Popular"),
|
||||
defaultSortFilterSelection()
|
||||
)
|
||||
|
||||
val appName by lazy {
|
||||
@@ -269,14 +280,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
}
|
||||
|
||||
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
||||
.newBuilder()
|
||||
.header("User-Agent",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||
"Chrome/56.0.2924.87 " +
|
||||
"Safari/537.36 " +
|
||||
"$appName/${BuildConfig.VERSION_CODE}")
|
||||
.tag(tag).build()
|
||||
.newBuilder()
|
||||
.header(
|
||||
"User-Agent",
|
||||
"Mozilla/5.0 (X11; Linux x86_64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||
"Chrome/56.0.2924.87 " +
|
||||
"Safari/537.36 " +
|
||||
"$appName/${BuildConfig.VERSION_CODE}"
|
||||
)
|
||||
.tag(tag).build()
|
||||
|
||||
override val id = NHENTAI_SOURCE_ID
|
||||
|
||||
@@ -291,12 +304,13 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
// === URL IMPORT STUFF
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"nhentai.net"
|
||||
"nhentai.net"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g")
|
||||
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g") {
|
||||
return null
|
||||
}
|
||||
|
||||
return "$baseUrl/g/${uri.pathSegments[1]}/"
|
||||
}
|
||||
@@ -308,10 +322,10 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
||||
private fun defaultSortFilterSelection() = Filter.Sort.Selection(0, false)
|
||||
|
||||
private val SOURCE_LANG_LIST = listOf(
|
||||
Pair("All", ""),
|
||||
Pair("English", " english"),
|
||||
Pair("Japanese", " japanese"),
|
||||
Pair("Chinese", " chinese")
|
||||
Pair("All", ""),
|
||||
Pair("English", " english"),
|
||||
Pair("Japanese", " japanese"),
|
||||
Pair("Chinese", " chinese")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,8 +33,10 @@ import org.jsoup.nodes.TextNode
|
||||
import rx.Observable
|
||||
|
||||
// TODO Transform into delegated source
|
||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
||||
LewdSource<PervEdenSearchMetadata, Document>, UrlImportableSource {
|
||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) :
|
||||
ParsedHttpSource(),
|
||||
LewdSource<PervEdenSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
/**
|
||||
* The class of the metadata used by this source
|
||||
*/
|
||||
@@ -62,9 +64,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
|
||||
// Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = "#mangaList > tbody > tr"
|
||||
|
||||
@@ -79,9 +81,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
override fun searchMangaNextPageSelector() = ".next"
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
val urlLang = if (lang == "en")
|
||||
val urlLang = if (lang == "en") {
|
||||
"eng"
|
||||
else "it"
|
||||
} else {
|
||||
"it"
|
||||
}
|
||||
return GET("$baseUrl/$urlLang/")
|
||||
}
|
||||
|
||||
@@ -129,12 +133,16 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
||||
initialized = true
|
||||
}))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -165,8 +173,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
"Alternative name(s)" -> {
|
||||
if (it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if (!text.isBlank())
|
||||
if (!text.isBlank()) {
|
||||
newAltTitles += text
|
||||
}
|
||||
}
|
||||
}
|
||||
"Artist" -> {
|
||||
@@ -176,21 +185,24 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
}
|
||||
}
|
||||
"Genres" -> {
|
||||
if (it is Element && it.tagName() == "a")
|
||||
if (it is Element && it.tagName() == "a") {
|
||||
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
|
||||
}
|
||||
}
|
||||
"Type" -> {
|
||||
if (it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if (!text.isBlank())
|
||||
if (!text.isBlank()) {
|
||||
type = text
|
||||
}
|
||||
}
|
||||
}
|
||||
"Status" -> {
|
||||
if (it is TextNode) {
|
||||
val text = it.text().trim()
|
||||
if (!text.isBlank())
|
||||
if (!text.isBlank()) {
|
||||
status = text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -224,10 +236,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
name = "Chapter " + linkElement.getElementsByTag("b").text()
|
||||
|
||||
ChapterRecognition.parseChapterNumber(
|
||||
this,
|
||||
SManga.create().apply {
|
||||
title = ""
|
||||
})
|
||||
this,
|
||||
SManga.create().apply {
|
||||
title = ""
|
||||
}
|
||||
)
|
||||
|
||||
try {
|
||||
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time
|
||||
@@ -242,37 +255,49 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
override fun imageUrlParse(document: Document) = "http:" + document.getElementById("mainImg").attr("src")!!
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
AuthorFilter(),
|
||||
ArtistFilter(),
|
||||
TypeFilterGroup(),
|
||||
ReleaseYearGroup(),
|
||||
StatusFilterGroup()
|
||||
AuthorFilter(),
|
||||
ArtistFilter(),
|
||||
TypeFilterGroup(),
|
||||
ReleaseYearGroup(),
|
||||
StatusFilterGroup()
|
||||
)
|
||||
|
||||
class StatusFilterGroup : UriGroup<StatusFilter>("Status", listOf(
|
||||
class StatusFilterGroup : UriGroup<StatusFilter>(
|
||||
"Status",
|
||||
listOf(
|
||||
StatusFilter("Ongoing", 1),
|
||||
StatusFilter("Completed", 2),
|
||||
StatusFilter("Suspended", 0)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
if (state)
|
||||
if (state) {
|
||||
builder.appendQueryParameter("status", id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Explicit type arg for listOf() to workaround this: KT-16570
|
||||
class ReleaseYearGroup : UriGroup<Filter<*>>("Release Year", listOf(
|
||||
class ReleaseYearGroup : UriGroup<Filter<*>>(
|
||||
"Release Year",
|
||||
listOf(
|
||||
ReleaseYearRangeFilter(),
|
||||
ReleaseYearYearFilter()
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
class ReleaseYearRangeFilter : Filter.Select<String>("Range", arrayOf(
|
||||
"on",
|
||||
"after",
|
||||
"before"
|
||||
)), UriFilter {
|
||||
class ReleaseYearRangeFilter :
|
||||
Filter.Select<String>(
|
||||
"Range",
|
||||
arrayOf(
|
||||
"on",
|
||||
"after",
|
||||
"before"
|
||||
)
|
||||
),
|
||||
UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
builder.appendQueryParameter("releasedType", state.toString())
|
||||
}
|
||||
@@ -296,18 +321,22 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
||||
}
|
||||
}
|
||||
|
||||
class TypeFilterGroup : UriGroup<TypeFilter>("Type", listOf(
|
||||
class TypeFilterGroup : UriGroup<TypeFilter>(
|
||||
"Type",
|
||||
listOf(
|
||||
TypeFilter("Japanese Manga", 0),
|
||||
TypeFilter("Korean Manhwa", 1),
|
||||
TypeFilter("Chinese Manhua", 2),
|
||||
TypeFilter("Comic", 3),
|
||||
TypeFilter("Doujinshi", 4)
|
||||
))
|
||||
)
|
||||
)
|
||||
|
||||
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||
override fun addToUri(builder: Uri.Builder) {
|
||||
if (state)
|
||||
if (state) {
|
||||
builder.appendQueryParameter("type", id.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,9 +41,10 @@ import rx.schedulers.Schedulers
|
||||
|
||||
typealias SiteMap = NakedTrie<Unit>
|
||||
|
||||
class EightMuses : HttpSource(),
|
||||
LewdSource<EightMusesSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
class EightMuses :
|
||||
HttpSource(),
|
||||
LewdSource<EightMusesSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
override val id = EIGHTMUSES_SOURCE_ID
|
||||
|
||||
/**
|
||||
@@ -74,10 +75,10 @@ class EightMuses : HttpSource(),
|
||||
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()
|
||||
.asObservableSuccess()
|
||||
.toSingle()
|
||||
.await(Schedulers.io())
|
||||
.body!!.string()
|
||||
|
||||
val parsed = Jsoup.parse(result)
|
||||
|
||||
@@ -93,10 +94,10 @@ class EightMuses : HttpSource(),
|
||||
|
||||
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")
|
||||
.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 {
|
||||
@@ -129,11 +130,11 @@ class EightMuses : HttpSource(),
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val urlBuilder = if (!query.isBlank()) {
|
||||
"$baseUrl/search".toHttpUrlOrNull()!!
|
||||
.newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
.newBuilder()
|
||||
.addQueryParameter("q", query)
|
||||
} else {
|
||||
"$baseUrl/comics".toHttpUrlOrNull()!!
|
||||
.newBuilder()
|
||||
.newBuilder()
|
||||
}
|
||||
|
||||
urlBuilder.addQueryParameter("page", page.toString())
|
||||
@@ -182,12 +183,14 @@ class EightMuses : HttpSource(),
|
||||
|
||||
private fun fetchListing(request: Request, dig: Boolean): Observable<MangasPage> {
|
||||
return client.newCall(request)
|
||||
.asObservableSuccess()
|
||||
.flatMapSingle { response ->
|
||||
RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
||||
.asObservableSuccess()
|
||||
.flatMapSingle { response ->
|
||||
RxJavaInterop.toV1Single(
|
||||
GlobalScope.async(Dispatchers.IO) {
|
||||
parseResultsPage(response, dig)
|
||||
}.asSingle(GlobalScope.coroutineContext))
|
||||
}
|
||||
}.asSingle(GlobalScope.coroutineContext)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun parseResultsPage(response: Response, dig: Boolean): MangasPage {
|
||||
@@ -197,32 +200,32 @@ class EightMuses : HttpSource(),
|
||||
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 {
|
||||
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 = it.attr("href")
|
||||
url = key
|
||||
|
||||
title = it.select(".title-text").text()
|
||||
|
||||
thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src")
|
||||
title = key.substringAfterLast('/').replace('-', ' ')
|
||||
}
|
||||
}
|
||||
},
|
||||
!onLastPage
|
||||
}
|
||||
} 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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -243,10 +246,10 @@ class EightMuses : HttpSource(),
|
||||
*/
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -259,9 +262,11 @@ class EightMuses : HttpSource(),
|
||||
}
|
||||
|
||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
||||
fetchAndParseChapterList("", manga.url)
|
||||
}.asSingle(GlobalScope.coroutineContext)).toObservable()
|
||||
return RxJavaInterop.toV1Single(
|
||||
GlobalScope.async(Dispatchers.IO) {
|
||||
fetchAndParseChapterList("", manga.url)
|
||||
}.asSingle(GlobalScope.coroutineContext)
|
||||
).toObservable()
|
||||
}
|
||||
|
||||
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
|
||||
@@ -309,9 +314,9 @@ class EightMuses : HttpSource(),
|
||||
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)
|
||||
index,
|
||||
element.attr("href"),
|
||||
"$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -325,30 +330,30 @@ class EightMuses : HttpSource(),
|
||||
title = breadcrumbs.selectFirst("li:nth-last-child(1) > a").text()
|
||||
|
||||
thumbnailUrl = parseSelf(input).let { it.albums + it.images }.firstOrNull()
|
||||
?.selectFirst(".lazyload")
|
||||
?.attr("data-src")?.let {
|
||||
baseUrl + it
|
||||
}
|
||||
?.selectFirst(".lazyload")
|
||||
?.attr("data-src")?.let {
|
||||
baseUrl + it
|
||||
}
|
||||
|
||||
tags.clear()
|
||||
tags += RaisedTag(
|
||||
EightMusesSearchMetadata.ARTIST_NAMESPACE,
|
||||
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
|
||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||
EightMusesSearchMetadata.ARTIST_NAMESPACE,
|
||||
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
|
||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||
)
|
||||
tags += input.select(".album-tags a").map {
|
||||
RaisedTag(
|
||||
EightMusesSearchMetadata.TAGS_NAMESPACE,
|
||||
it.text(),
|
||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||
EightMusesSearchMetadata.TAGS_NAMESPACE,
|
||||
it.text(),
|
||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SortFilter : Filter.Select<String>(
|
||||
"Sort",
|
||||
SORT_OPTIONS.map { it.second }.toTypedArray()
|
||||
"Sort",
|
||||
SORT_OPTIONS.map { it.second }.toTypedArray()
|
||||
) {
|
||||
fun addToUri(url: HttpUrl.Builder) {
|
||||
url.addQueryParameter("sort", SORT_OPTIONS[state].first)
|
||||
@@ -357,16 +362,16 @@ class EightMuses : HttpSource(),
|
||||
companion object {
|
||||
// <Internal, Display>
|
||||
private val SORT_OPTIONS = listOf(
|
||||
"" to "Views",
|
||||
"like" to "Likes",
|
||||
"date" to "Date",
|
||||
"az" to "A-Z"
|
||||
"" to "Views",
|
||||
"like" to "Likes",
|
||||
"date" to "Date",
|
||||
"az" to "A-Z"
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
SortFilter()
|
||||
SortFilter()
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -379,8 +384,8 @@ class EightMuses : HttpSource(),
|
||||
}
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"www.8muses.com",
|
||||
"8muses.com"
|
||||
"www.8muses.com",
|
||||
"8muses.com"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -19,8 +19,10 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import org.jsoup.nodes.Document
|
||||
import rx.Observable
|
||||
|
||||
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
LewdSource<HentaiCafeSearchMetadata, Document>, UrlImportableSource {
|
||||
class HentaiCafe(delegate: HttpSource) :
|
||||
DelegatedHttpSource(delegate),
|
||||
LewdSource<HentaiCafeSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
/**
|
||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||
*/
|
||||
@@ -32,18 +34,22 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
|
||||
// Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
||||
initialized = true
|
||||
}))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(
|
||||
Observable.just(
|
||||
manga.apply {
|
||||
initialized = true
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -57,8 +63,8 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
thumbnailUrl = contentElement.child(0).child(0).attr("src")
|
||||
|
||||
fun filterableTagsOfType(type: String) = contentElement.select("a")
|
||||
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
|
||||
.map { it.text() }
|
||||
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
|
||||
.map { it.text() }
|
||||
|
||||
tags.clear()
|
||||
tags += filterableTagsOfType("tag").map {
|
||||
@@ -78,29 +84,30 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
|
||||
override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) {
|
||||
client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.map { it.asJsoup() }
|
||||
.toSingle()
|
||||
.asObservableSuccess()
|
||||
.map { it.asJsoup() }
|
||||
.toSingle()
|
||||
}.map {
|
||||
listOf(
|
||||
SChapter.create().apply {
|
||||
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
|
||||
name = "Chapter"
|
||||
chapter_number = 0.0f
|
||||
}
|
||||
SChapter.create().apply {
|
||||
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
|
||||
name = "Chapter"
|
||||
chapter_number = 0.0f
|
||||
}
|
||||
)
|
||||
}.toObservable()
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"hentai.cafe"
|
||||
"hentai.cafe"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||
|
||||
return if (lcFirstPathSegment == "manga")
|
||||
return if (lcFirstPathSegment == "manga") {
|
||||
"https://hentai.cafe/${uri.pathSegments[2]}"
|
||||
else
|
||||
} else {
|
||||
"https://hentai.cafe/$lcFirstPathSegment"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,10 @@ import exh.util.urlImportFetchSearchManga
|
||||
import org.jsoup.nodes.Document
|
||||
import rx.Observable
|
||||
|
||||
class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
LewdSource<PururinSearchMetadata, Document>, UrlImportableSource {
|
||||
class Pururin(delegate: HttpSource) :
|
||||
DelegatedHttpSource(delegate),
|
||||
LewdSource<PururinSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
/**
|
||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
||||
*/
|
||||
@@ -43,11 +45,11 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup())
|
||||
.andThen(Observable.just(manga))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup())
|
||||
.andThen(Observable.just(manga))
|
||||
}
|
||||
}
|
||||
|
||||
override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) {
|
||||
@@ -87,9 +89,9 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
value.select("a").forEach { link ->
|
||||
val searchUrl = Uri.parse(link.attr("href"))
|
||||
tags += RaisedTag(
|
||||
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
|
||||
searchUrl.lastPathSegment!!.substringBefore("."),
|
||||
PururinSearchMetadata.TAG_TYPE_DEFAULT
|
||||
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
|
||||
searchUrl.lastPathSegment!!.substringBefore("."),
|
||||
PururinSearchMetadata.TAG_TYPE_DEFAULT
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -99,8 +101,8 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
}
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"pururin.io",
|
||||
"www.pururin.io"
|
||||
"pururin.io",
|
||||
"www.pururin.io"
|
||||
)
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
|
||||
@@ -19,30 +19,33 @@ import java.util.Locale
|
||||
import org.jsoup.nodes.Document
|
||||
import rx.Observable
|
||||
|
||||
class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource {
|
||||
class Tsumino(delegate: HttpSource) :
|
||||
DelegatedHttpSource(delegate),
|
||||
LewdSource<TsuminoSearchMetadata, Document>,
|
||||
UrlImportableSource {
|
||||
override val metaClass = TsuminoSearchMetadata::class
|
||||
override val lang = "en"
|
||||
|
||||
// Support direct URL importing
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
urlImportFetchSearchManga(query) {
|
||||
super.fetchSearchManga(page, query, filters)
|
||||
}
|
||||
|
||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||
if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry")
|
||||
if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry") {
|
||||
return null
|
||||
}
|
||||
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
return client.newCall(mangaDetailsRequest(manga))
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||
}
|
||||
.asObservableSuccess()
|
||||
.flatMap {
|
||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||
}
|
||||
}
|
||||
|
||||
override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) {
|
||||
@@ -106,16 +109,18 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
||||
character = newCharacter
|
||||
|
||||
input.getElementById("Tag")?.children()?.let {
|
||||
tags.addAll(it.map {
|
||||
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
||||
})
|
||||
tags.addAll(
|
||||
it.map {
|
||||
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val matchingHosts = listOf(
|
||||
"www.tsumino.com",
|
||||
"tsumino.com"
|
||||
"www.tsumino.com",
|
||||
"tsumino.com"
|
||||
)
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -131,10 +131,14 @@ class SourceController :
|
||||
// Open the catalogue view.
|
||||
openCatalogue(source, BrowseSourceController(source))
|
||||
}
|
||||
Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply {
|
||||
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
||||
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
||||
}).withFadeTransaction())
|
||||
Mode.SMART_SEARCH -> router.pushController(
|
||||
SmartSearchController(
|
||||
Bundle().apply {
|
||||
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
||||
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
||||
}
|
||||
).withFadeTransaction()
|
||||
)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+8
-6
@@ -413,9 +413,9 @@ open class BrowseSourcePresenter(
|
||||
}
|
||||
val newSerialized = searches.map {
|
||||
"${source.id}:" + jsonObject(
|
||||
"name" to it.name,
|
||||
"query" to it.query,
|
||||
"filters" to filterSerializer.serialize(it.filterList)
|
||||
"name" to it.name,
|
||||
"query" to it.query,
|
||||
"filters" to filterSerializer.serialize(it.filterList)
|
||||
).toString()
|
||||
}
|
||||
prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
|
||||
@@ -430,9 +430,11 @@ open class BrowseSourcePresenter(
|
||||
val content = JsonParser.parseString(it.substringAfter(':')).obj
|
||||
val originalFilters = source.getFilterList()
|
||||
filterSerializer.deserialize(originalFilters, content["filters"].array)
|
||||
EXHSavedSearch(content["name"].string,
|
||||
content["query"].string,
|
||||
originalFilters)
|
||||
EXHSavedSearch(
|
||||
content["name"].string,
|
||||
content["query"].string,
|
||||
originalFilters
|
||||
)
|
||||
} catch (t: RuntimeException) {
|
||||
// Load failed
|
||||
Timber.e(t, "Failed to load saved search!")
|
||||
|
||||
@@ -393,7 +393,6 @@ class LibraryPresenter(
|
||||
manga: Manga,
|
||||
replace: Boolean
|
||||
) {
|
||||
|
||||
val flags = preferences.migrateFlags().get()
|
||||
val migrateChapters = MigrationFlags.hasChapters(flags)
|
||||
val migrateCategories = MigrationFlags.hasCategories(flags)
|
||||
|
||||
@@ -181,29 +181,34 @@ class MangaInfoPresenter(
|
||||
|
||||
suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga {
|
||||
val originalManga = db.getManga(originalMangaId).await()
|
||||
?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId")
|
||||
?: throw IllegalArgumentException("Unknown manga ID: $originalMangaId")
|
||||
val toInsert = if (originalManga.source == MERGED_SOURCE_ID) {
|
||||
originalManga.apply {
|
||||
val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children
|
||||
if (originalChildren.any { it.source == manga.source && it.url == manga.url })
|
||||
if (originalChildren.any { it.source == manga.source && it.url == manga.url }) {
|
||||
throw IllegalArgumentException("This manga is already merged with the current manga!")
|
||||
}
|
||||
|
||||
url = MergedSource.MangaConfig(originalChildren + MergedSource.MangaSource(
|
||||
url = MergedSource.MangaConfig(
|
||||
originalChildren + MergedSource.MangaSource(
|
||||
manga.source,
|
||||
manga.url
|
||||
)).writeAsUrl(gson)
|
||||
)
|
||||
).writeAsUrl(gson)
|
||||
}
|
||||
} else {
|
||||
val newMangaConfig = MergedSource.MangaConfig(listOf(
|
||||
val newMangaConfig = MergedSource.MangaConfig(
|
||||
listOf(
|
||||
MergedSource.MangaSource(
|
||||
originalManga.source,
|
||||
originalManga.url
|
||||
originalManga.source,
|
||||
originalManga.url
|
||||
),
|
||||
MergedSource.MangaSource(
|
||||
manga.source,
|
||||
manga.url
|
||||
manga.source,
|
||||
manga.url
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
||||
copyFrom(originalManga)
|
||||
favorite = true
|
||||
|
||||
@@ -23,17 +23,22 @@ class MigrationMangaDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
||||
|
||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
|
||||
val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet,
|
||||
mangaSet, (
|
||||
val confirmString = applicationContext?.resources?.getQuantityString(
|
||||
confirmRes, mangaSet,
|
||||
mangaSet,
|
||||
(
|
||||
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
|
||||
else "")) ?: ""
|
||||
else ""
|
||||
)
|
||||
) ?: ""
|
||||
return MaterialDialog(activity!!)
|
||||
.message(text = confirmString)
|
||||
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
|
||||
if (copy)
|
||||
if (copy) {
|
||||
(targetController as? MigrationListController)?.copyMangas()
|
||||
else
|
||||
} else {
|
||||
(targetController as? MigrationListController)?.migrateMangas()
|
||||
}
|
||||
}
|
||||
.negativeButton(android.R.string.no)
|
||||
}
|
||||
|
||||
+17
-9
@@ -31,10 +31,12 @@ class MigrationBottomSheetDialog(
|
||||
activity: Activity,
|
||||
theme: Int,
|
||||
private val listener:
|
||||
StartMigrationListener
|
||||
StartMigrationListener
|
||||
) :
|
||||
BottomSheetDialog(activity,
|
||||
theme) {
|
||||
BottomSheetDialog(
|
||||
activity,
|
||||
theme
|
||||
) {
|
||||
/**
|
||||
* Preferences helper.
|
||||
*/
|
||||
@@ -47,8 +49,9 @@ class MigrationBottomSheetDialog(
|
||||
// scroll.addView(view)
|
||||
|
||||
setContentView(view)
|
||||
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
||||
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
sourceGroup.orientation = LinearLayout.HORIZONTAL
|
||||
}
|
||||
window?.setBackgroundDrawable(null)
|
||||
}
|
||||
|
||||
@@ -63,8 +66,10 @@ class MigrationBottomSheetDialog(
|
||||
fab.setOnClickListener {
|
||||
preferences.skipPreMigration().set(skip_step.isChecked)
|
||||
listener.startMigration(
|
||||
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank())
|
||||
extra_search_param_text.text.toString() else null)
|
||||
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) {
|
||||
extra_search_param_text.text.toString()
|
||||
} else null
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
}
|
||||
@@ -96,9 +101,12 @@ class MigrationBottomSheetDialog(
|
||||
|
||||
skip_step.isChecked = preferences.skipPreMigration().get()
|
||||
skip_step.setOnCheckedChangeListener { _, isChecked ->
|
||||
if (isChecked)
|
||||
(listener as? Controller)?.activity?.toast(R.string.pre_migration_skip_toast,
|
||||
Toast.LENGTH_LONG)
|
||||
if (isChecked) {
|
||||
(listener as? Controller)?.activity?.toast(
|
||||
R.string.pre_migration_skip_toast,
|
||||
Toast.LENGTH_LONG
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -9,16 +9,21 @@ class MigrationSourceAdapter(
|
||||
var items: List<MigrationSourceItem>,
|
||||
val controllerPre: PreMigrationController
|
||||
) : FlexibleAdapter<MigrationSourceItem>(
|
||||
items,
|
||||
controllerPre,
|
||||
true
|
||||
items,
|
||||
controllerPre,
|
||||
true
|
||||
) {
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
outState.putParcelableArrayList(SELECTED_SOURCES_KEY, ArrayList(currentItems.map {
|
||||
it.asParcelable()
|
||||
}))
|
||||
outState.putParcelableArrayList(
|
||||
SELECTED_SOURCES_KEY,
|
||||
ArrayList(
|
||||
currentItems.map {
|
||||
it.asParcelable()
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||
|
||||
+2
-2
@@ -66,8 +66,8 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) :
|
||||
val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null
|
||||
|
||||
return MigrationSourceItem(
|
||||
source,
|
||||
si.sourceEnabled
|
||||
source,
|
||||
si.sourceEnabled
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+23
-12
@@ -27,8 +27,11 @@ import exh.util.updateLayoutParams
|
||||
import exh.util.updatePaddingRelative
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrationControllerBinding>(bundle), FlexibleAdapter
|
||||
.OnItemClickListener, StartMigrationListener {
|
||||
class PreMigrationController(bundle: Bundle? = null) :
|
||||
BaseController<PreMigrationControllerBinding>(bundle),
|
||||
FlexibleAdapter
|
||||
.OnItemClickListener,
|
||||
StartMigrationListener {
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
@@ -69,8 +72,10 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
||||
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
|
||||
}
|
||||
// offset the recycler by the fab's inset + some inset on top
|
||||
v.updatePaddingRelative(bottom = padding.bottom + (binding.fab.marginBottom) +
|
||||
fabBaseMarginBottom + (binding.fab.height))
|
||||
v.updatePaddingRelative(
|
||||
bottom = padding.bottom + (binding.fab.marginBottom) +
|
||||
fabBaseMarginBottom + (binding.fab.height)
|
||||
)
|
||||
}
|
||||
|
||||
binding.fab.setOnClickListener {
|
||||
@@ -101,7 +106,8 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
||||
config.toList(),
|
||||
extraSearchParams = extraParam
|
||||
)
|
||||
).withFadeTransaction().tag(MigrationListController.TAG))
|
||||
).withFadeTransaction().tag(MigrationListController.TAG)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
@@ -136,10 +142,13 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
||||
.filter { it.lang in languages }
|
||||
.sortedBy { "(${it.lang}) ${it.name}" }
|
||||
sources =
|
||||
sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf(it.id
|
||||
.toString())
|
||||
} +
|
||||
sources.filterNot { isEnabled(it.id.toString()) }
|
||||
sources.filter { isEnabled(it.id.toString()) }.sortedBy {
|
||||
sourcesSaved.indexOf(
|
||||
it.id
|
||||
.toString()
|
||||
)
|
||||
} +
|
||||
sources.filterNot { isEnabled(it.id.toString()) }
|
||||
|
||||
return sources
|
||||
}
|
||||
@@ -167,9 +176,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
||||
}
|
||||
|
||||
fun create(mangaIds: List<Long>): PreMigrationController {
|
||||
return PreMigrationController(Bundle().apply {
|
||||
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
||||
})
|
||||
return PreMigrationController(
|
||||
Bundle().apply {
|
||||
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+11
-8
@@ -56,8 +56,10 @@ import rx.schedulers.Schedulers
|
||||
import timber.log.Timber
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class MigrationListController(bundle: Bundle? = null) : BaseController<MigrationListControllerBinding>(bundle),
|
||||
MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope {
|
||||
class MigrationListController(bundle: Bundle? = null) :
|
||||
BaseController<MigrationListControllerBinding>(bundle),
|
||||
MigrationProcessAdapter.MigrationProcessInterface,
|
||||
CoroutineScope {
|
||||
|
||||
init {
|
||||
setHasOptionsMenu(true)
|
||||
@@ -93,7 +95,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View) {
|
||||
|
||||
super.onViewCreated(view)
|
||||
view.applyWindowInsetsForController()
|
||||
setTitle()
|
||||
@@ -217,7 +218,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
||||
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
|
||||
val chapters = try {
|
||||
source.fetchChapterList(localManga)
|
||||
.toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) {
|
||||
.toSingle().await(Schedulers.io())
|
||||
} catch (e: java.lang.Exception) {
|
||||
Timber.e(e)
|
||||
emptyList<SChapter>()
|
||||
} ?: emptyList()
|
||||
@@ -313,7 +315,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(position: Int, item: MenuItem) {
|
||||
|
||||
when (item.itemId) {
|
||||
R.id.action_search_manually -> {
|
||||
launchUI {
|
||||
@@ -488,9 +489,11 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
||||
const val TAG = "migration_list"
|
||||
|
||||
fun create(config: MigrationProcedureConfig): MigrationListController {
|
||||
return MigrationListController(Bundle().apply {
|
||||
putParcelable(CONFIG_EXTRA, config)
|
||||
})
|
||||
return MigrationListController(
|
||||
Bundle().apply {
|
||||
putParcelable(CONFIG_EXTRA, config)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+8
-3
@@ -42,8 +42,12 @@ class MigrationProcessAdapter(
|
||||
if (allMangasDone()) menuItemListener.enableButtons()
|
||||
}
|
||||
|
||||
fun allMangasDone() = (items.all { it.manga.migrationStatus != MigrationStatus
|
||||
.RUNNUNG } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND })
|
||||
fun allMangasDone() = (
|
||||
items.all {
|
||||
it.manga.migrationStatus != MigrationStatus
|
||||
.RUNNUNG
|
||||
} && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND }
|
||||
)
|
||||
|
||||
fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
|
||||
|
||||
@@ -59,7 +63,8 @@ class MigrationProcessAdapter(
|
||||
migrateMangaInternal(
|
||||
manga.manga() ?: return@forEach,
|
||||
toMangaObj,
|
||||
!copy)
|
||||
!copy
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-9
@@ -48,10 +48,18 @@ class MigrationProcessHolder(
|
||||
val manga = item.manga.manga()
|
||||
val source = item.manga.mangaSource()
|
||||
|
||||
migration_menu.setVectorCompat(R.drawable.ic_more_vert_24dp, view.context
|
||||
.getResourceColor(R.attr.colorOnPrimary))
|
||||
skip_manga.setVectorCompat(R.drawable.ic_close_24dp, view.context.getResourceColor(R
|
||||
.attr.colorOnPrimary))
|
||||
migration_menu.setVectorCompat(
|
||||
R.drawable.ic_more_vert_24dp,
|
||||
view.context
|
||||
.getResourceColor(R.attr.colorOnPrimary)
|
||||
)
|
||||
skip_manga.setVectorCompat(
|
||||
R.drawable.ic_close_24dp,
|
||||
view.context.getResourceColor(
|
||||
R
|
||||
.attr.colorOnPrimary
|
||||
)
|
||||
)
|
||||
migration_menu.invisible()
|
||||
skip_manga.visible()
|
||||
migration_manga_card_to.resetManga()
|
||||
@@ -87,7 +95,8 @@ class MigrationProcessHolder(
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
|
||||
item.manga.migrationStatus == MigrationStatus.RUNNUNG) {
|
||||
item.manga.migrationStatus == MigrationStatus.RUNNUNG
|
||||
) {
|
||||
return@withContext
|
||||
}
|
||||
if (searchResult != null && resultSource != null) {
|
||||
@@ -152,11 +161,15 @@ class MigrationProcessHolder(
|
||||
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
||||
|
||||
if (latestChapter > 0f) {
|
||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
||||
DecimalFormat("#.#").format(latestChapter))
|
||||
manga_last_chapter_label.text = context.getString(
|
||||
R.string.latest_,
|
||||
DecimalFormat("#.#").format(latestChapter)
|
||||
)
|
||||
} else {
|
||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
||||
context.getString(R.string.unknown))
|
||||
manga_last_chapter_label.text = context.getString(
|
||||
R.string.latest_,
|
||||
context.getString(R.string.unknown)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -63,13 +63,13 @@ class SettingsEhController : SettingsController() {
|
||||
private fun Preference<*>.reconfigure(): Boolean {
|
||||
// Listen for change commit
|
||||
asObservable()
|
||||
.skip(1) // Skip first as it is emitted immediately
|
||||
.take(1) // Only listen for first commit
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeUntilDestroy {
|
||||
// Only listen for first change commit
|
||||
WarnConfigureDialogController.uploadSettings(router)
|
||||
}
|
||||
.skip(1) // Skip first as it is emitted immediately
|
||||
.take(1) // Only listen for first commit
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeUntilDestroy {
|
||||
// Only listen for first change commit
|
||||
WarnConfigureDialogController.uploadSettings(router)
|
||||
}
|
||||
|
||||
// Always return true to save changes
|
||||
return true
|
||||
@@ -85,12 +85,12 @@ class SettingsEhController : SettingsController() {
|
||||
isPersistent = false
|
||||
defaultValue = false
|
||||
preferences.enableExhentai()
|
||||
.asObservable()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeUntilDestroy {
|
||||
isChecked = it
|
||||
}
|
||||
.asObservable()
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribeUntilDestroy {
|
||||
isChecked = it
|
||||
}
|
||||
|
||||
onChange { newVal ->
|
||||
newVal as Boolean
|
||||
@@ -98,9 +98,11 @@ class SettingsEhController : SettingsController() {
|
||||
preferences.enableExhentai().set(false)
|
||||
true
|
||||
} else {
|
||||
router.pushController(RouterTransaction.with(LoginController())
|
||||
router.pushController(
|
||||
RouterTransaction.with(LoginController())
|
||||
.pushChangeHandler(FadeChangeHandler())
|
||||
.popChangeHandler(FadeChangeHandler()))
|
||||
.popChangeHandler(FadeChangeHandler())
|
||||
)
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -148,20 +150,20 @@ class SettingsEhController : SettingsController() {
|
||||
summary = "The quality of the downloaded images"
|
||||
title = "Image quality"
|
||||
entries = arrayOf(
|
||||
"Auto",
|
||||
"2400x",
|
||||
"1600x",
|
||||
"1280x",
|
||||
"980x",
|
||||
"780x"
|
||||
"Auto",
|
||||
"2400x",
|
||||
"1600x",
|
||||
"1280x",
|
||||
"980x",
|
||||
"780x"
|
||||
)
|
||||
entryValues = arrayOf(
|
||||
"auto",
|
||||
"ovrs_2400",
|
||||
"ovrs_1600",
|
||||
"high",
|
||||
"med",
|
||||
"low"
|
||||
"auto",
|
||||
"ovrs_2400",
|
||||
"ovrs_1600",
|
||||
"high",
|
||||
"med",
|
||||
"low"
|
||||
)
|
||||
|
||||
onChange { preferences.imageQuality().reconfigure() }
|
||||
@@ -202,21 +204,21 @@ class SettingsEhController : SettingsController() {
|
||||
onClick {
|
||||
activity?.let { activity ->
|
||||
MaterialDialog(activity)
|
||||
.title(R.string.eh_force_sync_reset_title)
|
||||
.message(R.string.eh_force_sync_reset_message)
|
||||
.positiveButton(android.R.string.yes) {
|
||||
LocalFavoritesStorage().apply {
|
||||
getRealm().use {
|
||||
it.trans {
|
||||
clearSnapshots(it)
|
||||
}
|
||||
.title(R.string.eh_force_sync_reset_title)
|
||||
.message(R.string.eh_force_sync_reset_message)
|
||||
.positiveButton(android.R.string.yes) {
|
||||
LocalFavoritesStorage().apply {
|
||||
getRealm().use {
|
||||
it.trans {
|
||||
clearSnapshots(it)
|
||||
}
|
||||
}
|
||||
activity.toast("Sync state reset", Toast.LENGTH_LONG)
|
||||
}
|
||||
.negativeButton(android.R.string.no)
|
||||
.cancelable(false)
|
||||
.show()
|
||||
activity.toast("Sync state reset", Toast.LENGTH_LONG)
|
||||
}
|
||||
.negativeButton(android.R.string.no)
|
||||
.cancelable(false)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,18 +313,18 @@ class SettingsEhController : SettingsController() {
|
||||
}
|
||||
|
||||
"""
|
||||
$statsText
|
||||
$statsText
|
||||
|
||||
Galleries that were checked in the last:
|
||||
- hour: ${metaInRelativeDuration(1.hours)}
|
||||
- 6 hours: ${metaInRelativeDuration(6.hours)}
|
||||
- 12 hours: ${metaInRelativeDuration(12.hours)}
|
||||
- day: ${metaInRelativeDuration(1.days)}
|
||||
- 2 days: ${metaInRelativeDuration(2.days)}
|
||||
- week: ${metaInRelativeDuration(7.days)}
|
||||
- month: ${metaInRelativeDuration(30.days)}
|
||||
- year: ${metaInRelativeDuration(365.days)}
|
||||
""".trimIndent()
|
||||
Galleries that were checked in the last:
|
||||
- hour: ${metaInRelativeDuration(1.hours)}
|
||||
- 6 hours: ${metaInRelativeDuration(6.hours)}
|
||||
- 12 hours: ${metaInRelativeDuration(12.hours)}
|
||||
- day: ${metaInRelativeDuration(1.days)}
|
||||
- 2 days: ${metaInRelativeDuration(2.days)}
|
||||
- week: ${metaInRelativeDuration(7.days)}
|
||||
- month: ${metaInRelativeDuration(30.days)}
|
||||
- year: ${metaInRelativeDuration(365.days)}
|
||||
""".trimIndent()
|
||||
} finally {
|
||||
progress.dismiss()
|
||||
}
|
||||
|
||||
@@ -75,10 +75,12 @@ class SettingsLibraryController : SettingsController() {
|
||||
intListPreference {
|
||||
key = Keys.eh_library_rounded_corners
|
||||
title = "Rounded Corner Radius"
|
||||
entriesRes = arrayOf(R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1,
|
||||
entriesRes = arrayOf(
|
||||
R.string.eh_rounded_corner_0, R.string.eh_rounded_corner_1,
|
||||
R.string.eh_rounded_corner_2, R.string.eh_rounded_corner_3, R.string.eh_rounded_corner_4,
|
||||
R.string.eh_rounded_corner_5, R.string.eh_rounded_corner_6, R.string.eh_rounded_corner_7,
|
||||
R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10)
|
||||
R.string.eh_rounded_corner_8, R.string.eh_rounded_corner_9, R.string.eh_rounded_corner_10
|
||||
)
|
||||
entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
|
||||
defaultValue = "4"
|
||||
summaryRes = R.string.eh_rounded_corners_desc
|
||||
@@ -211,7 +213,8 @@ class SettingsLibraryController : SettingsController() {
|
||||
}
|
||||
}
|
||||
if (preferences.skipPreMigration().get() || preferences.migrationSources()
|
||||
.getOrDefault().isNotEmpty()) {
|
||||
.getOrDefault().isNotEmpty()
|
||||
) {
|
||||
switchPreference {
|
||||
key = Keys.skipPreMigration
|
||||
titleRes = R.string.pref_skip_pre_migration
|
||||
|
||||
Reference in New Issue
Block a user