Linting Fixes AZ
This commit is contained in:
parent
03e5c5ca10
commit
7e99a9f789
@ -83,10 +83,12 @@ class ChapterCache(private val context: Context) {
|
|||||||
// --> EH
|
// --> EH
|
||||||
// Cache size is in MB
|
// Cache size is in MB
|
||||||
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
private fun setupDiskCache(cacheSize: Long): DiskLruCache {
|
||||||
return DiskLruCache.open(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
return DiskLruCache.open(
|
||||||
PARAMETER_APP_VERSION,
|
File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
|
||||||
PARAMETER_VALUE_COUNT,
|
PARAMETER_APP_VERSION,
|
||||||
cacheSize * 1024 * 1024)
|
PARAMETER_VALUE_COUNT,
|
||||||
|
cacheSize * 1024 * 1024
|
||||||
|
)
|
||||||
}
|
}
|
||||||
// <-- EH
|
// <-- EH
|
||||||
|
|
||||||
|
@ -19,18 +19,22 @@ interface ChapterQueries : DbProvider {
|
|||||||
|
|
||||||
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
fun getChaptersByMangaId(mangaId: Long?) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
.where("${ChapterTable.COL_MANGA_ID} = ?")
|
||||||
.whereArgs(mangaId)
|
.whereArgs(mangaId)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
fun getChaptersByMergedMangaId(mangaId: Long) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(RawQuery.builder()
|
.withQuery(
|
||||||
|
RawQuery.builder()
|
||||||
.query(getMergedChaptersQuery(mangaId))
|
.query(getMergedChaptersQuery(mangaId))
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.prepare()
|
||||||
|
|
||||||
fun getRecentChapters(date: Date) = db.get()
|
fun getRecentChapters(date: Date) = db.get()
|
||||||
@ -80,11 +84,13 @@ interface ChapterQueries : DbProvider {
|
|||||||
|
|
||||||
fun getChapters(url: String) = db.get()
|
fun getChapters(url: String) = db.get()
|
||||||
.listOfObjects(Chapter::class.java)
|
.listOfObjects(Chapter::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
|
Query.builder()
|
||||||
.table(ChapterTable.TABLE)
|
.table(ChapterTable.TABLE)
|
||||||
.where("${ChapterTable.COL_URL} = ?")
|
.where("${ChapterTable.COL_URL} = ?")
|
||||||
.whereArgs(url)
|
.whereArgs(url)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
.prepare()
|
.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
|
* Query to get the manga merged into a merged manga
|
||||||
*/
|
*/
|
||||||
fun getMergedMangaQuery(id: Long) = """
|
fun getMergedMangaQuery(id: Long) =
|
||||||
|
"""
|
||||||
SELECT ${Manga.TABLE}.*
|
SELECT ${Manga.TABLE}.*
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
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
|
* Query to get the chapters of all manga in a merged manga
|
||||||
*/
|
*/
|
||||||
fun getMergedChaptersQuery(id: Long) = """
|
fun getMergedChaptersQuery(id: Long) =
|
||||||
|
"""
|
||||||
SELECT ${Chapter.TABLE}.*
|
SELECT ${Chapter.TABLE}.*
|
||||||
FROM (
|
FROM (
|
||||||
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
SELECT ${Merged.COL_MANGA_ID} FROM ${Merged.TABLE} WHERE $(Merged.COL_MERGE_ID} = $id
|
||||||
|
@ -21,10 +21,10 @@ class MangaUrlPutResolver : PutResolver<Manga>() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
|
||||||
.table(MangaTable.TABLE)
|
.table(MangaTable.TABLE)
|
||||||
.where("${MangaTable.COL_ID} = ?")
|
.where("${MangaTable.COL_ID} = ?")
|
||||||
.whereArgs(manga.id)
|
.whereArgs(manga.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
fun mapToContentValues(manga: Manga) = ContentValues(1).apply {
|
||||||
put(MangaTable.COL_URL, manga.url)
|
put(MangaTable.COL_URL, manga.url)
|
||||||
|
@ -9,7 +9,8 @@ object MergedTable {
|
|||||||
const val COL_MANGA_ID = "mangaID"
|
const val COL_MANGA_ID = "mangaID"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_MERGE_ID INTEGER NOT NULL,
|
$COL_MERGE_ID INTEGER NOT NULL,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL
|
$COL_MANGA_ID INTEGER NOT NULL
|
||||||
)"""
|
)"""
|
||||||
|
@ -147,7 +147,7 @@ class ExtensionManager(
|
|||||||
|
|
||||||
fun Extension.isBlacklisted(
|
fun Extension.isBlacklisted(
|
||||||
blacklistEnabled: Boolean =
|
blacklistEnabled: Boolean =
|
||||||
preferences.eh_enableSourceBlacklist().get()
|
preferences.eh_enableSourceBlacklist().get()
|
||||||
): Boolean {
|
): Boolean {
|
||||||
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
|
return pkgName in BlacklistedSources.BLACKLISTED_EXTENSIONS && blacklistEnabled
|
||||||
}
|
}
|
||||||
|
@ -37,7 +37,7 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
|||||||
private fun newMetaInstance() = metaClass.constructors.find {
|
private fun newMetaInstance() = metaClass.constructors.find {
|
||||||
it.parameters.isEmpty()
|
it.parameters.isEmpty()
|
||||||
}?.call()
|
}?.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
|
* Parses metadata from the input and then copies it into the manga
|
||||||
|
@ -19,10 +19,12 @@ interface UrlImportableSource : Source {
|
|||||||
return try {
|
return try {
|
||||||
val uri = URI(url)
|
val uri = URI(url)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null) {
|
||||||
out += "?" + uri.query
|
out += "?" + uri.query
|
||||||
if (uri.fragment != null)
|
}
|
||||||
|
if (uri.fragment != null) {
|
||||||
out += "#" + uri.fragment
|
out += "#" + uri.fragment
|
||||||
|
}
|
||||||
out
|
out
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
url
|
url
|
||||||
|
@ -73,16 +73,18 @@ class EHentai(
|
|||||||
override val metaClass = EHentaiSearchMetadata::class
|
override val metaClass = EHentaiSearchMetadata::class
|
||||||
|
|
||||||
val schema: String
|
val schema: String
|
||||||
get() = if (prefs.secureEXH().getOrDefault())
|
get() = if (prefs.secureEXH().getOrDefault()) {
|
||||||
"https"
|
"https"
|
||||||
else
|
} else {
|
||||||
"http"
|
"http"
|
||||||
|
}
|
||||||
|
|
||||||
val domain: String
|
val domain: String
|
||||||
get() = if (exh)
|
get() = if (exh) {
|
||||||
"exhentai.org"
|
"exhentai.org"
|
||||||
else
|
} else {
|
||||||
"e-hentai.org"
|
"e-hentai.org"
|
||||||
|
}
|
||||||
|
|
||||||
override val baseUrl: String
|
override val baseUrl: String
|
||||||
get() = "$schema://$domain"
|
get() = "$schema://$domain"
|
||||||
@ -111,25 +113,27 @@ class EHentai(
|
|||||||
val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
|
val favElement = column2.children().find { it.attr("style").startsWith("border-color") }
|
||||||
|
|
||||||
ParsedManga(
|
ParsedManga(
|
||||||
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
|
fav = FAVORITES_BORDER_HEX_COLORS.indexOf(
|
||||||
favElement?.attr("style")?.substring(14, 17)
|
favElement?.attr("style")?.substring(14, 17)
|
||||||
),
|
),
|
||||||
manga = Manga.create(id).apply {
|
manga = Manga.create(id).apply {
|
||||||
// Get title
|
// Get title
|
||||||
title = thumbnailElement.attr("title")
|
title = thumbnailElement.attr("title")
|
||||||
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
|
url = EHentaiSearchMetadata.normalizeUrl(linkElement.attr("href"))
|
||||||
// Get image
|
// Get image
|
||||||
thumbnail_url = thumbnailElement.attr("src")
|
thumbnail_url = thumbnailElement.attr("src")
|
||||||
|
|
||||||
// TODO Parse genre + uploader + tags
|
// TODO Parse genre + uploader + tags
|
||||||
})
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parsedLocation = doc.location().toHttpUrlOrNull()
|
val parsedLocation = doc.location().toHttpUrlOrNull()
|
||||||
|
|
||||||
// Add to page if required
|
// Add to page if required
|
||||||
val hasNextPage = if (parsedLocation == null ||
|
val hasNextPage = if (parsedLocation == null ||
|
||||||
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)) {
|
!parsedLocation.queryParameterNames.contains(REVERSE_PARAM)
|
||||||
|
) {
|
||||||
select("a[onclick=return false]").last()?.let {
|
select("a[onclick=return false]").last()?.let {
|
||||||
it.text() == ">"
|
it.text() == ">"
|
||||||
} ?: false
|
} ?: false
|
||||||
@ -160,7 +164,7 @@ class EHentai(
|
|||||||
while (true) {
|
while (true) {
|
||||||
val gid = EHentaiSearchMetadata.galleryId(url).toInt()
|
val gid = EHentaiSearchMetadata.galleryId(url).toInt()
|
||||||
val cachedParent = updateHelper.parentLookupTable.get(
|
val cachedParent = updateHelper.parentLookupTable.get(
|
||||||
gid
|
gid
|
||||||
)
|
)
|
||||||
if (cachedParent == null) {
|
if (cachedParent == null) {
|
||||||
throttleFunc()
|
throttleFunc()
|
||||||
@ -175,19 +179,19 @@ class EHentai(
|
|||||||
|
|
||||||
if (parentLink != null) {
|
if (parentLink != null) {
|
||||||
updateHelper.parentLookupTable.put(
|
updateHelper.parentLookupTable.put(
|
||||||
gid,
|
gid,
|
||||||
GalleryEntry(
|
GalleryEntry(
|
||||||
EHentaiSearchMetadata.galleryId(parentLink),
|
EHentaiSearchMetadata.galleryId(parentLink),
|
||||||
EHentaiSearchMetadata.galleryToken(parentLink)
|
EHentaiSearchMetadata.galleryToken(parentLink)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
|
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
|
||||||
} else break
|
} else break
|
||||||
} else {
|
} else {
|
||||||
XLog.d("Parent cache hit: %s!", gid)
|
XLog.d("Parent cache hit: %s!", gid)
|
||||||
url = EHentaiSearchMetadata.idAndTokenToUrl(
|
url = EHentaiSearchMetadata.idAndTokenToUrl(
|
||||||
cachedParent.gId,
|
cachedParent.gId,
|
||||||
cachedParent.gToken
|
cachedParent.gToken
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -201,9 +205,11 @@ class EHentai(
|
|||||||
url = EHentaiSearchMetadata.normalizeUrl(d.location())
|
url = EHentaiSearchMetadata.normalizeUrl(d.location())
|
||||||
name = "v1: " + d.selectFirst("#gn").text()
|
name = "v1: " + d.selectFirst("#gn").text()
|
||||||
chapter_number = 1f
|
chapter_number = 1f
|
||||||
date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el ->
|
date_upload = EX_DATE_FORMAT.parse(
|
||||||
el.text().toLowerCase() == "posted:"
|
d.select("#gdd .gdt1").find { el ->
|
||||||
}!!.nextElementSibling().text()).time
|
el.text().toLowerCase() == "posted:"
|
||||||
|
}!!.nextElementSibling().text()
|
||||||
|
).time
|
||||||
}
|
}
|
||||||
// Build and append the rest of the galleries
|
// Build and append the rest of the galleries
|
||||||
if (DebugToggles.INCLUDE_ONLY_ROOT_WHEN_LOADING_EXH_VERSIONS.enabled) listOf(self)
|
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 }
|
}.sortedBy(Pair<Int, String>::first).map { it.second }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun chapterPageCall(np: String) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
|
private fun chapterPageCall(np: String): Observable<Response> {
|
||||||
private fun chapterPageRequest(np: String) = exGet(np, null, headers)
|
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 {
|
private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let {
|
||||||
return if (it.text() == ">") it.attr("href") else null
|
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)
|
latestUpdatesRequest(page)
|
||||||
else
|
} else {
|
||||||
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
|
exGet("$baseUrl/toplist.php?tl=15&p=${page - 1}", null) // Custom page logic for toplists
|
||||||
|
}
|
||||||
|
|
||||||
// Support direct URL importing
|
// Support direct URL importing
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
urlImportFetchSearchManga(query) {
|
urlImportFetchSearchManga(query) {
|
||||||
searchMangaRequestObservable(page, query, filters).flatMap {
|
searchMangaRequestObservable(page, query, filters).flatMap {
|
||||||
client.newCall(it).asObservableSuccess()
|
client.newCall(it).asObservableSuccess()
|
||||||
}.map { response ->
|
}.map { response ->
|
||||||
searchMangaParse(response)
|
searchMangaParse(response)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
|
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
|
||||||
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
|
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
|
||||||
@ -287,20 +298,20 @@ class EHentai(
|
|||||||
// Reverse search results on filter
|
// Reverse search results on filter
|
||||||
if (filters.any { it is ReverseFilter && it.state }) {
|
if (filters.any { it is ReverseFilter && it.state }) {
|
||||||
return client.newCall(request)
|
return client.newCall(request)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
val doc = it.asJsoup()
|
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 {
|
} else {
|
||||||
return Observable.just(request)
|
return Observable.just(request)
|
||||||
}
|
}
|
||||||
@ -314,22 +325,28 @@ class EHentai(
|
|||||||
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
override fun searchMangaParse(response: Response) = genericMangaParse(response)
|
||||||
override fun latestUpdatesParse(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 {
|
fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true): Request {
|
||||||
addParam(url, "page", Integer.toString(page - 1))
|
return GET(
|
||||||
} ?: url, additionalHeaders?.let {
|
page?.let {
|
||||||
val headers = headers.newBuilder()
|
addParam(url, "page", Integer.toString(page - 1))
|
||||||
it.toMultimap().forEach { (t, u) ->
|
} ?: url,
|
||||||
u.forEach {
|
additionalHeaders?.let {
|
||||||
headers.add(t, it)
|
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
|
* 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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableWithAsyncStacktrace()
|
.asObservableWithAsyncStacktrace()
|
||||||
.flatMap { (stacktrace, response) ->
|
.flatMap { (stacktrace, response) ->
|
||||||
if (response.isSuccessful) {
|
if (response.isSuccessful) {
|
||||||
// Pull to most recent
|
// Pull to most recent
|
||||||
val doc = response.asJsoup()
|
val doc = response.asJsoup()
|
||||||
val newerGallery = doc.select("#gnd a").lastOrNull()
|
val newerGallery = doc.select("#gnd a").lastOrNull()
|
||||||
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
|
val pre = if (newerGallery != null && DebugToggles.PULL_TO_ROOT_WHEN_LOADING_EXH_MANGA_DETAILS.enabled) {
|
||||||
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
|
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
|
||||||
client.newCall(mangaDetailsRequest(manga))
|
client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess().map { it.asJsoup() }
|
.asObservableSuccess().map { it.asJsoup() }
|
||||||
} else Observable.just(doc)
|
} else Observable.just(doc)
|
||||||
|
|
||||||
pre.flatMap {
|
pre.flatMap {
|
||||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it).andThen(
|
||||||
initialized = true
|
Observable.just(
|
||||||
}))
|
manga.apply {
|
||||||
}
|
initialized = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
response.close()
|
||||||
|
|
||||||
|
if (response.code == 404) {
|
||||||
|
throw GalleryNotFoundException(stacktrace)
|
||||||
} else {
|
} else {
|
||||||
response.close()
|
throw Exception("HTTP error ${response.code}", stacktrace)
|
||||||
|
|
||||||
if (response.code == 404) {
|
|
||||||
throw GalleryNotFoundException(stacktrace)
|
|
||||||
} else {
|
|
||||||
throw Exception("HTTP error ${response.code}", stacktrace)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -389,11 +410,11 @@ class EHentai(
|
|||||||
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
it.substring(it.indexOf('(') + 1 until it.lastIndexOf(')'))
|
||||||
}
|
}
|
||||||
genre = select(".cs")
|
genre = select(".cs")
|
||||||
.attr("onclick")
|
.attr("onclick")
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.trim()
|
?.trim()
|
||||||
?.substringAfterLast('/')
|
?.substringAfterLast('/')
|
||||||
?.removeSuffix("'")
|
?.removeSuffix("'")
|
||||||
|
|
||||||
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
uploader = select("#gdn").text().nullIfBlank()?.trim()
|
||||||
|
|
||||||
@ -404,8 +425,10 @@ class EHentai(
|
|||||||
val right = rightElement.text().nullIfBlank()?.trim()
|
val right = rightElement.text().nullIfBlank()?.trim()
|
||||||
if (left != null && right != null) {
|
if (left != null && right != null) {
|
||||||
ignore {
|
ignore {
|
||||||
when (left.removeSuffix(":")
|
when (
|
||||||
.toLowerCase()) {
|
left.removeSuffix(":")
|
||||||
|
.toLowerCase()
|
||||||
|
) {
|
||||||
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
|
||||||
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
|
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
|
||||||
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
|
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
|
||||||
@ -428,7 +451,8 @@ class EHentai(
|
|||||||
|
|
||||||
lastUpdateCheck = System.currentTimeMillis()
|
lastUpdateCheck = System.currentTimeMillis()
|
||||||
if (datePosted != null &&
|
if (datePosted != null &&
|
||||||
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME) {
|
lastUpdateCheck - datePosted!! > EHentaiUpdateWorkerConstants.GALLERY_AGE_TIME
|
||||||
|
) {
|
||||||
aged = true
|
aged = true
|
||||||
XLog.d("aged %s - too old", title)
|
XLog.d("aged %s - too old", title)
|
||||||
}
|
}
|
||||||
@ -436,32 +460,35 @@ class EHentai(
|
|||||||
// Parse ratings
|
// Parse ratings
|
||||||
ignore {
|
ignore {
|
||||||
averageRating = select("#rating_label")
|
averageRating = select("#rating_label")
|
||||||
.text()
|
.text()
|
||||||
.removePrefix("Average:")
|
.removePrefix("Average:")
|
||||||
.trim()
|
.trim()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.toDouble()
|
?.toDouble()
|
||||||
ratingCount = select("#rating_count")
|
ratingCount = select("#rating_count")
|
||||||
.text()
|
.text()
|
||||||
.trim()
|
.trim()
|
||||||
.nullIfBlank()
|
.nullIfBlank()
|
||||||
?.toInt()
|
?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse tags
|
// Parse tags
|
||||||
tags.clear()
|
tags.clear()
|
||||||
select("#taglist tr").forEach {
|
select("#taglist tr").forEach {
|
||||||
val namespace = it.select(".tc").text().removeSuffix(":")
|
val namespace = it.select(".tc").text().removeSuffix(":")
|
||||||
tags.addAll(it.select("div").map { element ->
|
tags.addAll(
|
||||||
RaisedTag(
|
it.select("div").map { element ->
|
||||||
|
RaisedTag(
|
||||||
namespace,
|
namespace,
|
||||||
element.text().trim(),
|
element.text().trim(),
|
||||||
if (element.hasClass("gtl"))
|
if (element.hasClass("gtl")) {
|
||||||
TAG_TYPE_LIGHT
|
TAG_TYPE_LIGHT
|
||||||
else
|
} else {
|
||||||
TAG_TYPE_NORMAL
|
TAG_TYPE_NORMAL
|
||||||
)
|
}
|
||||||
})
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add genre as virtual tag
|
// Add genre as virtual tag
|
||||||
@ -478,8 +505,8 @@ class EHentai(
|
|||||||
|
|
||||||
override fun fetchImageUrl(page: Page): Observable<String> {
|
override fun fetchImageUrl(page: Page): Observable<String> {
|
||||||
return client.newCall(imageUrlRequest(page))
|
return client.newCall(imageUrlRequest(page))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { realImageUrlParse(it, page) }
|
.map { realImageUrlParse(it, page) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun realImageUrlParse(response: Response, page: Page): String {
|
fun realImageUrlParse(response: Response, page: Page): String {
|
||||||
@ -505,9 +532,13 @@ class EHentai(
|
|||||||
var favNames: List<String>? = null
|
var favNames: List<String>? = null
|
||||||
|
|
||||||
do {
|
do {
|
||||||
val response2 = client.newCall(exGet(favoriteUrl,
|
val response2 = client.newCall(
|
||||||
|
exGet(
|
||||||
|
favoriteUrl,
|
||||||
page = page,
|
page = page,
|
||||||
cache = false)).execute()
|
cache = false
|
||||||
|
)
|
||||||
|
).execute()
|
||||||
val doc = response2.asJsoup()
|
val doc = response2.asJsoup()
|
||||||
|
|
||||||
// Parse favorites
|
// Parse favorites
|
||||||
@ -515,22 +546,24 @@ class EHentai(
|
|||||||
result += parsed.first
|
result += parsed.first
|
||||||
|
|
||||||
// Parse fav names
|
// Parse fav names
|
||||||
if (favNames == null)
|
if (favNames == null) {
|
||||||
favNames = doc.select(".fp:not(.fps)").mapNotNull {
|
favNames = doc.select(".fp:not(.fps)").mapNotNull {
|
||||||
it.child(2).text()
|
it.child(2).text()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Next page
|
// Next page
|
||||||
|
|
||||||
page++
|
page++
|
||||||
} while (parsed.second)
|
} while (parsed.second)
|
||||||
|
|
||||||
return Pair(result as List<ParsedManga>, favNames!!)
|
return Pair(result as List<ParsedManga>, favNames!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun spPref() = if (exh)
|
fun spPref() = if (exh) {
|
||||||
prefs.eh_exhSettingsProfile()
|
prefs.eh_exhSettingsProfile()
|
||||||
else
|
} else {
|
||||||
prefs.eh_ehSettingsProfile()
|
prefs.eh_ehSettingsProfile()
|
||||||
|
}
|
||||||
|
|
||||||
fun rawCookies(sp: Int): Map<String, String> {
|
fun rawCookies(sp: Int): Map<String, String> {
|
||||||
val cookies: MutableMap<String, String> = mutableMapOf()
|
val cookies: MutableMap<String, String> = mutableMapOf()
|
||||||
@ -541,16 +574,19 @@ class EHentai(
|
|||||||
cookies["sp"] = sp.toString()
|
cookies["sp"] = sp.toString()
|
||||||
|
|
||||||
val sessionKey = prefs.eh_settingsKey().getOrDefault()
|
val sessionKey = prefs.eh_settingsKey().getOrDefault()
|
||||||
if (sessionKey != null)
|
if (sessionKey != null) {
|
||||||
cookies["sk"] = sessionKey
|
cookies["sk"] = sessionKey
|
||||||
|
}
|
||||||
|
|
||||||
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
|
val sessionCookie = prefs.eh_sessionCookie().getOrDefault()
|
||||||
if (sessionCookie != null)
|
if (sessionCookie != null) {
|
||||||
cookies["s"] = sessionCookie
|
cookies["s"] = sessionCookie
|
||||||
|
}
|
||||||
|
|
||||||
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
|
val hathPerksCookie = prefs.eh_hathPerksCookies().getOrDefault()
|
||||||
if (hathPerksCookie != null)
|
if (hathPerksCookie != null) {
|
||||||
cookies["hath_perks"] = hathPerksCookie
|
cookies["hath_perks"] = hathPerksCookie
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Session-less extended display mode (for users without ExHentai)
|
// Session-less extended display mode (for users without ExHentai)
|
||||||
@ -568,51 +604,57 @@ class EHentai(
|
|||||||
override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!!
|
override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!!
|
||||||
|
|
||||||
fun addParam(url: String, param: String, value: String) = Uri.parse(url)
|
fun addParam(url: String, param: String, value: String) = Uri.parse(url)
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
.appendQueryParameter(param, value)
|
.appendQueryParameter(param, value)
|
||||||
.toString()
|
.toString()
|
||||||
|
|
||||||
override val client = network.client.newBuilder()
|
override val client = network.client.newBuilder()
|
||||||
.cookieJar(CookieJar.NO_COOKIES)
|
.cookieJar(CookieJar.NO_COOKIES)
|
||||||
.addInterceptor { chain ->
|
.addInterceptor { chain ->
|
||||||
val newReq = chain
|
val newReq = chain
|
||||||
.request()
|
.request()
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.removeHeader("Cookie")
|
.removeHeader("Cookie")
|
||||||
.addHeader("Cookie", cookiesHeader())
|
.addHeader("Cookie", cookiesHeader())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
chain.proceed(newReq)
|
chain.proceed(newReq)
|
||||||
}.build()!!
|
}.build()!!
|
||||||
|
|
||||||
// Filters
|
// Filters
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
Watched(),
|
Watched(),
|
||||||
GenreGroup(),
|
GenreGroup(),
|
||||||
AdvancedGroup(),
|
AdvancedGroup(),
|
||||||
ReverseFilter()
|
ReverseFilter()
|
||||||
)
|
)
|
||||||
|
|
||||||
class Watched : Filter.CheckBox("Watched List"), UriFilter {
|
class Watched : Filter.CheckBox("Watched List"), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendPath("watched")
|
builder.appendPath("watched")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
|
class GenreOption(name: String, val genreId: Int) : Filter.CheckBox(name, false)
|
||||||
class GenreGroup : Filter.Group<GenreOption>("Genres", listOf(
|
class GenreGroup :
|
||||||
GenreOption("Dōjinshi", 2),
|
Filter.Group<GenreOption>(
|
||||||
GenreOption("Manga", 4),
|
"Genres",
|
||||||
GenreOption("Artist CG", 8),
|
listOf(
|
||||||
GenreOption("Game CG", 16),
|
GenreOption("Dōjinshi", 2),
|
||||||
GenreOption("Western", 512),
|
GenreOption("Manga", 4),
|
||||||
GenreOption("Non-H", 256),
|
GenreOption("Artist CG", 8),
|
||||||
GenreOption("Image Set", 32),
|
GenreOption("Game CG", 16),
|
||||||
GenreOption("Cosplay", 64),
|
GenreOption("Western", 512),
|
||||||
GenreOption("Asian Porn", 128),
|
GenreOption("Non-H", 256),
|
||||||
GenreOption("Misc", 1)
|
GenreOption("Image Set", 32),
|
||||||
)), UriFilter {
|
GenreOption("Cosplay", 64),
|
||||||
|
GenreOption("Asian Porn", 128),
|
||||||
|
GenreOption("Misc", 1)
|
||||||
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
val bits = state.fold(0) { acc, genre ->
|
val bits = state.fold(0) { acc, genre ->
|
||||||
if (!genre.state) acc + genre.genreId else acc
|
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 {
|
class AdvancedOption(name: String, val param: String, defValue: Boolean = false) : Filter.CheckBox(name, defValue), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter(param, "on")
|
builder.appendQueryParameter(param, "on")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -643,13 +686,18 @@ class EHentai(
|
|||||||
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
class MinPagesOption : PageOption("Minimum Pages", "f_spf")
|
||||||
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
class MaxPagesOption : PageOption("Maximum Pages", "f_spt")
|
||||||
|
|
||||||
class RatingOption : Filter.Select<String>("Minimum Rating", arrayOf(
|
class RatingOption :
|
||||||
"Any",
|
Filter.Select<String>(
|
||||||
"2 stars",
|
"Minimum Rating",
|
||||||
"3 stars",
|
arrayOf(
|
||||||
"4 stars",
|
"Any",
|
||||||
"5 stars"
|
"2 stars",
|
||||||
)), UriFilter {
|
"3 stars",
|
||||||
|
"4 stars",
|
||||||
|
"5 stars"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state > 0) {
|
if (state > 0) {
|
||||||
builder.appendQueryParameter("f_srdd", Integer.toString(state + 1))
|
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 Name", "f_sname", true),
|
||||||
AdvancedOption("Search Gallery Tags", "f_stags", true),
|
AdvancedOption("Search Gallery Tags", "f_stags", true),
|
||||||
AdvancedOption("Search Gallery Description", "f_sdesc"),
|
AdvancedOption("Search Gallery Description", "f_sdesc"),
|
||||||
@ -670,24 +720,26 @@ class EHentai(
|
|||||||
RatingOption(),
|
RatingOption(),
|
||||||
MinPagesOption(),
|
MinPagesOption(),
|
||||||
MaxPagesOption()
|
MaxPagesOption()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class ReverseFilter : Filter.CheckBox("Reverse search results")
|
class ReverseFilter : Filter.CheckBox("Reverse search results")
|
||||||
|
|
||||||
override val name = if (exh)
|
override val name = if (exh) {
|
||||||
"ExHentai"
|
"ExHentai"
|
||||||
else
|
} else {
|
||||||
"E-Hentai"
|
"E-Hentai"
|
||||||
|
}
|
||||||
|
|
||||||
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
|
class GalleryNotFoundException(cause: Throwable) : RuntimeException("Gallery not found!", cause)
|
||||||
|
|
||||||
// === URL IMPORT STUFF
|
// === URL IMPORT STUFF
|
||||||
|
|
||||||
override val matchingHosts: List<String> = if (exh) listOf(
|
override val matchingHosts: List<String> = if (exh) listOf(
|
||||||
"exhentai.org"
|
"exhentai.org"
|
||||||
) else listOf(
|
) else listOf(
|
||||||
"g.e-hentai.org",
|
"g.e-hentai.org",
|
||||||
"e-hentai.org"
|
"e-hentai.org"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
@ -717,17 +769,23 @@ class EHentai(
|
|||||||
val json = JsonObject()
|
val json = JsonObject()
|
||||||
json["method"] = "gtoken"
|
json["method"] = "gtoken"
|
||||||
json["pagelist"] = JsonArray().apply {
|
json["pagelist"] = JsonArray().apply {
|
||||||
add(JsonArray().apply {
|
add(
|
||||||
add(gallery.toInt())
|
JsonArray().apply {
|
||||||
add(pageToken)
|
add(gallery.toInt())
|
||||||
add(pageNum.toInt())
|
add(pageToken)
|
||||||
})
|
add(pageNum.toInt())
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val outJson = JsonParser.parseString(client.newCall(Request.Builder()
|
val outJson = JsonParser.parseString(
|
||||||
.url(EH_API_BASE)
|
client.newCall(
|
||||||
.post(RequestBody.create(JSON, json.toString()))
|
Request.Builder()
|
||||||
.build()).execute().body!!.string()).obj
|
.url(EH_API_BASE)
|
||||||
|
.post(RequestBody.create(JSON, json.toString()))
|
||||||
|
.build()
|
||||||
|
).execute().body!!.string()
|
||||||
|
).obj
|
||||||
|
|
||||||
val obj = outJson["tokenlist"].array.first()
|
val obj = outJson["tokenlist"].array.first()
|
||||||
return "${uri.scheme}://${uri.host}/g/${obj["gid"].int}/${obj["token"].string}/"
|
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 JSON = "application/json; charset=utf-8".toMediaTypeOrNull()!!
|
||||||
|
|
||||||
private val FAVORITES_BORDER_HEX_COLORS = listOf(
|
private val FAVORITES_BORDER_HEX_COLORS = listOf(
|
||||||
"000",
|
"000",
|
||||||
"f00",
|
"f00",
|
||||||
"fa0",
|
"fa0",
|
||||||
"dd0",
|
"dd0",
|
||||||
"080",
|
"080",
|
||||||
"9f4",
|
"9f4",
|
||||||
"4bf",
|
"4bf",
|
||||||
"00f",
|
"00f",
|
||||||
"508",
|
"508",
|
||||||
"e8e"
|
"e8e"
|
||||||
)
|
)
|
||||||
|
|
||||||
fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") {
|
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> {
|
private fun tagIndexVersion(): Single<Long> {
|
||||||
val sCachedTagIndexVersion = cachedTagIndexVersion
|
val sCachedTagIndexVersion = cachedTagIndexVersion
|
||||||
return if (sCachedTagIndexVersion == null ||
|
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 {
|
HitomiNozomi.getIndexVersion(client, "tagindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||||
cachedTagIndexVersion = it
|
cachedTagIndexVersion = it
|
||||||
tagIndexVersionCacheTime = System.currentTimeMillis()
|
tagIndexVersionCacheTime = System.currentTimeMillis()
|
||||||
@ -80,7 +81,8 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
private fun galleryIndexVersion(): Single<Long> {
|
private fun galleryIndexVersion(): Single<Long> {
|
||||||
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
val sCachedGalleryIndexVersion = cachedGalleryIndexVersion
|
||||||
return if (sCachedGalleryIndexVersion == null ||
|
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 {
|
HitomiNozomi.getIndexVersion(client, "galleriesindex").subscribeOn(Schedulers.io()).doOnNext {
|
||||||
cachedGalleryIndexVersion = it
|
cachedGalleryIndexVersion = it
|
||||||
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
galleryIndexVersionCacheTime = System.currentTimeMillis()
|
||||||
@ -162,9 +164,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet(
|
override fun popularMangaRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||||
"$LTN_BASE_URL/popular-all.nozomi",
|
"$LTN_BASE_URL/popular-all.nozomi",
|
||||||
100L * (page - 1),
|
100L * (page - 1),
|
||||||
99L + 100 * (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
|
// TODO Cache the results coming out of HitomiNozomi
|
||||||
val hn = Single.zip(tagIndexVersion(), galleryIndexVersion()) { tv, gv -> tv to gv }
|
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()) {
|
var base = if (positive.isEmpty()) {
|
||||||
hn.flatMap { n -> n.getGalleryIdsFromNozomi(null, "index", "all").map { n to it.toSet() } }
|
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.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet(
|
override fun latestUpdatesRequest(page: Int) = HitomiNozomi.rangedGet(
|
||||||
"$LTN_BASE_URL/index-all.nozomi",
|
"$LTN_BASE_URL/index-all.nozomi",
|
||||||
100L * (page - 1),
|
100L * (page - 1),
|
||||||
99L + 100 * (page - 1)
|
99L + 100 * (page - 1)
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -254,14 +256,14 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
|
|
||||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
return client.newCall(popularMangaRequest(page))
|
return client.newCall(popularMangaRequest(page))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap { responseToMangas(it) }
|
.flatMap { responseToMangas(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||||
return client.newCall(latestUpdatesRequest(page))
|
return client.newCall(latestUpdatesRequest(page))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap { responseToMangas(it) }
|
.flatMap { responseToMangas(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun responseToMangas(response: Response): Observable<MangasPage> {
|
fun responseToMangas(response: Response): Observable<MangasPage> {
|
||||||
@ -270,9 +272,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
val end = range.substringBefore('/').substringAfter('-').toLong()
|
val end = range.substringBefore('/').substringAfter('-').toLong()
|
||||||
val body = response.body!!
|
val body = response.body!!
|
||||||
return parseNozomiPage(body.bytes())
|
return parseNozomiPage(body.bytes())
|
||||||
.map {
|
.map {
|
||||||
MangasPage(it, end < total - 1)
|
MangasPage(it, end < total - 1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> {
|
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>> {
|
private fun nozomiIdsToMangas(ids: List<Int>): Single<List<SManga>> {
|
||||||
return Single.zip(ids.map {
|
return Single.zip(
|
||||||
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
ids.map {
|
||||||
|
client.newCall(GET("$LTN_BASE_URL/galleryblock/$it.html"))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
.subscribeOn(Schedulers.io()) // Perform all these requests in parallel
|
||||||
.map { parseGalleryBlock(it) }
|
.map { parseGalleryBlock(it) }
|
||||||
.toSingle()
|
.toSingle()
|
||||||
}) { it.map { m -> m as SManga } }
|
}
|
||||||
|
) { it.map { m -> m as SManga } }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseGalleryBlock(response: Response): 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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
initialized = true
|
Observable.just(
|
||||||
}))
|
manga.apply {
|
||||||
}
|
initialized = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return Observable.just(
|
return Observable.just(
|
||||||
listOf(
|
listOf(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
url = manga.url
|
url = manga.url
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
chapter_number = 0.0f
|
chapter_number = 0.0f
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -372,9 +380,9 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
|
|||||||
val hashPath1 = hash.takeLast(1)
|
val hashPath1 = hash.takeLast(1)
|
||||||
val hashPath2 = hash.takeLast(3).take(2)
|
val hashPath2 = hash.takeLast(3).take(2)
|
||||||
Page(
|
Page(
|
||||||
index,
|
index,
|
||||||
"",
|
"",
|
||||||
"https://${subdomainFromGalleryId(hlId)}a.hitomi.la/$path/$hashPath1/$hashPath2/$hash.$ext"
|
"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]
|
it[it.lastIndex - 1]
|
||||||
}
|
}
|
||||||
return request.newBuilder()
|
return request.newBuilder()
|
||||||
.header("Referer", "$BASE_URL/reader/$hlId.html")
|
.header("Referer", "$BASE_URL/reader/$hlId.html")
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"hitomi.la"
|
"hitomi.la"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader")
|
if (lcFirstPathSegment != "manga" && lcFirstPathSegment != "reader") {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return "https://hitomi.la/manga/${uri.pathSegments[1].substringBefore('.')}.html"
|
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 NUMBER_OF_FRONTENDS = 2
|
||||||
|
|
||||||
private val DATE_FORMAT by lazy {
|
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)
|
SimpleDateFormat("yyyy-MM-dd HH:mm:ssX", Locale.US)
|
||||||
else
|
} else {
|
||||||
SimpleDateFormat("yyyy-MM-dd HH:mm:ss'-05'", Locale.US)
|
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
|
val sortFilter = filters.filterIsInstance<SortFilter>().firstOrNull()?.state
|
||||||
?: defaultSortFilterSelection()
|
?: defaultSortFilterSelection()
|
||||||
|
|
||||||
if (sortFilter.index == 1) {
|
if (sortFilter.index == 1) {
|
||||||
if (query.isBlank()) error("You must specify a search query if you wish to sort by popularity!")
|
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) {
|
if (sortFilter.ascending) {
|
||||||
return client.newCall(nhGet(uri.toString()))
|
return client.newCall(nhGet(uri.toString()))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
val doc = it.asJsoup()
|
val doc = it.asJsoup()
|
||||||
|
|
||||||
val lastPage = doc.selectFirst(".last")
|
val lastPage = doc.selectFirst(".last")
|
||||||
?.attr("href")
|
?.attr("href")
|
||||||
?.substringAfterLast('=')
|
?.substringAfterLast('=')
|
||||||
?.toIntOrNull() ?: 1
|
?.toIntOrNull() ?: 1
|
||||||
|
|
||||||
val thisPage = lastPage - (page - 1)
|
val thisPage = lastPage - (page - 1)
|
||||||
|
|
||||||
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
uri.appendQueryParameter(REVERSE_PARAM, (thisPage > 1).toString())
|
||||||
uri.appendQueryParameter("page", thisPage.toString())
|
uri.appendQueryParameter("page", thisPage.toString())
|
||||||
|
|
||||||
nhGet(uri.toString(), page)
|
nhGet(uri.toString(), page)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uri.appendQueryParameter("page", page.toString())
|
uri.appendQueryParameter("page", page.toString())
|
||||||
@ -134,12 +134,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
*/
|
*/
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it).andThen(
|
||||||
initialized = true
|
Observable.just(
|
||||||
}))
|
manga.apply {
|
||||||
}
|
initialized = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url)
|
override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url)
|
||||||
@ -208,31 +212,38 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
}?.apply {
|
}?.apply {
|
||||||
tags.clear()
|
tags.clear()
|
||||||
}?.forEach {
|
}?.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))
|
tags.add(RaisedTag(it.first!!, it.second!!, TAG_TYPE_DEFAULT))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
|
fun getOrLoadMetadata(mangaId: Long?, nhId: Long) = getOrLoadMetadata(mangaId) {
|
||||||
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
|
client.newCall(nhGet(baseUrl + NHentaiSearchMetadata.nhIdToPath(nhId)))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.toSingle()
|
.toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga) = Observable.just(listOf(SChapter.create().apply {
|
override fun fetchChapterList(manga: SManga) = Observable.just(
|
||||||
url = manga.url
|
listOf(
|
||||||
name = "Chapter"
|
SChapter.create().apply {
|
||||||
chapter_number = 1f
|
url = manga.url
|
||||||
}))
|
name = "Chapter"
|
||||||
|
chapter_number = 1f
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
|
override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
|
||||||
if (metadata.mediaId == null) emptyList()
|
if (metadata.mediaId == null) {
|
||||||
else
|
emptyList()
|
||||||
|
} else {
|
||||||
metadata.pageImageTypes.mapIndexed { index, s ->
|
metadata.pageImageTypes.mapIndexed { index, s ->
|
||||||
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
|
val imageUrl = imageUrlFromType(metadata.mediaId!!, index + 1, s)
|
||||||
Page(index, imageUrl!!, imageUrl)
|
Page(index, imageUrl!!, imageUrl)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}.toObservable()
|
}.toObservable()
|
||||||
|
|
||||||
override fun fetchImageUrl(page: Page) = Observable.just(page.imageUrl!!)!!
|
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())
|
private class filterLang : Filter.Select<String>("Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray())
|
||||||
|
|
||||||
class SortFilter : Filter.Sort(
|
class SortFilter : Filter.Sort(
|
||||||
"Sort",
|
"Sort",
|
||||||
arrayOf("Date", "Popular"),
|
arrayOf("Date", "Popular"),
|
||||||
defaultSortFilterSelection()
|
defaultSortFilterSelection()
|
||||||
)
|
)
|
||||||
|
|
||||||
val appName by lazy {
|
val appName by lazy {
|
||||||
@ -269,14 +280,16 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
fun nhGet(url: String, tag: Any? = null) = GET(url)
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.header("User-Agent",
|
.header(
|
||||||
"Mozilla/5.0 (X11; Linux x86_64) " +
|
"User-Agent",
|
||||||
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
"Mozilla/5.0 (X11; Linux x86_64) " +
|
||||||
"Chrome/56.0.2924.87 " +
|
"AppleWebKit/537.36 (KHTML, like Gecko) " +
|
||||||
"Safari/537.36 " +
|
"Chrome/56.0.2924.87 " +
|
||||||
"$appName/${BuildConfig.VERSION_CODE}")
|
"Safari/537.36 " +
|
||||||
.tag(tag).build()
|
"$appName/${BuildConfig.VERSION_CODE}"
|
||||||
|
)
|
||||||
|
.tag(tag).build()
|
||||||
|
|
||||||
override val id = NHENTAI_SOURCE_ID
|
override val id = NHENTAI_SOURCE_ID
|
||||||
|
|
||||||
@ -291,12 +304,13 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
|
|||||||
// === URL IMPORT STUFF
|
// === URL IMPORT STUFF
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"nhentai.net"
|
"nhentai.net"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g")
|
if (uri.pathSegments.firstOrNull()?.toLowerCase() != "g") {
|
||||||
return null
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return "$baseUrl/g/${uri.pathSegments[1]}/"
|
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 fun defaultSortFilterSelection() = Filter.Sort.Selection(0, false)
|
||||||
|
|
||||||
private val SOURCE_LANG_LIST = listOf(
|
private val SOURCE_LANG_LIST = listOf(
|
||||||
Pair("All", ""),
|
Pair("All", ""),
|
||||||
Pair("English", " english"),
|
Pair("English", " english"),
|
||||||
Pair("Japanese", " japanese"),
|
Pair("Japanese", " japanese"),
|
||||||
Pair("Chinese", " chinese")
|
Pair("Chinese", " chinese")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,10 @@ import org.jsoup.nodes.TextNode
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
// TODO Transform into delegated source
|
// TODO Transform into delegated source
|
||||||
class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSource(),
|
class PervEden(override val id: Long, val pvLang: PervEdenLang) :
|
||||||
LewdSource<PervEdenSearchMetadata, Document>, UrlImportableSource {
|
ParsedHttpSource(),
|
||||||
|
LewdSource<PervEdenSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* The class of the metadata used by this source
|
* 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
|
// Support direct URL importing
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
urlImportFetchSearchManga(query) {
|
urlImportFetchSearchManga(query) {
|
||||||
super.fetchSearchManga(page, query, filters)
|
super.fetchSearchManga(page, query, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaSelector() = "#mangaList > tbody > tr"
|
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 searchMangaNextPageSelector() = ".next"
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
val urlLang = if (lang == "en")
|
val urlLang = if (lang == "en") {
|
||||||
"eng"
|
"eng"
|
||||||
else "it"
|
} else {
|
||||||
|
"it"
|
||||||
|
}
|
||||||
return GET("$baseUrl/$urlLang/")
|
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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
initialized = true
|
Observable.just(
|
||||||
}))
|
manga.apply {
|
||||||
}
|
initialized = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -165,8 +173,9 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
"Alternative name(s)" -> {
|
"Alternative name(s)" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
newAltTitles += text
|
newAltTitles += text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Artist" -> {
|
"Artist" -> {
|
||||||
@ -176,21 +185,24 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Genres" -> {
|
"Genres" -> {
|
||||||
if (it is Element && it.tagName() == "a")
|
if (it is Element && it.tagName() == "a") {
|
||||||
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
|
tags += RaisedTag(null, it.text().toLowerCase(), TAG_TYPE_DEFAULT)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
"Type" -> {
|
"Type" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
type = text
|
type = text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
"Status" -> {
|
"Status" -> {
|
||||||
if (it is TextNode) {
|
if (it is TextNode) {
|
||||||
val text = it.text().trim()
|
val text = it.text().trim()
|
||||||
if (!text.isBlank())
|
if (!text.isBlank()) {
|
||||||
status = text
|
status = text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -224,10 +236,11 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
|
|||||||
name = "Chapter " + linkElement.getElementsByTag("b").text()
|
name = "Chapter " + linkElement.getElementsByTag("b").text()
|
||||||
|
|
||||||
ChapterRecognition.parseChapterNumber(
|
ChapterRecognition.parseChapterNumber(
|
||||||
this,
|
this,
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
title = ""
|
title = ""
|
||||||
})
|
}
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
date_upload = DATE_FORMAT.parse(element.getElementsByClass("chapterDate").first().text().trim())!!.time
|
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 imageUrlParse(document: Document) = "http:" + document.getElementById("mainImg").attr("src")!!
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
AuthorFilter(),
|
AuthorFilter(),
|
||||||
ArtistFilter(),
|
ArtistFilter(),
|
||||||
TypeFilterGroup(),
|
TypeFilterGroup(),
|
||||||
ReleaseYearGroup(),
|
ReleaseYearGroup(),
|
||||||
StatusFilterGroup()
|
StatusFilterGroup()
|
||||||
)
|
)
|
||||||
|
|
||||||
class StatusFilterGroup : UriGroup<StatusFilter>("Status", listOf(
|
class StatusFilterGroup : UriGroup<StatusFilter>(
|
||||||
|
"Status",
|
||||||
|
listOf(
|
||||||
StatusFilter("Ongoing", 1),
|
StatusFilter("Ongoing", 1),
|
||||||
StatusFilter("Completed", 2),
|
StatusFilter("Completed", 2),
|
||||||
StatusFilter("Suspended", 0)
|
StatusFilter("Suspended", 0)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
class StatusFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter("status", id.toString())
|
builder.appendQueryParameter("status", id.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Explicit type arg for listOf() to workaround this: KT-16570
|
// 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(),
|
ReleaseYearRangeFilter(),
|
||||||
ReleaseYearYearFilter()
|
ReleaseYearYearFilter()
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class ReleaseYearRangeFilter : Filter.Select<String>("Range", arrayOf(
|
class ReleaseYearRangeFilter :
|
||||||
"on",
|
Filter.Select<String>(
|
||||||
"after",
|
"Range",
|
||||||
"before"
|
arrayOf(
|
||||||
)), UriFilter {
|
"on",
|
||||||
|
"after",
|
||||||
|
"before"
|
||||||
|
)
|
||||||
|
),
|
||||||
|
UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
builder.appendQueryParameter("releasedType", state.toString())
|
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("Japanese Manga", 0),
|
||||||
TypeFilter("Korean Manhwa", 1),
|
TypeFilter("Korean Manhwa", 1),
|
||||||
TypeFilter("Chinese Manhua", 2),
|
TypeFilter("Chinese Manhua", 2),
|
||||||
TypeFilter("Comic", 3),
|
TypeFilter("Comic", 3),
|
||||||
TypeFilter("Doujinshi", 4)
|
TypeFilter("Doujinshi", 4)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
|
|
||||||
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
class TypeFilter(n: String, val id: Int) : Filter.CheckBox(n, false), UriFilter {
|
||||||
override fun addToUri(builder: Uri.Builder) {
|
override fun addToUri(builder: Uri.Builder) {
|
||||||
if (state)
|
if (state) {
|
||||||
builder.appendQueryParameter("type", id.toString())
|
builder.appendQueryParameter("type", id.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,9 +41,10 @@ import rx.schedulers.Schedulers
|
|||||||
|
|
||||||
typealias SiteMap = NakedTrie<Unit>
|
typealias SiteMap = NakedTrie<Unit>
|
||||||
|
|
||||||
class EightMuses : HttpSource(),
|
class EightMuses :
|
||||||
LewdSource<EightMusesSearchMetadata, Document>,
|
HttpSource(),
|
||||||
UrlImportableSource {
|
LewdSource<EightMusesSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
override val id = EIGHTMUSES_SOURCE_ID
|
override val id = EIGHTMUSES_SOURCE_ID
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -74,10 +75,10 @@ class EightMuses : HttpSource(),
|
|||||||
private suspend fun obtainSiteMap() = siteMapCache.obtain {
|
private suspend fun obtainSiteMap() = siteMapCache.obtain {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
val result = client.newCall(eightMusesGet("$baseUrl/sitemap/1.xml"))
|
val result = client.newCall(eightMusesGet("$baseUrl/sitemap/1.xml"))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.toSingle()
|
.toSingle()
|
||||||
.await(Schedulers.io())
|
.await(Schedulers.io())
|
||||||
.body!!.string()
|
.body!!.string()
|
||||||
|
|
||||||
val parsed = Jsoup.parse(result)
|
val parsed = Jsoup.parse(result)
|
||||||
|
|
||||||
@ -93,10 +94,10 @@ class EightMuses : HttpSource(),
|
|||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
override fun headersBuilder(): Headers.Builder {
|
||||||
return Headers.Builder()
|
return Headers.Builder()
|
||||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;")
|
.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("Accept-Language", "en-GB,en-US;q=0.9,en;q=0.8")
|
||||||
.add("Referer", "https://www.8muses.com")
|
.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("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 {
|
private fun eightMusesGet(url: String): Request {
|
||||||
@ -129,11 +130,11 @@ class EightMuses : HttpSource(),
|
|||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
val urlBuilder = if (!query.isBlank()) {
|
val urlBuilder = if (!query.isBlank()) {
|
||||||
"$baseUrl/search".toHttpUrlOrNull()!!
|
"$baseUrl/search".toHttpUrlOrNull()!!
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("q", query)
|
.addQueryParameter("q", query)
|
||||||
} else {
|
} else {
|
||||||
"$baseUrl/comics".toHttpUrlOrNull()!!
|
"$baseUrl/comics".toHttpUrlOrNull()!!
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
}
|
}
|
||||||
|
|
||||||
urlBuilder.addQueryParameter("page", page.toString())
|
urlBuilder.addQueryParameter("page", page.toString())
|
||||||
@ -182,12 +183,14 @@ class EightMuses : HttpSource(),
|
|||||||
|
|
||||||
private fun fetchListing(request: Request, dig: Boolean): Observable<MangasPage> {
|
private fun fetchListing(request: Request, dig: Boolean): Observable<MangasPage> {
|
||||||
return client.newCall(request)
|
return client.newCall(request)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMapSingle { response ->
|
.flatMapSingle { response ->
|
||||||
RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
RxJavaInterop.toV1Single(
|
||||||
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
parseResultsPage(response, dig)
|
parseResultsPage(response, dig)
|
||||||
}.asSingle(GlobalScope.coroutineContext))
|
}.asSingle(GlobalScope.coroutineContext)
|
||||||
}
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun parseResultsPage(response: Response, dig: Boolean): MangasPage {
|
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
|
val onLastPage = doc.selectFirst(".current:nth-last-child(2)") != null
|
||||||
|
|
||||||
return MangasPage(
|
return MangasPage(
|
||||||
if (dig) {
|
if (dig) {
|
||||||
contents.albums.flatMap {
|
contents.albums.flatMap {
|
||||||
val href = it.attr("href")
|
val href = it.attr("href")
|
||||||
val splitHref = href.split('/')
|
val splitHref = href.split('/')
|
||||||
obtainSiteMap().subMap(href).filter {
|
obtainSiteMap().subMap(href).filter {
|
||||||
it.key.split('/').size - splitHref.size == 1
|
it.key.split('/').size - splitHref.size == 1
|
||||||
}.map { (key, _) ->
|
}.map { (key, _) ->
|
||||||
SManga.create().apply {
|
|
||||||
url = key
|
|
||||||
|
|
||||||
title = key.substringAfterLast('/').replace('-', ' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
contents.albums.map {
|
|
||||||
SManga.create().apply {
|
SManga.create().apply {
|
||||||
url = it.attr("href")
|
url = key
|
||||||
|
|
||||||
title = it.select(".title-text").text()
|
title = key.substringAfterLast('/').replace('-', ' ')
|
||||||
|
|
||||||
thumbnail_url = baseUrl + it.select(".lazyload").attr("data-src")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
!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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -259,9 +262,11 @@ class EightMuses : HttpSource(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
|
||||||
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
|
return RxJavaInterop.toV1Single(
|
||||||
fetchAndParseChapterList("", manga.url)
|
GlobalScope.async(Dispatchers.IO) {
|
||||||
}.asSingle(GlobalScope.coroutineContext)).toObservable()
|
fetchAndParseChapterList("", manga.url)
|
||||||
|
}.asSingle(GlobalScope.coroutineContext)
|
||||||
|
).toObservable()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
|
private suspend fun fetchAndParseChapterList(prefix: String, url: String): List<SChapter> {
|
||||||
@ -309,9 +314,9 @@ class EightMuses : HttpSource(),
|
|||||||
val contents = parseSelf(response.asJsoup())
|
val contents = parseSelf(response.asJsoup())
|
||||||
return contents.images.mapIndexed { index, element ->
|
return contents.images.mapIndexed { index, element ->
|
||||||
Page(
|
Page(
|
||||||
index,
|
index,
|
||||||
element.attr("href"),
|
element.attr("href"),
|
||||||
"$baseUrl/image/fl" + element.select(".lazyload").attr("data-src").substring(9)
|
"$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()
|
title = breadcrumbs.selectFirst("li:nth-last-child(1) > a").text()
|
||||||
|
|
||||||
thumbnailUrl = parseSelf(input).let { it.albums + it.images }.firstOrNull()
|
thumbnailUrl = parseSelf(input).let { it.albums + it.images }.firstOrNull()
|
||||||
?.selectFirst(".lazyload")
|
?.selectFirst(".lazyload")
|
||||||
?.attr("data-src")?.let {
|
?.attr("data-src")?.let {
|
||||||
baseUrl + it
|
baseUrl + it
|
||||||
}
|
}
|
||||||
|
|
||||||
tags.clear()
|
tags.clear()
|
||||||
tags += RaisedTag(
|
tags += RaisedTag(
|
||||||
EightMusesSearchMetadata.ARTIST_NAMESPACE,
|
EightMusesSearchMetadata.ARTIST_NAMESPACE,
|
||||||
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
|
breadcrumbs.selectFirst("li:nth-child(2) > a").text(),
|
||||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||||
)
|
)
|
||||||
tags += input.select(".album-tags a").map {
|
tags += input.select(".album-tags a").map {
|
||||||
RaisedTag(
|
RaisedTag(
|
||||||
EightMusesSearchMetadata.TAGS_NAMESPACE,
|
EightMusesSearchMetadata.TAGS_NAMESPACE,
|
||||||
it.text(),
|
it.text(),
|
||||||
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
EightMusesSearchMetadata.TAG_TYPE_DEFAULT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SortFilter : Filter.Select<String>(
|
class SortFilter : Filter.Select<String>(
|
||||||
"Sort",
|
"Sort",
|
||||||
SORT_OPTIONS.map { it.second }.toTypedArray()
|
SORT_OPTIONS.map { it.second }.toTypedArray()
|
||||||
) {
|
) {
|
||||||
fun addToUri(url: HttpUrl.Builder) {
|
fun addToUri(url: HttpUrl.Builder) {
|
||||||
url.addQueryParameter("sort", SORT_OPTIONS[state].first)
|
url.addQueryParameter("sort", SORT_OPTIONS[state].first)
|
||||||
@ -357,16 +362,16 @@ class EightMuses : HttpSource(),
|
|||||||
companion object {
|
companion object {
|
||||||
// <Internal, Display>
|
// <Internal, Display>
|
||||||
private val SORT_OPTIONS = listOf(
|
private val SORT_OPTIONS = listOf(
|
||||||
"" to "Views",
|
"" to "Views",
|
||||||
"like" to "Likes",
|
"like" to "Likes",
|
||||||
"date" to "Date",
|
"date" to "Date",
|
||||||
"az" to "A-Z"
|
"az" to "A-Z"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFilterList() = FilterList(
|
override fun getFilterList() = FilterList(
|
||||||
SortFilter()
|
SortFilter()
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -379,8 +384,8 @@ class EightMuses : HttpSource(),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"www.8muses.com",
|
"www.8muses.com",
|
||||||
"8muses.com"
|
"8muses.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
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 org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class HentaiCafe(delegate: HttpSource) :
|
||||||
LewdSource<HentaiCafeSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<HentaiCafeSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* 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
|
// Support direct URL importing
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
urlImportFetchSearchManga(query) {
|
urlImportFetchSearchManga(query) {
|
||||||
super.fetchSearchManga(page, query, filters)
|
super.fetchSearchManga(page, query, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga.apply {
|
parseToManga(manga, it.asJsoup()).andThen(
|
||||||
initialized = true
|
Observable.just(
|
||||||
}))
|
manga.apply {
|
||||||
}
|
initialized = true
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,8 +63,8 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
thumbnailUrl = contentElement.child(0).child(0).attr("src")
|
thumbnailUrl = contentElement.child(0).child(0).attr("src")
|
||||||
|
|
||||||
fun filterableTagsOfType(type: String) = contentElement.select("a")
|
fun filterableTagsOfType(type: String) = contentElement.select("a")
|
||||||
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
|
.filter { "$baseUrl/hc.fyi/$type/" in it.attr("href") }
|
||||||
.map { it.text() }
|
.map { it.text() }
|
||||||
|
|
||||||
tags.clear()
|
tags.clear()
|
||||||
tags += filterableTagsOfType("tag").map {
|
tags += filterableTagsOfType("tag").map {
|
||||||
@ -78,29 +84,30 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
|
|
||||||
override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) {
|
override fun fetchChapterList(manga: SManga) = getOrLoadMetadata(manga.id) {
|
||||||
client.newCall(mangaDetailsRequest(manga))
|
client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { it.asJsoup() }
|
.map { it.asJsoup() }
|
||||||
.toSingle()
|
.toSingle()
|
||||||
}.map {
|
}.map {
|
||||||
listOf(
|
listOf(
|
||||||
SChapter.create().apply {
|
SChapter.create().apply {
|
||||||
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
|
setUrlWithoutDomain("/manga/read/${it.readerId}/en/0/1/")
|
||||||
name = "Chapter"
|
name = "Chapter"
|
||||||
chapter_number = 0.0f
|
chapter_number = 0.0f
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}.toObservable()
|
}.toObservable()
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"hentai.cafe"
|
"hentai.cafe"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
||||||
|
|
||||||
return if (lcFirstPathSegment == "manga")
|
return if (lcFirstPathSegment == "manga") {
|
||||||
"https://hentai.cafe/${uri.pathSegments[2]}"
|
"https://hentai.cafe/${uri.pathSegments[2]}"
|
||||||
else
|
} else {
|
||||||
"https://hentai.cafe/$lcFirstPathSegment"
|
"https://hentai.cafe/$lcFirstPathSegment"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,8 +18,10 @@ import exh.util.urlImportFetchSearchManga
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class Pururin(delegate: HttpSource) :
|
||||||
LewdSource<PururinSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<PururinSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
/**
|
/**
|
||||||
* An ISO 639-1 compliant language code (two letters in lower case).
|
* 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> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup())
|
parseToManga(manga, it.asJsoup())
|
||||||
.andThen(Observable.just(manga))
|
.andThen(Observable.just(manga))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) {
|
override fun parseIntoMetadata(metadata: PururinSearchMetadata, input: Document) {
|
||||||
@ -87,9 +89,9 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
value.select("a").forEach { link ->
|
value.select("a").forEach { link ->
|
||||||
val searchUrl = Uri.parse(link.attr("href"))
|
val searchUrl = Uri.parse(link.attr("href"))
|
||||||
tags += RaisedTag(
|
tags += RaisedTag(
|
||||||
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
|
searchUrl.pathSegments[searchUrl.pathSegments.lastIndex - 2],
|
||||||
searchUrl.lastPathSegment!!.substringBefore("."),
|
searchUrl.lastPathSegment!!.substringBefore("."),
|
||||||
PururinSearchMetadata.TAG_TYPE_DEFAULT
|
PururinSearchMetadata.TAG_TYPE_DEFAULT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -99,8 +101,8 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
}
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"pururin.io",
|
"pururin.io",
|
||||||
"www.pururin.io"
|
"www.pururin.io"
|
||||||
)
|
)
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
|
@ -19,30 +19,33 @@ import java.util.Locale
|
|||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
class Tsumino(delegate: HttpSource) :
|
||||||
LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource {
|
DelegatedHttpSource(delegate),
|
||||||
|
LewdSource<TsuminoSearchMetadata, Document>,
|
||||||
|
UrlImportableSource {
|
||||||
override val metaClass = TsuminoSearchMetadata::class
|
override val metaClass = TsuminoSearchMetadata::class
|
||||||
override val lang = "en"
|
override val lang = "en"
|
||||||
|
|
||||||
// Support direct URL importing
|
// Support direct URL importing
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
urlImportFetchSearchManga(query) {
|
urlImportFetchSearchManga(query) {
|
||||||
super.fetchSearchManga(page, query, filters)
|
super.fetchSearchManga(page, query, filters)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
override fun mapUrlToMangaUrl(uri: Uri): String? {
|
||||||
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
|
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 null
|
||||||
|
}
|
||||||
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
|
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||||
return client.newCall(mangaDetailsRequest(manga))
|
return client.newCall(mangaDetailsRequest(manga))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.flatMap {
|
.flatMap {
|
||||||
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) {
|
override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) {
|
||||||
@ -106,16 +109,18 @@ class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
|
|||||||
character = newCharacter
|
character = newCharacter
|
||||||
|
|
||||||
input.getElementById("Tag")?.children()?.let {
|
input.getElementById("Tag")?.children()?.let {
|
||||||
tags.addAll(it.map {
|
tags.addAll(
|
||||||
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
it.map {
|
||||||
})
|
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override val matchingHosts = listOf(
|
override val matchingHosts = listOf(
|
||||||
"www.tsumino.com",
|
"www.tsumino.com",
|
||||||
"tsumino.com"
|
"tsumino.com"
|
||||||
)
|
)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -131,10 +131,14 @@ class SourceController :
|
|||||||
// Open the catalogue view.
|
// Open the catalogue view.
|
||||||
openCatalogue(source, BrowseSourceController(source))
|
openCatalogue(source, BrowseSourceController(source))
|
||||||
}
|
}
|
||||||
Mode.SMART_SEARCH -> router.pushController(SmartSearchController(Bundle().apply {
|
Mode.SMART_SEARCH -> router.pushController(
|
||||||
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
SmartSearchController(
|
||||||
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
Bundle().apply {
|
||||||
}).withFadeTransaction())
|
putLong(SmartSearchController.ARG_SOURCE_ID, source.id)
|
||||||
|
putParcelable(SmartSearchController.ARG_SMART_SEARCH_CONFIG, smartSearchConfig)
|
||||||
|
}
|
||||||
|
).withFadeTransaction()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -413,9 +413,9 @@ open class BrowseSourcePresenter(
|
|||||||
}
|
}
|
||||||
val newSerialized = searches.map {
|
val newSerialized = searches.map {
|
||||||
"${source.id}:" + jsonObject(
|
"${source.id}:" + jsonObject(
|
||||||
"name" to it.name,
|
"name" to it.name,
|
||||||
"query" to it.query,
|
"query" to it.query,
|
||||||
"filters" to filterSerializer.serialize(it.filterList)
|
"filters" to filterSerializer.serialize(it.filterList)
|
||||||
).toString()
|
).toString()
|
||||||
}
|
}
|
||||||
prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
|
prefs.eh_savedSearches().set((otherSerialized + newSerialized).toSet())
|
||||||
@ -430,9 +430,11 @@ open class BrowseSourcePresenter(
|
|||||||
val content = JsonParser.parseString(it.substringAfter(':')).obj
|
val content = JsonParser.parseString(it.substringAfter(':')).obj
|
||||||
val originalFilters = source.getFilterList()
|
val originalFilters = source.getFilterList()
|
||||||
filterSerializer.deserialize(originalFilters, content["filters"].array)
|
filterSerializer.deserialize(originalFilters, content["filters"].array)
|
||||||
EXHSavedSearch(content["name"].string,
|
EXHSavedSearch(
|
||||||
content["query"].string,
|
content["name"].string,
|
||||||
originalFilters)
|
content["query"].string,
|
||||||
|
originalFilters
|
||||||
|
)
|
||||||
} catch (t: RuntimeException) {
|
} catch (t: RuntimeException) {
|
||||||
// Load failed
|
// Load failed
|
||||||
Timber.e(t, "Failed to load saved search!")
|
Timber.e(t, "Failed to load saved search!")
|
||||||
|
@ -393,7 +393,6 @@ class LibraryPresenter(
|
|||||||
manga: Manga,
|
manga: Manga,
|
||||||
replace: Boolean
|
replace: Boolean
|
||||||
) {
|
) {
|
||||||
|
|
||||||
val flags = preferences.migrateFlags().get()
|
val flags = preferences.migrateFlags().get()
|
||||||
val migrateChapters = MigrationFlags.hasChapters(flags)
|
val migrateChapters = MigrationFlags.hasChapters(flags)
|
||||||
val migrateCategories = MigrationFlags.hasCategories(flags)
|
val migrateCategories = MigrationFlags.hasCategories(flags)
|
||||||
|
@ -181,29 +181,34 @@ class MangaInfoPresenter(
|
|||||||
|
|
||||||
suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga {
|
suspend fun smartSearchMerge(manga: Manga, originalMangaId: Long): Manga {
|
||||||
val originalManga = db.getManga(originalMangaId).await()
|
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) {
|
val toInsert = if (originalManga.source == MERGED_SOURCE_ID) {
|
||||||
originalManga.apply {
|
originalManga.apply {
|
||||||
val originalChildren = MergedSource.MangaConfig.readFromUrl(gson, url).children
|
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!")
|
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.source,
|
||||||
manga.url
|
manga.url
|
||||||
)).writeAsUrl(gson)
|
)
|
||||||
|
).writeAsUrl(gson)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val newMangaConfig = MergedSource.MangaConfig(listOf(
|
val newMangaConfig = MergedSource.MangaConfig(
|
||||||
|
listOf(
|
||||||
MergedSource.MangaSource(
|
MergedSource.MangaSource(
|
||||||
originalManga.source,
|
originalManga.source,
|
||||||
originalManga.url
|
originalManga.url
|
||||||
),
|
),
|
||||||
MergedSource.MangaSource(
|
MergedSource.MangaSource(
|
||||||
manga.source,
|
manga.source,
|
||||||
manga.url
|
manga.url
|
||||||
)
|
)
|
||||||
))
|
)
|
||||||
|
)
|
||||||
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
Manga.create(newMangaConfig.writeAsUrl(gson), originalManga.title, MERGED_SOURCE_ID).apply {
|
||||||
copyFrom(originalManga)
|
copyFrom(originalManga)
|
||||||
favorite = true
|
favorite = true
|
||||||
|
@ -23,17 +23,22 @@ class MigrationMangaDialog<T>(bundle: Bundle? = null) : DialogController(bundle)
|
|||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
|
val confirmRes = if (copy) R.plurals.copy_manga else R.plurals.migrate_manga
|
||||||
val confirmString = applicationContext?.resources?.getQuantityString(confirmRes, mangaSet,
|
val confirmString = applicationContext?.resources?.getQuantityString(
|
||||||
mangaSet, (
|
confirmRes, mangaSet,
|
||||||
|
mangaSet,
|
||||||
|
(
|
||||||
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
|
if (mangaSkipped > 0) " " + applicationContext?.getString(R.string.skipping_, mangaSkipped)
|
||||||
else "")) ?: ""
|
else ""
|
||||||
|
)
|
||||||
|
) ?: ""
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.message(text = confirmString)
|
.message(text = confirmString)
|
||||||
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
|
.positiveButton(if (copy) R.string.copy else R.string.migrate) {
|
||||||
if (copy)
|
if (copy) {
|
||||||
(targetController as? MigrationListController)?.copyMangas()
|
(targetController as? MigrationListController)?.copyMangas()
|
||||||
else
|
} else {
|
||||||
(targetController as? MigrationListController)?.migrateMangas()
|
(targetController as? MigrationListController)?.migrateMangas()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.negativeButton(android.R.string.no)
|
.negativeButton(android.R.string.no)
|
||||||
}
|
}
|
||||||
|
@ -31,10 +31,12 @@ class MigrationBottomSheetDialog(
|
|||||||
activity: Activity,
|
activity: Activity,
|
||||||
theme: Int,
|
theme: Int,
|
||||||
private val listener:
|
private val listener:
|
||||||
StartMigrationListener
|
StartMigrationListener
|
||||||
) :
|
) :
|
||||||
BottomSheetDialog(activity,
|
BottomSheetDialog(
|
||||||
theme) {
|
activity,
|
||||||
|
theme
|
||||||
|
) {
|
||||||
/**
|
/**
|
||||||
* Preferences helper.
|
* Preferences helper.
|
||||||
*/
|
*/
|
||||||
@ -47,8 +49,9 @@ class MigrationBottomSheetDialog(
|
|||||||
// scroll.addView(view)
|
// scroll.addView(view)
|
||||||
|
|
||||||
setContentView(view)
|
setContentView(view)
|
||||||
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE)
|
if (activity.resources.configuration?.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
sourceGroup.orientation = LinearLayout.HORIZONTAL
|
sourceGroup.orientation = LinearLayout.HORIZONTAL
|
||||||
|
}
|
||||||
window?.setBackgroundDrawable(null)
|
window?.setBackgroundDrawable(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +66,10 @@ class MigrationBottomSheetDialog(
|
|||||||
fab.setOnClickListener {
|
fab.setOnClickListener {
|
||||||
preferences.skipPreMigration().set(skip_step.isChecked)
|
preferences.skipPreMigration().set(skip_step.isChecked)
|
||||||
listener.startMigration(
|
listener.startMigration(
|
||||||
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank())
|
if (use_smart_search.isChecked && extra_search_param_text.text.isNotBlank()) {
|
||||||
extra_search_param_text.text.toString() else null)
|
extra_search_param_text.text.toString()
|
||||||
|
} else null
|
||||||
|
)
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -96,9 +101,12 @@ class MigrationBottomSheetDialog(
|
|||||||
|
|
||||||
skip_step.isChecked = preferences.skipPreMigration().get()
|
skip_step.isChecked = preferences.skipPreMigration().get()
|
||||||
skip_step.setOnCheckedChangeListener { _, isChecked ->
|
skip_step.setOnCheckedChangeListener { _, isChecked ->
|
||||||
if (isChecked)
|
if (isChecked) {
|
||||||
(listener as? Controller)?.activity?.toast(R.string.pre_migration_skip_toast,
|
(listener as? Controller)?.activity?.toast(
|
||||||
Toast.LENGTH_LONG)
|
R.string.pre_migration_skip_toast,
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,16 +9,21 @@ class MigrationSourceAdapter(
|
|||||||
var items: List<MigrationSourceItem>,
|
var items: List<MigrationSourceItem>,
|
||||||
val controllerPre: PreMigrationController
|
val controllerPre: PreMigrationController
|
||||||
) : FlexibleAdapter<MigrationSourceItem>(
|
) : FlexibleAdapter<MigrationSourceItem>(
|
||||||
items,
|
items,
|
||||||
controllerPre,
|
controllerPre,
|
||||||
true
|
true
|
||||||
) {
|
) {
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
super.onSaveInstanceState(outState)
|
super.onSaveInstanceState(outState)
|
||||||
|
|
||||||
outState.putParcelableArrayList(SELECTED_SOURCES_KEY, ArrayList(currentItems.map {
|
outState.putParcelableArrayList(
|
||||||
it.asParcelable()
|
SELECTED_SOURCES_KEY,
|
||||||
}))
|
ArrayList(
|
||||||
|
currentItems.map {
|
||||||
|
it.asParcelable()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
override fun onRestoreInstanceState(savedInstanceState: Bundle) {
|
||||||
|
@ -66,8 +66,8 @@ class MigrationSourceItem(val source: HttpSource, var sourceEnabled: Boolean) :
|
|||||||
val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null
|
val source = sourceManager.get(si.sourceId) as? HttpSource ?: return null
|
||||||
|
|
||||||
return MigrationSourceItem(
|
return MigrationSourceItem(
|
||||||
source,
|
source,
|
||||||
si.sourceEnabled
|
si.sourceEnabled
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,8 +27,11 @@ import exh.util.updateLayoutParams
|
|||||||
import exh.util.updatePaddingRelative
|
import exh.util.updatePaddingRelative
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrationControllerBinding>(bundle), FlexibleAdapter
|
class PreMigrationController(bundle: Bundle? = null) :
|
||||||
.OnItemClickListener, StartMigrationListener {
|
BaseController<PreMigrationControllerBinding>(bundle),
|
||||||
|
FlexibleAdapter
|
||||||
|
.OnItemClickListener,
|
||||||
|
StartMigrationListener {
|
||||||
private val sourceManager: SourceManager by injectLazy()
|
private val sourceManager: SourceManager by injectLazy()
|
||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
@ -69,8 +72,10 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
|
bottomMargin = fabBaseMarginBottom + insets.systemWindowInsetBottom
|
||||||
}
|
}
|
||||||
// offset the recycler by the fab's inset + some inset on top
|
// offset the recycler by the fab's inset + some inset on top
|
||||||
v.updatePaddingRelative(bottom = padding.bottom + (binding.fab.marginBottom) +
|
v.updatePaddingRelative(
|
||||||
fabBaseMarginBottom + (binding.fab.height))
|
bottom = padding.bottom + (binding.fab.marginBottom) +
|
||||||
|
fabBaseMarginBottom + (binding.fab.height)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.fab.setOnClickListener {
|
binding.fab.setOnClickListener {
|
||||||
@ -101,7 +106,8 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
config.toList(),
|
config.toList(),
|
||||||
extraSearchParams = extraParam
|
extraSearchParams = extraParam
|
||||||
)
|
)
|
||||||
).withFadeTransaction().tag(MigrationListController.TAG))
|
).withFadeTransaction().tag(MigrationListController.TAG)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
override fun onSaveInstanceState(outState: Bundle) {
|
||||||
@ -136,10 +142,13 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
.filter { it.lang in languages }
|
.filter { it.lang in languages }
|
||||||
.sortedBy { "(${it.lang}) ${it.name}" }
|
.sortedBy { "(${it.lang}) ${it.name}" }
|
||||||
sources =
|
sources =
|
||||||
sources.filter { isEnabled(it.id.toString()) }.sortedBy { sourcesSaved.indexOf(it.id
|
sources.filter { isEnabled(it.id.toString()) }.sortedBy {
|
||||||
.toString())
|
sourcesSaved.indexOf(
|
||||||
} +
|
it.id
|
||||||
sources.filterNot { isEnabled(it.id.toString()) }
|
.toString()
|
||||||
|
)
|
||||||
|
} +
|
||||||
|
sources.filterNot { isEnabled(it.id.toString()) }
|
||||||
|
|
||||||
return sources
|
return sources
|
||||||
}
|
}
|
||||||
@ -167,9 +176,11 @@ class PreMigrationController(bundle: Bundle? = null) : BaseController<PreMigrati
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun create(mangaIds: List<Long>): PreMigrationController {
|
fun create(mangaIds: List<Long>): PreMigrationController {
|
||||||
return PreMigrationController(Bundle().apply {
|
return PreMigrationController(
|
||||||
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
Bundle().apply {
|
||||||
})
|
putLongArray(MANGA_IDS_EXTRA, mangaIds.toLongArray())
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,8 +56,10 @@ import rx.schedulers.Schedulers
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class MigrationListController(bundle: Bundle? = null) : BaseController<MigrationListControllerBinding>(bundle),
|
class MigrationListController(bundle: Bundle? = null) :
|
||||||
MigrationProcessAdapter.MigrationProcessInterface, CoroutineScope {
|
BaseController<MigrationListControllerBinding>(bundle),
|
||||||
|
MigrationProcessAdapter.MigrationProcessInterface,
|
||||||
|
CoroutineScope {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
setHasOptionsMenu(true)
|
setHasOptionsMenu(true)
|
||||||
@ -93,7 +95,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View) {
|
override fun onViewCreated(view: View) {
|
||||||
|
|
||||||
super.onViewCreated(view)
|
super.onViewCreated(view)
|
||||||
view.applyWindowInsetsForController()
|
view.applyWindowInsetsForController()
|
||||||
setTitle()
|
setTitle()
|
||||||
@ -217,7 +218,8 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
|
val localManga = smartSearchEngine.networkToLocalManga(searchResult, source.id)
|
||||||
val chapters = try {
|
val chapters = try {
|
||||||
source.fetchChapterList(localManga)
|
source.fetchChapterList(localManga)
|
||||||
.toSingle().await(Schedulers.io()) } catch (e: java.lang.Exception) {
|
.toSingle().await(Schedulers.io())
|
||||||
|
} catch (e: java.lang.Exception) {
|
||||||
Timber.e(e)
|
Timber.e(e)
|
||||||
emptyList<SChapter>()
|
emptyList<SChapter>()
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
@ -313,7 +315,6 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onMenuItemClick(position: Int, item: MenuItem) {
|
override fun onMenuItemClick(position: Int, item: MenuItem) {
|
||||||
|
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.action_search_manually -> {
|
R.id.action_search_manually -> {
|
||||||
launchUI {
|
launchUI {
|
||||||
@ -488,9 +489,11 @@ class MigrationListController(bundle: Bundle? = null) : BaseController<Migration
|
|||||||
const val TAG = "migration_list"
|
const val TAG = "migration_list"
|
||||||
|
|
||||||
fun create(config: MigrationProcedureConfig): MigrationListController {
|
fun create(config: MigrationProcedureConfig): MigrationListController {
|
||||||
return MigrationListController(Bundle().apply {
|
return MigrationListController(
|
||||||
putParcelable(CONFIG_EXTRA, config)
|
Bundle().apply {
|
||||||
})
|
putParcelable(CONFIG_EXTRA, config)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,8 +42,12 @@ class MigrationProcessAdapter(
|
|||||||
if (allMangasDone()) menuItemListener.enableButtons()
|
if (allMangasDone()) menuItemListener.enableButtons()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun allMangasDone() = (items.all { it.manga.migrationStatus != MigrationStatus
|
fun allMangasDone() = (
|
||||||
.RUNNUNG } && items.any { it.manga.migrationStatus == MigrationStatus.MANGA_FOUND })
|
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 })
|
fun mangasSkipped() = (items.count { it.manga.migrationStatus == MigrationStatus.MANGA_NOT_FOUND })
|
||||||
|
|
||||||
@ -59,7 +63,8 @@ class MigrationProcessAdapter(
|
|||||||
migrateMangaInternal(
|
migrateMangaInternal(
|
||||||
manga.manga() ?: return@forEach,
|
manga.manga() ?: return@forEach,
|
||||||
toMangaObj,
|
toMangaObj,
|
||||||
!copy)
|
!copy
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -48,10 +48,18 @@ class MigrationProcessHolder(
|
|||||||
val manga = item.manga.manga()
|
val manga = item.manga.manga()
|
||||||
val source = item.manga.mangaSource()
|
val source = item.manga.mangaSource()
|
||||||
|
|
||||||
migration_menu.setVectorCompat(R.drawable.ic_more_vert_24dp, view.context
|
migration_menu.setVectorCompat(
|
||||||
.getResourceColor(R.attr.colorOnPrimary))
|
R.drawable.ic_more_vert_24dp,
|
||||||
skip_manga.setVectorCompat(R.drawable.ic_close_24dp, view.context.getResourceColor(R
|
view.context
|
||||||
.attr.colorOnPrimary))
|
.getResourceColor(R.attr.colorOnPrimary)
|
||||||
|
)
|
||||||
|
skip_manga.setVectorCompat(
|
||||||
|
R.drawable.ic_close_24dp,
|
||||||
|
view.context.getResourceColor(
|
||||||
|
R
|
||||||
|
.attr.colorOnPrimary
|
||||||
|
)
|
||||||
|
)
|
||||||
migration_menu.invisible()
|
migration_menu.invisible()
|
||||||
skip_manga.visible()
|
skip_manga.visible()
|
||||||
migration_manga_card_to.resetManga()
|
migration_manga_card_to.resetManga()
|
||||||
@ -87,7 +95,8 @@ class MigrationProcessHolder(
|
|||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
|
if (item.manga.mangaId != this@MigrationProcessHolder.item?.manga?.mangaId ||
|
||||||
item.manga.migrationStatus == MigrationStatus.RUNNUNG) {
|
item.manga.migrationStatus == MigrationStatus.RUNNUNG
|
||||||
|
) {
|
||||||
return@withContext
|
return@withContext
|
||||||
}
|
}
|
||||||
if (searchResult != null && resultSource != null) {
|
if (searchResult != null && resultSource != null) {
|
||||||
@ -152,11 +161,15 @@ class MigrationProcessHolder(
|
|||||||
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
val latestChapter = mangaChapters.maxBy { it.chapter_number }?.chapter_number ?: -1f
|
||||||
|
|
||||||
if (latestChapter > 0f) {
|
if (latestChapter > 0f) {
|
||||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
manga_last_chapter_label.text = context.getString(
|
||||||
DecimalFormat("#.#").format(latestChapter))
|
R.string.latest_,
|
||||||
|
DecimalFormat("#.#").format(latestChapter)
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
manga_last_chapter_label.text = context.getString(R.string.latest_,
|
manga_last_chapter_label.text = context.getString(
|
||||||
context.getString(R.string.unknown))
|
R.string.latest_,
|
||||||
|
context.getString(R.string.unknown)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,13 +63,13 @@ class SettingsEhController : SettingsController() {
|
|||||||
private fun Preference<*>.reconfigure(): Boolean {
|
private fun Preference<*>.reconfigure(): Boolean {
|
||||||
// Listen for change commit
|
// Listen for change commit
|
||||||
asObservable()
|
asObservable()
|
||||||
.skip(1) // Skip first as it is emitted immediately
|
.skip(1) // Skip first as it is emitted immediately
|
||||||
.take(1) // Only listen for first commit
|
.take(1) // Only listen for first commit
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeUntilDestroy {
|
.subscribeUntilDestroy {
|
||||||
// Only listen for first change commit
|
// Only listen for first change commit
|
||||||
WarnConfigureDialogController.uploadSettings(router)
|
WarnConfigureDialogController.uploadSettings(router)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always return true to save changes
|
// Always return true to save changes
|
||||||
return true
|
return true
|
||||||
@ -85,12 +85,12 @@ class SettingsEhController : SettingsController() {
|
|||||||
isPersistent = false
|
isPersistent = false
|
||||||
defaultValue = false
|
defaultValue = false
|
||||||
preferences.enableExhentai()
|
preferences.enableExhentai()
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeUntilDestroy {
|
.subscribeUntilDestroy {
|
||||||
isChecked = it
|
isChecked = it
|
||||||
}
|
}
|
||||||
|
|
||||||
onChange { newVal ->
|
onChange { newVal ->
|
||||||
newVal as Boolean
|
newVal as Boolean
|
||||||
@ -98,9 +98,11 @@ class SettingsEhController : SettingsController() {
|
|||||||
preferences.enableExhentai().set(false)
|
preferences.enableExhentai().set(false)
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
router.pushController(RouterTransaction.with(LoginController())
|
router.pushController(
|
||||||
|
RouterTransaction.with(LoginController())
|
||||||
.pushChangeHandler(FadeChangeHandler())
|
.pushChangeHandler(FadeChangeHandler())
|
||||||
.popChangeHandler(FadeChangeHandler()))
|
.popChangeHandler(FadeChangeHandler())
|
||||||
|
)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -148,20 +150,20 @@ class SettingsEhController : SettingsController() {
|
|||||||
summary = "The quality of the downloaded images"
|
summary = "The quality of the downloaded images"
|
||||||
title = "Image quality"
|
title = "Image quality"
|
||||||
entries = arrayOf(
|
entries = arrayOf(
|
||||||
"Auto",
|
"Auto",
|
||||||
"2400x",
|
"2400x",
|
||||||
"1600x",
|
"1600x",
|
||||||
"1280x",
|
"1280x",
|
||||||
"980x",
|
"980x",
|
||||||
"780x"
|
"780x"
|
||||||
)
|
)
|
||||||
entryValues = arrayOf(
|
entryValues = arrayOf(
|
||||||
"auto",
|
"auto",
|
||||||
"ovrs_2400",
|
"ovrs_2400",
|
||||||
"ovrs_1600",
|
"ovrs_1600",
|
||||||
"high",
|
"high",
|
||||||
"med",
|
"med",
|
||||||
"low"
|
"low"
|
||||||
)
|
)
|
||||||
|
|
||||||
onChange { preferences.imageQuality().reconfigure() }
|
onChange { preferences.imageQuality().reconfigure() }
|
||||||
@ -202,21 +204,21 @@ class SettingsEhController : SettingsController() {
|
|||||||
onClick {
|
onClick {
|
||||||
activity?.let { activity ->
|
activity?.let { activity ->
|
||||||
MaterialDialog(activity)
|
MaterialDialog(activity)
|
||||||
.title(R.string.eh_force_sync_reset_title)
|
.title(R.string.eh_force_sync_reset_title)
|
||||||
.message(R.string.eh_force_sync_reset_message)
|
.message(R.string.eh_force_sync_reset_message)
|
||||||
.positiveButton(android.R.string.yes) {
|
.positiveButton(android.R.string.yes) {
|
||||||
LocalFavoritesStorage().apply {
|
LocalFavoritesStorage().apply {
|
||||||
getRealm().use {
|
getRealm().use {
|
||||||
it.trans {
|
it.trans {
|
||||||
clearSnapshots(it)
|
clearSnapshots(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
activity.toast("Sync state reset", Toast.LENGTH_LONG)
|
|
||||||
}
|
}
|
||||||
.negativeButton(android.R.string.no)
|
activity.toast("Sync state reset", Toast.LENGTH_LONG)
|
||||||
.cancelable(false)
|
}
|
||||||
.show()
|
.negativeButton(android.R.string.no)
|
||||||
|
.cancelable(false)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,18 +313,18 @@ class SettingsEhController : SettingsController() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
"""
|
"""
|
||||||
$statsText
|
$statsText
|
||||||
|
|
||||||
Galleries that were checked in the last:
|
Galleries that were checked in the last:
|
||||||
- hour: ${metaInRelativeDuration(1.hours)}
|
- hour: ${metaInRelativeDuration(1.hours)}
|
||||||
- 6 hours: ${metaInRelativeDuration(6.hours)}
|
- 6 hours: ${metaInRelativeDuration(6.hours)}
|
||||||
- 12 hours: ${metaInRelativeDuration(12.hours)}
|
- 12 hours: ${metaInRelativeDuration(12.hours)}
|
||||||
- day: ${metaInRelativeDuration(1.days)}
|
- day: ${metaInRelativeDuration(1.days)}
|
||||||
- 2 days: ${metaInRelativeDuration(2.days)}
|
- 2 days: ${metaInRelativeDuration(2.days)}
|
||||||
- week: ${metaInRelativeDuration(7.days)}
|
- week: ${metaInRelativeDuration(7.days)}
|
||||||
- month: ${metaInRelativeDuration(30.days)}
|
- month: ${metaInRelativeDuration(30.days)}
|
||||||
- year: ${metaInRelativeDuration(365.days)}
|
- year: ${metaInRelativeDuration(365.days)}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
} finally {
|
} finally {
|
||||||
progress.dismiss()
|
progress.dismiss()
|
||||||
}
|
}
|
||||||
|
@ -75,10 +75,12 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
intListPreference {
|
intListPreference {
|
||||||
key = Keys.eh_library_rounded_corners
|
key = Keys.eh_library_rounded_corners
|
||||||
title = "Rounded Corner Radius"
|
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_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_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")
|
entryValues = arrayOf("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10")
|
||||||
defaultValue = "4"
|
defaultValue = "4"
|
||||||
summaryRes = R.string.eh_rounded_corners_desc
|
summaryRes = R.string.eh_rounded_corners_desc
|
||||||
@ -211,7 +213,8 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (preferences.skipPreMigration().get() || preferences.migrationSources()
|
if (preferences.skipPreMigration().get() || preferences.migrationSources()
|
||||||
.getOrDefault().isNotEmpty()) {
|
.getOrDefault().isNotEmpty()
|
||||||
|
) {
|
||||||
switchPreference {
|
switchPreference {
|
||||||
key = Keys.skipPreMigration
|
key = Keys.skipPreMigration
|
||||||
titleRes = R.string.pref_skip_pre_migration
|
titleRes = R.string.pref_skip_pre_migration
|
||||||
|
@ -26,19 +26,19 @@ const val HBROWSE_SOURCE_ID = LEWD_SOURCE_SERIES + 12
|
|||||||
const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
|
const val MERGED_SOURCE_ID = LEWD_SOURCE_SERIES + 69
|
||||||
|
|
||||||
private val DELEGATED_LEWD_SOURCES = listOf(
|
private val DELEGATED_LEWD_SOURCES = listOf(
|
||||||
HentaiCafe::class,
|
HentaiCafe::class,
|
||||||
Pururin::class,
|
Pururin::class,
|
||||||
Tsumino::class
|
Tsumino::class
|
||||||
)
|
)
|
||||||
|
|
||||||
val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf(
|
val LIBRARY_UPDATE_EXCLUDED_SOURCES = listOf(
|
||||||
EH_SOURCE_ID,
|
EH_SOURCE_ID,
|
||||||
EXH_SOURCE_ID,
|
EXH_SOURCE_ID,
|
||||||
NHENTAI_SOURCE_ID,
|
NHENTAI_SOURCE_ID,
|
||||||
HENTAI_CAFE_SOURCE_ID,
|
HENTAI_CAFE_SOURCE_ID,
|
||||||
TSUMINO_SOURCE_ID,
|
TSUMINO_SOURCE_ID,
|
||||||
HITOMI_SOURCE_ID,
|
HITOMI_SOURCE_ID,
|
||||||
PURURIN_SOURCE_ID
|
PURURIN_SOURCE_ID
|
||||||
)
|
)
|
||||||
|
|
||||||
private inline fun <reified T> delegatedSourceId(): Long {
|
private inline fun <reified T> delegatedSourceId(): Long {
|
||||||
@ -54,6 +54,6 @@ private val lewdDelegatedSourceIds = SourceManager.DELEGATED_SOURCES.filter {
|
|||||||
|
|
||||||
// This method MUST be fast!
|
// This method MUST be fast!
|
||||||
fun isLewdSource(source: Long) = source in 6900..6999 ||
|
fun isLewdSource(source: Long) = source in 6900..6999 ||
|
||||||
lewdDelegatedSourceIds.binarySearch(source) >= 0
|
lewdDelegatedSourceIds.binarySearch(source) >= 0
|
||||||
|
|
||||||
fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID
|
fun Source.isEhBasedSource() = id == EH_SOURCE_ID || id == EXH_SOURCE_ID
|
||||||
|
@ -45,35 +45,41 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 1) {
|
if (oldVersion < 1) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate HentaiCafe source IDs
|
// Migrate HentaiCafe source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
UPDATE ${MangaTable.TABLE}
|
.query(
|
||||||
SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID
|
"""
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 6908
|
UPDATE ${MangaTable.TABLE}
|
||||||
""".trimIndent())
|
SET ${MangaTable.COL_SOURCE} = $HENTAI_CAFE_SOURCE_ID
|
||||||
|
WHERE ${MangaTable.COL_SOURCE} = 6908
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
|
|
||||||
// Migrate nhentai URLs
|
// Migrate nhentai URLs
|
||||||
val nhentaiManga = db.db.get()
|
val nhentaiManga = db.db.get()
|
||||||
.listOfObjects(Manga::class.java)
|
.listOfObjects(Manga::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(MangaTable.TABLE)
|
Query.builder()
|
||||||
.where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID")
|
.table(MangaTable.TABLE)
|
||||||
.build())
|
.where("${MangaTable.COL_SOURCE} = $NHENTAI_SOURCE_ID")
|
||||||
.prepare()
|
.build()
|
||||||
.executeAsBlocking()
|
)
|
||||||
|
.prepare()
|
||||||
|
.executeAsBlocking()
|
||||||
|
|
||||||
nhentaiManga.forEach {
|
nhentaiManga.forEach {
|
||||||
it.url = getUrlWithoutDomain(it.url)
|
it.url = getUrlWithoutDomain(it.url)
|
||||||
}
|
}
|
||||||
|
|
||||||
db.db.put()
|
db.db.put()
|
||||||
.objects(nhentaiManga)
|
.objects(nhentaiManga)
|
||||||
// Extremely slow without the resolver :/
|
// Extremely slow without the resolver :/
|
||||||
.withPutResolver(MangaUrlPutResolver())
|
.withPutResolver(MangaUrlPutResolver())
|
||||||
.prepare()
|
.prepare()
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,14 +91,18 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 8405) {
|
if (oldVersion < 8405) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate HBrowse source IDs
|
// Migrate HBrowse source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
UPDATE ${MangaTable.TABLE}
|
.query(
|
||||||
SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID
|
"""
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222
|
UPDATE ${MangaTable.TABLE}
|
||||||
""".trimIndent())
|
SET ${MangaTable.COL_SOURCE} = $HBROWSE_SOURCE_ID
|
||||||
|
WHERE ${MangaTable.COL_SOURCE} = 1401584337232758222
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cancel old scheduler jobs with old ids
|
// Cancel old scheduler jobs with old ids
|
||||||
@ -101,14 +111,18 @@ object EXHMigrations {
|
|||||||
if (oldVersion < 8408) {
|
if (oldVersion < 8408) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
// Migrate Tsumino source IDs
|
// Migrate Tsumino source IDs
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
UPDATE ${MangaTable.TABLE}
|
.query(
|
||||||
SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID
|
"""
|
||||||
WHERE ${MangaTable.COL_SOURCE} = 6909
|
UPDATE ${MangaTable.TABLE}
|
||||||
""".trimIndent())
|
SET ${MangaTable.COL_SOURCE} = $TSUMINO_SOURCE_ID
|
||||||
|
WHERE ${MangaTable.COL_SOURCE} = 6909
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (oldVersion < 8409) {
|
if (oldVersion < 8409) {
|
||||||
@ -214,10 +228,12 @@ object EXHMigrations {
|
|||||||
return try {
|
return try {
|
||||||
val uri = URI(orig)
|
val uri = URI(orig)
|
||||||
var out = uri.path
|
var out = uri.path
|
||||||
if (uri.query != null)
|
if (uri.query != null) {
|
||||||
out += "?" + uri.query
|
out += "?" + uri.query
|
||||||
if (uri.fragment != null)
|
}
|
||||||
|
if (uri.fragment != null) {
|
||||||
out += "#" + uri.fragment
|
out += "#" + uri.fragment
|
||||||
|
}
|
||||||
out
|
out
|
||||||
} catch (e: URISyntaxException) {
|
} catch (e: URISyntaxException) {
|
||||||
orig
|
orig
|
||||||
|
@ -37,15 +37,15 @@ class GalleryAdder {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sourceManager.getVisibleCatalogueSources()
|
sourceManager.getVisibleCatalogueSources()
|
||||||
.filterIsInstance<UrlImportableSource>()
|
.filterIsInstance<UrlImportableSource>()
|
||||||
.find {
|
.find {
|
||||||
try {
|
try {
|
||||||
it.matchesUri(uri)
|
it.matchesUri(uri)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
XLog.e("Source URI match check error!", e)
|
XLog.e("Source URI match check error!", e)
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
} ?: return GalleryAddEvent.Fail.UnknownType(url)
|
} ?: return GalleryAddEvent.Fail.UnknownType(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map URL to manga URL
|
// Map URL to manga URL
|
||||||
@ -66,10 +66,10 @@ class GalleryAdder {
|
|||||||
|
|
||||||
// Use manga in DB if possible, otherwise, make a new manga
|
// Use manga in DB if possible, otherwise, make a new manga
|
||||||
val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking()
|
val manga = db.getManga(cleanedUrl, source.id).executeAsBlocking()
|
||||||
?: Manga.create(source.id).apply {
|
?: Manga.create(source.id).apply {
|
||||||
this.url = cleanedUrl
|
this.url = cleanedUrl
|
||||||
title = realUrl
|
title = realUrl
|
||||||
}
|
}
|
||||||
|
|
||||||
// Insert created manga if not in DB before fetching details
|
// Insert created manga if not in DB before fetching details
|
||||||
// This allows us to keep the metadata when fetching details
|
// This allows us to keep the metadata when fetching details
|
||||||
@ -111,8 +111,10 @@ class GalleryAdder {
|
|||||||
return GalleryAddEvent.Fail.NotFound(url)
|
return GalleryAddEvent.Fail.NotFound(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return GalleryAddEvent.Fail.Error(url,
|
return GalleryAddEvent.Fail.Error(
|
||||||
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim())
|
url,
|
||||||
|
((e.message ?: "Unknown error!") + " (Gallery: $url)").trim()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -141,6 +143,6 @@ sealed class GalleryAddEvent {
|
|||||||
) : Fail()
|
) : Fail()
|
||||||
|
|
||||||
class NotFound(galleryUrl: String) :
|
class NotFound(galleryUrl: String) :
|
||||||
Error(galleryUrl, "Gallery does not exist: $galleryUrl")
|
Error(galleryUrl, "Gallery does not exist: $galleryUrl")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ object DebugFunctions {
|
|||||||
val sourceManager: SourceManager by injectLazy()
|
val sourceManager: SourceManager by injectLazy()
|
||||||
|
|
||||||
fun forceUpgradeMigration() {
|
fun forceUpgradeMigration() {
|
||||||
prefs.eh_lastVersionCode().set(0)
|
prefs.eh_lastVersionCode().set(0)
|
||||||
EXHMigrations.upgrade(prefs)
|
EXHMigrations.upgrade(prefs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,8 +38,9 @@ object DebugFunctions {
|
|||||||
val metadataManga = db.getFavoriteMangaWithMetadata().await()
|
val metadataManga = db.getFavoriteMangaWithMetadata().await()
|
||||||
|
|
||||||
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||||
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID)
|
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
manga
|
manga
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
@ -56,13 +57,17 @@ object DebugFunctions {
|
|||||||
|
|
||||||
fun addAllMangaInDatabaseToLibrary() {
|
fun addAllMangaInDatabaseToLibrary() {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
UPDATE ${MangaTable.TABLE}
|
.query(
|
||||||
SET ${MangaTable.COL_FAVORITE} = 1
|
"""
|
||||||
""".trimIndent())
|
UPDATE ${MangaTable.TABLE}
|
||||||
|
SET ${MangaTable.COL_FAVORITE} = 1
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,25 +103,29 @@ object DebugFunctions {
|
|||||||
|
|
||||||
fun listScheduledJobs() = app.jobScheduler.allPendingJobs.map { j ->
|
fun listScheduledJobs() = app.jobScheduler.allPendingJobs.map { j ->
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
info: ${j.id},
|
info: ${j.id},
|
||||||
isPeriod: ${j.isPeriodic},
|
isPeriod: ${j.isPeriodic},
|
||||||
isPersisted: ${j.isPersisted},
|
isPersisted: ${j.isPersisted},
|
||||||
intervalMillis: ${j.intervalMillis},
|
intervalMillis: ${j.intervalMillis},
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}.joinToString(",\n")
|
}.joinToString(",\n")
|
||||||
|
|
||||||
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
|
fun cancelAllScheduledJobs() = app.jobScheduler.cancelAll()
|
||||||
|
|
||||||
private fun convertSources(from: Long, to: Long) {
|
private fun convertSources(from: Long, to: Long) {
|
||||||
db.lowLevel().executeSQL(RawQuery.builder()
|
db.lowLevel().executeSQL(
|
||||||
.query("""
|
RawQuery.builder()
|
||||||
UPDATE ${MangaTable.TABLE}
|
.query(
|
||||||
SET ${MangaTable.COL_SOURCE} = $to
|
"""
|
||||||
WHERE ${MangaTable.COL_SOURCE} = $from
|
UPDATE ${MangaTable.TABLE}
|
||||||
""".trimIndent())
|
SET ${MangaTable.COL_SOURCE} = $to
|
||||||
|
WHERE ${MangaTable.COL_SOURCE} = $from
|
||||||
|
""".trimIndent()
|
||||||
|
)
|
||||||
.affectsTables(MangaTable.TABLE)
|
.affectsTables(MangaTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,11 +45,11 @@ class SettingsDebugController : SettingsController() {
|
|||||||
val result = it.call(DebugFunctions)
|
val result = it.call(DebugFunctions)
|
||||||
view.text = "Function returned result:\n\n$result"
|
view.text = "Function returned result:\n\n$result"
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.customView(view = hView, scrollable = true)
|
.customView(view = hView, scrollable = true)
|
||||||
} catch (t: Throwable) {
|
} catch (t: Throwable) {
|
||||||
view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}"
|
view.text = "Function threw exception:\n\n${Log.getStackTraceString(t)}"
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.customView(view = hView, scrollable = true)
|
.customView(view = hView, scrollable = true)
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,11 +12,13 @@ class EHentaiThrottleManager(
|
|||||||
// Throttle requests if necessary
|
// Throttle requests if necessary
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
val timeDiff = now - lastThrottleTime
|
val timeDiff = now - lastThrottleTime
|
||||||
if (timeDiff < throttleTime)
|
if (timeDiff < throttleTime) {
|
||||||
Thread.sleep(throttleTime - timeDiff)
|
Thread.sleep(throttleTime - timeDiff)
|
||||||
|
}
|
||||||
|
|
||||||
if (throttleTime < max)
|
if (throttleTime < max) {
|
||||||
throttleTime += inc
|
throttleTime += inc
|
||||||
|
}
|
||||||
|
|
||||||
lastThrottleTime = System.currentTimeMillis()
|
lastThrottleTime = System.currentTimeMillis()
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ data class ChapterChain(val manga: Manga, val chapters: List<Chapter>)
|
|||||||
|
|
||||||
class EHentaiUpdateHelper(context: Context) {
|
class EHentaiUpdateHelper(context: Context) {
|
||||||
val parentLookupTable =
|
val parentLookupTable =
|
||||||
MemAutoFlushingLookupTable(
|
MemAutoFlushingLookupTable(
|
||||||
File(context.filesDir, "exh-plt.maftable"),
|
File(context.filesDir, "exh-plt.maftable"),
|
||||||
GalleryEntry.Serializer()
|
GalleryEntry.Serializer()
|
||||||
)
|
)
|
||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -30,22 +30,24 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
*/
|
*/
|
||||||
fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List<Chapter>): Single<Triple<ChapterChain, List<ChapterChain>, Boolean>> {
|
fun findAcceptedRootAndDiscardOthers(sourceId: Long, chapters: List<Chapter>): Single<Triple<ChapterChain, List<ChapterChain>, Boolean>> {
|
||||||
// Find other chains
|
// Find other chains
|
||||||
val chainsObservable = Observable.merge(chapters.map { chapter ->
|
val chainsObservable = Observable.merge(
|
||||||
db.getChapters(chapter.url).asRxSingle().toObservable()
|
chapters.map { chapter ->
|
||||||
}).toList().map { allChapters ->
|
db.getChapters(chapter.url).asRxSingle().toObservable()
|
||||||
|
}
|
||||||
|
).toList().map { allChapters ->
|
||||||
allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct()
|
allChapters.flatMap { innerChapters -> innerChapters.map { it.manga_id!! } }.distinct()
|
||||||
}.flatMap { mangaIds ->
|
}.flatMap { mangaIds ->
|
||||||
Observable.merge(
|
Observable.merge(
|
||||||
mangaIds.map { mangaId ->
|
mangaIds.map { mangaId ->
|
||||||
Single.zip(
|
Single.zip(
|
||||||
db.getManga(mangaId).asRxSingle(),
|
db.getManga(mangaId).asRxSingle(),
|
||||||
db.getChaptersByMangaId(mangaId).asRxSingle()
|
db.getChaptersByMangaId(mangaId).asRxSingle()
|
||||||
) { manga, chapters ->
|
) { manga, chapters ->
|
||||||
ChapterChain(manga, chapters)
|
ChapterChain(manga, chapters)
|
||||||
}.toObservable().filter {
|
}.toObservable().filter {
|
||||||
it.manga.source == sourceId
|
it.manga.source == sourceId
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}.toList()
|
}.toList()
|
||||||
|
|
||||||
@ -66,65 +68,66 @@ class EHentaiUpdateHelper(context: Context) {
|
|||||||
|
|
||||||
// Copy chain chapters to curChapters
|
// Copy chain chapters to curChapters
|
||||||
val newChapters = toDiscard
|
val newChapters = toDiscard
|
||||||
.flatMap { chain ->
|
.flatMap { chain ->
|
||||||
val meta by lazy {
|
val meta by lazy {
|
||||||
db.getFlatMetadataForManga(chain.manga.id!!)
|
db.getFlatMetadataForManga(chain.manga.id!!)
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
?.raise<EHentaiSearchMetadata>()
|
?.raise<EHentaiSearchMetadata>()
|
||||||
}
|
|
||||||
|
|
||||||
chain.chapters.map { chapter ->
|
|
||||||
// Convert old style chapters to new style chapters if possible
|
|
||||||
if (chapter.date_upload <= 0 &&
|
|
||||||
meta?.datePosted != null &&
|
|
||||||
meta?.title != null) {
|
|
||||||
chapter.name = meta!!.title!!
|
|
||||||
chapter.date_upload = meta!!.datePosted!!
|
|
||||||
}
|
|
||||||
chapter
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.fold(accepted.chapters) { curChapters, chapter ->
|
|
||||||
val existing = curChapters.find { it.url == chapter.url }
|
|
||||||
|
|
||||||
val newLastPageRead = chainsAsChapters.maxBy { it.last_page_read }?.last_page_read
|
chain.chapters.map { chapter ->
|
||||||
|
// Convert old style chapters to new style chapters if possible
|
||||||
if (existing != null) {
|
if (chapter.date_upload <= 0 &&
|
||||||
existing.read = existing.read || chapter.read
|
meta?.datePosted != null &&
|
||||||
existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read)
|
meta?.title != null
|
||||||
if (newLastPageRead != null && existing.last_page_read <= 0) {
|
) {
|
||||||
existing.last_page_read = newLastPageRead
|
chapter.name = meta!!.title!!
|
||||||
}
|
chapter.date_upload = meta!!.datePosted!!
|
||||||
existing.bookmark = existing.bookmark || chapter.bookmark
|
|
||||||
curChapters
|
|
||||||
} else if (chapter.date_upload > 0) { // Ignore chapters using the old system
|
|
||||||
new = true
|
|
||||||
curChapters + ChapterImpl().apply {
|
|
||||||
manga_id = accepted.manga.id
|
|
||||||
url = chapter.url
|
|
||||||
name = chapter.name
|
|
||||||
read = chapter.read
|
|
||||||
bookmark = chapter.bookmark
|
|
||||||
|
|
||||||
last_page_read = chapter.last_page_read
|
|
||||||
if (newLastPageRead != null && last_page_read <= 0) {
|
|
||||||
last_page_read = newLastPageRead
|
|
||||||
}
|
|
||||||
|
|
||||||
date_fetch = chapter.date_fetch
|
|
||||||
date_upload = chapter.date_upload
|
|
||||||
}
|
|
||||||
} else curChapters
|
|
||||||
}
|
|
||||||
.filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert)
|
|
||||||
.sortedBy { it.date_upload }
|
|
||||||
.apply {
|
|
||||||
mapIndexed { index, chapter ->
|
|
||||||
chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ")
|
|
||||||
chapter.chapter_number = index + 1f
|
|
||||||
chapter.source_order = lastIndex - index
|
|
||||||
}
|
}
|
||||||
|
chapter
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
.fold(accepted.chapters) { curChapters, chapter ->
|
||||||
|
val existing = curChapters.find { it.url == chapter.url }
|
||||||
|
|
||||||
|
val newLastPageRead = chainsAsChapters.maxBy { it.last_page_read }?.last_page_read
|
||||||
|
|
||||||
|
if (existing != null) {
|
||||||
|
existing.read = existing.read || chapter.read
|
||||||
|
existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read)
|
||||||
|
if (newLastPageRead != null && existing.last_page_read <= 0) {
|
||||||
|
existing.last_page_read = newLastPageRead
|
||||||
|
}
|
||||||
|
existing.bookmark = existing.bookmark || chapter.bookmark
|
||||||
|
curChapters
|
||||||
|
} else if (chapter.date_upload > 0) { // Ignore chapters using the old system
|
||||||
|
new = true
|
||||||
|
curChapters + ChapterImpl().apply {
|
||||||
|
manga_id = accepted.manga.id
|
||||||
|
url = chapter.url
|
||||||
|
name = chapter.name
|
||||||
|
read = chapter.read
|
||||||
|
bookmark = chapter.bookmark
|
||||||
|
|
||||||
|
last_page_read = chapter.last_page_read
|
||||||
|
if (newLastPageRead != null && last_page_read <= 0) {
|
||||||
|
last_page_read = newLastPageRead
|
||||||
|
}
|
||||||
|
|
||||||
|
date_fetch = chapter.date_fetch
|
||||||
|
date_upload = chapter.date_upload
|
||||||
|
}
|
||||||
|
} else curChapters
|
||||||
|
}
|
||||||
|
.filter { it.date_upload > 0 } // Ignore chapters using the old system (filter after to prevent dupes from insert)
|
||||||
|
.sortedBy { it.date_upload }
|
||||||
|
.apply {
|
||||||
|
mapIndexed { index, chapter ->
|
||||||
|
chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ")
|
||||||
|
chapter.chapter_number = index + 1f
|
||||||
|
chapter.source_order = lastIndex - index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
toDiscard.forEach { it.manga.favorite = false }
|
toDiscard.forEach { it.manga.favorite = false }
|
||||||
accepted.manga.favorite = true
|
accepted.manga.favorite = true
|
||||||
@ -165,8 +168,8 @@ data class GalleryEntry(val gId: String, val gToken: String) {
|
|||||||
override fun read(string: String): GalleryEntry {
|
override fun read(string: String): GalleryEntry {
|
||||||
val colonIndex = string.indexOf(':')
|
val colonIndex = string.indexOf(':')
|
||||||
return GalleryEntry(
|
return GalleryEntry(
|
||||||
string.substring(0, colonIndex),
|
string.substring(0, colonIndex),
|
||||||
string.substring(colonIndex + 1, string.length)
|
string.substring(colonIndex + 1, string.length)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -137,17 +137,19 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
logger.d("Filtering manga and raising metadata...")
|
logger.d("Filtering manga and raising metadata...")
|
||||||
val curTime = System.currentTimeMillis()
|
val curTime = System.currentTimeMillis()
|
||||||
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||||
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID)
|
if (manga.source != EH_SOURCE_ID && manga.source != EXH_SOURCE_ID) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await()
|
val meta = db.getFlatMetadataForManga(manga.id!!).asRxSingle().await()
|
||||||
?: return@mapNotNull null
|
?: return@mapNotNull null
|
||||||
|
|
||||||
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
|
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
|
||||||
|
|
||||||
// Don't update galleries too frequently
|
// Don't update galleries too frequently
|
||||||
if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled))
|
if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled)) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
|
}
|
||||||
|
|
||||||
val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minBy {
|
val chapter = db.getChaptersByMangaId(manga.id!!).asRxSingle().await().minBy {
|
||||||
it.date_upload
|
it.date_upload
|
||||||
@ -172,13 +174,15 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.d("Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...",
|
logger.d(
|
||||||
index,
|
"Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...",
|
||||||
manga.id,
|
index,
|
||||||
meta.gId,
|
manga.id,
|
||||||
meta.gToken,
|
meta.gId,
|
||||||
failuresThisIteration,
|
meta.gToken,
|
||||||
modifiedThisIteration.size)
|
failuresThisIteration,
|
||||||
|
modifiedThisIteration.size
|
||||||
|
)
|
||||||
|
|
||||||
if (manga.id in modifiedThisIteration) {
|
if (manga.id in modifiedThisIteration) {
|
||||||
// We already processed this manga!
|
// We already processed this manga!
|
||||||
@ -194,32 +198,37 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
failuresThisIteration++
|
failuresThisIteration++
|
||||||
|
|
||||||
logger.e("> Network error while updating gallery!", e)
|
logger.e("> Network error while updating gallery!", e)
|
||||||
logger.e("> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)",
|
logger.e(
|
||||||
manga.id,
|
"> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)",
|
||||||
meta.gId,
|
manga.id,
|
||||||
meta.gToken,
|
meta.gId,
|
||||||
failuresThisIteration)
|
meta.gToken,
|
||||||
|
failuresThisIteration
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chapters.isEmpty()) {
|
if (chapters.isEmpty()) {
|
||||||
logger.e("No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!",
|
logger.e(
|
||||||
manga.id,
|
"No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!",
|
||||||
meta.gId,
|
manga.id,
|
||||||
meta.gToken,
|
meta.gId,
|
||||||
failuresThisIteration)
|
meta.gToken,
|
||||||
|
failuresThisIteration
|
||||||
|
)
|
||||||
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find accepted root and discard others
|
// Find accepted root and discard others
|
||||||
val (acceptedRoot, discardedRoots, hasNew) =
|
val (acceptedRoot, discardedRoots, hasNew) =
|
||||||
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await()
|
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).await()
|
||||||
|
|
||||||
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
|
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
|
||||||
(hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })) {
|
(hasNew && updatedManga.none { it.id == acceptedRoot.manga.id })
|
||||||
|
) {
|
||||||
updatedManga += acceptedRoot.manga
|
updatedManga += acceptedRoot.manga
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -229,13 +238,13 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
prefs.eh_autoUpdateStats().set(
|
prefs.eh_autoUpdateStats().set(
|
||||||
gson.toJson(
|
gson.toJson(
|
||||||
EHentaiUpdaterStats(
|
EHentaiUpdaterStats(
|
||||||
startTime,
|
startTime,
|
||||||
allMeta.size,
|
allMeta.size,
|
||||||
updatedThisIteration
|
updatedThisIteration
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
if (updatedManga.isNotEmpty()) {
|
if (updatedManga.isNotEmpty()) {
|
||||||
@ -247,7 +256,7 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
// New, current
|
// New, current
|
||||||
suspend fun updateEntryAndGetChapters(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
|
suspend fun updateEntryAndGetChapters(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
|
||||||
val source = sourceManager.get(manga.source) as? EHentai
|
val source = sourceManager.get(manga.source) as? EHentai
|
||||||
?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!"))
|
?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!"))
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val updatedManga = source.fetchMangaDetails(manga).toSingle().await(Schedulers.io())
|
val updatedManga = source.fetchMangaDetails(manga).toSingle().await(Schedulers.io())
|
||||||
@ -288,8 +297,10 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
|
|
||||||
private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder {
|
private fun Context.baseBackgroundJobInfo(isTest: Boolean): JobInfo.Builder {
|
||||||
return JobInfo.Builder(
|
return JobInfo.Builder(
|
||||||
if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST
|
if (isTest) JOB_ID_UPDATE_BACKGROUND_TEST
|
||||||
else JOB_ID_UPDATE_BACKGROUND, componentName())
|
else JOB_ID_UPDATE_BACKGROUND,
|
||||||
|
componentName()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.periodicBackgroundJobInfo(
|
private fun Context.periodicBackgroundJobInfo(
|
||||||
@ -298,29 +309,32 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
requireUnmetered: Boolean
|
requireUnmetered: Boolean
|
||||||
): JobInfo {
|
): JobInfo {
|
||||||
return baseBackgroundJobInfo(false)
|
return baseBackgroundJobInfo(false)
|
||||||
.setPeriodic(period)
|
.setPeriodic(period)
|
||||||
.setPersisted(true)
|
.setPersisted(true)
|
||||||
.setRequiredNetworkType(
|
.setRequiredNetworkType(
|
||||||
if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED
|
if (requireUnmetered) JobInfo.NETWORK_TYPE_UNMETERED
|
||||||
else JobInfo.NETWORK_TYPE_ANY)
|
else JobInfo.NETWORK_TYPE_ANY
|
||||||
.apply {
|
)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
.apply {
|
||||||
setRequiresBatteryNotLow(true)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
}
|
setRequiresBatteryNotLow(true)
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
|
||||||
setEstimatedNetworkBytes(15000L * UPDATES_PER_ITERATION,
|
|
||||||
1000L * UPDATES_PER_ITERATION)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.setRequiresCharging(requireCharging)
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
|
setEstimatedNetworkBytes(
|
||||||
|
15000L * UPDATES_PER_ITERATION,
|
||||||
|
1000L * UPDATES_PER_ITERATION
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.setRequiresCharging(requireCharging)
|
||||||
// .setRequiresDeviceIdle(true) Job never seems to run with this
|
// .setRequiresDeviceIdle(true) Job never seems to run with this
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Context.testBackgroundJobInfo(): JobInfo {
|
private fun Context.testBackgroundJobInfo(): JobInfo {
|
||||||
return baseBackgroundJobInfo(true)
|
return baseBackgroundJobInfo(true)
|
||||||
.setOverrideDeadline(1)
|
.setOverrideDeadline(1)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchBackgroundTest(context: Context) {
|
fun launchBackgroundTest(context: Context) {
|
||||||
@ -343,9 +357,9 @@ class EHentaiUpdateWorker : JobService(), CoroutineScope {
|
|||||||
val wifiRestriction = "wifi" in restrictions
|
val wifiRestriction = "wifi" in restrictions
|
||||||
|
|
||||||
val jobInfo = context.periodicBackgroundJobInfo(
|
val jobInfo = context.periodicBackgroundJobInfo(
|
||||||
interval.hours.inMilliseconds.longValue,
|
interval.hours.inMilliseconds.longValue,
|
||||||
acRestriction,
|
acRestriction,
|
||||||
wifiRestriction
|
wifiRestriction
|
||||||
)
|
)
|
||||||
|
|
||||||
if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
|
if (context.jobScheduler.schedule(jobInfo) == JobScheduler.RESULT_FAILURE) {
|
||||||
|
@ -10,15 +10,16 @@ class FavoritesIntroDialog {
|
|||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
fun show(context: Context) = MaterialDialog(context)
|
fun show(context: Context) = MaterialDialog(context)
|
||||||
.title(text = "IMPORTANT FAVORITES SYNC NOTES")
|
.title(text = "IMPORTANT FAVORITES SYNC NOTES")
|
||||||
.message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY))
|
.message(text = HtmlCompat.fromHtml(FAVORITES_INTRO_TEXT, HtmlCompat.FROM_HTML_MODE_LEGACY))
|
||||||
.positiveButton(android.R.string.ok) {
|
.positiveButton(android.R.string.ok) {
|
||||||
prefs.eh_showSyncIntro().set(false)
|
prefs.eh_showSyncIntro().set(false)
|
||||||
}
|
}
|
||||||
.cancelable(false)
|
.cancelable(false)
|
||||||
.show()
|
.show()
|
||||||
|
|
||||||
private val FAVORITES_INTRO_TEXT = """
|
private val FAVORITES_INTRO_TEXT =
|
||||||
|
"""
|
||||||
1. Changes to category names in the app are <b>NOT</b> synced! Please <i>change the category names on ExHentai instead</i>. The category names will be copied from the ExHentai servers every sync.
|
1. Changes to category names in the app are <b>NOT</b> synced! Please <i>change the category names on ExHentai instead</i>. The category names will be copied from the ExHentai servers every sync.
|
||||||
<br><br>
|
<br><br>
|
||||||
2. The favorite categories on ExHentai correspond to the <b>first 10 categories in the app</b> (excluding the 'Default' category). <i>Galleries in other categories will <b>NOT</b> be synced!</i>
|
2. The favorite categories on ExHentai correspond to the <b>first 10 categories in the app</b> (excluding the 'Default' category). <i>Galleries in other categories will <b>NOT</b> be synced!</i>
|
||||||
@ -30,5 +31,5 @@ class FavoritesIntroDialog {
|
|||||||
5. <b>Do NOT put favorites in multiple categories</b> (the app supports this). This can confuse the sync algorithm as ExHentai only allows each favorite to be in one category.
|
5. <b>Do NOT put favorites in multiple categories</b> (the app supports this). This can confuse the sync algorithm as ExHentai only allows each favorite to be in one category.
|
||||||
<br><br>
|
<br><br>
|
||||||
This dialog will only popup once. You can read these notes again by going to 'Settings > E-Hentai > Show favorites sync notes'.
|
This dialog will only popup once. You can read these notes again by going to 'Settings > E-Hentai > Show favorites sync notes'.
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
private val exh by lazy {
|
private val exh by lazy {
|
||||||
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
|
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
|
||||||
?: EHentai(0, true, context)
|
?: EHentai(0, true, context)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val storage = LocalFavoritesStorage()
|
private val storage = LocalFavoritesStorage()
|
||||||
@ -82,8 +82,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
if (it.id in seenManga) {
|
if (it.id in seenManga) {
|
||||||
val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
|
val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
|
||||||
status.onNext(FavoritesSyncStatus.BadLibraryState
|
status.onNext(
|
||||||
.MangaInMultipleCategories(it, inCategories))
|
FavoritesSyncStatus.BadLibraryState
|
||||||
|
.MangaInMultipleCategories(it, inCategories)
|
||||||
|
)
|
||||||
logger.w("Manga %s is in multiple categories!", it.id)
|
logger.w("Manga %s is in multiple categories!", it.id)
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@ -107,13 +109,17 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Take wake + wifi locks
|
// Take wake + wifi locks
|
||||||
ignore { wakeLock?.release() }
|
ignore { wakeLock?.release() }
|
||||||
wakeLock = ignore {
|
wakeLock = ignore {
|
||||||
context.powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
|
context.powerManager.newWakeLock(
|
||||||
"teh:ExhFavoritesSyncWakelock")
|
PowerManager.PARTIAL_WAKE_LOCK,
|
||||||
|
"teh:ExhFavoritesSyncWakelock"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ignore { wifiLock?.release() }
|
ignore { wifiLock?.release() }
|
||||||
wifiLock = ignore {
|
wifiLock = ignore {
|
||||||
context.wifiManager.createWifiLock(WifiManager.WIFI_MODE_FULL,
|
context.wifiManager.createWifiLock(
|
||||||
"teh:ExhFavoritesSyncWifi")
|
WifiManager.WIFI_MODE_FULL,
|
||||||
|
"teh:ExhFavoritesSyncWifi"
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Do not update galleries while syncing favorites
|
// Do not update galleries while syncing favorites
|
||||||
@ -137,8 +143,9 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
|
|
||||||
// Apply change sets
|
// Apply change sets
|
||||||
applyChangeSetToLocal(errorList, remoteChanges)
|
applyChangeSetToLocal(errorList, remoteChanges)
|
||||||
if (localChanges != null)
|
if (localChanges != null) {
|
||||||
applyChangeSetToRemote(errorList, localChanges)
|
applyChangeSetToRemote(errorList, localChanges)
|
||||||
|
}
|
||||||
|
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Cleaning up"))
|
status.onNext(FavoritesSyncStatus.Processing("Cleaning up"))
|
||||||
storage.snapshotEntries(realm)
|
storage.snapshotEntries(realm)
|
||||||
@ -173,10 +180,11 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
EHentaiUpdateWorker.scheduleBackground(context)
|
EHentaiUpdateWorker.scheduleBackground(context)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (errorList.isEmpty())
|
if (errorList.isEmpty()) {
|
||||||
status.onNext(FavoritesSyncStatus.Idle())
|
status.onNext(FavoritesSyncStatus.Idle())
|
||||||
else
|
} else {
|
||||||
status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList))
|
status.onNext(FavoritesSyncStatus.CompleteWithErrors(errorList))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun applyRemoteCategories(errorList: MutableList<String>, categories: List<String>) {
|
private fun applyRemoteCategories(errorList: MutableList<String>, categories: List<String>) {
|
||||||
@ -217,22 +225,25 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Only insert categories if changed
|
// Only insert categories if changed
|
||||||
if (changed)
|
if (changed) {
|
||||||
db.insertCategories(newLocalCategories).executeAsBlocking()
|
db.insertCategories(newLocalCategories).executeAsBlocking()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
|
private fun addGalleryRemote(errorList: MutableList<String>, gallery: FavoriteEntry) {
|
||||||
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
val url = "${exh.baseUrl}/gallerypopups.php?gid=${gallery.gid}&t=${gallery.token}&act=addfav"
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.post(FormBody.Builder()
|
.post(
|
||||||
.add("favcat", gallery.category.toString())
|
FormBody.Builder()
|
||||||
.add("favnote", "")
|
.add("favcat", gallery.category.toString())
|
||||||
.add("apply", "Add to Favorites")
|
.add("favnote", "")
|
||||||
.add("update", "1")
|
.add("apply", "Add to Favorites")
|
||||||
.build())
|
.add("update", "1")
|
||||||
.build()
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
|
||||||
if (!explicitlyRetryExhRequest(10, request)) {
|
if (!explicitlyRetryExhRequest(10, request)) {
|
||||||
val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!"
|
val errorString = "Unable to add gallery to remote server: '${gallery.title}' (GID: ${gallery.gid})!"
|
||||||
@ -271,8 +282,8 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
status.onNext(FavoritesSyncStatus.Processing("Removing ${changeSet.removed.size} galleries from remote server"))
|
status.onNext(FavoritesSyncStatus.Processing("Removing ${changeSet.removed.size} galleries from remote server"))
|
||||||
|
|
||||||
val formBody = FormBody.Builder()
|
val formBody = FormBody.Builder()
|
||||||
.add("ddact", "delete")
|
.add("ddact", "delete")
|
||||||
.add("apply", "Apply")
|
.add("apply", "Apply")
|
||||||
|
|
||||||
// Add change set to form
|
// Add change set to form
|
||||||
changeSet.removed.forEach {
|
changeSet.removed.forEach {
|
||||||
@ -280,9 +291,9 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url("https://exhentai.org/favorites.php")
|
.url("https://exhentai.org/favorites.php")
|
||||||
.post(formBody.build())
|
.post(formBody.build())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
if (!explicitlyRetryExhRequest(10, request)) {
|
if (!explicitlyRetryExhRequest(10, request)) {
|
||||||
val errorString = "Unable to delete galleries from the remote servers!"
|
val errorString = "Unable to delete galleries from the remote servers!"
|
||||||
@ -299,8 +310,12 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to remote server",
|
status.onNext(
|
||||||
needWarnThrottle()))
|
FavoritesSyncStatus.Processing(
|
||||||
|
"Adding gallery ${index + 1} of ${changeSet.added.size} to remote server",
|
||||||
|
needWarnThrottle()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
throttleManager.throttle()
|
throttleManager.throttle()
|
||||||
|
|
||||||
@ -317,8 +332,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
val url = it.getUrl()
|
val url = it.getUrl()
|
||||||
|
|
||||||
// Consider both EX and EH sources
|
// Consider both EX and EH sources
|
||||||
listOf(db.getManga(url, EXH_SOURCE_ID),
|
listOf(
|
||||||
db.getManga(url, EH_SOURCE_ID)).forEach {
|
db.getManga(url, EXH_SOURCE_ID),
|
||||||
|
db.getManga(url, EH_SOURCE_ID)
|
||||||
|
).forEach {
|
||||||
val manga = it.executeAsBlocking()
|
val manga = it.executeAsBlocking()
|
||||||
|
|
||||||
if (manga?.favorite == true) {
|
if (manga?.favorite == true) {
|
||||||
@ -340,16 +357,22 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
// Apply additions
|
// Apply additions
|
||||||
throttleManager.resetThrottle()
|
throttleManager.resetThrottle()
|
||||||
changeSet.added.forEachIndexed { index, it ->
|
changeSet.added.forEachIndexed { index, it ->
|
||||||
status.onNext(FavoritesSyncStatus.Processing("Adding gallery ${index + 1} of ${changeSet.added.size} to local library",
|
status.onNext(
|
||||||
needWarnThrottle()))
|
FavoritesSyncStatus.Processing(
|
||||||
|
"Adding gallery ${index + 1} of ${changeSet.added.size} to local library",
|
||||||
|
needWarnThrottle()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
throttleManager.throttle()
|
throttleManager.throttle()
|
||||||
|
|
||||||
// Import using gallery adder
|
// Import using gallery adder
|
||||||
val result = galleryAdder.addGallery("${exh.baseUrl}${it.getUrl()}",
|
val result = galleryAdder.addGallery(
|
||||||
true,
|
"${exh.baseUrl}${it.getUrl()}",
|
||||||
exh,
|
true,
|
||||||
throttleManager::throttle)
|
exh,
|
||||||
|
throttleManager::throttle
|
||||||
|
)
|
||||||
|
|
||||||
if (result is GalleryAddEvent.Fail) {
|
if (result is GalleryAddEvent.Fail) {
|
||||||
if (result is GalleryAddEvent.Fail.NotFound) {
|
if (result is GalleryAddEvent.Fail.NotFound) {
|
||||||
@ -370,8 +393,10 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
throw IgnoredException()
|
throw IgnoredException()
|
||||||
}
|
}
|
||||||
} else if (result is GalleryAddEvent.Success) {
|
} else if (result is GalleryAddEvent.Success) {
|
||||||
insertedMangaCategories += MangaCategory.create(result.manga,
|
insertedMangaCategories += MangaCategory.create(
|
||||||
categories[it.category]) to result.manga
|
result.manga,
|
||||||
|
categories[it.category]
|
||||||
|
) to result.manga
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -379,12 +404,12 @@ class FavoritesSyncHelper(val context: Context) {
|
|||||||
insertedMangaCategories.chunked(10).map {
|
insertedMangaCategories.chunked(10).map {
|
||||||
Pair(it.map { it.first }, it.map { it.second })
|
Pair(it.map { it.first }, it.map { it.second })
|
||||||
}.forEach {
|
}.forEach {
|
||||||
db.setMangaCategories(it.first, it.second)
|
db.setMangaCategories(it.first, it.second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun needWarnThrottle() =
|
fun needWarnThrottle() =
|
||||||
throttleManager.throttleTime >= THROTTLE_WARN
|
throttleManager.throttleTime >= THROTTLE_WARN
|
||||||
|
|
||||||
class IgnoredException : RuntimeException()
|
class IgnoredException : RuntimeException()
|
||||||
|
|
||||||
@ -401,12 +426,15 @@ sealed class FavoritesSyncStatus(val message: String) {
|
|||||||
val manga: Manga,
|
val manga: Manga,
|
||||||
val categories: List<Category>
|
val categories: List<Category>
|
||||||
) :
|
) :
|
||||||
BadLibraryState("The gallery: ${manga.title} is in more than one category (${categories.joinToString { it.name }})!")
|
BadLibraryState("The gallery: ${manga.title} is in more than one category (${categories.joinToString { it.name }})!")
|
||||||
}
|
}
|
||||||
class Initializing : FavoritesSyncStatus("Initializing sync")
|
class Initializing : FavoritesSyncStatus("Initializing sync")
|
||||||
class Processing(message: String, isThrottle: Boolean = false) : FavoritesSyncStatus(if (isThrottle)
|
class Processing(message: String, isThrottle: Boolean = false) : FavoritesSyncStatus(
|
||||||
"$message\n\nSync is currently throttling (to avoid being banned from ExHentai) and may take a long time to complete."
|
if (isThrottle) {
|
||||||
else
|
"$message\n\nSync is currently throttling (to avoid being banned from ExHentai) and may take a long time to complete."
|
||||||
message)
|
} else {
|
||||||
|
message
|
||||||
|
}
|
||||||
|
)
|
||||||
class CompleteWithErrors(messages: List<String>) : FavoritesSyncStatus(messages.joinToString("\n"))
|
class CompleteWithErrors(messages: List<String>) : FavoritesSyncStatus(messages.joinToString("\n"))
|
||||||
}
|
}
|
||||||
|
@ -14,41 +14,46 @@ class LocalFavoritesStorage {
|
|||||||
private val db: DatabaseHelper by injectLazy()
|
private val db: DatabaseHelper by injectLazy()
|
||||||
|
|
||||||
private val realmConfig = RealmConfiguration.Builder()
|
private val realmConfig = RealmConfiguration.Builder()
|
||||||
.name("fav-sync")
|
.name("fav-sync")
|
||||||
.deleteRealmIfMigrationNeeded()
|
.deleteRealmIfMigrationNeeded()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
fun getRealm() = Realm.getInstance(realmConfig)
|
fun getRealm() = Realm.getInstance(realmConfig)
|
||||||
|
|
||||||
fun getChangedDbEntries(realm: Realm) =
|
fun getChangedDbEntries(realm: Realm) =
|
||||||
getChangedEntries(realm,
|
getChangedEntries(
|
||||||
|
realm,
|
||||||
parseToFavoriteEntries(
|
parseToFavoriteEntries(
|
||||||
loadDbCategories(
|
loadDbCategories(
|
||||||
db.getFavoriteMangas()
|
db.getFavoriteMangas()
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) =
|
fun getChangedRemoteEntries(realm: Realm, entries: List<EHentai.ParsedManga>) =
|
||||||
getChangedEntries(realm,
|
getChangedEntries(
|
||||||
|
realm,
|
||||||
parseToFavoriteEntries(
|
parseToFavoriteEntries(
|
||||||
entries.asSequence().map {
|
entries.asSequence().map {
|
||||||
Pair(it.fav, it.manga.apply {
|
Pair(
|
||||||
|
it.fav,
|
||||||
|
it.manga.apply {
|
||||||
favorite = true
|
favorite = true
|
||||||
})
|
}
|
||||||
}
|
)
|
||||||
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
fun snapshotEntries(realm: Realm) {
|
fun snapshotEntries(realm: Realm) {
|
||||||
val dbMangas = parseToFavoriteEntries(
|
val dbMangas = parseToFavoriteEntries(
|
||||||
loadDbCategories(
|
loadDbCategories(
|
||||||
db.getFavoriteMangas()
|
db.getFavoriteMangas()
|
||||||
.executeAsBlocking()
|
.executeAsBlocking()
|
||||||
.asSequence()
|
.asSequence()
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
// Delete old snapshot
|
// Delete old snapshot
|
||||||
@ -70,29 +75,29 @@ class LocalFavoritesStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val removed = realm.where(FavoriteEntry::class.java)
|
val removed = realm.where(FavoriteEntry::class.java)
|
||||||
.findAll()
|
.findAll()
|
||||||
.filter {
|
.filter {
|
||||||
queryListForEntry(terminated, it) == null
|
queryListForEntry(terminated, it) == null
|
||||||
}.map {
|
}.map {
|
||||||
realm.copyFromRealm(it)
|
realm.copyFromRealm(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ChangeSet(added, removed)
|
return ChangeSet(added, removed)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun Realm.queryRealmForEntry(entry: FavoriteEntry) =
|
private fun Realm.queryRealmForEntry(entry: FavoriteEntry) =
|
||||||
where(FavoriteEntry::class.java)
|
where(FavoriteEntry::class.java)
|
||||||
.equalTo(FavoriteEntry::gid.name, entry.gid)
|
.equalTo(FavoriteEntry::gid.name, entry.gid)
|
||||||
.equalTo(FavoriteEntry::token.name, entry.token)
|
.equalTo(FavoriteEntry::token.name, entry.token)
|
||||||
.equalTo(FavoriteEntry::category.name, entry.category)
|
.equalTo(FavoriteEntry::category.name, entry.category)
|
||||||
.findFirst()
|
.findFirst()
|
||||||
|
|
||||||
private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) =
|
private fun queryListForEntry(list: List<FavoriteEntry>, entry: FavoriteEntry) =
|
||||||
list.find {
|
list.find {
|
||||||
it.gid == entry.gid &&
|
it.gid == entry.gid &&
|
||||||
it.token == entry.token &&
|
it.token == entry.token &&
|
||||||
it.category == entry.category
|
it.category == entry.category
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> {
|
private fun loadDbCategories(manga: Sequence<Manga>): Sequence<Pair<Int, Manga>> {
|
||||||
val dbCategories = db.getCategories().executeAsBlocking()
|
val dbCategories = db.getCategories().executeAsBlocking()
|
||||||
@ -100,28 +105,34 @@ class LocalFavoritesStorage {
|
|||||||
return manga.filter(this::validateDbManga).mapNotNull {
|
return manga.filter(this::validateDbManga).mapNotNull {
|
||||||
val category = db.getCategoriesForManga(it).executeAsBlocking()
|
val category = db.getCategoriesForManga(it).executeAsBlocking()
|
||||||
|
|
||||||
Pair(dbCategories.indexOf(category.firstOrNull()
|
Pair(
|
||||||
?: return@mapNotNull null), it)
|
dbCategories.indexOf(
|
||||||
|
category.firstOrNull()
|
||||||
|
?: return@mapNotNull null
|
||||||
|
),
|
||||||
|
it
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) =
|
private fun parseToFavoriteEntries(manga: Sequence<Pair<Int, Manga>>) =
|
||||||
manga.filter {
|
manga.filter {
|
||||||
validateDbManga(it.second)
|
validateDbManga(it.second)
|
||||||
}.mapNotNull {
|
}.mapNotNull {
|
||||||
FavoriteEntry().apply {
|
FavoriteEntry().apply {
|
||||||
title = it.second.title
|
title = it.second.title
|
||||||
gid = EHentaiSearchMetadata.galleryId(it.second.url)
|
gid = EHentaiSearchMetadata.galleryId(it.second.url)
|
||||||
token = EHentaiSearchMetadata.galleryToken(it.second.url)
|
token = EHentaiSearchMetadata.galleryToken(it.second.url)
|
||||||
category = it.first
|
category = it.first
|
||||||
|
|
||||||
if (this.category > MAX_CATEGORIES)
|
if (this.category > MAX_CATEGORIES) {
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun validateDbManga(manga: Manga) =
|
private fun validateDbManga(manga: Manga) =
|
||||||
manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID)
|
manga.favorite && (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val MAX_CATEGORIES = 9
|
const val MAX_CATEGORIES = 9
|
||||||
|
@ -71,39 +71,43 @@ class HitomiNozomi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getGalleryIdsFromData(data: DataPair?): Single<List<Int>> {
|
private fun getGalleryIdsFromData(data: DataPair?): Single<List<Int>> {
|
||||||
if (data == null)
|
if (data == null) {
|
||||||
return Single.just(emptyList())
|
return Single.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data"
|
val url = "$LTN_BASE_URL/$GALLERIES_INDEX_DIR/galleries.$galleriesIndexVersion.data"
|
||||||
val (offset, length) = data
|
val (offset, length) = data
|
||||||
if (length > 100000000 || length <= 0)
|
if (length > 100000000 || length <= 0) {
|
||||||
return Single.just(emptyList())
|
return Single.just(emptyList())
|
||||||
|
}
|
||||||
|
|
||||||
return client.newCall(rangedGet(url, offset, offset + length - 1))
|
return client.newCall(rangedGet(url, offset, offset + length - 1))
|
||||||
.asObservable()
|
.asObservable()
|
||||||
.map {
|
.map {
|
||||||
it.body?.bytes() ?: ByteArray(0)
|
it.body?.bytes() ?: ByteArray(0)
|
||||||
|
}
|
||||||
|
.onErrorReturn { ByteArray(0) }
|
||||||
|
.map { inbuf ->
|
||||||
|
if (inbuf.isEmpty()) {
|
||||||
|
return@map emptyList<Int>()
|
||||||
}
|
}
|
||||||
.onErrorReturn { ByteArray(0) }
|
|
||||||
.map { inbuf ->
|
|
||||||
if (inbuf.isEmpty())
|
|
||||||
return@map emptyList<Int>()
|
|
||||||
|
|
||||||
val view = ByteCursor(inbuf)
|
val view = ByteCursor(inbuf)
|
||||||
val numberOfGalleryIds = view.nextInt()
|
val numberOfGalleryIds = view.nextInt()
|
||||||
|
|
||||||
val expectedLength = numberOfGalleryIds * 4 + 4
|
val expectedLength = numberOfGalleryIds * 4 + 4
|
||||||
|
|
||||||
if (numberOfGalleryIds > 10000000 ||
|
if (numberOfGalleryIds > 10000000 ||
|
||||||
numberOfGalleryIds <= 0 ||
|
numberOfGalleryIds <= 0 ||
|
||||||
inbuf.size != expectedLength) {
|
inbuf.size != expectedLength
|
||||||
return@map emptyList<Int>()
|
) {
|
||||||
}
|
return@map emptyList<Int>()
|
||||||
|
}
|
||||||
|
|
||||||
(1..numberOfGalleryIds).map {
|
(1..numberOfGalleryIds).map {
|
||||||
view.nextInt()
|
view.nextInt()
|
||||||
}
|
}
|
||||||
}.toSingle()
|
}.toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BSearch(field: String, key: ByteArray, node: Node?): Single<DataPair?> {
|
private fun BSearch(field: String, key: ByteArray, node: Node?): Single<DataPair?> {
|
||||||
@ -112,10 +116,11 @@ class HitomiNozomi(
|
|||||||
for (i in 0 until top) {
|
for (i in 0 until top) {
|
||||||
val dv1i = dv1[i].toInt() and 0xFF
|
val dv1i = dv1[i].toInt() and 0xFF
|
||||||
val dv2i = dv2[i].toInt() and 0xFF
|
val dv2i = dv2[i].toInt() and 0xFF
|
||||||
if (dv1i < dv2i)
|
if (dv1i < dv2i) {
|
||||||
return -1
|
return -1
|
||||||
else if (dv1i > dv2i)
|
} else if (dv1i > dv2i) {
|
||||||
return 1
|
return 1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@ -185,16 +190,16 @@ class HitomiNozomi(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return client.newCall(rangedGet(url, address, address + MAX_NODE_SIZE - 1))
|
return client.newCall(rangedGet(url, address, address + MAX_NODE_SIZE - 1))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map {
|
.map {
|
||||||
it.body?.bytes() ?: ByteArray(0)
|
it.body?.bytes() ?: ByteArray(0)
|
||||||
}
|
}
|
||||||
.onErrorReturn { ByteArray(0) }
|
.onErrorReturn { ByteArray(0) }
|
||||||
.map { nodedata ->
|
.map { nodedata ->
|
||||||
if (nodedata.isNotEmpty()) {
|
if (nodedata.isNotEmpty()) {
|
||||||
decodeNode(nodedata)
|
decodeNode(nodedata)
|
||||||
} else null
|
} else null
|
||||||
}.toSingle()
|
}.toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getGalleryIdsFromNozomi(area: String?, tag: String, language: String): Single<List<Int>> {
|
fun getGalleryIdsFromNozomi(area: String?, tag: String, language: String): Single<List<Int>> {
|
||||||
@ -203,17 +208,19 @@ class HitomiNozomi(
|
|||||||
nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION"
|
nozomiAddress = "$LTN_BASE_URL/$COMPRESSED_NOZOMI_PREFIX/$area/$tag-$language$NOZOMI_EXTENSION"
|
||||||
}
|
}
|
||||||
|
|
||||||
return client.newCall(Request.Builder()
|
return client.newCall(
|
||||||
|
Request.Builder()
|
||||||
.url(nozomiAddress)
|
.url(nozomiAddress)
|
||||||
.build())
|
.build()
|
||||||
.asObservableSuccess()
|
)
|
||||||
.map { resp ->
|
.asObservableSuccess()
|
||||||
val body = resp.body!!.bytes()
|
.map { resp ->
|
||||||
val cursor = ByteCursor(body)
|
val body = resp.body!!.bytes()
|
||||||
(1..body.size / 4).map {
|
val cursor = ByteCursor(body)
|
||||||
cursor.nextInt()
|
(1..body.size / 4).map {
|
||||||
}
|
cursor.nextInt()
|
||||||
}.toSingle()
|
}
|
||||||
|
}.toSingle()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun hashTerm(query: String): HashedTerm {
|
private fun hashTerm(query: String): HashedTerm {
|
||||||
@ -233,15 +240,18 @@ class HitomiNozomi(
|
|||||||
private val HASH_CHARSET = Charsets.UTF_8
|
private val HASH_CHARSET = Charsets.UTF_8
|
||||||
|
|
||||||
fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request {
|
fun rangedGet(url: String, rangeBegin: Long, rangeEnd: Long?): Request {
|
||||||
return GET(url, Headers.Builder()
|
return GET(
|
||||||
|
url,
|
||||||
|
Headers.Builder()
|
||||||
.add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}")
|
.add("Range", "bytes=$rangeBegin-${rangeEnd ?: ""}")
|
||||||
.build())
|
.build()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable<Long> {
|
fun getIndexVersion(httpClient: OkHttpClient, name: String): Observable<Long> {
|
||||||
return httpClient.newCall(GET("$LTN_BASE_URL/$name/version?_=${System.currentTimeMillis()}"))
|
return httpClient.newCall(GET("$LTN_BASE_URL/$name/version?_=${System.currentTimeMillis()}"))
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.map { it.body!!.string().toLong() }
|
.map { it.body!!.string().toLong() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule<String>(n
|
|||||||
override fun createView(root: ViewGroup, textColor: Int, textSize: Float, textAlpha: Float): View {
|
override fun createView(root: ViewGroup, textColor: Int, textSize: Float, textAlpha: Float): View {
|
||||||
val view = LinearLayout(root.context)
|
val view = LinearLayout(root.context)
|
||||||
view.layoutParams = ViewGroup.LayoutParams(
|
view.layoutParams = ViewGroup.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
view.setPadding(4.dpToPx, 0, 4.dpToPx, 4.dpToPx)
|
view.setPadding(4.dpToPx, 0, 4.dpToPx, 4.dpToPx)
|
||||||
val textView = TextView(view.context)
|
val textView = TextView(view.context)
|
||||||
@ -42,15 +42,16 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule<String>(n
|
|||||||
textView.alpha = textAlpha
|
textView.alpha = textAlpha
|
||||||
textView.text = HtmlCompat.fromHtml(buildInfo(), HtmlCompat.FROM_HTML_MODE_LEGACY)
|
textView.text = HtmlCompat.fromHtml(buildInfo(), HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||||
textView.layoutParams = LinearLayout.LayoutParams(
|
textView.layoutParams = LinearLayout.LayoutParams(
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT,
|
ViewGroup.LayoutParams.WRAP_CONTENT,
|
||||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
)
|
)
|
||||||
view.addView(textView)
|
view.addView(textView)
|
||||||
this.textView = textView
|
this.textView = textView
|
||||||
return view
|
return view
|
||||||
}
|
}
|
||||||
|
|
||||||
fun buildInfo() = """
|
fun buildInfo() =
|
||||||
|
"""
|
||||||
<font color='green'>===[ ${context.getString(R.string.app_name)} ]===</font><br>
|
<font color='green'>===[ ${context.getString(R.string.app_name)} ]===</font><br>
|
||||||
<b>Build type:</b> ${BuildConfig.BUILD_TYPE}<br>
|
<b>Build type:</b> ${BuildConfig.BUILD_TYPE}<br>
|
||||||
<b>Debug mode:</b> ${BuildConfig.DEBUG.asEnabledString()}<br>
|
<b>Debug mode:</b> ${BuildConfig.DEBUG.asEnabledString()}<br>
|
||||||
@ -58,7 +59,7 @@ class EHDebugModeOverlay(private val context: Context) : OverlayModule<String>(n
|
|||||||
<b>Commit SHA:</b> ${BuildConfig.COMMIT_SHA}<br>
|
<b>Commit SHA:</b> ${BuildConfig.COMMIT_SHA}<br>
|
||||||
<b>Log level:</b> ${EHLogLevel.currentLogLevel.name.toLowerCase()}<br>
|
<b>Log level:</b> ${EHLogLevel.currentLogLevel.name.toLowerCase()}<br>
|
||||||
<b>Source blacklist:</b> ${prefs.eh_enableSourceBlacklist().get().asEnabledString()}
|
<b>Source blacklist:</b> ${prefs.eh_enableSourceBlacklist().get().asEnabledString()}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
private fun Boolean.asEnabledString() = if (this) "enabled" else "disabled"
|
private fun Boolean.asEnabledString() = if (this) "enabled" else "disabled"
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ enum class EHLogLevel(val description: String) {
|
|||||||
|
|
||||||
fun init(context: Context) {
|
fun init(context: Context) {
|
||||||
curLogLevel = PreferenceManager.getDefaultSharedPreferences(context)
|
curLogLevel = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getInt(PreferenceKeys.eh_logLevel, 0)
|
.getInt(PreferenceKeys.eh_logLevel, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun shouldLog(requiredLogLevel: EHLogLevel): Boolean {
|
fun shouldLog(requiredLogLevel: EHLogLevel): Boolean {
|
||||||
|
@ -35,31 +35,32 @@ fun parseHumanReadableByteCount(arg0: String): Double? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun String?.nullIfBlank(): String? = if (isNullOrBlank())
|
fun String?.nullIfBlank(): String? = if (isNullOrBlank()) {
|
||||||
null
|
null
|
||||||
else
|
} else {
|
||||||
this
|
this
|
||||||
|
}
|
||||||
|
|
||||||
fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) {
|
fun <K, V> Set<Map.Entry<K, V>>.forEach(action: (K, V) -> Unit) {
|
||||||
forEach { action(it.key, it.value) }
|
forEach { action(it.key, it.value) }
|
||||||
}
|
}
|
||||||
|
|
||||||
val ONGOING_SUFFIX = arrayOf(
|
val ONGOING_SUFFIX = arrayOf(
|
||||||
"[ongoing]",
|
"[ongoing]",
|
||||||
"(ongoing)",
|
"(ongoing)",
|
||||||
"{ongoing}",
|
"{ongoing}",
|
||||||
"<ongoing>",
|
"<ongoing>",
|
||||||
"ongoing",
|
"ongoing",
|
||||||
"[incomplete]",
|
"[incomplete]",
|
||||||
"(incomplete)",
|
"(incomplete)",
|
||||||
"{incomplete}",
|
"{incomplete}",
|
||||||
"<incomplete>",
|
"<incomplete>",
|
||||||
"incomplete",
|
"incomplete",
|
||||||
"[wip]",
|
"[wip]",
|
||||||
"(wip)",
|
"(wip)",
|
||||||
"{wip}",
|
"{wip}",
|
||||||
"<wip>",
|
"<wip>",
|
||||||
"wip"
|
"wip"
|
||||||
)
|
)
|
||||||
|
|
||||||
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
val EX_DATE_FORMAT = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.US)
|
||||||
|
@ -50,10 +50,11 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
thumbnailUrl?.let { manga.thumbnail_url = it }
|
thumbnailUrl?.let { manga.thumbnail_url = it }
|
||||||
|
|
||||||
// No title bug?
|
// No title bug?
|
||||||
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault())
|
val titleObj = if (Injekt.get<PreferencesHelper>().useJapaneseTitle().getOrDefault()) {
|
||||||
altTitle ?: title
|
altTitle ?: title
|
||||||
else
|
} else {
|
||||||
title
|
title
|
||||||
|
}
|
||||||
titleObj?.let { manga.title = it }
|
titleObj?.let { manga.title = it }
|
||||||
|
|
||||||
// Set artist (if we can find one)
|
// Set artist (if we can find one)
|
||||||
@ -102,8 +103,8 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -117,24 +118,25 @@ class EHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
private const val EH_ARTIST_NAMESPACE = "artist"
|
private const val EH_ARTIST_NAMESPACE = "artist"
|
||||||
|
|
||||||
private fun splitGalleryUrl(url: String) =
|
private fun splitGalleryUrl(url: String) =
|
||||||
url.let {
|
url.let {
|
||||||
// Only parse URL if is full URL
|
// Only parse URL if is full URL
|
||||||
val pathSegments = if (it.startsWith("http"))
|
val pathSegments = if (it.startsWith("http")) {
|
||||||
Uri.parse(it).pathSegments
|
Uri.parse(it).pathSegments
|
||||||
else
|
} else {
|
||||||
it.split('/')
|
it.split('/')
|
||||||
pathSegments.filterNot(String::isNullOrBlank)
|
}
|
||||||
}
|
pathSegments.filterNot(String::isNullOrBlank)
|
||||||
|
}
|
||||||
|
|
||||||
fun galleryId(url: String) = splitGalleryUrl(url)[1]
|
fun galleryId(url: String) = splitGalleryUrl(url)[1]
|
||||||
|
|
||||||
fun galleryToken(url: String) =
|
fun galleryToken(url: String) =
|
||||||
splitGalleryUrl(url)[2]
|
splitGalleryUrl(url)[2]
|
||||||
|
|
||||||
fun normalizeUrl(url: String) =
|
fun normalizeUrl(url: String) =
|
||||||
idAndTokenToUrl(galleryId(url), galleryToken(url))
|
idAndTokenToUrl(galleryId(url), galleryToken(url))
|
||||||
|
|
||||||
fun idAndTokenToUrl(id: String, token: String) =
|
fun idAndTokenToUrl(id: String, token: String) =
|
||||||
"/g/$id/$token/?nw=always"
|
"/g/$id/$token/?nw=always"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,8 +32,8 @@ class EightMusesSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -34,8 +34,8 @@ class HBrowseSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -31,15 +31,15 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
|
|||||||
manga.status = SManga.UNKNOWN
|
manga.status = SManga.UNKNOWN
|
||||||
|
|
||||||
val detailsDesc = "Title: $title\n" +
|
val detailsDesc = "Title: $title\n" +
|
||||||
"Artist: $artist\n"
|
"Artist: $artist\n"
|
||||||
|
|
||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.genre = tagsToGenreString()
|
manga.genre = tagsToGenreString()
|
||||||
|
|
||||||
manga.description = listOf(detailsDesc, tagsDesc.toString())
|
manga.description = listOf(detailsDesc, tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -50,6 +50,6 @@ class HentaiCafeSearchMetadata : RaisedSearchMetadata() {
|
|||||||
const val BASE_URL = "https://hentai.cafe"
|
const val BASE_URL = "https://hentai.cafe"
|
||||||
|
|
||||||
fun hcIdFromUrl(url: String) =
|
fun hcIdFromUrl(url: String) =
|
||||||
url.split("/").last { it.isNotBlank() }
|
url.split("/").last { it.isNotBlank() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,11 +62,13 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
detailsDesc += "Language: ${it.capitalize()}\n"
|
detailsDesc += "Language: ${it.capitalize()}\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
if (series.isNotEmpty())
|
if (series.isNotEmpty()) {
|
||||||
detailsDesc += "Series: ${series.joinToString()}\n"
|
detailsDesc += "Series: ${series.joinToString()}\n"
|
||||||
|
}
|
||||||
|
|
||||||
if (characters.isNotEmpty())
|
if (characters.isNotEmpty()) {
|
||||||
detailsDesc += "Characters: ${characters.joinToString()}\n"
|
detailsDesc += "Characters: ${characters.joinToString()}\n"
|
||||||
|
}
|
||||||
|
|
||||||
uploadDate?.let {
|
uploadDate?.let {
|
||||||
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
|
detailsDesc += "Upload date: ${EX_DATE_FORMAT.format(Date(it))}\n"
|
||||||
@ -80,8 +82,8 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -93,9 +95,9 @@ class HitomiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
const val BASE_URL = "https://hitomi.la"
|
const val BASE_URL = "https://hitomi.la"
|
||||||
|
|
||||||
fun hlIdFromUrl(url: String) =
|
fun hlIdFromUrl(url: String) =
|
||||||
url.split('/').last().split('-').last().substringBeforeLast('.')
|
url.split('/').last().split('-').last().substringBeforeLast('.')
|
||||||
|
|
||||||
fun urlFromHlId(id: String) =
|
fun urlFromHlId(id: String) =
|
||||||
"$BASE_URL/galleries/$id.html"
|
"$BASE_URL/galleries/$id.html"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,9 +44,11 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
if (mediaId != null) {
|
if (mediaId != null) {
|
||||||
val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault()
|
val hqThumbs = Injekt.get<PreferencesHelper>().eh_nh_useHighQualityThumbs().getOrDefault()
|
||||||
typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let {
|
typeToExtension(if (hqThumbs) coverImageType else thumbnailImageType)?.let {
|
||||||
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs)
|
manga.thumbnail_url = "https://t.nhentai.net/galleries/$mediaId/${if (hqThumbs) {
|
||||||
"cover"
|
"cover"
|
||||||
else "thumb"}.$it"
|
} else {
|
||||||
|
"thumb"
|
||||||
|
}}.$it"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,8 +93,8 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -108,14 +110,14 @@ class NHentaiSearchMetadata : RaisedSearchMetadata() {
|
|||||||
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
private const val NHENTAI_CATEGORIES_NAMESPACE = "category"
|
||||||
|
|
||||||
fun typeToExtension(t: String?) =
|
fun typeToExtension(t: String?) =
|
||||||
when (t) {
|
when (t) {
|
||||||
"p" -> "png"
|
"p" -> "png"
|
||||||
"j" -> "jpg"
|
"j" -> "jpg"
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nhUrlToId(url: String) =
|
fun nhUrlToId(url: String) =
|
||||||
url.split("/").last { it.isNotBlank() }.toLong()
|
url.split("/").last { it.isNotBlank() }.toLong()
|
||||||
|
|
||||||
fun nhIdToPath(id: Long) = "/g/$id/"
|
fun nhIdToPath(id: Long) = "/g/$id/"
|
||||||
}
|
}
|
||||||
|
@ -41,11 +41,12 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
|||||||
manga.title = it
|
manga.title = it
|
||||||
titleDesc += "Title: $it\n"
|
titleDesc += "Title: $it\n"
|
||||||
}
|
}
|
||||||
if (altTitles.isNotEmpty())
|
if (altTitles.isNotEmpty()) {
|
||||||
titleDesc += "Alternate Titles: \n" + altTitles
|
titleDesc += "Alternate Titles: \n" + altTitles
|
||||||
.joinToString(separator = "\n", postfix = "\n") {
|
.joinToString(separator = "\n", postfix = "\n") {
|
||||||
"▪ $it"
|
"▪ $it"
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val detailsDesc = StringBuilder()
|
val detailsDesc = StringBuilder()
|
||||||
artist?.let {
|
artist?.let {
|
||||||
@ -76,8 +77,8 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -87,9 +88,9 @@ class PervEdenSearchMetadata : RaisedSearchMetadata() {
|
|||||||
const val TAG_TYPE_DEFAULT = 0
|
const val TAG_TYPE_DEFAULT = 0
|
||||||
|
|
||||||
private fun splitGalleryUrl(url: String) =
|
private fun splitGalleryUrl(url: String) =
|
||||||
url.let {
|
url.let {
|
||||||
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
Uri.parse(it).pathSegments.filterNot(String::isNullOrBlank)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
|
fun pvIdFromUrl(url: String) = splitGalleryUrl(url).last()
|
||||||
}
|
}
|
||||||
@ -102,7 +103,7 @@ enum class PervEdenLang(val id: Long) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun source(id: Long) =
|
fun source(id: Long) =
|
||||||
values().find { it.id == id }
|
values().find { it.id == id }
|
||||||
?: throw IllegalArgumentException("Unknown source ID: $id!")
|
?: throw IllegalArgumentException("Unknown source ID: $id!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -55,8 +55,8 @@ class PururinSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc.toString(), detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -65,8 +65,8 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val tagsDesc = tagsToDescription()
|
val tagsDesc = tagsToDescription()
|
||||||
|
|
||||||
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
|
manga.description = listOf(titleDesc, detailsDesc.toString(), tagsDesc.toString())
|
||||||
.filter(String::isNotBlank)
|
.filter(String::isNotBlank)
|
||||||
.joinToString(separator = "\n")
|
.joinToString(separator = "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
@ -77,7 +77,7 @@ class TsuminoSearchMetadata : RaisedSearchMetadata() {
|
|||||||
val BASE_URL = "https://www.tsumino.com"
|
val BASE_URL = "https://www.tsumino.com"
|
||||||
|
|
||||||
fun tmIdFromUrl(url: String) =
|
fun tmIdFromUrl(url: String) =
|
||||||
Uri.parse(url).lastPathSegment
|
Uri.parse(url).lastPathSegment
|
||||||
|
|
||||||
fun mangaUrlFromId(id: String) = "/Book/Info/$id"
|
fun mangaUrlFromId(id: String) = "/Book/Info/$id"
|
||||||
|
|
||||||
|
@ -18,9 +18,9 @@ data class FlatMetadata(
|
|||||||
|
|
||||||
fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>) =
|
fun <T : RaisedSearchMetadata> raise(clazz: KClass<T>) =
|
||||||
RaisedSearchMetadata.raiseFlattenGson
|
RaisedSearchMetadata.raiseFlattenGson
|
||||||
.fromJson(metadata.extra, clazz.java).apply {
|
.fromJson(metadata.extra, clazz.java).apply {
|
||||||
fillBaseFields(this@FlatMetadata)
|
fillBaseFields(this@FlatMetadata)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> {
|
fun DatabaseHelper.getFlatMetadataForManga(mangaId: Long): PreparedOperation<FlatMetadata?> {
|
||||||
|
@ -36,29 +36,29 @@ abstract class RaisedSearchMetadata {
|
|||||||
abstract fun copyTo(manga: SManga)
|
abstract fun copyTo(manga: SManga)
|
||||||
|
|
||||||
fun tagsToGenreString() =
|
fun tagsToGenreString() =
|
||||||
tags.filter { it.type != TAG_TYPE_VIRTUAL }
|
tags.filter { it.type != TAG_TYPE_VIRTUAL }
|
||||||
.joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
|
.joinToString { (if (it.namespace != null) "${it.namespace}: " else "") + it.name }
|
||||||
|
|
||||||
fun tagsToDescription() =
|
fun tagsToDescription() =
|
||||||
StringBuilder("Tags:\n").apply {
|
StringBuilder("Tags:\n").apply {
|
||||||
// BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
// BiConsumer only available in Java 8, don't bother calling forEach directly on 'tags'
|
||||||
val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy {
|
val groupedTags = tags.filter { it.type != TAG_TYPE_VIRTUAL }.groupBy {
|
||||||
it.namespace
|
it.namespace
|
||||||
}.entries
|
}.entries
|
||||||
|
|
||||||
groupedTags.forEach { namespace, tags ->
|
groupedTags.forEach { namespace, tags ->
|
||||||
if (tags.isNotEmpty()) {
|
if (tags.isNotEmpty()) {
|
||||||
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
val joinedTags = tags.joinToString(separator = " ", transform = { "<${it.name}>" })
|
||||||
if (namespace != null) {
|
if (namespace != null) {
|
||||||
this += "▪ "
|
this += "▪ "
|
||||||
this += namespace
|
this += namespace
|
||||||
this += ": "
|
this += ": "
|
||||||
|
}
|
||||||
|
this += joinedTags
|
||||||
|
this += "\n"
|
||||||
}
|
}
|
||||||
this += joinedTags
|
|
||||||
this += "\n"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun List<RaisedTag>.ofNamespace(ns: String): List<RaisedTag> {
|
fun List<RaisedTag>.ofNamespace(ns: String): List<RaisedTag> {
|
||||||
return filter { it.namespace == ns }
|
return filter { it.namespace == ns }
|
||||||
@ -76,23 +76,23 @@ abstract class RaisedSearchMetadata {
|
|||||||
indexedExtra,
|
indexedExtra,
|
||||||
0
|
0
|
||||||
),
|
),
|
||||||
tags.map {
|
tags.map {
|
||||||
SearchTag(
|
SearchTag(
|
||||||
null,
|
null,
|
||||||
mangaId,
|
mangaId,
|
||||||
it.namespace,
|
it.namespace,
|
||||||
it.name,
|
it.name,
|
||||||
it.type
|
it.type
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
titles.map {
|
titles.map {
|
||||||
SearchTitle(
|
SearchTitle(
|
||||||
null,
|
null,
|
||||||
mangaId,
|
mangaId,
|
||||||
it.title,
|
it.title,
|
||||||
it.type
|
it.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ abstract class RaisedSearchMetadata {
|
|||||||
* @return the property value.
|
* @return the property value.
|
||||||
*/
|
*/
|
||||||
override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) =
|
override fun getValue(thisRef: RaisedSearchMetadata, property: KProperty<*>) =
|
||||||
thisRef.getTitleOfType(type)
|
thisRef.getTitleOfType(type)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the value of the property for the given object.
|
* Sets the value of the property for the given object.
|
||||||
@ -135,7 +135,7 @@ abstract class RaisedSearchMetadata {
|
|||||||
* @param value the value to set.
|
* @param value the value to set.
|
||||||
*/
|
*/
|
||||||
override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) =
|
override fun setValue(thisRef: RaisedSearchMetadata, property: KProperty<*>, value: String?) =
|
||||||
thisRef.replaceTitleOfType(type, value)
|
thisRef.replaceTitleOfType(type, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,22 +18,22 @@ import exh.metadata.sql.tables.SearchTagTable.COL_TYPE
|
|||||||
import exh.metadata.sql.tables.SearchTagTable.TABLE
|
import exh.metadata.sql.tables.SearchTagTable.TABLE
|
||||||
|
|
||||||
class SearchTagTypeMapping : SQLiteTypeMapping<SearchTag>(
|
class SearchTagTypeMapping : SQLiteTypeMapping<SearchTag>(
|
||||||
SearchTagPutResolver(),
|
SearchTagPutResolver(),
|
||||||
SearchTagGetResolver(),
|
SearchTagGetResolver(),
|
||||||
SearchTagDeleteResolver()
|
SearchTagDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
|
class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: SearchTag) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: SearchTag) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: SearchTag) = ContentValues(5).apply {
|
override fun mapToContentValues(obj: SearchTag) = ContentValues(5).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -47,19 +47,19 @@ class SearchTagPutResolver : DefaultPutResolver<SearchTag>() {
|
|||||||
class SearchTagGetResolver : DefaultGetResolver<SearchTag>() {
|
class SearchTagGetResolver : DefaultGetResolver<SearchTag>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag(
|
override fun mapFromCursor(cursor: Cursor): SearchTag = SearchTag(
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
||||||
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
|
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
|
||||||
namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)),
|
namespace = cursor.getString(cursor.getColumnIndex(COL_NAMESPACE)),
|
||||||
name = cursor.getString(cursor.getColumnIndex(COL_NAME)),
|
name = cursor.getString(cursor.getColumnIndex(COL_NAME)),
|
||||||
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
|
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchTagDeleteResolver : DefaultDeleteResolver<SearchTag>() {
|
class SearchTagDeleteResolver : DefaultDeleteResolver<SearchTag>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: SearchTag) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@ -17,22 +17,22 @@ import exh.metadata.sql.tables.SearchTitleTable.COL_TYPE
|
|||||||
import exh.metadata.sql.tables.SearchTitleTable.TABLE
|
import exh.metadata.sql.tables.SearchTitleTable.TABLE
|
||||||
|
|
||||||
class SearchTitleTypeMapping : SQLiteTypeMapping<SearchTitle>(
|
class SearchTitleTypeMapping : SQLiteTypeMapping<SearchTitle>(
|
||||||
SearchTitlePutResolver(),
|
SearchTitlePutResolver(),
|
||||||
SearchTitleGetResolver(),
|
SearchTitleGetResolver(),
|
||||||
SearchTitleDeleteResolver()
|
SearchTitleDeleteResolver()
|
||||||
)
|
)
|
||||||
|
|
||||||
class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
|
class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
|
||||||
|
|
||||||
override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder()
|
override fun mapToInsertQuery(obj: SearchTitle) = InsertQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder()
|
override fun mapToUpdateQuery(obj: SearchTitle) = UpdateQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun mapToContentValues(obj: SearchTitle) = ContentValues(4).apply {
|
override fun mapToContentValues(obj: SearchTitle) = ContentValues(4).apply {
|
||||||
put(COL_ID, obj.id)
|
put(COL_ID, obj.id)
|
||||||
@ -45,18 +45,18 @@ class SearchTitlePutResolver : DefaultPutResolver<SearchTitle>() {
|
|||||||
class SearchTitleGetResolver : DefaultGetResolver<SearchTitle>() {
|
class SearchTitleGetResolver : DefaultGetResolver<SearchTitle>() {
|
||||||
|
|
||||||
override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle(
|
override fun mapFromCursor(cursor: Cursor): SearchTitle = SearchTitle(
|
||||||
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
id = cursor.getLong(cursor.getColumnIndex(COL_ID)),
|
||||||
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
|
mangaId = cursor.getLong(cursor.getColumnIndex(COL_MANGA_ID)),
|
||||||
title = cursor.getString(cursor.getColumnIndex(COL_TITLE)),
|
title = cursor.getString(cursor.getColumnIndex(COL_TITLE)),
|
||||||
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
|
type = cursor.getInt(cursor.getColumnIndex(COL_TYPE))
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
class SearchTitleDeleteResolver : DefaultDeleteResolver<SearchTitle>() {
|
class SearchTitleDeleteResolver : DefaultDeleteResolver<SearchTitle>() {
|
||||||
|
|
||||||
override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder()
|
override fun mapToDeleteQuery(obj: SearchTitle) = DeleteQuery.builder()
|
||||||
.table(TABLE)
|
.table(TABLE)
|
||||||
.where("$COL_ID = ?")
|
.where("$COL_ID = ?")
|
||||||
.whereArgs(obj.id)
|
.whereArgs(obj.id)
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
package exh.metadata.sql.models
|
package exh.metadata.sql.models
|
||||||
|
|
||||||
data class SearchTag(
|
data class SearchTag(
|
||||||
// Tag identifier, unique
|
// Tag identifier, unique
|
||||||
val id: Long?,
|
val id: Long?,
|
||||||
|
|
||||||
// Metadata this tag is attached to
|
// Metadata this tag is attached to
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
|
|
||||||
// Tag namespace
|
// Tag namespace
|
||||||
val namespace: String?,
|
val namespace: String?,
|
||||||
|
|
||||||
// Tag name
|
// Tag name
|
||||||
val name: String,
|
val name: String,
|
||||||
|
|
||||||
// Tag type
|
// Tag type
|
||||||
val type: Int
|
val type: Int
|
||||||
)
|
)
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
package exh.metadata.sql.models
|
package exh.metadata.sql.models
|
||||||
|
|
||||||
data class SearchTitle(
|
data class SearchTitle(
|
||||||
// Title identifier, unique
|
// Title identifier, unique
|
||||||
val id: Long?,
|
val id: Long?,
|
||||||
|
|
||||||
// Metadata this title is attached to
|
// Metadata this title is attached to
|
||||||
val mangaId: Long,
|
val mangaId: Long,
|
||||||
|
|
||||||
// Title
|
// Title
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
||||||
// Title type, useful for distinguishing between main/alt titles
|
// Title type, useful for distinguishing between main/alt titles
|
||||||
val type: Int
|
val type: Int
|
||||||
)
|
)
|
||||||
|
@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTagTable
|
|||||||
|
|
||||||
interface SearchTagQueries : DbProvider {
|
interface SearchTagQueries : DbProvider {
|
||||||
fun getSearchTagsForManga(mangaId: Long) = db.get()
|
fun getSearchTagsForManga(mangaId: Long) = db.get()
|
||||||
.listOfObjects(SearchTag::class.java)
|
.listOfObjects(SearchTag::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(SearchTagTable.TABLE)
|
Query.builder()
|
||||||
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
.table(SearchTagTable.TABLE)
|
||||||
.whereArgs(mangaId)
|
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
||||||
.build())
|
.whereArgs(mangaId)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
|
fun deleteSearchTagsForManga(mangaId: Long) = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(SearchTagTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
.table(SearchTagTable.TABLE)
|
||||||
.whereArgs(mangaId)
|
.where("${SearchTagTable.COL_MANGA_ID} = ?")
|
||||||
.build())
|
.whereArgs(mangaId)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
|
fun insertSearchTag(searchTag: SearchTag) = db.put().`object`(searchTag).prepare()
|
||||||
|
|
||||||
@ -31,10 +35,12 @@ interface SearchTagQueries : DbProvider {
|
|||||||
|
|
||||||
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
|
fun deleteSearchTag(searchTag: SearchTag) = db.delete().`object`(searchTag).prepare()
|
||||||
|
|
||||||
fun deleteAllSearchTags() = db.delete().byQuery(DeleteQuery.builder()
|
fun deleteAllSearchTags() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTagTable.TABLE)
|
.table(SearchTagTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
.prepare()
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
|
fun setSearchTagsForManga(mangaId: Long, tags: List<SearchTag>) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
|
@ -9,21 +9,25 @@ import exh.metadata.sql.tables.SearchTitleTable
|
|||||||
|
|
||||||
interface SearchTitleQueries : DbProvider {
|
interface SearchTitleQueries : DbProvider {
|
||||||
fun getSearchTitlesForManga(mangaId: Long) = db.get()
|
fun getSearchTitlesForManga(mangaId: Long) = db.get()
|
||||||
.listOfObjects(SearchTitle::class.java)
|
.listOfObjects(SearchTitle::class.java)
|
||||||
.withQuery(Query.builder()
|
.withQuery(
|
||||||
.table(SearchTitleTable.TABLE)
|
Query.builder()
|
||||||
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
.table(SearchTitleTable.TABLE)
|
||||||
.whereArgs(mangaId)
|
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
||||||
.build())
|
.whereArgs(mangaId)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
|
fun deleteSearchTitlesForManga(mangaId: Long) = db.delete()
|
||||||
.byQuery(DeleteQuery.builder()
|
.byQuery(
|
||||||
.table(SearchTitleTable.TABLE)
|
DeleteQuery.builder()
|
||||||
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
.table(SearchTitleTable.TABLE)
|
||||||
.whereArgs(mangaId)
|
.where("${SearchTitleTable.COL_MANGA_ID} = ?")
|
||||||
.build())
|
.whereArgs(mangaId)
|
||||||
.prepare()
|
.build()
|
||||||
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
|
fun insertSearchTitle(searchTitle: SearchTitle) = db.put().`object`(searchTitle).prepare()
|
||||||
|
|
||||||
@ -31,10 +35,12 @@ interface SearchTitleQueries : DbProvider {
|
|||||||
|
|
||||||
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
|
fun deleteSearchTitle(searchTitle: SearchTitle) = db.delete().`object`(searchTitle).prepare()
|
||||||
|
|
||||||
fun deleteAllSearchTitle() = db.delete().byQuery(DeleteQuery.builder()
|
fun deleteAllSearchTitle() = db.delete().byQuery(
|
||||||
|
DeleteQuery.builder()
|
||||||
.table(SearchTitleTable.TABLE)
|
.table(SearchTitleTable.TABLE)
|
||||||
.build())
|
.build()
|
||||||
.prepare()
|
)
|
||||||
|
.prepare()
|
||||||
|
|
||||||
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
|
fun setSearchTitlesForManga(mangaId: Long, titles: List<SearchTitle>) {
|
||||||
db.inTransaction {
|
db.inTransaction {
|
||||||
|
@ -17,7 +17,8 @@ object SearchMetadataTable {
|
|||||||
|
|
||||||
// Insane foreign, primary key to avoid touch manga table
|
// Insane foreign, primary key to avoid touch manga table
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_MANGA_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_UPLOADER TEXT,
|
$COL_UPLOADER TEXT,
|
||||||
$COL_EXTRA TEXT NOT NULL,
|
$COL_EXTRA TEXT NOT NULL,
|
||||||
|
@ -16,7 +16,8 @@ object SearchTagTable {
|
|||||||
const val COL_TYPE = "type"
|
const val COL_TYPE = "type"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL,
|
$COL_MANGA_ID INTEGER NOT NULL,
|
||||||
$COL_NAMESPACE TEXT,
|
$COL_NAMESPACE TEXT,
|
||||||
|
@ -14,7 +14,8 @@ object SearchTitleTable {
|
|||||||
const val COL_TYPE = "type"
|
const val COL_TYPE = "type"
|
||||||
|
|
||||||
val createTableQuery: String
|
val createTableQuery: String
|
||||||
get() = """CREATE TABLE $TABLE(
|
get() =
|
||||||
|
"""CREATE TABLE $TABLE(
|
||||||
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
$COL_ID INTEGER NOT NULL PRIMARY KEY,
|
||||||
$COL_MANGA_ID INTEGER NOT NULL,
|
$COL_MANGA_ID INTEGER NOT NULL,
|
||||||
$COL_TITLE TEXT NOT NULL,
|
$COL_TITLE TEXT NOT NULL,
|
||||||
|
@ -9,13 +9,14 @@ import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
|||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
|
||||||
private val HIDE_SCRIPT = """
|
private val HIDE_SCRIPT =
|
||||||
document.querySelector("#forgot_button").style.visibility = "hidden";
|
"""
|
||||||
document.querySelector("#signup_button").style.visibility = "hidden";
|
document.querySelector("#forgot_button").style.visibility = "hidden";
|
||||||
document.querySelector("#announcement").style.visibility = "hidden";
|
document.querySelector("#signup_button").style.visibility = "hidden";
|
||||||
document.querySelector("nav").style.visibility = "hidden";
|
document.querySelector("#announcement").style.visibility = "hidden";
|
||||||
document.querySelector("footer").style.visibility = "hidden";
|
document.querySelector("nav").style.visibility = "hidden";
|
||||||
""".trimIndent()
|
document.querySelector("footer").style.visibility = "hidden";
|
||||||
|
""".trimIndent()
|
||||||
|
|
||||||
private fun verifyComplete(url: String): Boolean {
|
private fun verifyComplete(url: String): Boolean {
|
||||||
return url.toHttpUrlOrNull()?.let { parsed ->
|
return url.toHttpUrlOrNull()?.let { parsed ->
|
||||||
@ -28,14 +29,14 @@ val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId ->
|
|||||||
response.interceptAsHtml { doc ->
|
response.interceptAsHtml { doc ->
|
||||||
if (doc.title().trim().equals("Login - MangaDex", true)) {
|
if (doc.title().trim().equals("Login - MangaDex", true)) {
|
||||||
BrowserActionActivity.launchAction(
|
BrowserActionActivity.launchAction(
|
||||||
Injekt.get<Application>(),
|
Injekt.get<Application>(),
|
||||||
::verifyComplete,
|
::verifyComplete,
|
||||||
HIDE_SCRIPT,
|
HIDE_SCRIPT,
|
||||||
"https://mangadex.org/login",
|
"https://mangadex.org/login",
|
||||||
"Login",
|
"Login",
|
||||||
(Injekt.get<SourceManager>().get(sourceId) as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
(Injekt.get<SourceManager>().get(sourceId) as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
||||||
it.value.joinToString(",")
|
it.value.joinToString(",")
|
||||||
} ?: emptyMap()
|
} ?: emptyMap()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -43,43 +44,43 @@ val MANGADEX_LOGIN_PATCH: EHInterceptor = { request, response, sourceId ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
val MANGADEX_SOURCE_IDS = listOf(
|
val MANGADEX_SOURCE_IDS = listOf(
|
||||||
2499283573021220255,
|
2499283573021220255,
|
||||||
8033579885162383068,
|
8033579885162383068,
|
||||||
1952071260038453057,
|
1952071260038453057,
|
||||||
2098905203823335614,
|
2098905203823335614,
|
||||||
5098537545549490547,
|
5098537545549490547,
|
||||||
4505830566611664829,
|
4505830566611664829,
|
||||||
9194073792736219759,
|
9194073792736219759,
|
||||||
6400665728063187402,
|
6400665728063187402,
|
||||||
4938773340256184018,
|
4938773340256184018,
|
||||||
5860541308324630662,
|
5860541308324630662,
|
||||||
5189216366882819742,
|
5189216366882819742,
|
||||||
2655149515337070132,
|
2655149515337070132,
|
||||||
1145824452519314725,
|
1145824452519314725,
|
||||||
3846770256925560569,
|
3846770256925560569,
|
||||||
3807502156582598786,
|
3807502156582598786,
|
||||||
4284949320785450865,
|
4284949320785450865,
|
||||||
5463447640980279236,
|
5463447640980279236,
|
||||||
8578871918181236609,
|
8578871918181236609,
|
||||||
6750440049024086587,
|
6750440049024086587,
|
||||||
3339599426223341161,
|
3339599426223341161,
|
||||||
5148895169070562838,
|
5148895169070562838,
|
||||||
1493666528525752601,
|
1493666528525752601,
|
||||||
1713554459881080228,
|
1713554459881080228,
|
||||||
4150470519566206911,
|
4150470519566206911,
|
||||||
1347402746269051958,
|
1347402746269051958,
|
||||||
3578612018159256808,
|
3578612018159256808,
|
||||||
425785191804166217,
|
425785191804166217,
|
||||||
8254121249433835847,
|
8254121249433835847,
|
||||||
3260701926561129943,
|
3260701926561129943,
|
||||||
1411768577036936240,
|
1411768577036936240,
|
||||||
3285208643537017688,
|
3285208643537017688,
|
||||||
737986167355114438,
|
737986167355114438,
|
||||||
1471784905273036181,
|
1471784905273036181,
|
||||||
5967745367608513818,
|
5967745367608513818,
|
||||||
3781216447842245147,
|
3781216447842245147,
|
||||||
4774459486579224459,
|
4774459486579224459,
|
||||||
4710920497926776490,
|
4710920497926776490,
|
||||||
5779037855201976894
|
5779037855201976894
|
||||||
)
|
)
|
||||||
const val MANGADEX_DOMAIN = "mangadex.org"
|
const val MANGADEX_DOMAIN = "mangadex.org"
|
||||||
|
@ -16,8 +16,10 @@ fun OkHttpClient.Builder.injectPatches(sourceIdProducer: () -> Long): OkHttpClie
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun findAndApplyPatches(sourceId: Long): EHInterceptor {
|
fun findAndApplyPatches(sourceId: Long): EHInterceptor {
|
||||||
return ((EH_INTERCEPTORS[sourceId] ?: emptyList()) +
|
return (
|
||||||
(EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())).merge()
|
(EH_INTERCEPTORS[sourceId] ?: emptyList()) +
|
||||||
|
(EH_INTERCEPTORS[EH_UNIVERSAL_INTERCEPTOR] ?: emptyList())
|
||||||
|
).merge()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun List<EHInterceptor>.merge(): EHInterceptor {
|
fun List<EHInterceptor>.merge(): EHInterceptor {
|
||||||
@ -30,12 +32,12 @@ fun List<EHInterceptor>.merge(): EHInterceptor {
|
|||||||
|
|
||||||
private const val EH_UNIVERSAL_INTERCEPTOR = -1L
|
private const val EH_UNIVERSAL_INTERCEPTOR = -1L
|
||||||
private val EH_INTERCEPTORS: Map<Long, List<EHInterceptor>> = mapOf(
|
private val EH_INTERCEPTORS: Map<Long, List<EHInterceptor>> = mapOf(
|
||||||
EH_UNIVERSAL_INTERCEPTOR to listOf(
|
EH_UNIVERSAL_INTERCEPTOR to listOf(
|
||||||
CAPTCHA_DETECTION_PATCH // Auto captcha detection
|
CAPTCHA_DETECTION_PATCH // Auto captcha detection
|
||||||
),
|
),
|
||||||
|
|
||||||
// MangaDex login support
|
// MangaDex login support
|
||||||
*MANGADEX_SOURCE_IDS.map { id ->
|
*MANGADEX_SOURCE_IDS.map { id ->
|
||||||
id to listOf(MANGADEX_LOGIN_PATCH)
|
id to listOf(MANGADEX_LOGIN_PATCH)
|
||||||
}.toTypedArray()
|
}.toTypedArray()
|
||||||
)
|
)
|
||||||
|
@ -13,9 +13,9 @@ val CAPTCHA_DETECTION_PATCH: EHInterceptor = { request, response, sourceId ->
|
|||||||
if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) {
|
if (doc.getElementsByClass("g-recaptcha").isNotEmpty()) {
|
||||||
// Found it, allow the user to solve this thing
|
// Found it, allow the user to solve this thing
|
||||||
BrowserActionActivity.launchUniversal(
|
BrowserActionActivity.launchUniversal(
|
||||||
Injekt.get<Application>(),
|
Injekt.get<Application>(),
|
||||||
sourceId,
|
sourceId,
|
||||||
request.url.toString()
|
request.url.toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,10 +12,11 @@ class SearchEngine {
|
|||||||
component: Text?
|
component: Text?
|
||||||
): Pair<String, List<String>>? {
|
): Pair<String, List<String>>? {
|
||||||
val maybeLenientComponent = component?.let {
|
val maybeLenientComponent = component?.let {
|
||||||
if (!it.exact)
|
if (!it.exact) {
|
||||||
it.asLenientTagQueries()
|
it.asLenientTagQueries()
|
||||||
else
|
} else {
|
||||||
listOf(it.asQuery())
|
listOf(it.asQuery())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val componentTagQuery = maybeLenientComponent?.let {
|
val componentTagQuery = maybeLenientComponent?.let {
|
||||||
val params = mutableListOf<String>()
|
val params = mutableListOf<String>()
|
||||||
@ -25,11 +26,12 @@ class SearchEngine {
|
|||||||
}.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params
|
}.joinToString(separator = " OR ", prefix = "(", postfix = ")") to params
|
||||||
}
|
}
|
||||||
return if (namespace != null) {
|
return if (namespace != null) {
|
||||||
var query = """
|
var query =
|
||||||
|
"""
|
||||||
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
(SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||||
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
WHERE ${SearchTagTable.COL_NAMESPACE} IS NOT NULL
|
||||||
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
AND ${SearchTagTable.COL_NAMESPACE} LIKE ?
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
val params = mutableListOf(escapeLike(namespace))
|
val params = mutableListOf(escapeLike(namespace))
|
||||||
if (componentTagQuery != null) {
|
if (componentTagQuery != null) {
|
||||||
query += "\n AND ${componentTagQuery.first}"
|
query += "\n AND ${componentTagQuery.first}"
|
||||||
@ -39,18 +41,20 @@ class SearchEngine {
|
|||||||
"$query)" to params
|
"$query)" to params
|
||||||
} else if (component != null) {
|
} else if (component != null) {
|
||||||
// Match title + tags
|
// Match title + tags
|
||||||
val tagQuery = """
|
val tagQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
SELECT ${SearchTagTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTagTable.TABLE}
|
||||||
WHERE ${componentTagQuery!!.first}
|
WHERE ${componentTagQuery!!.first}
|
||||||
""".trimIndent() to componentTagQuery.second
|
""".trimIndent() to componentTagQuery.second
|
||||||
|
|
||||||
val titleQuery = """
|
val titleQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
SELECT ${SearchTitleTable.COL_MANGA_ID} AS $COL_MANGA_ID FROM ${SearchTitleTable.TABLE}
|
||||||
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
WHERE ${SearchTitleTable.COL_TITLE} LIKE ?
|
||||||
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
""".trimIndent() to listOf(component.asLenientTitleQuery())
|
||||||
|
|
||||||
"(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to
|
"(${tagQuery.first} UNION ${titleQuery.first})".trimIndent() to
|
||||||
(tagQuery.second + titleQuery.second)
|
(tagQuery.second + titleQuery.second)
|
||||||
} else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,22 +90,25 @@ class SearchEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val completeParams = mutableListOf<String>()
|
val completeParams = mutableListOf<String>()
|
||||||
var baseQuery = """
|
var baseQuery =
|
||||||
|
"""
|
||||||
SELECT ${SearchMetadataTable.COL_MANGA_ID}
|
SELECT ${SearchMetadataTable.COL_MANGA_ID}
|
||||||
FROM ${SearchMetadataTable.TABLE} meta
|
FROM ${SearchMetadataTable.TABLE} meta
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
include.forEachIndexed { index, pair ->
|
include.forEachIndexed { index, pair ->
|
||||||
baseQuery += "\n" + ("""
|
baseQuery += "\n" + (
|
||||||
|
"""
|
||||||
INNER JOIN ${pair.first} i$index
|
INNER JOIN ${pair.first} i$index
|
||||||
ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID}
|
ON i$index.$COL_MANGA_ID = meta.${SearchMetadataTable.COL_MANGA_ID}
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
|
)
|
||||||
completeParams += pair.second
|
completeParams += pair.second
|
||||||
}
|
}
|
||||||
|
|
||||||
exclude.forEach {
|
exclude.forEach {
|
||||||
wheres += """
|
wheres += """
|
||||||
(meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first})
|
(meta.${SearchMetadataTable.COL_MANGA_ID} NOT IN ${it.first})
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
whereParams += it.second
|
whereParams += it.second
|
||||||
}
|
}
|
||||||
@ -196,8 +203,8 @@ class SearchEngine {
|
|||||||
|
|
||||||
fun escapeLike(string: String): String {
|
fun escapeLike(string: String): String {
|
||||||
return string.replace("\\", "\\\\")
|
return string.replace("\\", "\\\\")
|
||||||
.replace("_", "\\_")
|
.replace("_", "\\_")
|
||||||
.replace("%", "\\%")
|
.replace("%", "\\%")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,13 +28,13 @@ class Text : QueryComponent() {
|
|||||||
fun asLenientTagQueries(): List<String> {
|
fun asLenientTagQueries(): List<String> {
|
||||||
if (lenientTagQueries == null) {
|
if (lenientTagQueries == null) {
|
||||||
lenientTagQueries = listOf(
|
lenientTagQueries = listOf(
|
||||||
// Match beginning of tag
|
// Match beginning of tag
|
||||||
rBaseBuilder().append("%").toString(),
|
rBaseBuilder().append("%").toString(),
|
||||||
// Tag word matcher (that matches multiple words)
|
// Tag word matcher (that matches multiple words)
|
||||||
// Can't make it match a single word in Realm :(
|
// Can't make it match a single word in Realm :(
|
||||||
StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(),
|
StringBuilder(" ").append(rBaseBuilder()).append(" ").toString(),
|
||||||
StringBuilder(" ").append(rBaseBuilder()).toString(),
|
StringBuilder(" ").append(rBaseBuilder()).toString(),
|
||||||
rBaseBuilder().append(" ").toString()
|
rBaseBuilder().append(" ").toString()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return lenientTagQueries!!
|
return lenientTagQueries!!
|
||||||
@ -52,11 +52,11 @@ class Text : QueryComponent() {
|
|||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
fun rawTextOnly() = if (rawText != null)
|
fun rawTextOnly() = if (rawText != null) {
|
||||||
rawText!!
|
rawText!!
|
||||||
else {
|
} else {
|
||||||
rawText = components
|
rawText = components
|
||||||
.joinToString(separator = "", transform = { it.rawText })
|
.joinToString(separator = "", transform = { it.rawText })
|
||||||
rawText!!
|
rawText!!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,9 @@ class SmartSearchEngine(
|
|||||||
} else title
|
} else title
|
||||||
val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io())
|
val searchResults = source.fetchSearchManga(1, searchQuery, FilterList()).toSingle().await(Schedulers.io())
|
||||||
|
|
||||||
if (searchResults.mangas.size == 1)
|
if (searchResults.mangas.size == 1) {
|
||||||
return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0))
|
return@supervisorScope listOf(SearchEntry(searchResults.mangas.first(), 0.0))
|
||||||
|
}
|
||||||
|
|
||||||
searchResults.mangas.map {
|
searchResults.mangas.map {
|
||||||
val normalizedDistance = normalizedLevenshtein.similarity(title, it.title)
|
val normalizedDistance = normalizedLevenshtein.similarity(title, it.title)
|
||||||
|
@ -7,38 +7,38 @@ object BlacklistedSources {
|
|||||||
val PERVEDEN_EN_EXT_SOURCES = listOf(4673633799850248749)
|
val PERVEDEN_EN_EXT_SOURCES = listOf(4673633799850248749)
|
||||||
val PERVEDEN_IT_EXT_SOURCES = listOf(1433898225963724122)
|
val PERVEDEN_IT_EXT_SOURCES = listOf(1433898225963724122)
|
||||||
val EHENTAI_EXT_SOURCES = listOf(
|
val EHENTAI_EXT_SOURCES = listOf(
|
||||||
8100626124886895451,
|
8100626124886895451,
|
||||||
57122881048805941,
|
57122881048805941,
|
||||||
4678440076103929247,
|
4678440076103929247,
|
||||||
1876021963378735852,
|
1876021963378735852,
|
||||||
3955189842350477641,
|
3955189842350477641,
|
||||||
4348288691341764259,
|
4348288691341764259,
|
||||||
773611868725221145,
|
773611868725221145,
|
||||||
5759417018342755550,
|
5759417018342755550,
|
||||||
825187715438990384,
|
825187715438990384,
|
||||||
6116711405602166104,
|
6116711405602166104,
|
||||||
7151438547982231541,
|
7151438547982231541,
|
||||||
2171445159732592630,
|
2171445159732592630,
|
||||||
3032959619549451093,
|
3032959619549451093,
|
||||||
5980349886941016589,
|
5980349886941016589,
|
||||||
6073266008352078708,
|
6073266008352078708,
|
||||||
5499077866612745456,
|
5499077866612745456,
|
||||||
6140480779421365791
|
6140480779421365791
|
||||||
)
|
)
|
||||||
|
|
||||||
val BLACKLISTED_EXT_SOURCES = NHENTAI_EXT_SOURCES +
|
val BLACKLISTED_EXT_SOURCES = NHENTAI_EXT_SOURCES +
|
||||||
PERVEDEN_EN_EXT_SOURCES +
|
PERVEDEN_EN_EXT_SOURCES +
|
||||||
PERVEDEN_IT_EXT_SOURCES +
|
PERVEDEN_IT_EXT_SOURCES +
|
||||||
EHENTAI_EXT_SOURCES
|
EHENTAI_EXT_SOURCES
|
||||||
|
|
||||||
val BLACKLISTED_EXTENSIONS = listOf(
|
val BLACKLISTED_EXTENSIONS = listOf(
|
||||||
"eu.kanade.tachiyomi.extension.all.ehentai",
|
"eu.kanade.tachiyomi.extension.all.ehentai",
|
||||||
"eu.kanade.tachiyomi.extension.all.nhentai",
|
"eu.kanade.tachiyomi.extension.all.nhentai",
|
||||||
"eu.kanade.tachiyomi.extension.en.perveden",
|
"eu.kanade.tachiyomi.extension.en.perveden",
|
||||||
"eu.kanade.tachiyomi.extension.it.perveden"
|
"eu.kanade.tachiyomi.extension.it.perveden"
|
||||||
)
|
)
|
||||||
|
|
||||||
val HIDDEN_SOURCES = listOf(
|
val HIDDEN_SOURCES = listOf(
|
||||||
MERGED_SOURCE_ID
|
MERGED_SOURCE_ID
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun popularMangaRequest(page: Int) =
|
override fun popularMangaRequest(page: Int) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -26,7 +26,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun popularMangaParse(response: Response) =
|
override fun popularMangaParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for the search manga given the page.
|
* Returns the request for the search manga given the page.
|
||||||
@ -36,7 +36,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -44,7 +44,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun searchMangaParse(response: Response) =
|
override fun searchMangaParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for latest manga given the page.
|
* Returns the request for latest manga given the page.
|
||||||
@ -52,7 +52,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesRequest(page: Int) =
|
override fun latestUpdatesRequest(page: Int) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -60,7 +60,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesParse(response: Response) =
|
override fun latestUpdatesParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns the details of a manga.
|
* Parses the response from the site and returns the details of a manga.
|
||||||
@ -68,7 +68,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun mangaDetailsParse(response: Response) =
|
override fun mangaDetailsParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of chapters.
|
* Parses the response from the site and returns a list of chapters.
|
||||||
@ -76,7 +76,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun chapterListParse(response: Response) =
|
override fun chapterListParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of pages.
|
* Parses the response from the site and returns a list of pages.
|
||||||
@ -84,7 +84,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun pageListParse(response: Response) =
|
override fun pageListParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns the absolute url to the source image.
|
* Parses the response from the site and returns the absolute url to the source image.
|
||||||
@ -92,7 +92,7 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun imageUrlParse(response: Response) =
|
override fun imageUrlParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||||
@ -240,7 +240,8 @@ abstract class DelegatedHttpSource(val delegate: HttpSource) : HttpSource() {
|
|||||||
|
|
||||||
private fun ensureDelegateCompatible() {
|
private fun ensureDelegateCompatible() {
|
||||||
if (versionId != delegate.versionId ||
|
if (versionId != delegate.versionId ||
|
||||||
lang != delegate.lang) {
|
lang != delegate.lang
|
||||||
|
) {
|
||||||
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
throw IncompatibleDelegateException("Delegate source is not compatible (versionId: $versionId <=> ${delegate.versionId}, lang: $lang <=> ${delegate.lang})!")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ class EnhancedHttpSource(
|
|||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun popularMangaRequest(page: Int) =
|
override fun popularMangaRequest(page: Int) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -31,7 +31,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun popularMangaParse(response: Response) =
|
override fun popularMangaParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for the search manga given the page.
|
* Returns the request for the search manga given the page.
|
||||||
@ -41,7 +41,7 @@ class EnhancedHttpSource(
|
|||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -49,7 +49,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun searchMangaParse(response: Response) =
|
override fun searchMangaParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the request for latest manga given the page.
|
* Returns the request for latest manga given the page.
|
||||||
@ -57,7 +57,7 @@ class EnhancedHttpSource(
|
|||||||
* @param page the page number to retrieve.
|
* @param page the page number to retrieve.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesRequest(page: Int) =
|
override fun latestUpdatesRequest(page: Int) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a [MangasPage] object.
|
* Parses the response from the site and returns a [MangasPage] object.
|
||||||
@ -65,7 +65,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun latestUpdatesParse(response: Response) =
|
override fun latestUpdatesParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns the details of a manga.
|
* Parses the response from the site and returns the details of a manga.
|
||||||
@ -73,7 +73,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun mangaDetailsParse(response: Response) =
|
override fun mangaDetailsParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of chapters.
|
* Parses the response from the site and returns a list of chapters.
|
||||||
@ -81,7 +81,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun chapterListParse(response: Response) =
|
override fun chapterListParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns a list of pages.
|
* Parses the response from the site and returns a list of pages.
|
||||||
@ -89,7 +89,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun pageListParse(response: Response) =
|
override fun pageListParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses the response from the site and returns the absolute url to the source image.
|
* Parses the response from the site and returns the absolute url to the source image.
|
||||||
@ -97,7 +97,7 @@ class EnhancedHttpSource(
|
|||||||
* @param response the response from the site.
|
* @param response the response from the site.
|
||||||
*/
|
*/
|
||||||
override fun imageUrlParse(response: Response) =
|
override fun imageUrlParse(response: Response) =
|
||||||
throw UnsupportedOperationException("Should never be called!")
|
throw UnsupportedOperationException("Should never be called!")
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base url of the website without the trailing slash, like: http://mysite.com
|
* Base url of the website without the trailing slash, like: http://mysite.com
|
||||||
@ -153,7 +153,7 @@ class EnhancedHttpSource(
|
|||||||
* @param filters the list of filters to apply.
|
* @param filters the list of filters to apply.
|
||||||
*/
|
*/
|
||||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
override fun fetchSearchManga(page: Int, query: String, filters: FilterList) =
|
||||||
source().fetchSearchManga(page, query, filters)
|
source().fetchSearchManga(page, query, filters)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns an observable containing a page with a list of latest manga updates.
|
* Returns an observable containing a page with a list of latest manga updates.
|
||||||
@ -209,7 +209,7 @@ class EnhancedHttpSource(
|
|||||||
* @param manga the manga of the chapter.
|
* @param manga the manga of the chapter.
|
||||||
*/
|
*/
|
||||||
override fun prepareNewChapter(chapter: SChapter, manga: SManga) =
|
override fun prepareNewChapter(chapter: SChapter, manga: SManga) =
|
||||||
source().prepareNewChapter(chapter, manga)
|
source().prepareNewChapter(chapter, manga)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of filters for the source.
|
* Returns the list of filters for the source.
|
||||||
|
@ -14,7 +14,7 @@ class ConfiguringDialogController : DialogController() {
|
|||||||
private var materialDialog: MaterialDialog? = null
|
private var materialDialog: MaterialDialog? = null
|
||||||
|
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
if (savedViewState == null)
|
if (savedViewState == null) {
|
||||||
thread {
|
thread {
|
||||||
try {
|
try {
|
||||||
EHConfigurator().configureAll()
|
EHConfigurator().configureAll()
|
||||||
@ -25,10 +25,10 @@ class ConfiguringDialogController : DialogController() {
|
|||||||
activity?.let {
|
activity?.let {
|
||||||
it.runOnUiThread {
|
it.runOnUiThread {
|
||||||
MaterialDialog(it)
|
MaterialDialog(it)
|
||||||
.title(text = "Configuration failed!")
|
.title(text = "Configuration failed!")
|
||||||
.message(text = "An error occurred during the configuration process: " + e.message)
|
.message(text = "An error occurred during the configuration process: " + e.message)
|
||||||
.positiveButton(android.R.string.ok)
|
.positiveButton(android.R.string.ok)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.e(e, "Configuration error!")
|
Timber.e(e, "Configuration error!")
|
||||||
@ -37,14 +37,15 @@ class ConfiguringDialogController : DialogController() {
|
|||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.title(text = "Uploading settings to server")
|
.title(text = "Uploading settings to server")
|
||||||
.message(text = "Please wait, this may take some time...")
|
.message(text = "Please wait, this may take some time...")
|
||||||
.cancelable(false)
|
.cancelable(false)
|
||||||
.also {
|
.also {
|
||||||
materialDialog = it
|
materialDialog = it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroyView(view: View) {
|
override fun onDestroyView(view: View) {
|
||||||
|
@ -18,11 +18,11 @@ class EHConfigurator {
|
|||||||
private val sources: SourceManager by injectLazy()
|
private val sources: SourceManager by injectLazy()
|
||||||
|
|
||||||
private val configuratorClient = OkHttpClient.Builder()
|
private val configuratorClient = OkHttpClient.Builder()
|
||||||
.maybeInjectEHLogger()
|
.maybeInjectEHLogger()
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder()
|
private fun EHentai.requestWithCreds(sp: Int = 1) = Request.Builder()
|
||||||
.addHeader("Cookie", cookiesHeader(sp))
|
.addHeader("Cookie", cookiesHeader(sp))
|
||||||
|
|
||||||
private fun EHentai.execProfileActions(
|
private fun EHentai.execProfileActions(
|
||||||
action: String,
|
action: String,
|
||||||
@ -30,15 +30,19 @@ class EHConfigurator {
|
|||||||
set: String,
|
set: String,
|
||||||
sp: Int
|
sp: Int
|
||||||
) =
|
) =
|
||||||
configuratorClient.newCall(requestWithCreds(sp)
|
configuratorClient.newCall(
|
||||||
|
requestWithCreds(sp)
|
||||||
.url(uconfigUrl)
|
.url(uconfigUrl)
|
||||||
.post(FormBody.Builder()
|
.post(
|
||||||
|
FormBody.Builder()
|
||||||
.add("profile_action", action)
|
.add("profile_action", action)
|
||||||
.add("profile_name", name)
|
.add("profile_name", name)
|
||||||
.add("profile_set", set)
|
.add("profile_set", set)
|
||||||
.build())
|
.build()
|
||||||
.build())
|
)
|
||||||
.execute()
|
.build()
|
||||||
|
)
|
||||||
|
.execute()
|
||||||
|
|
||||||
private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL
|
private val EHentai.uconfigUrl get() = baseUrl + UCONFIG_URL
|
||||||
|
|
||||||
@ -47,10 +51,12 @@ class EHConfigurator {
|
|||||||
val exhSource = sources.get(EXH_SOURCE_ID) as EHentai
|
val exhSource = sources.get(EXH_SOURCE_ID) as EHentai
|
||||||
|
|
||||||
// Get hath perks
|
// Get hath perks
|
||||||
val perksPage = configuratorClient.newCall(ehSource.requestWithCreds()
|
val perksPage = configuratorClient.newCall(
|
||||||
|
ehSource.requestWithCreds()
|
||||||
.url(HATH_PERKS_URL)
|
.url(HATH_PERKS_URL)
|
||||||
.build())
|
.build()
|
||||||
.execute().asJsoup()
|
)
|
||||||
|
.execute().asJsoup()
|
||||||
|
|
||||||
val hathPerks = EHHathPerksResponse()
|
val hathPerks = EHHathPerksResponse()
|
||||||
|
|
||||||
@ -97,24 +103,29 @@ class EHConfigurator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// No profile slots left :(
|
// No profile slots left :(
|
||||||
if (availableProfiles.isEmpty())
|
if (availableProfiles.isEmpty()) {
|
||||||
throw IllegalStateException("You are out of profile slots on ${source.name}, please delete a profile!")
|
throw IllegalStateException("You are out of profile slots on ${source.name}, please delete a profile!")
|
||||||
|
}
|
||||||
// Create profile in available slot
|
// Create profile in available slot
|
||||||
|
|
||||||
val slot = availableProfiles.first()
|
val slot = availableProfiles.first()
|
||||||
val response = source.execProfileActions("create",
|
val response = source.execProfileActions(
|
||||||
PROFILE_NAME,
|
"create",
|
||||||
slot.toString(),
|
PROFILE_NAME,
|
||||||
1)
|
slot.toString(),
|
||||||
|
1
|
||||||
|
)
|
||||||
|
|
||||||
// Build new profile
|
// Build new profile
|
||||||
val form = EhUConfigBuilder().build(hathPerks)
|
val form = EhUConfigBuilder().build(hathPerks)
|
||||||
|
|
||||||
// Send new profile to server
|
// Send new profile to server
|
||||||
configuratorClient.newCall(source.requestWithCreds(sp = slot)
|
configuratorClient.newCall(
|
||||||
|
source.requestWithCreds(sp = slot)
|
||||||
.url(source.uconfigUrl)
|
.url(source.uconfigUrl)
|
||||||
.post(form)
|
.post(form)
|
||||||
.build()).execute()
|
.build()
|
||||||
|
).execute()
|
||||||
|
|
||||||
// Persist slot + sk
|
// Persist slot + sk
|
||||||
source.spPref().set(slot)
|
source.spPref().set(slot)
|
||||||
@ -129,12 +140,15 @@ class EHConfigurator {
|
|||||||
it.startsWith("hath_perks=")
|
it.startsWith("hath_perks=")
|
||||||
}?.removePrefix("hath_perks=")?.substringBefore(';')
|
}?.removePrefix("hath_perks=")?.substringBefore(';')
|
||||||
|
|
||||||
if (keyCookie != null)
|
if (keyCookie != null) {
|
||||||
prefs.eh_settingsKey().set(keyCookie)
|
prefs.eh_settingsKey().set(keyCookie)
|
||||||
if (sessionCookie != null)
|
}
|
||||||
|
if (sessionCookie != null) {
|
||||||
prefs.eh_sessionCookie().set(sessionCookie)
|
prefs.eh_sessionCookie().set(sessionCookie)
|
||||||
if (hathPerksCookie != null)
|
}
|
||||||
|
if (hathPerksCookie != null) {
|
||||||
prefs.eh_hathPerksCookies().set(hathPerksCookie)
|
prefs.eh_hathPerksCookies().set(hathPerksCookie)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
@ -11,9 +11,11 @@ class EhUConfigBuilder {
|
|||||||
fun build(hathPerks: EHHathPerksResponse): FormBody {
|
fun build(hathPerks: EHHathPerksResponse): FormBody {
|
||||||
val configItems = mutableListOf<ConfigItem>()
|
val configItems = mutableListOf<ConfigItem>()
|
||||||
|
|
||||||
configItems += when (prefs.imageQuality()
|
configItems += when (
|
||||||
|
prefs.imageQuality()
|
||||||
.getOrDefault()
|
.getOrDefault()
|
||||||
.toLowerCase()) {
|
.toLowerCase()
|
||||||
|
) {
|
||||||
"ovrs_2400" -> Entry.ImageSize.`2400`
|
"ovrs_2400" -> Entry.ImageSize.`2400`
|
||||||
"ovrs_1600" -> Entry.ImageSize.`1600`
|
"ovrs_1600" -> Entry.ImageSize.`1600`
|
||||||
"high" -> Entry.ImageSize.`1280`
|
"high" -> Entry.ImageSize.`1280`
|
||||||
@ -23,20 +25,23 @@ class EhUConfigBuilder {
|
|||||||
else -> Entry.ImageSize.AUTO
|
else -> Entry.ImageSize.AUTO
|
||||||
}
|
}
|
||||||
|
|
||||||
configItems += if (prefs.useHentaiAtHome().getOrDefault())
|
configItems += if (prefs.useHentaiAtHome().getOrDefault()) {
|
||||||
Entry.UseHentaiAtHome.YES
|
Entry.UseHentaiAtHome.YES
|
||||||
else
|
} else {
|
||||||
Entry.UseHentaiAtHome.NO
|
Entry.UseHentaiAtHome.NO
|
||||||
|
}
|
||||||
|
|
||||||
configItems += if (prefs.useJapaneseTitle().getOrDefault())
|
configItems += if (prefs.useJapaneseTitle().getOrDefault()) {
|
||||||
Entry.TitleDisplayLanguage.JAPANESE
|
Entry.TitleDisplayLanguage.JAPANESE
|
||||||
else
|
} else {
|
||||||
Entry.TitleDisplayLanguage.DEFAULT
|
Entry.TitleDisplayLanguage.DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
configItems += if (prefs.eh_useOriginalImages().getOrDefault())
|
configItems += if (prefs.eh_useOriginalImages().getOrDefault()) {
|
||||||
Entry.UseOriginalImages.YES
|
Entry.UseOriginalImages.YES
|
||||||
else
|
} else {
|
||||||
Entry.UseOriginalImages.NO
|
Entry.UseOriginalImages.NO
|
||||||
|
}
|
||||||
|
|
||||||
configItems += when {
|
configItems += when {
|
||||||
hathPerks.allThumbs -> Entry.ThumbnailRows.`40`
|
hathPerks.allThumbs -> Entry.ThumbnailRows.`40`
|
||||||
|
@ -14,25 +14,29 @@ class WarnConfigureDialogController : DialogController() {
|
|||||||
private val prefs: PreferencesHelper by injectLazy()
|
private val prefs: PreferencesHelper by injectLazy()
|
||||||
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
override fun onCreateDialog(savedViewState: Bundle?): Dialog {
|
||||||
return MaterialDialog(activity!!)
|
return MaterialDialog(activity!!)
|
||||||
.title(text = "Settings profile note")
|
.title(text = "Settings profile note")
|
||||||
.message(text = """
|
.message(
|
||||||
|
text =
|
||||||
|
"""
|
||||||
The app will now add a new settings profile on E-Hentai and ExHentai to optimize app performance. Please ensure that you have less than three profiles on both sites.
|
The app will now add a new settings profile on E-Hentai and ExHentai to optimize app performance. Please ensure that you have less than three profiles on both sites.
|
||||||
|
|
||||||
If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'.
|
If you have no idea what settings profiles are, then it probably doesn't matter, just hit 'OK'.
|
||||||
""".trimIndent())
|
""".trimIndent()
|
||||||
.positiveButton(android.R.string.ok) {
|
)
|
||||||
prefs.eh_showSettingsUploadWarning().set(false)
|
.positiveButton(android.R.string.ok) {
|
||||||
ConfiguringDialogController().showDialog(router)
|
prefs.eh_showSettingsUploadWarning().set(false)
|
||||||
}
|
ConfiguringDialogController().showDialog(router)
|
||||||
.cancelable(false)
|
}
|
||||||
|
.cancelable(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun uploadSettings(router: Router) {
|
fun uploadSettings(router: Router) {
|
||||||
if (Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().get())
|
if (Injekt.get<PreferencesHelper>().eh_showSettingsUploadWarning().get()) {
|
||||||
WarnConfigureDialogController().showDialog(router)
|
WarnConfigureDialogController().showDialog(router)
|
||||||
else
|
} else {
|
||||||
ConfiguringDialogController().showDialog(router)
|
ConfiguringDialogController().showDialog(router)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,66 +49,68 @@ class BatchAddController : NucleusController<EhFragmentBatchAddBinding, BatchAdd
|
|||||||
val progressSubscriptions = CompositeSubscription()
|
val progressSubscriptions = CompositeSubscription()
|
||||||
|
|
||||||
presenter.currentlyAddingRelay
|
presenter.currentlyAddingRelay
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeUntilDestroy {
|
.subscribeUntilDestroy {
|
||||||
progressSubscriptions.clear()
|
progressSubscriptions.clear()
|
||||||
if (it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) {
|
if (it == BatchAddPresenter.STATE_INPUT_TO_PROGRESS) {
|
||||||
showProgress(this)
|
showProgress(this)
|
||||||
progressSubscriptions += presenter.progressRelay
|
progressSubscriptions += presenter.progressRelay
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.combineLatest(presenter.progressTotalRelay) { progress, total ->
|
.combineLatest(presenter.progressTotalRelay) { progress, total ->
|
||||||
// Show hide dismiss button
|
// Show hide dismiss button
|
||||||
binding.progressDismissBtn.visibility =
|
binding.progressDismissBtn.visibility =
|
||||||
if (progress == total)
|
if (progress == total) {
|
||||||
View.VISIBLE
|
View.VISIBLE
|
||||||
else View.GONE
|
} else {
|
||||||
|
View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
formatProgress(progress, total)
|
formatProgress(progress, total)
|
||||||
}.subscribeUntilDestroy {
|
}.subscribeUntilDestroy {
|
||||||
binding.progressText.text = it
|
binding.progressText.text = it
|
||||||
}
|
}
|
||||||
|
|
||||||
progressSubscriptions += presenter.progressTotalRelay
|
progressSubscriptions += presenter.progressTotalRelay
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribeUntilDestroy {
|
.subscribeUntilDestroy {
|
||||||
binding.progressBar.max = it
|
binding.progressBar.max = it
|
||||||
}
|
|
||||||
|
|
||||||
progressSubscriptions += presenter.progressRelay
|
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
.subscribeUntilDestroy {
|
|
||||||
binding.progressBar.progress = it
|
|
||||||
}
|
|
||||||
|
|
||||||
presenter.eventRelay
|
|
||||||
?.observeOn(AndroidSchedulers.mainThread())
|
|
||||||
?.subscribeUntilDestroy {
|
|
||||||
binding.progressLog.append("$it\n")
|
|
||||||
}?.let {
|
|
||||||
progressSubscriptions += it
|
|
||||||
}
|
}
|
||||||
} else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) {
|
|
||||||
hideProgress(this)
|
progressSubscriptions += presenter.progressRelay
|
||||||
presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE)
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
.subscribeUntilDestroy {
|
||||||
|
binding.progressBar.progress = it
|
||||||
|
}
|
||||||
|
|
||||||
|
presenter.eventRelay
|
||||||
|
?.observeOn(AndroidSchedulers.mainThread())
|
||||||
|
?.subscribeUntilDestroy {
|
||||||
|
binding.progressLog.append("$it\n")
|
||||||
|
}?.let {
|
||||||
|
progressSubscriptions += it
|
||||||
}
|
}
|
||||||
|
} else if (it == BatchAddPresenter.STATE_PROGRESS_TO_INPUT) {
|
||||||
|
hideProgress(this)
|
||||||
|
presenter.currentlyAddingRelay.call(BatchAddPresenter.STATE_IDLE)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val View.progressViews
|
private val View.progressViews
|
||||||
get() = listOf(
|
get() = listOf(
|
||||||
binding.progressTitleView,
|
binding.progressTitleView,
|
||||||
binding.progressLogWrapper,
|
binding.progressLogWrapper,
|
||||||
binding.progressBar,
|
binding.progressBar,
|
||||||
binding.progressText,
|
binding.progressText,
|
||||||
binding.progressDismissBtn
|
binding.progressDismissBtn
|
||||||
)
|
)
|
||||||
|
|
||||||
private val View.inputViews
|
private val View.inputViews
|
||||||
get() = listOf(
|
get() = listOf(
|
||||||
binding.inputTitleView,
|
binding.inputTitleView,
|
||||||
binding.galleriesBox,
|
binding.galleriesBox,
|
||||||
binding.btnAddGalleries
|
binding.btnAddGalleries
|
||||||
)
|
)
|
||||||
|
|
||||||
private var List<View>.visibility: Int
|
private var List<View>.visibility: Int
|
||||||
@ -144,12 +146,12 @@ class BatchAddController : NucleusController<EhFragmentBatchAddBinding, BatchAdd
|
|||||||
private fun noGalleriesSpecified() {
|
private fun noGalleriesSpecified() {
|
||||||
activity?.let {
|
activity?.let {
|
||||||
MaterialDialog(it)
|
MaterialDialog(it)
|
||||||
.title(text = "No galleries to add!")
|
.title(text = "No galleries to add!")
|
||||||
.message(text = "You must specify at least one gallery to add!")
|
.message(text = "You must specify at least one gallery to add!")
|
||||||
.positiveButton(android.R.string.ok) { materialDialog -> materialDialog.dismiss() }
|
.positiveButton(android.R.string.ok) { materialDialog -> materialDialog.dismiss() }
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(true)
|
.cancelOnTouchOutside(true)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -40,10 +40,14 @@ class BatchAddPresenter : BasePresenter<BatchAddController>() {
|
|||||||
failed.add(s)
|
failed.add(s)
|
||||||
}
|
}
|
||||||
progressRelay.call(i + 1)
|
progressRelay.call(i + 1)
|
||||||
eventRelay?.call((when (result) {
|
eventRelay?.call(
|
||||||
is GalleryAddEvent.Success -> "[OK]"
|
(
|
||||||
is GalleryAddEvent.Fail -> "[ERROR]"
|
when (result) {
|
||||||
}) + " " + result.logMessage)
|
is GalleryAddEvent.Success -> "[OK]"
|
||||||
|
is GalleryAddEvent.Fail -> "[ERROR]"
|
||||||
|
}
|
||||||
|
) + " " + result.logMessage
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Show report
|
// Show report
|
||||||
|
@ -26,9 +26,9 @@ class AutoSolvingWebViewClient(
|
|||||||
val doc = response.asJsoup()
|
val doc = response.asJsoup()
|
||||||
doc.body().appendChild(Element("script").appendChild(DataNode(CROSS_WINDOW_SCRIPT_INNER)))
|
doc.body().appendChild(Element("script").appendChild(DataNode(CROSS_WINDOW_SCRIPT_INNER)))
|
||||||
return WebResourceResponse(
|
return WebResourceResponse(
|
||||||
"text/html",
|
"text/html",
|
||||||
"UTF-8",
|
"UTF-8",
|
||||||
doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered()
|
doc.toString().byteInputStream(Charset.forName("UTF-8")).buffered()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
return super.shouldInterceptRequest(view, request)
|
return super.shouldInterceptRequest(view, request)
|
||||||
|
@ -14,8 +14,9 @@ open class BasicWebViewClient(
|
|||||||
if (verifyComplete(url)) {
|
if (verifyComplete(url)) {
|
||||||
activity.finish()
|
activity.finish()
|
||||||
} else {
|
} else {
|
||||||
if (injectScript != null)
|
if (injectScript != null) {
|
||||||
view.evaluateJavascript("(function() {$injectScript})();", null)
|
view.evaluateJavascript("(function() {$injectScript})();", null)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -62,24 +62,27 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null
|
val originalSource = if (sourceId != -1L) sourceManager.get(sourceId) else null
|
||||||
val source = if (originalSource != null) {
|
val source = if (originalSource != null) {
|
||||||
originalSource as? ActionCompletionVerifier
|
originalSource as? ActionCompletionVerifier
|
||||||
?: run {
|
?: run {
|
||||||
(originalSource as? HttpSource)?.let {
|
(originalSource as? HttpSource)?.let {
|
||||||
NoopActionCompletionVerifier(it)
|
NoopActionCompletionVerifier(it)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else null
|
} else null
|
||||||
|
|
||||||
val headers = ((source as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
val headers = (
|
||||||
it.value.joinToString(",")
|
(source as? HttpSource)?.headers?.toMultimap()?.mapValues {
|
||||||
} ?: emptyMap()) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
|
it.value.joinToString(",")
|
||||||
|
} ?: emptyMap()
|
||||||
|
) + (intent.getSerializableExtra(HEADERS_EXTRA) as? HashMap<String, String> ?: emptyMap())
|
||||||
|
|
||||||
val cookies: HashMap<String, String>? =
|
val cookies: HashMap<String, String>? =
|
||||||
intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
|
intent.getSerializableExtra(COOKIES_EXTRA) as? HashMap<String, String>
|
||||||
val script: String? = intent.getStringExtra(SCRIPT_EXTRA)
|
val script: String? = intent.getStringExtra(SCRIPT_EXTRA)
|
||||||
val url: String? = intent.getStringExtra(URL_EXTRA)
|
val url: String? = intent.getStringExtra(URL_EXTRA)
|
||||||
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
|
val actionName = intent.getStringExtra(ACTION_NAME_EXTRA)
|
||||||
|
|
||||||
@Suppress("NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE") val verifyComplete = if (source != null) {
|
@Suppress("NOT_NULL_ASSERTION_ON_CALLABLE_REFERENCE")
|
||||||
|
val verifyComplete = if (source != null) {
|
||||||
source::verifyComplete!!
|
source::verifyComplete!!
|
||||||
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
|
} else intent.getSerializableExtra(VERIFY_LAMBDA_EXTRA) as? (String) -> Boolean
|
||||||
|
|
||||||
@ -139,10 +142,12 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
webview.webViewClient = if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
|
webview.webViewClient = if (actionName == null && preferencesHelper.eh_autoSolveCaptchas().getOrDefault()) {
|
||||||
// Fetch auto-solve credentials early for speed
|
// Fetch auto-solve credentials early for speed
|
||||||
credentialsObservable = httpClient.newCall(Request.Builder()
|
credentialsObservable = httpClient.newCall(
|
||||||
// Rob demo credentials
|
Request.Builder()
|
||||||
.url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
|
// Rob demo credentials
|
||||||
.build())
|
.url("https://speech-to-text-demo.ng.bluemix.net/api/v1/credentials")
|
||||||
|
.build()
|
||||||
|
)
|
||||||
.asObservableSuccess()
|
.asObservableSuccess()
|
||||||
.subscribeOn(Schedulers.io())
|
.subscribeOn(Schedulers.io())
|
||||||
.map {
|
.map {
|
||||||
@ -176,12 +181,12 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null)
|
webview.evaluateJavascript(SOLVE_UI_SCRIPT_HIDE, null)
|
||||||
MaterialDialog(this)
|
MaterialDialog(this)
|
||||||
.title(text = "Captcha solve failure")
|
.title(text = "Captcha solve failure")
|
||||||
.message(text = "Failed to auto-solve the captcha!")
|
.message(text = "Failed to auto-solve the captcha!")
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(true)
|
.cancelOnTouchOutside(true)
|
||||||
.positiveButton(android.R.string.ok)
|
.positiveButton(android.R.string.ok)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,13 +197,19 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
when (stage) {
|
when (stage) {
|
||||||
STAGE_CHECKBOX -> {
|
STAGE_CHECKBOX -> {
|
||||||
if (result!!.toBoolean()) {
|
if (result!!.toBoolean()) {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
getAudioButtonLocation(loopId)
|
{
|
||||||
}, 250)
|
getAudioButtonLocation(loopId)
|
||||||
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
doStageCheckbox(loopId)
|
{
|
||||||
}, 250)
|
doStageCheckbox(loopId)
|
||||||
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_GET_AUDIO_BTN_LOCATION -> {
|
STAGE_GET_AUDIO_BTN_LOCATION -> {
|
||||||
@ -216,31 +227,43 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
doStageDownloadAudio(loopId)
|
doStageDownloadAudio(loopId)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
getAudioButtonLocation(loopId)
|
{
|
||||||
}, 250)
|
getAudioButtonLocation(loopId)
|
||||||
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_DOWNLOAD_AUDIO -> {
|
STAGE_DOWNLOAD_AUDIO -> {
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
Timber.d("Got audio URL: $result")
|
Timber.d("Got audio URL: $result")
|
||||||
performRecognize(result)
|
performRecognize(result)
|
||||||
.observeOn(Schedulers.io())
|
.observeOn(Schedulers.io())
|
||||||
.subscribe({
|
.subscribe(
|
||||||
|
{
|
||||||
Timber.d("Got audio transcript: $it")
|
Timber.d("Got audio transcript: $it")
|
||||||
webview.post {
|
webview.post {
|
||||||
typeResult(loopId, it!!
|
typeResult(
|
||||||
|
loopId,
|
||||||
|
it!!
|
||||||
.replace(TRANSCRIPT_CLEANER_REGEX, "")
|
.replace(TRANSCRIPT_CLEANER_REGEX, "")
|
||||||
.replace(SPACE_DEDUPE_REGEX, " ")
|
.replace(SPACE_DEDUPE_REGEX, " ")
|
||||||
.trim())
|
.trim()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
captchaSolveFail()
|
captchaSolveFail()
|
||||||
})
|
}
|
||||||
|
)
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
doStageDownloadAudio(loopId)
|
{
|
||||||
}, 250)
|
doStageDownloadAudio(loopId)
|
||||||
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
STAGE_TYPE_RESULT -> {
|
STAGE_TYPE_RESULT -> {
|
||||||
@ -256,27 +279,37 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
fun performRecognize(url: String): Single<String> {
|
fun performRecognize(url: String): Single<String> {
|
||||||
return credentialsObservable.flatMap { token ->
|
return credentialsObservable.flatMap { token ->
|
||||||
httpClient.newCall(Request.Builder()
|
httpClient.newCall(
|
||||||
|
Request.Builder()
|
||||||
.url(url)
|
.url(url)
|
||||||
.build()).asObservableSuccess().map {
|
.build()
|
||||||
|
).asObservableSuccess().map {
|
||||||
token to it
|
token to it
|
||||||
}
|
}
|
||||||
}.flatMap { (token, response) ->
|
}.flatMap { (token, response) ->
|
||||||
val audioFile = response.body!!.bytes()
|
val audioFile = response.body!!.bytes()
|
||||||
|
|
||||||
httpClient.newCall(Request.Builder()
|
httpClient.newCall(
|
||||||
.url("https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!!
|
Request.Builder()
|
||||||
|
.url(
|
||||||
|
"https://stream.watsonplatform.net/speech-to-text/api/v1/recognize".toHttpUrlOrNull()!!
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addQueryParameter("watson-token", token)
|
.addQueryParameter("watson-token", token)
|
||||||
.build())
|
.build()
|
||||||
.post(MultipartBody.Builder()
|
)
|
||||||
|
.post(
|
||||||
|
MultipartBody.Builder()
|
||||||
.setType(MultipartBody.FORM)
|
.setType(MultipartBody.FORM)
|
||||||
.addFormDataPart("jsonDescription", RECOGNIZE_JSON)
|
.addFormDataPart("jsonDescription", RECOGNIZE_JSON)
|
||||||
.addFormDataPart("audio.mp3",
|
.addFormDataPart(
|
||||||
"audio.mp3",
|
"audio.mp3",
|
||||||
RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile))
|
"audio.mp3",
|
||||||
.build())
|
RequestBody.create("audio/mp3".toMediaTypeOrNull(), audioFile)
|
||||||
.build()).asObservableSuccess()
|
)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
).asObservableSuccess()
|
||||||
}.map { response ->
|
}.map { response ->
|
||||||
JsonParser.parseString(response.body!!.string())["results"][0]["alternatives"][0]["transcript"].string.trim()
|
JsonParser.parseString(response.body!!.string())["results"][0]["alternatives"][0]["transcript"].string.trim()
|
||||||
}.toSingle()
|
}.toSingle()
|
||||||
@ -285,7 +318,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
fun doStageCheckbox(loopId: String) {
|
fun doStageCheckbox(loopId: String) {
|
||||||
if (loopId != currentLoopId) return
|
if (loopId != currentLoopId) return
|
||||||
|
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -307,11 +341,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback("false", '$loopId', $STAGE_CHECKBOX);
|
exh.callback("false", '$loopId', $STAGE_CHECKBOX);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getAudioButtonLocation(loopId: String) {
|
fun getAudioButtonLocation(loopId: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -339,11 +376,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback(null, '$loopId', $STAGE_GET_AUDIO_BTN_LOCATION);
|
exh.callback(null, '$loopId', $STAGE_GET_AUDIO_BTN_LOCATION);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun doStageDownloadAudio(loopId: String) {
|
fun doStageDownloadAudio(loopId: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -364,11 +404,14 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback(null, '$loopId', $STAGE_DOWNLOAD_AUDIO);
|
exh.callback(null, '$loopId', $STAGE_DOWNLOAD_AUDIO);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun typeResult(loopId: String, result: String) {
|
fun typeResult(loopId: String, result: String) {
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -392,7 +435,9 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.callback("false", '$loopId', $STAGE_TYPE_RESULT);
|
exh.callback("false", '$loopId', $STAGE_TYPE_RESULT);
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginSolveLoop() {
|
fun beginSolveLoop() {
|
||||||
@ -419,12 +464,16 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
val savedStrictValidationStartTime = strictValidationStartTime
|
val savedStrictValidationStartTime = strictValidationStartTime
|
||||||
if (savedStrictValidationStartTime != null &&
|
if (savedStrictValidationStartTime != null &&
|
||||||
System.currentTimeMillis() > savedStrictValidationStartTime) {
|
System.currentTimeMillis() > savedStrictValidationStartTime
|
||||||
|
) {
|
||||||
captchaSolveFail()
|
captchaSolveFail()
|
||||||
} else {
|
} else {
|
||||||
webview.postDelayed({
|
webview.postDelayed(
|
||||||
runValidateCaptcha(loopId)
|
{
|
||||||
}, 250)
|
runValidateCaptcha(loopId)
|
||||||
|
},
|
||||||
|
250
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -432,7 +481,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
fun runValidateCaptcha(loopId: String) {
|
fun runValidateCaptcha(loopId: String) {
|
||||||
if (loopId != validateCurrentLoopId) return
|
if (loopId != validateCurrentLoopId) return
|
||||||
|
|
||||||
webview.evaluateJavascript("""
|
webview.evaluateJavascript(
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
$CROSS_WINDOW_SCRIPT_OUTER
|
$CROSS_WINDOW_SCRIPT_OUTER
|
||||||
|
|
||||||
@ -453,7 +503,9 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh.validateCaptchaCallback(false, '$loopId');
|
exh.validateCaptchaCallback(false, '$loopId');
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
""".trimIndent().replace("\n", ""), null)
|
""".trimIndent().replace("\n", ""),
|
||||||
|
null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginValidateCaptchaLoop() {
|
fun beginValidateCaptchaLoop() {
|
||||||
@ -502,7 +554,8 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
const val STAGE_DOWNLOAD_AUDIO = 2
|
const val STAGE_DOWNLOAD_AUDIO = 2
|
||||||
const val STAGE_TYPE_RESULT = 3
|
const val STAGE_TYPE_RESULT = 3
|
||||||
|
|
||||||
val CROSS_WINDOW_SCRIPT_OUTER = """
|
val CROSS_WINDOW_SCRIPT_OUTER =
|
||||||
|
"""
|
||||||
function cwmExec(element, code, cb) {
|
function cwmExec(element, code, cb) {
|
||||||
console.log(">>> [CWM-Outer] Running: " + code);
|
console.log(">>> [CWM-Outer] Running: " + code);
|
||||||
let runId = Math.random();
|
let runId = Math.random();
|
||||||
@ -523,9 +576,10 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
let runRequest = { id: runId, code: code };
|
let runRequest = { id: runId, code: code };
|
||||||
element.contentWindow.postMessage("exh-" + JSON.stringify(runRequest), "*");
|
element.contentWindow.postMessage("exh-" + JSON.stringify(runRequest), "*");
|
||||||
}
|
}
|
||||||
""".trimIndent().replace("\n", "")
|
""".trimIndent().replace("\n", "")
|
||||||
|
|
||||||
val CROSS_WINDOW_SCRIPT_INNER = """
|
val CROSS_WINDOW_SCRIPT_INNER =
|
||||||
|
"""
|
||||||
window.addEventListener('message', function(event) {
|
window.addEventListener('message', function(event) {
|
||||||
if(typeof event.data === "string" && event.data.startsWith("exh-")) {
|
if(typeof event.data === "string" && event.data.startsWith("exh-")) {
|
||||||
let request = JSON.parse(event.data.substring(4));
|
let request = JSON.parse(event.data.substring(4));
|
||||||
@ -538,9 +592,10 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
}, false);
|
}, false);
|
||||||
console.log(">>> [CWM-Inner] Loaded!");
|
console.log(">>> [CWM-Inner] Loaded!");
|
||||||
alert("exh-");
|
alert("exh-");
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val SOLVE_UI_SCRIPT_SHOW = """
|
val SOLVE_UI_SCRIPT_SHOW =
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
let exh_overlay = document.createElement("div");
|
let exh_overlay = document.createElement("div");
|
||||||
exh_overlay.id = "exh_overlay";
|
exh_overlay.id = "exh_overlay";
|
||||||
@ -568,18 +623,20 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
exh_otext.textContent = "Solving captcha..."
|
exh_otext.textContent = "Solving captcha..."
|
||||||
document.body.appendChild(exh_otext);
|
document.body.appendChild(exh_otext);
|
||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val SOLVE_UI_SCRIPT_HIDE = """
|
val SOLVE_UI_SCRIPT_HIDE =
|
||||||
|
"""
|
||||||
(function() {
|
(function() {
|
||||||
let exh_overlay = document.getElementById("exh_overlay");
|
let exh_overlay = document.getElementById("exh_overlay");
|
||||||
let exh_otext = document.getElementById("exh_otext");
|
let exh_otext = document.getElementById("exh_otext");
|
||||||
if(exh_overlay != null) exh_overlay.remove();
|
if(exh_overlay != null) exh_overlay.remove();
|
||||||
if(exh_otext != null) exh_otext.remove();
|
if(exh_otext != null) exh_otext.remove();
|
||||||
})();
|
})();
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val RECOGNIZE_JSON = """
|
val RECOGNIZE_JSON =
|
||||||
|
"""
|
||||||
{
|
{
|
||||||
"part_content_type": "audio/mp3",
|
"part_content_type": "audio/mp3",
|
||||||
"keywords": [],
|
"keywords": [],
|
||||||
@ -596,15 +653,15 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
"customGrammarWords": [],
|
"customGrammarWords": [],
|
||||||
"action": "recognize"
|
"action": "recognize"
|
||||||
}
|
}
|
||||||
""".trimIndent()
|
""".trimIndent()
|
||||||
|
|
||||||
val TRANSCRIPT_CLEANER_REGEX = Regex("[^0-9a-zA-Z_ -]")
|
val TRANSCRIPT_CLEANER_REGEX = Regex("[^0-9a-zA-Z_ -]")
|
||||||
val SPACE_DEDUPE_REGEX = Regex(" +")
|
val SPACE_DEDUPE_REGEX = Regex(" +")
|
||||||
|
|
||||||
private fun baseIntent(context: Context) =
|
private fun baseIntent(context: Context) =
|
||||||
Intent(context, BrowserActionActivity::class.java).apply {
|
Intent(context, BrowserActionActivity::class.java).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchCaptcha(
|
fun launchCaptcha(
|
||||||
context: Context,
|
context: Context,
|
||||||
@ -689,8 +746,9 @@ class BrowserActionActivity : AppCompatActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class NoopActionCompletionVerifier(private val source: HttpSource) : DelegatedHttpSource(source),
|
class NoopActionCompletionVerifier(private val source: HttpSource) :
|
||||||
ActionCompletionVerifier {
|
DelegatedHttpSource(source),
|
||||||
|
ActionCompletionVerifier {
|
||||||
override val versionId get() = source.versionId
|
override val versionId get() = source.versionId
|
||||||
override val lang: String get() = source.lang
|
override val lang: String get() = source.lang
|
||||||
|
|
||||||
|
@ -37,43 +37,43 @@ open class HeadersInjectingWebViewClient(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val FALLBACK_REASON_PHRASES = mapOf(
|
private val FALLBACK_REASON_PHRASES = mapOf(
|
||||||
100 to "Continue",
|
100 to "Continue",
|
||||||
101 to "Switching Protocols",
|
101 to "Switching Protocols",
|
||||||
200 to "OK",
|
200 to "OK",
|
||||||
201 to "Created",
|
201 to "Created",
|
||||||
202 to "Accepted",
|
202 to "Accepted",
|
||||||
203 to "Non-Authoritative Information",
|
203 to "Non-Authoritative Information",
|
||||||
204 to "No Content",
|
204 to "No Content",
|
||||||
205 to "Reset Content",
|
205 to "Reset Content",
|
||||||
206 to "Partial Content",
|
206 to "Partial Content",
|
||||||
300 to "Multiple Choices",
|
300 to "Multiple Choices",
|
||||||
301 to "Moved Permanently",
|
301 to "Moved Permanently",
|
||||||
302 to "Moved Temporarily",
|
302 to "Moved Temporarily",
|
||||||
303 to "See Other",
|
303 to "See Other",
|
||||||
304 to "Not Modified",
|
304 to "Not Modified",
|
||||||
305 to "Use Proxy",
|
305 to "Use Proxy",
|
||||||
400 to "Bad Request",
|
400 to "Bad Request",
|
||||||
401 to "Unauthorized",
|
401 to "Unauthorized",
|
||||||
402 to "Payment Required",
|
402 to "Payment Required",
|
||||||
403 to "Forbidden",
|
403 to "Forbidden",
|
||||||
404 to "Not Found",
|
404 to "Not Found",
|
||||||
405 to "Method Not Allowed",
|
405 to "Method Not Allowed",
|
||||||
406 to "Not Acceptable",
|
406 to "Not Acceptable",
|
||||||
407 to "Proxy Authentication Required",
|
407 to "Proxy Authentication Required",
|
||||||
408 to "Request Time-out",
|
408 to "Request Time-out",
|
||||||
409 to "Conflict",
|
409 to "Conflict",
|
||||||
410 to "Gone",
|
410 to "Gone",
|
||||||
411 to "Length Required",
|
411 to "Length Required",
|
||||||
412 to "Precondition Failed",
|
412 to "Precondition Failed",
|
||||||
413 to "Request Entity Too Large",
|
413 to "Request Entity Too Large",
|
||||||
414 to "Request-URI Too Large",
|
414 to "Request-URI Too Large",
|
||||||
415 to "Unsupported Media Type",
|
415 to "Unsupported Media Type",
|
||||||
500 to "Internal Server Error",
|
500 to "Internal Server Error",
|
||||||
501 to "Not Implemented",
|
501 to "Not Implemented",
|
||||||
502 to "Bad Gateway",
|
502 to "Bad Gateway",
|
||||||
503 to "Service Unavailable",
|
503 to "Service Unavailable",
|
||||||
504 to "Gateway Time-out",
|
504 to "Gateway Time-out",
|
||||||
505 to "HTTP Version not supported"
|
505 to "HTTP Version not supported"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,8 @@ import okhttp3.Request
|
|||||||
|
|
||||||
fun WebResourceRequest.toOkHttpRequest(): Request {
|
fun WebResourceRequest.toOkHttpRequest(): Request {
|
||||||
val request = Request.Builder()
|
val request = Request.Builder()
|
||||||
.url(url.toString())
|
.url(url.toString())
|
||||||
.method(method, null)
|
.method(method, null)
|
||||||
|
|
||||||
requestHeaders.entries.forEach { (t, u) ->
|
requestHeaders.entries.forEach { (t, u) ->
|
||||||
request.addHeader(t, u)
|
request.addHeader(t, u)
|
||||||
|
@ -54,33 +54,35 @@ class InterceptActivity : BaseRxActivity<EhActivityInterceptBinding, InterceptAc
|
|||||||
super.onStart()
|
super.onStart()
|
||||||
statusSubscription?.unsubscribe()
|
statusSubscription?.unsubscribe()
|
||||||
statusSubscription = presenter.status
|
statusSubscription = presenter.status
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
when (it) {
|
when (it) {
|
||||||
is InterceptResult.Success -> {
|
is InterceptResult.Success -> {
|
||||||
binding.interceptProgress.gone()
|
binding.interceptProgress.gone()
|
||||||
binding.interceptStatus.text = "Launching app..."
|
binding.interceptStatus.text = "Launching app..."
|
||||||
onBackPressed()
|
onBackPressed()
|
||||||
startActivity(Intent(this, MainActivity::class.java)
|
startActivity(
|
||||||
.setAction(MainActivity.SHORTCUT_MANGA)
|
Intent(this, MainActivity::class.java)
|
||||||
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
.setAction(MainActivity.SHORTCUT_MANGA)
|
||||||
.putExtra(MangaController.MANGA_EXTRA, it.mangaId))
|
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
|
||||||
}
|
.putExtra(MangaController.MANGA_EXTRA, it.mangaId)
|
||||||
is InterceptResult.Failure -> {
|
)
|
||||||
binding.interceptProgress.gone()
|
}
|
||||||
binding.interceptStatus.text = "Error: ${it.reason}"
|
is InterceptResult.Failure -> {
|
||||||
MaterialDialog(this)
|
binding.interceptProgress.gone()
|
||||||
.title(text = "Error")
|
binding.interceptStatus.text = "Error: ${it.reason}"
|
||||||
.message(text = "Could not open this gallery:\n\n${it.reason}")
|
MaterialDialog(this)
|
||||||
.cancelable(true)
|
.title(text = "Error")
|
||||||
.cancelOnTouchOutside(true)
|
.message(text = "Could not open this gallery:\n\n${it.reason}")
|
||||||
.positiveButton(android.R.string.ok)
|
.cancelable(true)
|
||||||
.onCancel { onBackPressed() }
|
.cancelOnTouchOutside(true)
|
||||||
.onDismiss { onBackPressed() }
|
.positiveButton(android.R.string.ok)
|
||||||
.show()
|
.onCancel { onBackPressed() }
|
||||||
}
|
.onDismiss { onBackPressed() }
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
|
@ -21,12 +21,14 @@ class InterceptActivityPresenter : BasePresenter<InterceptActivity>() {
|
|||||||
thread {
|
thread {
|
||||||
val result = galleryAdder.addGallery(gallery)
|
val result = galleryAdder.addGallery(gallery)
|
||||||
|
|
||||||
status.onNext(when (result) {
|
status.onNext(
|
||||||
is GalleryAddEvent.Success -> result.manga.id?.let {
|
when (result) {
|
||||||
InterceptResult.Success(it)
|
is GalleryAddEvent.Success -> result.manga.id?.let {
|
||||||
} ?: InterceptResult.Failure("Manga ID is null!")
|
InterceptResult.Success(it)
|
||||||
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
|
} ?: InterceptResult.Failure("Manga ID is null!")
|
||||||
})
|
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,18 +25,18 @@ import rx.android.schedulers.AndroidSchedulers
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
SwitchPreferenceCompat(context, attrs) {
|
SwitchPreferenceCompat(context, attrs) {
|
||||||
|
|
||||||
val prefs: PreferencesHelper by injectLazy()
|
val prefs: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
val fingerprintSupported
|
val fingerprintSupported
|
||||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||||
Reprint.isHardwarePresent() &&
|
Reprint.isHardwarePresent() &&
|
||||||
Reprint.hasFingerprintRegistered()
|
Reprint.hasFingerprintRegistered()
|
||||||
|
|
||||||
val useFingerprint
|
val useFingerprint
|
||||||
get() = fingerprintSupported &&
|
get() = fingerprintSupported &&
|
||||||
prefs.eh_lockUseFingerprint().getOrDefault()
|
prefs.eh_lockUseFingerprint().getOrDefault()
|
||||||
|
|
||||||
@SuppressLint("NewApi")
|
@SuppressLint("NewApi")
|
||||||
override fun onAttached() {
|
override fun onAttached() {
|
||||||
@ -44,29 +44,32 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
if (fingerprintSupported) {
|
if (fingerprintSupported) {
|
||||||
updateSummary()
|
updateSummary()
|
||||||
onChange {
|
onChange {
|
||||||
if (it as Boolean)
|
if (it as Boolean) {
|
||||||
tryChange()
|
tryChange()
|
||||||
else
|
} else {
|
||||||
prefs.eh_lockUseFingerprint().set(false)
|
prefs.eh_lockUseFingerprint().set(false)
|
||||||
|
}
|
||||||
!it
|
!it
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
title = "Fingerprint unsupported"
|
title = "Fingerprint unsupported"
|
||||||
shouldDisableView = true
|
shouldDisableView = true
|
||||||
summary = if (!Reprint.hasFingerprintRegistered())
|
summary = if (!Reprint.hasFingerprintRegistered()) {
|
||||||
"No fingerprints enrolled!"
|
"No fingerprints enrolled!"
|
||||||
else
|
} else {
|
||||||
"Fingerprint unlock is unsupported on this device!"
|
"Fingerprint unlock is unsupported on this device!"
|
||||||
|
}
|
||||||
onChange { false }
|
onChange { false }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateSummary() {
|
private fun updateSummary() {
|
||||||
isChecked = useFingerprint
|
isChecked = useFingerprint
|
||||||
title = if (isChecked)
|
title = if (isChecked) {
|
||||||
"Fingerprint enabled"
|
"Fingerprint enabled"
|
||||||
else
|
} else {
|
||||||
"Fingerprint disabled"
|
"Fingerprint disabled"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
@TargetApi(Build.VERSION_CODES.M)
|
||||||
@ -74,9 +77,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
val statusTextView = TextView(context).apply {
|
val statusTextView = TextView(context).apply {
|
||||||
text = "Please touch the fingerprint sensor"
|
text = "Please touch the fingerprint sensor"
|
||||||
val size = ViewGroup.LayoutParams.WRAP_CONTENT
|
val size = ViewGroup.LayoutParams.WRAP_CONTENT
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
setPadding(0, 0, dpToPx(context, 8), 0)
|
setPadding(0, 0, dpToPx(context, 8), 0)
|
||||||
@ -84,9 +89,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
}
|
}
|
||||||
val iconView = SwirlView(context).apply {
|
val iconView = SwirlView(context).apply {
|
||||||
val size = dpToPx(context, 30)
|
val size = dpToPx(context, 30)
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
}
|
}
|
||||||
@ -96,9 +103,11 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
orientation = LinearLayoutCompat.HORIZONTAL
|
orientation = LinearLayoutCompat.HORIZONTAL
|
||||||
gravity = Gravity.CENTER_VERTICAL
|
gravity = Gravity.CENTER_VERTICAL
|
||||||
val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT
|
val size = LinearLayoutCompat.LayoutParams.WRAP_CONTENT
|
||||||
layoutParams = (layoutParams ?: LinearLayoutCompat.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: LinearLayoutCompat.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
val pSize = dpToPx(context, 24)
|
val pSize = dpToPx(context, 24)
|
||||||
@ -109,39 +118,39 @@ class FingerLockPreference @JvmOverloads constructor(context: Context, attrs: At
|
|||||||
addView(iconView)
|
addView(iconView)
|
||||||
}
|
}
|
||||||
val dialog = MaterialDialog(context)
|
val dialog = MaterialDialog(context)
|
||||||
.title(text = "Fingerprint verification")
|
.title(text = "Fingerprint verification")
|
||||||
.customView(view = linearLayout)
|
.customView(view = linearLayout)
|
||||||
.negativeButton(R.string.action_cancel)
|
.negativeButton(R.string.action_cancel)
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(true)
|
.cancelOnTouchOutside(true)
|
||||||
dialog.show()
|
dialog.show()
|
||||||
iconView.setState(SwirlView.State.ON)
|
iconView.setState(SwirlView.State.ON)
|
||||||
val subscription = RxReprint.authenticate()
|
val subscription = RxReprint.authenticate()
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe { result ->
|
.subscribe { result ->
|
||||||
when (result.status) {
|
when (result.status) {
|
||||||
AuthenticationResult.Status.SUCCESS -> {
|
AuthenticationResult.Status.SUCCESS -> {
|
||||||
iconView.setState(SwirlView.State.ON)
|
iconView.setState(SwirlView.State.ON)
|
||||||
prefs.eh_lockUseFingerprint().set(true)
|
prefs.eh_lockUseFingerprint().set(true)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
updateSummary()
|
updateSummary()
|
||||||
}
|
}
|
||||||
AuthenticationResult.Status.NONFATAL_FAILURE -> {
|
AuthenticationResult.Status.NONFATAL_FAILURE -> {
|
||||||
iconView.setState(SwirlView.State.ERROR)
|
iconView.setState(SwirlView.State.ERROR)
|
||||||
statusTextView.text = result.errorMessage
|
statusTextView.text = result.errorMessage
|
||||||
}
|
}
|
||||||
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Fingerprint verification failed!")
|
.title(text = "Fingerprint verification failed!")
|
||||||
.message(text = result.errorMessage)
|
.message(text = result.errorMessage)
|
||||||
.positiveButton(android.R.string.ok)
|
.positiveButton(android.R.string.ok)
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(false)
|
.cancelOnTouchOutside(false)
|
||||||
.show()
|
.show()
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
dialog.setOnDismissListener {
|
dialog.setOnDismissListener {
|
||||||
subscription.unsubscribe()
|
subscription.unsubscribe()
|
||||||
}
|
}
|
||||||
|
@ -20,19 +20,21 @@ object LockActivityDelegate {
|
|||||||
private val uiScope = CoroutineScope(Dispatchers.Main)
|
private val uiScope = CoroutineScope(Dispatchers.Main)
|
||||||
|
|
||||||
fun doLock(router: Router, animate: Boolean = false) {
|
fun doLock(router: Router, animate: Boolean = false) {
|
||||||
router.pushController(RouterTransaction.with(LockController())
|
router.pushController(
|
||||||
.popChangeHandler(LockChangeHandler(animate)))
|
RouterTransaction.with(LockController())
|
||||||
|
.popChangeHandler(LockChangeHandler(animate))
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onCreate(activity: FragmentActivity) {
|
fun onCreate(activity: FragmentActivity) {
|
||||||
preferences.secureScreen().asFlow()
|
preferences.secureScreen().asFlow()
|
||||||
.onEach {
|
.onEach {
|
||||||
if (it) {
|
if (it) {
|
||||||
activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
activity.window.setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
} else {
|
} else {
|
||||||
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
activity.window.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.launchIn(uiScope)
|
.launchIn(uiScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,5 +35,5 @@ class LockChangeHandler : AnimatorChangeHandler {
|
|||||||
override fun resetFromView(from: View) {}
|
override fun resetFromView(from: View) {}
|
||||||
|
|
||||||
override fun copy(): ControllerChangeHandler =
|
override fun copy(): ControllerChangeHandler =
|
||||||
LockChangeHandler(animationDuration, removesFromViewOnPush())
|
LockChangeHandler(animationDuration, removesFromViewOnPush())
|
||||||
}
|
}
|
||||||
|
@ -53,12 +53,12 @@ class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
|||||||
closeLock()
|
closeLock()
|
||||||
} else {
|
} else {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "PIN code incorrect")
|
.title(text = "PIN code incorrect")
|
||||||
.message(text = "The PIN code you entered is incorrect. Please try again.")
|
.message(text = "The PIN code you entered is incorrect. Please try again.")
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(true)
|
.cancelOnTouchOutside(true)
|
||||||
.positiveButton(android.R.string.ok)
|
.positiveButton(android.R.string.ok)
|
||||||
.show()
|
.show()
|
||||||
binding.pinLockView.resetPinLockView()
|
binding.pinLockView.resetPinLockView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,9 +79,11 @@ class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
|||||||
binding.swirlContainer.removeAllViews()
|
binding.swirlContainer.removeAllViews()
|
||||||
val icon = SwirlView(context).apply {
|
val icon = SwirlView(context).apply {
|
||||||
val size = dpToPx(context, 60)
|
val size = dpToPx(context, 60)
|
||||||
layoutParams = (layoutParams ?: ViewGroup.LayoutParams(
|
layoutParams = (
|
||||||
|
layoutParams ?: ViewGroup.LayoutParams(
|
||||||
size, size
|
size, size
|
||||||
)).apply {
|
)
|
||||||
|
).apply {
|
||||||
width = size
|
width = size
|
||||||
height = size
|
height = size
|
||||||
|
|
||||||
@ -92,29 +94,30 @@ class LockController : NucleusController<ActivityLockBinding, LockPresenter>() {
|
|||||||
setBackgroundColor(lockColor)
|
setBackgroundColor(lockColor)
|
||||||
val bgColor = resolvColor(android.R.attr.colorBackground)
|
val bgColor = resolvColor(android.R.attr.colorBackground)
|
||||||
// Disable elevation if lock color is same as background color
|
// Disable elevation if lock color is same as background color
|
||||||
if (lockColor == bgColor)
|
if (lockColor == bgColor) {
|
||||||
this@with.swirl_container.cardElevation = 0f
|
this@with.swirl_container.cardElevation = 0f
|
||||||
|
}
|
||||||
setState(SwirlView.State.OFF, true)
|
setState(SwirlView.State.OFF, true)
|
||||||
}
|
}
|
||||||
binding.swirlContainer.addView(icon)
|
binding.swirlContainer.addView(icon)
|
||||||
icon.setState(SwirlView.State.ON)
|
icon.setState(SwirlView.State.ON)
|
||||||
RxReprint.authenticate()
|
RxReprint.authenticate()
|
||||||
.subscribeUntilDetach {
|
.subscribeUntilDetach {
|
||||||
when (it.status) {
|
when (it.status) {
|
||||||
AuthenticationResult.Status.SUCCESS -> closeLock()
|
AuthenticationResult.Status.SUCCESS -> closeLock()
|
||||||
AuthenticationResult.Status.NONFATAL_FAILURE -> icon.setState(SwirlView.State.ERROR)
|
AuthenticationResult.Status.NONFATAL_FAILURE -> icon.setState(SwirlView.State.ERROR)
|
||||||
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
AuthenticationResult.Status.FATAL_FAILURE, null -> {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Fingerprint error!")
|
.title(text = "Fingerprint error!")
|
||||||
.message(text = it.errorMessage)
|
.message(text = it.errorMessage)
|
||||||
.cancelable(false)
|
.cancelable(false)
|
||||||
.cancelOnTouchOutside(false)
|
.cancelOnTouchOutside(false)
|
||||||
.positiveButton(android.R.string.ok)
|
.positiveButton(android.R.string.ok)
|
||||||
.show()
|
.show()
|
||||||
icon.setState(SwirlView.State.OFF)
|
icon.setState(SwirlView.State.OFF)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
binding.swirlContainer.visibility = View.GONE
|
binding.swirlContainer.visibility = View.GONE
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ import rx.schedulers.Schedulers
|
|||||||
import uy.kohesive.injekt.injectLazy
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
|
||||||
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
class LockPreference @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
|
||||||
SwitchPreferenceCompat(context, attrs) {
|
SwitchPreferenceCompat(context, attrs) {
|
||||||
|
|
||||||
private val secureRandom by lazy { SecureRandom() }
|
private val secureRandom by lazy { SecureRandom() }
|
||||||
|
|
||||||
@ -46,28 +46,28 @@ class LockPreference @JvmOverloads constructor(context: Context, attrs: Attribut
|
|||||||
fun tryChange() {
|
fun tryChange() {
|
||||||
if (!notifyLockSecurity(context)) {
|
if (!notifyLockSecurity(context)) {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Lock application")
|
.title(text = "Lock application")
|
||||||
.message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.")
|
.message(text = "Enter a pin to lock the application. Enter nothing to disable the pin lock.")
|
||||||
// .inputRangeRes(0, 10, R.color.material_red_500)
|
// .inputRangeRes(0, 10, R.color.material_red_500)
|
||||||
// .inputType(InputType.TYPE_CLASS_NUMBER)
|
// .inputType(InputType.TYPE_CLASS_NUMBER)
|
||||||
.input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c ->
|
.input(maxLength = 10, inputType = InputType.TYPE_CLASS_NUMBER, allowEmpty = true) { _, c ->
|
||||||
val progressDialog = MaterialDialog(context)
|
val progressDialog = MaterialDialog(context)
|
||||||
.title(text = "Saving password")
|
.title(text = "Saving password")
|
||||||
.cancelable(false)
|
.cancelable(false)
|
||||||
progressDialog.show()
|
progressDialog.show()
|
||||||
Observable.fromCallable {
|
Observable.fromCallable {
|
||||||
savePassword(c.toString())
|
savePassword(c.toString())
|
||||||
}.subscribeOn(Schedulers.computation())
|
}.subscribeOn(Schedulers.computation())
|
||||||
.observeOn(AndroidSchedulers.mainThread())
|
.observeOn(AndroidSchedulers.mainThread())
|
||||||
.subscribe {
|
.subscribe {
|
||||||
progressDialog.dismiss()
|
progressDialog.dismiss()
|
||||||
updateSummary()
|
updateSummary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.negativeButton(R.string.action_cancel)
|
.negativeButton(R.string.action_cancel)
|
||||||
.cancelable(true)
|
.cancelable(true)
|
||||||
.cancelOnTouchOutside(true)
|
.cancelOnTouchOutside(true)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -12,7 +12,7 @@ class LockPresenter : BasePresenter<LockController>() {
|
|||||||
|
|
||||||
val useFingerprint
|
val useFingerprint
|
||||||
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &&
|
||||||
Reprint.isHardwarePresent() &&
|
Reprint.isHardwarePresent() &&
|
||||||
Reprint.hasFingerprintRegistered() &&
|
Reprint.hasFingerprintRegistered() &&
|
||||||
prefs.eh_lockUseFingerprint().getOrDefault()
|
prefs.eh_lockUseFingerprint().getOrDefault()
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,8 @@ fun sha512(passwordToHash: String, salt: String): String {
|
|||||||
*/
|
*/
|
||||||
fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) =
|
fun lockEnabled(prefs: PreferencesHelper = Injekt.get()) =
|
||||||
prefs.eh_lockHash().get() != null &&
|
prefs.eh_lockHash().get() != null &&
|
||||||
prefs.eh_lockSalt().get() != null &&
|
prefs.eh_lockSalt().get() != null &&
|
||||||
prefs.eh_lockLength().getOrDefault() != -1
|
prefs.eh_lockLength().getOrDefault() != -1
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the lock will function properly
|
* Check if the lock will function properly
|
||||||
@ -53,30 +53,35 @@ fun notifyLockSecurity(
|
|||||||
): Boolean {
|
): Boolean {
|
||||||
return false
|
return false
|
||||||
if (!prefs.eh_lockManually().getOrDefault() &&
|
if (!prefs.eh_lockManually().getOrDefault() &&
|
||||||
!hasAccessToUsageStats(context)) {
|
!hasAccessToUsageStats(context)
|
||||||
|
) {
|
||||||
MaterialDialog(context)
|
MaterialDialog(context)
|
||||||
.title(text = "Permission required")
|
.title(text = "Permission required")
|
||||||
.message(text = "${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
|
.message(
|
||||||
"This is required for the application lock to function properly. " +
|
text = "${context.getString(R.string.app_name)} requires the usage stats permission to detect when you leave the app. " +
|
||||||
"Press OK to grant this permission now.")
|
"This is required for the application lock to function properly. " +
|
||||||
.negativeButton(R.string.action_cancel)
|
"Press OK to grant this permission now."
|
||||||
.positiveButton(android.R.string.ok) {
|
)
|
||||||
try {
|
.negativeButton(R.string.action_cancel)
|
||||||
context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
|
.positiveButton(android.R.string.ok) {
|
||||||
} catch (e: ActivityNotFoundException) {
|
try {
|
||||||
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
|
context.startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
|
||||||
MaterialDialog(context)
|
} catch (e: ActivityNotFoundException) {
|
||||||
.title(text = "Grant permission manually")
|
XLog.e("Device does not support USAGE_ACCESS_SETTINGS shortcut!")
|
||||||
.message(text = "Failed to launch the window used to grant the usage stats permission. " +
|
MaterialDialog(context)
|
||||||
"You can still grant this permission manually: go to your phone's settings and search for 'usage access'.")
|
.title(text = "Grant permission manually")
|
||||||
.positiveButton(android.R.string.ok) { it.dismiss() }
|
.message(
|
||||||
.cancelable(true)
|
text = "Failed to launch the window used to grant the usage stats permission. " +
|
||||||
.cancelOnTouchOutside(false)
|
"You can still grant this permission manually: go to your phone's settings and search for 'usage access'."
|
||||||
.show()
|
)
|
||||||
}
|
.positiveButton(android.R.string.ok) { it.dismiss() }
|
||||||
|
.cancelable(true)
|
||||||
|
.cancelOnTouchOutside(false)
|
||||||
|
.show()
|
||||||
}
|
}
|
||||||
.cancelable(false)
|
}
|
||||||
.show()
|
.cancelable(false)
|
||||||
|
.show()
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
|
@ -97,10 +97,11 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
val parsedUrl = Uri.parse(url)
|
val parsedUrl = Uri.parse(url)
|
||||||
if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
|
if (parsedUrl.host.equals("forums.e-hentai.org", ignoreCase = true)) {
|
||||||
// Hide distracting content
|
// Hide distracting content
|
||||||
if (!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT))
|
if (!parsedUrl.queryParameterNames.contains(PARAM_SKIP_INJECT)) {
|
||||||
view.evaluateJavascript(HIDE_JS, null)
|
view.evaluateJavascript(HIDE_JS, null)
|
||||||
|
}
|
||||||
// Check login result
|
// Check login result
|
||||||
|
|
||||||
if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
|
if (parsedUrl.getQueryParameter("code")?.toInt() != 0) {
|
||||||
if (checkLoginCookies(url)) view.loadUrl("https://exhentai.org/")
|
if (checkLoginCookies(url)) view.loadUrl("https://exhentai.org/")
|
||||||
}
|
}
|
||||||
@ -128,9 +129,11 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
fun checkLoginCookies(url: String): Boolean {
|
fun checkLoginCookies(url: String): Boolean {
|
||||||
getCookies(url)?.let { parsed ->
|
getCookies(url)?.let { parsed ->
|
||||||
return parsed.filter {
|
return parsed.filter {
|
||||||
(it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) ||
|
(
|
||||||
it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)) &&
|
it.name.equals(MEMBER_ID_COOKIE, ignoreCase = true) ||
|
||||||
it.value.isNotBlank()
|
it.name.equals(PASS_HASH_COOKIE, ignoreCase = true)
|
||||||
|
) &&
|
||||||
|
it.value.isNotBlank()
|
||||||
}.count() >= 2
|
}.count() >= 2
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -168,11 +171,11 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getCookies(url: String): List<HttpCookie>? =
|
fun getCookies(url: String): List<HttpCookie>? =
|
||||||
CookieManager.getInstance().getCookie(url)?.let {
|
CookieManager.getInstance().getCookie(url)?.let {
|
||||||
it.split("; ").flatMap {
|
it.split("; ").flatMap {
|
||||||
HttpCookie.parse(it)
|
HttpCookie.parse(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val PARAM_SKIP_INJECT = "TEH_SKIP_INJECT"
|
const val PARAM_SKIP_INJECT = "TEH_SKIP_INJECT"
|
||||||
@ -181,7 +184,8 @@ class LoginController : NucleusController<EhActivityLoginBinding, LoginPresenter
|
|||||||
const val PASS_HASH_COOKIE = "ipb_pass_hash"
|
const val PASS_HASH_COOKIE = "ipb_pass_hash"
|
||||||
const val IGNEOUS_COOKIE = "igneous"
|
const val IGNEOUS_COOKIE = "igneous"
|
||||||
|
|
||||||
const val HIDE_JS = """
|
const val HIDE_JS =
|
||||||
|
"""
|
||||||
javascript:(function () {
|
javascript:(function () {
|
||||||
document.getElementsByTagName('body')[0].style.visibility = 'hidden';
|
document.getElementsByTagName('body')[0].style.visibility = 'hidden';
|
||||||
document.getElementsByName('submit')[0].style.visibility = 'visible';
|
document.getElementsByName('submit')[0].style.visibility = 'visible';
|
||||||
|
@ -16,7 +16,7 @@ import kotlinx.coroutines.channels.Channel
|
|||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) :
|
class SmartSearchPresenter(private val source: CatalogueSource?, private val config: SourceController.SmartSearchConfig?) :
|
||||||
BasePresenter<SmartSearchController>(), CoroutineScope {
|
BasePresenter<SmartSearchController>(), CoroutineScope {
|
||||||
|
|
||||||
override val coroutineContext = Job() + Dispatchers.Main
|
override val coroutineContext = Job() + Dispatchers.Main
|
||||||
|
|
||||||
|
@ -14,16 +14,16 @@ import java.util.Date
|
|||||||
|
|
||||||
inline fun <reified E : RealmModel> RealmQuery<out E>.beginLog(
|
inline fun <reified E : RealmModel> RealmQuery<out E>.beginLog(
|
||||||
clazz: Class<out E>? =
|
clazz: Class<out E>? =
|
||||||
E::class.java
|
E::class.java
|
||||||
): LoggingRealmQuery<out E> =
|
): LoggingRealmQuery<out E> =
|
||||||
LoggingRealmQuery.fromQuery(this, clazz)
|
LoggingRealmQuery.fromQuery(this, clazz)
|
||||||
|
|
||||||
class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
||||||
companion object {
|
companion object {
|
||||||
fun <E : RealmModel> fromQuery(q: RealmQuery<out E>, clazz: Class<out E>?) =
|
fun <E : RealmModel> fromQuery(q: RealmQuery<out E>, clazz: Class<out E>?) =
|
||||||
LoggingRealmQuery(q).apply {
|
LoggingRealmQuery(q).apply {
|
||||||
log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE"
|
log += "SELECT * FROM ${clazz?.name ?: "???"} WHERE"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val log = mutableListOf<String>()
|
private val log = mutableListOf<String>()
|
||||||
@ -47,9 +47,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) {
|
private fun appendEqualTo(fieldName: String, value: String, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" == \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" == \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun equalTo(fieldName: String, value: String): RealmQuery<E> {
|
fun equalTo(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -108,11 +112,18 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun appendIn(fieldName: String, values: Array<out Any?>, casing: Case? = null) {
|
fun appendIn(fieldName: String, values: Array<out Any?>, casing: Case? = null) {
|
||||||
log += sec("[${values.joinToString(separator = ", ", transform = {
|
log += sec(
|
||||||
"\"$it\""
|
"[${values.joinToString(
|
||||||
})}] IN \"$fieldName\"" + (casing?.let {
|
separator = ", ",
|
||||||
" CASE ${casing.name}"
|
transform = {
|
||||||
} ?: ""))
|
"\"$it\""
|
||||||
|
}
|
||||||
|
)}] IN \"$fieldName\"" + (
|
||||||
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun `in`(fieldName: String, values: Array<String>): RealmQuery<E> {
|
fun `in`(fieldName: String, values: Array<String>): RealmQuery<E> {
|
||||||
@ -166,9 +177,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendNotEqualTo(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" != \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" != \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notEqualTo(fieldName: String, value: String): RealmQuery<E> {
|
fun notEqualTo(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -372,9 +387,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendContains(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" CONTAINS \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" CONTAINS \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun contains(fieldName: String, value: String): RealmQuery<E> {
|
fun contains(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -388,9 +407,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendBeginsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" BEGINS WITH \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" BEGINS WITH \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun beginsWith(fieldName: String, value: String): RealmQuery<E> {
|
fun beginsWith(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -404,9 +427,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendEndsWith(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" ENDS WITH \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" ENDS WITH \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun endsWith(fieldName: String, value: String): RealmQuery<E> {
|
fun endsWith(fieldName: String, value: String): RealmQuery<E> {
|
||||||
@ -420,9 +447,13 @@ class LoggingRealmQuery<E : RealmModel>(val query: RealmQuery<E>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) {
|
private fun appendLike(fieldName: String, value: Any?, casing: Case? = null) {
|
||||||
log += sec("\"$fieldName\" LIKE \"$value\"" + (casing?.let {
|
log += sec(
|
||||||
" CASE ${casing.name}"
|
"\"$fieldName\" LIKE \"$value\"" + (
|
||||||
} ?: ""))
|
casing?.let {
|
||||||
|
" CASE ${casing.name}"
|
||||||
|
} ?: ""
|
||||||
|
)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun like(fieldName: String, value: String): RealmQuery<E> {
|
fun like(fieldName: String, value: String): RealmQuery<E> {
|
||||||
|
@ -209,10 +209,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val entries: Set<Map.Entry<String, T>>
|
override val entries: Set<Map.Entry<String, T>>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<Map.Entry<String, T>>()
|
val out = mutableSetOf<Map.Entry<String, T>>()
|
||||||
node.walk("", { k, v ->
|
node.walk(
|
||||||
out.add(AbstractMap.SimpleImmutableEntry(k, v))
|
"",
|
||||||
true
|
{ k, v ->
|
||||||
}, leavesOnly)
|
out.add(AbstractMap.SimpleImmutableEntry(k, v))
|
||||||
|
true
|
||||||
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -221,10 +225,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val keys: Set<String>
|
override val keys: Set<String>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<String>()
|
val out = mutableSetOf<String>()
|
||||||
node.walk("", { k, _ ->
|
node.walk(
|
||||||
out.add(k)
|
"",
|
||||||
true
|
{ k, _ ->
|
||||||
}, leavesOnly)
|
out.add(k)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,10 +251,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
override val values: Collection<T>
|
override val values: Collection<T>
|
||||||
get() {
|
get() {
|
||||||
val out = mutableSetOf<T>()
|
val out = mutableSetOf<T>()
|
||||||
node.walk("", { _, v ->
|
node.walk(
|
||||||
out.add(v)
|
"",
|
||||||
true
|
{ _, v ->
|
||||||
}, leavesOnly)
|
out.add(v)
|
||||||
|
true
|
||||||
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,10 +276,14 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
* Returns `true` if the map maps one or more keys to the specified [value].
|
* Returns `true` if the map maps one or more keys to the specified [value].
|
||||||
*/
|
*/
|
||||||
override fun containsValue(value: T): Boolean {
|
override fun containsValue(value: T): Boolean {
|
||||||
node.walk("", { _, v ->
|
node.walk(
|
||||||
if (v == value) return true
|
"",
|
||||||
true
|
{ _, v ->
|
||||||
}, leavesOnly)
|
if (v == value) return true
|
||||||
|
true
|
||||||
|
},
|
||||||
|
leavesOnly
|
||||||
|
)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,32 +331,38 @@ class NakedTrie<T> : MutableMap<String, T> {
|
|||||||
* Returns a [MutableSet] of all key/value pairs in this map.
|
* Returns a [MutableSet] of all key/value pairs in this map.
|
||||||
*/
|
*/
|
||||||
override val entries: MutableSet<MutableMap.MutableEntry<String, T>>
|
override val entries: MutableSet<MutableMap.MutableEntry<String, T>>
|
||||||
get() = FakeMutableSet.fromSet(mutableSetOf<MutableMap.MutableEntry<String, T>>().apply {
|
get() = FakeMutableSet.fromSet(
|
||||||
walk { k, v ->
|
mutableSetOf<MutableMap.MutableEntry<String, T>>().apply {
|
||||||
this += FakeMutableEntry.fromPair(k, v)
|
walk { k, v ->
|
||||||
true
|
this += FakeMutableEntry.fromPair(k, v)
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [MutableSet] of all keys in this map.
|
* Returns a [MutableSet] of all keys in this map.
|
||||||
*/
|
*/
|
||||||
override val keys: MutableSet<String>
|
override val keys: MutableSet<String>
|
||||||
get() = FakeMutableSet.fromSet(mutableSetOf<String>().apply {
|
get() = FakeMutableSet.fromSet(
|
||||||
walk { k, _ ->
|
mutableSetOf<String>().apply {
|
||||||
this += k
|
walk { k, _ ->
|
||||||
true
|
this += k
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values.
|
* Returns a [MutableCollection] of all values in this map. Note that this collection may contain duplicate values.
|
||||||
*/
|
*/
|
||||||
override val values: MutableCollection<T>
|
override val values: MutableCollection<T>
|
||||||
get() = FakeMutableCollection.fromCollection(mutableListOf<T>().apply {
|
get() = FakeMutableCollection.fromCollection(
|
||||||
walk { _, v ->
|
mutableListOf<T>().apply {
|
||||||
this += v
|
walk { _, v ->
|
||||||
true
|
this += v
|
||||||
|
true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
}
|
}
|
||||||
|
@ -9,11 +9,12 @@ import org.jsoup.nodes.Document
|
|||||||
fun Response.interceptAsHtml(block: (Document) -> Unit): Response {
|
fun Response.interceptAsHtml(block: (Document) -> Unit): Response {
|
||||||
val body = body
|
val body = body
|
||||||
if (body?.contentType()?.type == "text" &&
|
if (body?.contentType()?.type == "text" &&
|
||||||
body.contentType()?.subtype == "html") {
|
body.contentType()?.subtype == "html"
|
||||||
|
) {
|
||||||
val bodyString = body.string()
|
val bodyString = body.string()
|
||||||
val rebuiltResponse = newBuilder()
|
val rebuiltResponse = newBuilder()
|
||||||
.body(ResponseBody.create(body.contentType(), bodyString))
|
.body(ResponseBody.create(body.contentType(), bodyString))
|
||||||
.build()
|
.build()
|
||||||
try {
|
try {
|
||||||
// Search for captcha
|
// Search for captcha
|
||||||
val parsed = asJsoup(html = bodyString)
|
val parsed = asJsoup(html = bodyString)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user