Run default Android Studio formatter on code

(cherry picked from commit 3ecc883944cba6102fb68940023d90ef959c8b9c)
This commit is contained in:
jobobby04 2020-04-04 14:20:02 -04:00 committed by Jobobby04
parent 319571edf3
commit c85825f3c7
28 changed files with 328 additions and 847 deletions

View File

@ -51,6 +51,7 @@ class ChapterCache(private val context: Context) {
/** Cache class used for cache management. */ /** Cache class used for cache management. */
// --> EH // --> EH
private var diskCache = setupDiskCache(prefs.eh_cacheSize().getOrDefault().toLong()) private var diskCache = setupDiskCache(prefs.eh_cacheSize().getOrDefault().toLong())
init { init {
prefs.eh_cacheSize().asObservable().skip(1).subscribe { prefs.eh_cacheSize().asObservable().skip(1).subscribe {
// Save old cache for destruction later // Save old cache for destruction later
@ -83,9 +84,9 @@ class ChapterCache(private val context: Context) {
// 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(File(context.cacheDir, PARAMETER_CACHE_DIRECTORY),
PARAMETER_APP_VERSION, PARAMETER_APP_VERSION,
PARAMETER_VALUE_COUNT, PARAMETER_VALUE_COUNT,
cacheSize * 1024 * 1024) cacheSize * 1024 * 1024)
} }
// <-- EH // <-- EH

View File

@ -12,7 +12,7 @@ interface Category : Serializable {
var flags: Int var flags: Int
var mangaOrder:List<Long> var mangaOrder: List<Long>
val nameLower: String val nameLower: String
get() = name.toLowerCase() get() = name.toLowerCase()

View File

@ -1,505 +0,0 @@
package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri
import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.model.TrackSearch
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.util.lang.toCalendar
import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText
import java.io.BufferedReader
import java.io.InputStreamReader
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.GregorianCalendar
import java.util.Locale
import java.util.zip.GZIPInputStream
import okhttp3.FormBody
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.OkHttpClient
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import org.json.JSONObject
import org.jsoup.Jsoup
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import org.jsoup.parser.Parser
import rx.Observable
class MyAnimeListApi(private val client: OkHttpClient, interceptor: MyAnimeListInterceptor) {
private val authClient = client.newBuilder().addInterceptor(interceptor).build()
fun search(query: String): Observable<List<TrackSearch>> {
return if (query.startsWith(PREFIX_MY)) {
val realQuery = query.removePrefix(PREFIX_MY)
getList()
.flatMap { Observable.from(it) }
.filter { it.title.contains(realQuery, true) }
.toList()
} else {
client.newCall(GET(searchUrl(query)))
.asObservable()
.flatMap { response ->
Observable.from(
Jsoup.parse(response.consumeBody())
.select("div.js-categories-seasonal.js-block-list.list")
.select("table").select("tbody")
.select("tr").drop(1)
)
}
.filter { row ->
row.select(TD)[2].text() != "Novel"
}
.map { row ->
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = row.searchTitle()
media_id = row.searchMediaId()
total_chapters = row.searchTotalChapters()
summary = row.searchSummary()
cover_url = row.searchCoverUrl()
tracking_url = mangaUrl(media_id)
publishing_status = row.searchPublishingStatus()
publishing_type = row.searchPublishingType()
start_date = row.searchStartDate()
}
}
.toList()
}
}
fun addLibManga(track: Track): Observable<Track> {
return Observable.defer {
authClient.newCall(POST(url = addUrl(), body = mangaPostPayload(track)))
.asObservableSuccess()
.map { track }
}
}
fun updateLibManga(track: Track): Observable<Track> {
return Observable.defer {
// Get track data
val response = authClient.newCall(GET(url = editPageUrl(track.media_id))).execute()
val editData = response.use {
val page = Jsoup.parse(it.consumeBody())
// Extract track data from MAL page
extractDataFromEditPage(page).apply {
// Apply changes to the just fetched data
copyPersonalFrom(track)
}
}
// Update remote
authClient.newCall(POST(url = editPageUrl(track.media_id), body = mangaEditPostBody(editData)))
.asObservableSuccess()
.map {
track
}
}
}
fun findLibManga(track: Track): Observable<Track?> {
return authClient.newCall(GET(url = editPageUrl(track.media_id)))
.asObservable()
.map { response ->
var libTrack: Track? = null
response.use {
if (it.priorResponse?.isRedirect != true) {
val trackForm = Jsoup.parse(it.consumeBody())
libTrack = Track.create(TrackManager.MYANIMELIST).apply {
last_chapter_read = trackForm.select("#add_manga_num_read_chapters").`val`().toInt()
total_chapters = trackForm.select("#totalChap").text().toInt()
status = trackForm.select("#add_manga_status > option[selected]").`val`().toInt()
score = trackForm.select("#add_manga_score > option[selected]").`val`().toFloatOrNull()
?: 0f
started_reading_date = trackForm.searchDatePicker("#add_manga_start_date")
finished_reading_date = trackForm.searchDatePicker("#add_manga_finish_date")
}
}
}
libTrack
}
}
fun getLibManga(track: Track): Observable<Track> {
return findLibManga(track)
.map { it ?: throw Exception("Could not find manga") }
}
fun login(username: String, password: String): String {
val csrf = getSessionInfo()
login(username, password, csrf)
return csrf
}
private fun getSessionInfo(): String {
val response = client.newCall(GET(loginUrl())).execute()
return Jsoup.parse(response.consumeBody())
.select("meta[name=csrf_token]")
.attr("content")
}
private fun login(username: String, password: String, csrf: String) {
val response = client.newCall(POST(url = loginUrl(), body = loginPostBody(username, password, csrf))).execute()
response.use {
if (response.priorResponse?.code != 302) throw Exception("Authentication error")
}
}
private fun getList(): Observable<List<TrackSearch>> {
return getListUrl()
.flatMap { url ->
getListXml(url)
}
.flatMap { doc ->
Observable.from(doc.select("manga"))
}
.map {
TrackSearch.create(TrackManager.MYANIMELIST).apply {
title = it.selectText("manga_title")!!
media_id = it.selectInt("manga_mangadb_id")
last_chapter_read = it.selectInt("my_read_chapters")
status = getStatus(it.selectText("my_status")!!)
score = it.selectInt("my_score").toFloat()
total_chapters = it.selectInt("manga_chapters")
tracking_url = mangaUrl(media_id)
started_reading_date = it.searchDateXml("my_start_date")
finished_reading_date = it.searchDateXml("my_finish_date")
}
}
.toList()
}
private fun getListUrl(): Observable<String> {
return authClient.newCall(POST(url = exportListUrl(), body = exportPostBody()))
.asObservable()
.map { response ->
baseUrl + Jsoup.parse(response.consumeBody())
.select("div.goodresult")
.select("a")
.attr("href")
}
}
private fun getListXml(url: String): Observable<Document> {
return authClient.newCall(GET(url))
.asObservable()
.map { response ->
Jsoup.parse(response.consumeXmlBody(), "", Parser.xmlParser())
}
}
private fun Response.consumeBody(): String? {
use {
if (it.code != 200) throw Exception("HTTP error ${it.code}")
return it.body?.string()
}
}
private fun Response.consumeXmlBody(): String? {
use { res ->
if (res.code != 200) throw Exception("Export list error")
BufferedReader(InputStreamReader(GZIPInputStream(res.body?.source()?.inputStream()))).use { reader ->
val sb = StringBuilder()
reader.forEachLine { line ->
sb.append(line)
}
return sb.toString()
}
}
}
private fun extractDataFromEditPage(page: Document): MyAnimeListEditData {
val tables = page.select("form#main-form table")
return MyAnimeListEditData(
entry_id = tables[0].select("input[name=entry_id]").`val`(), // Always 0
manga_id = tables[0].select("#manga_id").`val`(),
status = tables[0].select("#add_manga_status > option[selected]").`val`(),
num_read_volumes = tables[0].select("#add_manga_num_read_volumes").`val`(),
last_completed_vol = tables[0].select("input[name=last_completed_vol]").`val`(), // Always empty
num_read_chapters = tables[0].select("#add_manga_num_read_chapters").`val`(),
score = tables[0].select("#add_manga_score > option[selected]").`val`(),
start_date_month = tables[0].select("#add_manga_start_date_month > option[selected]").`val`(),
start_date_day = tables[0].select("#add_manga_start_date_day > option[selected]").`val`(),
start_date_year = tables[0].select("#add_manga_start_date_year > option[selected]").`val`(),
finish_date_month = tables[0].select("#add_manga_finish_date_month > option[selected]").`val`(),
finish_date_day = tables[0].select("#add_manga_finish_date_day > option[selected]").`val`(),
finish_date_year = tables[0].select("#add_manga_finish_date_year > option[selected]").`val`(),
tags = tables[1].select("#add_manga_tags").`val`(),
priority = tables[1].select("#add_manga_priority > option[selected]").`val`(),
storage_type = tables[1].select("#add_manga_storage_type > option[selected]").`val`(),
num_retail_volumes = tables[1].select("#add_manga_num_retail_volumes").`val`(),
num_read_times = tables[1].select("#add_manga_num_read_times").`val`(),
reread_value = tables[1].select("#add_manga_reread_value > option[selected]").`val`(),
comments = tables[1].select("#add_manga_comments").`val`(),
is_asked_to_discuss = tables[1].select("#add_manga_is_asked_to_discuss > option[selected]").`val`(),
sns_post_type = tables[1].select("#add_manga_sns_post_type > option[selected]").`val`()
)
}
companion object {
const val CSRF = "csrf_token"
private const val baseUrl = "https://myanimelist.net"
private const val baseMangaUrl = "$baseUrl/manga/"
private const val baseModifyListUrl = "$baseUrl/ownlist/manga"
private const val PREFIX_MY = "my:"
private const val TD = "td"
private fun mangaUrl(remoteId: Int) = baseMangaUrl + remoteId
private fun loginUrl() = Uri.parse(baseUrl).buildUpon()
.appendPath("login.php")
.toString()
private fun searchUrl(query: String): String {
val col = "c[]"
return Uri.parse(baseUrl).buildUpon()
.appendPath("manga.php")
.appendQueryParameter("q", query)
.appendQueryParameter(col, "a")
.appendQueryParameter(col, "b")
.appendQueryParameter(col, "c")
.appendQueryParameter(col, "d")
.appendQueryParameter(col, "e")
.appendQueryParameter(col, "g")
.toString()
}
private fun exportListUrl() = Uri.parse(baseUrl).buildUpon()
.appendPath("panel.php")
.appendQueryParameter("go", "export")
.toString()
private fun editPageUrl(mediaId: Int) = Uri.parse(baseModifyListUrl).buildUpon()
.appendPath(mediaId.toString())
.appendPath("edit")
.toString()
private fun addUrl() = Uri.parse(baseModifyListUrl).buildUpon()
.appendPath("add.json")
.toString()
private fun loginPostBody(username: String, password: String, csrf: String): RequestBody {
return FormBody.Builder()
.add("user_name", username)
.add("password", password)
.add("cookie", "1")
.add("sublogin", "Login")
.add("submit", "1")
.add(CSRF, csrf)
.build()
}
private fun exportPostBody(): RequestBody {
return FormBody.Builder()
.add("type", "2")
.add("subexport", "Export My List")
.build()
}
private fun mangaPostPayload(track: Track): RequestBody {
val body = JSONObject()
.put("manga_id", track.media_id)
.put("status", track.status)
.put("score", track.score)
.put("num_read_chapters", track.last_chapter_read)
return body.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
}
private fun mangaEditPostBody(track: MyAnimeListEditData): RequestBody {
return FormBody.Builder()
.add("entry_id", track.entry_id)
.add("manga_id", track.manga_id)
.add("add_manga[status]", track.status)
.add("add_manga[num_read_volumes]", track.num_read_volumes)
.add("last_completed_vol", track.last_completed_vol)
.add("add_manga[num_read_chapters]", track.num_read_chapters)
.add("add_manga[score]", track.score)
.add("add_manga[start_date][month]", track.start_date_month)
.add("add_manga[start_date][day]", track.start_date_day)
.add("add_manga[start_date][year]", track.start_date_year)
.add("add_manga[finish_date][month]", track.finish_date_month)
.add("add_manga[finish_date][day]", track.finish_date_day)
.add("add_manga[finish_date][year]", track.finish_date_year)
.add("add_manga[tags]", track.tags)
.add("add_manga[priority]", track.priority)
.add("add_manga[storage_type]", track.storage_type)
.add("add_manga[num_retail_volumes]", track.num_retail_volumes)
.add("add_manga[num_read_times]", track.num_read_times)
.add("add_manga[reread_value]", track.reread_value)
.add("add_manga[comments]", track.comments)
.add("add_manga[is_asked_to_discuss]", track.is_asked_to_discuss)
.add("add_manga[sns_post_type]", track.sns_post_type)
.add("submitIt", track.submitIt)
.build()
}
private fun Element.searchDateXml(field: String): Long {
val text = selectText(field, "0000-00-00")!!
// MAL sets the data to 0000-00-00 when date is invalid or missing
if (text == "0000-00-00") {
return 0L
}
return SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(text)?.time ?: 0L
}
private fun Element.searchDatePicker(id: String): Long {
val month = select(id + "_month > option[selected]").`val`().toIntOrNull()
val day = select(id + "_day > option[selected]").`val`().toIntOrNull()
val year = select(id + "_year > option[selected]").`val`().toIntOrNull()
if (year == null || month == null || day == null) {
return 0L
}
return GregorianCalendar(year, month - 1, day).timeInMillis
}
private fun Element.searchTitle() = select("strong").text()!!
private fun Element.searchTotalChapters() = if (select(TD)[4].text() == "-") 0 else select(TD)[4].text().toInt()
private fun Element.searchCoverUrl() = select("img")
.attr("data-src")
.split("\\?")[0]
.replace("/r/50x70/", "/")
private fun Element.searchMediaId() = select("div.picSurround")
.select("a").attr("id")
.replace("sarea", "")
.toInt()
private fun Element.searchSummary() = select("div.pt4")
.first()
.ownText()!!
private fun Element.searchPublishingStatus() = if (select(TD).last().text() == "-") "Publishing" else "Finished"
private fun Element.searchPublishingType() = select(TD)[2].text()!!
private fun Element.searchStartDate() = select(TD)[6].text()!!
private fun getStatus(status: String) = when (status) {
"Reading" -> 1
"Completed" -> 2
"On-Hold" -> 3
"Dropped" -> 4
"Plan to Read" -> 6
else -> 1
}
}
private class MyAnimeListEditData(
// entry_id
var entry_id: String,
// manga_id
var manga_id: String,
// add_manga[status]
var status: String,
// add_manga[num_read_volumes]
var num_read_volumes: String,
// last_completed_vol
var last_completed_vol: String,
// add_manga[num_read_chapters]
var num_read_chapters: String,
// add_manga[score]
var score: String,
// add_manga[start_date][month]
var start_date_month: String, // [1-12]
// add_manga[start_date][day]
var start_date_day: String,
// add_manga[start_date][year]
var start_date_year: String,
// add_manga[finish_date][month]
var finish_date_month: String, // [1-12]
// add_manga[finish_date][day]
var finish_date_day: String,
// add_manga[finish_date][year]
var finish_date_year: String,
// add_manga[tags]
var tags: String,
// add_manga[priority]
var priority: String,
// add_manga[storage_type]
var storage_type: String,
// add_manga[num_retail_volumes]
var num_retail_volumes: String,
// add_manga[num_read_times]
var num_read_times: String,
// add_manga[reread_value]
var reread_value: String,
// add_manga[comments]
var comments: String,
// add_manga[is_asked_to_discuss]
var is_asked_to_discuss: String,
// add_manga[sns_post_type]
var sns_post_type: String,
// submitIt
val submitIt: String = "0"
) {
fun copyPersonalFrom(track: Track) {
num_read_chapters = track.last_chapter_read.toString()
val numScore = track.score.toInt()
if (numScore in 1..9) {
score = numScore.toString()
}
status = track.status.toString()
if (track.started_reading_date == 0L) {
start_date_month = ""
start_date_day = ""
start_date_year = ""
}
if (track.finished_reading_date == 0L) {
finish_date_month = ""
finish_date_day = ""
finish_date_year = ""
}
track.started_reading_date.toCalendar()?.let { cal ->
start_date_month = (cal[Calendar.MONTH] + 1).toString()
start_date_day = cal[Calendar.DAY_OF_MONTH].toString()
start_date_year = cal[Calendar.YEAR].toString()
}
track.finished_reading_date.toCalendar()?.let { cal ->
finish_date_month = (cal[Calendar.MONTH] + 1).toString()
finish_date_day = cal[Calendar.DAY_OF_MONTH].toString()
finish_date_year = cal[Calendar.YEAR].toString()
}
}
}
}

View File

@ -138,7 +138,7 @@ class ExtensionManager(
fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> { fun <T : Extension> Iterable<T>.filterNotBlacklisted(): List<T> {
val blacklistEnabled = preferences.eh_enableSourceBlacklist().getOrDefault() val blacklistEnabled = preferences.eh_enableSourceBlacklist().getOrDefault()
return filter { return filter {
if(it.isBlacklisted(blacklistEnabled)) { if (it.isBlacklisted(blacklistEnabled)) {
XLog.d("[EXH] Removing blacklisted extension: (name: %s, pkgName: %s)!", it.name, it.pkgName) XLog.d("[EXH] Removing blacklisted extension: (name: %s, pkgName: %s)!", it.name, it.pkgName)
false false
} else true } else true
@ -304,7 +304,7 @@ class ExtensionManager(
* @param extension The extension to be registered. * @param extension The extension to be registered.
*/ */
private fun registerNewExtension(extension: Extension.Installed) { private fun registerNewExtension(extension: Extension.Installed) {
if(extension.isBlacklisted()) { if (extension.isBlacklisted()) {
XLog.d("[EXH] Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName) XLog.d("[EXH] Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName)
return return
} }
@ -320,7 +320,7 @@ class ExtensionManager(
* @param extension The extension to be registered. * @param extension The extension to be registered.
*/ */
private fun registerUpdatedExtension(extension: Extension.Installed) { private fun registerUpdatedExtension(extension: Extension.Installed) {
if(extension.isBlacklisted()) { if (extension.isBlacklisted()) {
XLog.d("[EXH] Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName) XLog.d("[EXH] Removing blacklisted extension: (name: String, pkgName: %s)!", extension.name, extension.pkgName)
return return
} }

View File

@ -24,7 +24,7 @@ interface SManga : Serializable {
fun copyFrom(other: SManga) { fun copyFrom(other: SManga) {
// EXH --> // EXH -->
if(other.title.isNotBlank()) if (other.title.isNotBlank())
title = other.title title = other.title
// EXH <-- // EXH <--

View File

@ -36,7 +36,8 @@ 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() ?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!") }?.call()
?: 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
@ -45,12 +46,12 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
*/ */
fun parseToManga(manga: SManga, input: I): Completable { fun parseToManga(manga: SManga, input: I): Completable {
val mangaId = (manga as? Manga)?.id val mangaId = (manga as? Manga)?.id
val metaObservable = if(mangaId != null) { val metaObservable = if (mangaId != null) {
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
Single.fromCallable { Single.fromCallable {
db.getFlatMetadataForManga(mangaId).executeAsBlocking() db.getFlatMetadataForManga(mangaId).executeAsBlocking()
} .map { }.map {
if(it != null) it.raise(metaClass) if (it != null) it.raise(metaClass)
else newMetaInstance() else newMetaInstance()
} }
} else { } else {
@ -62,7 +63,7 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
it.copyTo(manga) it.copyTo(manga)
it it
}.flatMapCompletable { }.flatMapCompletable {
if(mangaId != null) { if (mangaId != null) {
it.mangaId = mangaId it.mangaId = mangaId
db.insertFlatMetadata(it.flatten()) db.insertFlatMetadata(it.flatten())
} else Completable.complete() } else Completable.complete()
@ -77,7 +78,7 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
* also be saved to the DB. * also be saved to the DB.
*/ */
fun getOrLoadMetadata(mangaId: Long?, inputProducer: () -> Single<I>): Single<M> { fun getOrLoadMetadata(mangaId: Long?, inputProducer: () -> Single<I>): Single<M> {
val metaObservable = if(mangaId != null) { val metaObservable = if (mangaId != null) {
// We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions // We have to use fromCallable because StorIO messes up the thread scheduling if we use their rx functions
Single.fromCallable { Single.fromCallable {
db.getFlatMetadataForManga(mangaId).executeAsBlocking() db.getFlatMetadataForManga(mangaId).executeAsBlocking()
@ -87,12 +88,12 @@ interface LewdSource<M : RaisedSearchMetadata, I> : CatalogueSource {
} else Single.just(null) } else Single.just(null)
return metaObservable.flatMap { existingMeta -> return metaObservable.flatMap { existingMeta ->
if(existingMeta == null) { if (existingMeta == null) {
inputProducer().flatMap { input -> inputProducer().flatMap { input ->
val newMeta = newMetaInstance() val newMeta = newMetaInstance()
parseIntoMetadata(newMeta, input) parseIntoMetadata(newMeta, input)
val newMetaSingle = Single.just(newMeta) val newMetaSingle = Single.just(newMeta)
if(mangaId != null) { if (mangaId != null) {
newMeta.mangaId = mangaId newMeta.mangaId = mangaId
db.insertFlatMetadata(newMeta.flatten()).andThen(newMetaSingle) db.insertFlatMetadata(newMeta.flatten()).andThen(newMetaSingle)
} else newMetaSingle } else newMetaSingle

View File

@ -56,13 +56,13 @@ class EHentai(override val id: Long,
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"
@ -81,8 +81,7 @@ class EHentai(override val id: Long,
*/ */
data class ParsedManga(val fav: Int, val manga: Manga) data class ParsedManga(val fav: Int, val manga: Manga)
fun extendedGenericMangaParse(doc: Document) fun extendedGenericMangaParse(doc: Document) = with(doc) {
= with(doc) {
// Parse mangas (supports compact + extended layout) // Parse mangas (supports compact + extended layout)
val parsedMangas = select(".itg > tbody > tr").filter { val parsedMangas = select(".itg > tbody > tr").filter {
// Do not parse header and ads // Do not parse header and ads
@ -112,7 +111,7 @@ class EHentai(override val id: Long,
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() == ">"
@ -126,13 +125,11 @@ class EHentai(override val id: Long,
/** /**
* Parse a list of galleries * Parse a list of galleries
*/ */
fun genericMangaParse(response: Response) fun genericMangaParse(response: Response) = extendedGenericMangaParse(response.asJsoup()).let {
= extendedGenericMangaParse(response.asJsoup()).let {
MangasPage(it.first.map { it.manga }, it.second) MangasPage(it.first.map { it.manga }, it.second)
} }
override fun fetchChapterList(manga: SManga) override fun fetchChapterList(manga: SManga) = fetchChapterList(manga) {}
= fetchChapterList(manga) {}
fun fetchChapterList(manga: SManga, throttleFunc: () -> Unit): Observable<List<SChapter>> { fun fetchChapterList(manga: SManga, throttleFunc: () -> Unit): Observable<List<SChapter>> {
return Single.fromCallable { return Single.fromCallable {
@ -148,7 +145,7 @@ class EHentai(override val id: Long,
val cachedParent = updateHelper.parentLookupTable.get( val cachedParent = updateHelper.parentLookupTable.get(
gid gid
) )
if(cachedParent == null) { if (cachedParent == null) {
throttleFunc() throttleFunc()
val resp = client.newCall(exGet(baseUrl + url)).execute() val resp = client.newCall(exGet(baseUrl + url)).execute()
@ -192,7 +189,7 @@ class EHentai(override val id: Long,
}!!.nextElementSibling().text()).time }!!.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)
else { else {
newDisplay.mapIndexed { index, newGallery -> newDisplay.mapIndexed { index, newGallery ->
val link = newGallery.attr("href") val link = newGallery.attr("href")
@ -209,8 +206,7 @@ class EHentai(override val id: Long,
}.toObservable() }.toObservable()
} }
override fun fetchPageList(chapter: SChapter) override fun fetchPageList(chapter: SChapter) = fetchChapterPage(chapter, baseUrl + chapter.url).map {
= fetchChapterPage(chapter, baseUrl + chapter.url).map {
it.mapIndexed { i, s -> it.mapIndexed { i, s ->
Page(i, s) Page(i, s)
} }
@ -223,28 +219,28 @@ class EHentai(override val id: Long,
val jsoup = it.asJsoup() val jsoup = it.asJsoup()
urls += parseChapterPage(jsoup) urls += parseChapterPage(jsoup)
val nextUrl = nextPageUrl(jsoup) val nextUrl = nextPageUrl(jsoup)
if(nextUrl != null) { if (nextUrl != null) {
fetchChapterPage(chapter, nextUrl, urls) fetchChapterPage(chapter, nextUrl, urls)
} else { } else {
Observable.just(urls) Observable.just(urls)
} }
} }
} }
private fun parseChapterPage(response: Element)
= with(response) { private fun parseChapterPage(response: Element) = with(response) {
select(".gdtm a").map { select(".gdtm a").map {
Pair(it.child(0).attr("alt").toInt(), it.attr("href")) Pair(it.child(0).attr("alt").toInt(), it.attr("href"))
}.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) = client.newCall(chapterPageRequest(np)).asObservableSuccess()
private fun chapterPageRequest(np: String) = exGet(np, null, headers) private fun chapterPageRequest(np: String) = exGet(np, null, headers)
private fun nextPageUrl(element: Element): String? private fun nextPageUrl(element: Element): String? = element.select("a[onclick=return false]").last()?.let {
= 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
@ -254,7 +250,7 @@ class EHentai(override val id: Long,
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)
} }
} }
@ -263,13 +259,13 @@ class EHentai(override val id: Long,
val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon() val uri = Uri.parse("$baseUrl$QUERY_PREFIX").buildUpon()
uri.appendQueryParameter("f_search", query) uri.appendQueryParameter("f_search", query)
filters.forEach { filters.forEach {
if(it is UriFilter) it.addToUri(uri) if (it is UriFilter) it.addToUri(uri)
} }
val request = exGet(uri.toString(), page) val request = exGet(uri.toString(), page)
// 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 {
@ -290,8 +286,7 @@ class EHentai(override val id: Long,
} }
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
= throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page) override fun latestUpdatesRequest(page: Int) = exGet(baseUrl, page)
@ -299,8 +294,7 @@ class EHentai(override val id: Long,
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) fun exGet(url: String, page: Int? = null, additionalHeaders: Headers? = null, cache: Boolean = true) = GET(page?.let {
= GET(page?.let {
addParam(url, "page", Integer.toString(page - 1)) addParam(url, "page", Integer.toString(page - 1))
} ?: url, additionalHeaders?.let { } ?: url, additionalHeaders?.let {
val headers = headers.newBuilder() val headers = headers.newBuilder()
@ -311,7 +305,7 @@ class EHentai(override val id: Long,
} }
headers.build() headers.build()
} ?: headers).let { } ?: headers).let {
if(!cache) if (!cache)
it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build() it.newBuilder().cacheControl(CacheControl.FORCE_NETWORK).build()
else else
it it
@ -327,11 +321,11 @@ class EHentai(override val id: Long,
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() }
@ -388,7 +382,7 @@ class EHentai(override val id: Long,
val left = it.select(".gdt1").text().nullIfBlank()?.trim() val left = it.select(".gdt1").text().nullIfBlank()?.trim()
val rightElement = it.selectFirst(".gdt2") val rightElement = it.selectFirst(".gdt2")
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 (left.removeSuffix(":")
.toLowerCase()) { .toLowerCase()) {
@ -413,10 +407,10 @@ class EHentai(override val id: Long,
} }
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)
} }
//Parse ratings //Parse ratings
@ -442,7 +436,7 @@ class EHentai(override val id: Long,
RaisedTag( 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
@ -458,11 +452,9 @@ class EHentai(override val id: Long,
} }
} }
override fun chapterListParse(response: Response) override fun chapterListParse(response: Response) = throw UnsupportedOperationException("Unused method was called somehow!")
= throw UnsupportedOperationException("Unused method was called somehow!")
override fun pageListParse(response: Response) override fun pageListParse(response: Response) = throw UnsupportedOperationException("Unused method was called somehow!")
= throw UnsupportedOperationException("Unused method was called somehow!")
override fun fetchImageUrl(page: Page): Observable<String> { override fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page)) return client.newCall(imageUrlRequest(page))
@ -515,29 +507,29 @@ class EHentai(override val id: Long,
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()
if(prefs.enableExhentai().getOrDefault()) { if (prefs.enableExhentai().getOrDefault()) {
cookies[LoginController.MEMBER_ID_COOKIE] = prefs.memberIdVal().get()!! cookies[LoginController.MEMBER_ID_COOKIE] = prefs.memberIdVal().get()!!
cookies[LoginController.PASS_HASH_COOKIE] = prefs.passHashVal().get()!! cookies[LoginController.PASS_HASH_COOKIE] = prefs.passHashVal().get()!!
cookies[LoginController.IGNEOUS_COOKIE] = prefs.igneousVal().get()!! cookies[LoginController.IGNEOUS_COOKIE] = prefs.igneousVal().get()!!
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
} }
@ -550,15 +542,12 @@ class EHentai(override val id: Long,
return cookies return cookies
} }
fun cookiesHeader(sp: Int = spPref().getOrDefault()) fun cookiesHeader(sp: Int = spPref().getOrDefault()) = buildCookies(rawCookies(sp))
= buildCookies(rawCookies(sp))
//Headers //Headers
override fun headersBuilder() override fun headersBuilder() = super.headersBuilder().add("Cookie", cookiesHeader())!!
= super.headersBuilder().add("Cookie", cookiesHeader())!!
fun addParam(url: String, param: String, value: String) fun addParam(url: String, param: String, value: String) = Uri.parse(url)
= Uri.parse(url)
.buildUpon() .buildUpon()
.appendQueryParameter(param, value) .appendQueryParameter(param, value)
.toString() .toString()
@ -586,12 +575,12 @@ class EHentai(override val id: Long,
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 : Filter.Group<GenreOption>("Genres", listOf(
GenreOption("Dōjinshi", 2), GenreOption("Dōjinshi", 2),
GenreOption("Manga", 4), GenreOption("Manga", 4),
@ -606,22 +595,22 @@ class EHentai(override val id: Long,
)), UriFilter { )), 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
} }
builder.appendQueryParameter("f_cats", bits.toString()) builder.appendQueryParameter("f_cats", bits.toString())
} }
} }
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")
} }
} }
open class PageOption(name: String, private val queryKey: String) : Filter.Text(name), UriFilter { open class PageOption(name: String, private val queryKey: String) : Filter.Text(name), UriFilter {
override fun addToUri(builder: Uri.Builder) { override fun addToUri(builder: Uri.Builder) {
if(state.isNotBlank()) { if (state.isNotBlank()) {
if (builder.build().getQueryParameters("f_sp").isEmpty()) { if (builder.build().getQueryParameters("f_sp").isEmpty()) {
builder.appendQueryParameter("f_sp", "on") builder.appendQueryParameter("f_sp", "on")
} }
@ -630,6 +619,7 @@ class EHentai(override val id: Long,
} }
} }
} }
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")
@ -641,7 +631,7 @@ class EHentai(override val id: Long,
"5 stars" "5 stars"
)), UriFilter { )), 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))
builder.appendQueryParameter("f_sr", "on") builder.appendQueryParameter("f_sr", "on")
} }
@ -664,16 +654,16 @@ class EHentai(override val id: Long,
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",
@ -745,8 +735,7 @@ class EHentai(override val id: Long,
"e8e" "e8e"
) )
fun buildCookies(cookies: Map<String, String>) fun buildCookies(cookies: Map<String, String>) = cookies.entries.joinToString(separator = "; ") {
= cookies.entries.joinToString(separator = "; ") {
"${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}" "${URLEncoder.encode(it.key, "UTF-8")}=${URLEncoder.encode(it.value, "UTF-8")}"
} }

View File

@ -61,7 +61,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private var tagIndexVersionCacheTime: Long = 0 private var tagIndexVersionCacheTime: Long = 0
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
@ -76,7 +76,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private var galleryIndexVersionCacheTime: Long = 0 private var galleryIndexVersionCacheTime: Long = 0
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
@ -106,7 +106,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
input.select(".gallery-info tr").forEach { input.select(".gallery-info tr").forEach {
val content = it.child(1) val content = it.child(1)
when(it.child(0).text().toLowerCase()) { when (it.child(0).text().toLowerCase()) {
"group" -> { "group" -> {
group = content.text() group = content.text()
tags += RaisedTag("group", group!!, TAG_TYPE_VIRTUAL) tags += RaisedTag("group", group!!, TAG_TYPE_VIRTUAL)
@ -133,10 +133,10 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
} }
"tags" -> { "tags" -> {
tags += content.select("a").map { tags += content.select("a").map {
val ns = if(it.attr("href").startsWith("/tag/male")) "male" val ns = if (it.attr("href").startsWith("/tag/male")) "male"
else if (it.attr("href").startsWith("/tag/female")) "female" else if (it.attr("href").startsWith("/tag/female")) "female"
else "misc" else "misc"
RaisedTag(ns, it.text().dropLast(if (ns=="misc") 0 else 2), TAG_TYPE_DEFAULT) RaisedTag(ns, it.text().dropLast(if (ns == "misc") 0 else 2), TAG_TYPE_DEFAULT)
} }
} }
} }
@ -178,8 +178,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
* @param query the search query. * @param query the search query.
* @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()
= throw UnsupportedOperationException()
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return urlImportFetchSearchManga(query) { return urlImportFetchSearchManga(query) {
@ -192,7 +191,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
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() } }
} else { } else {
val q = positive.removeAt(0) val q = positive.removeAt(0)
@ -275,7 +274,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> { private fun parseNozomiPage(array: ByteArray): Observable<List<SManga>> {
val cursor = ByteCursor(array) val cursor = ByteCursor(array)
val ids = (1 .. array.size / 4).map { val ids = (1..array.size / 4).map {
cursor.nextInt() cursor.nextInt()
} }
@ -297,7 +296,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
return SManga.create().apply { return SManga.create().apply {
val titleElement = doc.selectFirst("h1") val titleElement = doc.selectFirst("h1")
title = titleElement.text() title = titleElement.text()
thumbnail_url = "https:" + if(prefs.eh_hl_useHighQualityThumbs().getOrDefault()) { thumbnail_url = "https:" + if (prefs.eh_hl_useHighQualityThumbs().getOrDefault()) {
doc.selectFirst("img").attr("data-srcset").substringBefore(' ') doc.selectFirst("img").attr("data-srcset").substringBefore(' ')
} else { } else {
doc.selectFirst("img").attr("data-src") doc.selectFirst("img").attr("data-src")
@ -364,16 +363,16 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
val hlId = response.request.url.pathSegments.last().removeSuffix(".js").toLong() val hlId = response.request.url.pathSegments.last().removeSuffix(".js").toLong()
val str = response.body!!.string() val str = response.body!!.string()
val json = jsonParser.parse(str.removePrefix("var galleryinfo = ")) val json = jsonParser.parse(str.removePrefix("var galleryinfo = "))
return json["files"].array.mapIndexed { index, jsonElement -> return json["files"].array.mapIndexed { index, jsonElement ->
val hash = jsonElement["hash"].string val hash = jsonElement["hash"].string
val ext = if (jsonElement["haswebp"].string=="0") jsonElement["name"].string.split('.').last() else "webp" val ext = if (jsonElement["haswebp"].string == "0") jsonElement["name"].string.split('.').last() else "webp"
val path = if (jsonElement["haswebp"].string=="0") "images" else "webp" val path = if (jsonElement["haswebp"].string == "0") "images" else "webp"
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"
) )
} }
} }
@ -406,7 +405,7 @@ class Hitomi : HttpSource(), LewdSource<HitomiSearchMetadata, Document>, UrlImpo
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"
@ -418,7 +417,7 @@ 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)

View File

@ -42,7 +42,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
//Support direct URL importing //Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val trimmedIdQuery = query.trim().removePrefix("id:") val trimmedIdQuery = query.trim().removePrefix("id:")
val newQuery = if(trimmedIdQuery.toIntOrNull() ?: -1 >= 0) { val newQuery = if (trimmedIdQuery.toIntOrNull() ?: -1 >= 0) {
"$baseUrl/g/$trimmedIdQuery/" "$baseUrl/g/$trimmedIdQuery/"
} else query } else query
@ -56,15 +56,15 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
} }
private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> { private fun searchMangaRequestObservable(page: Int, query: String, filters: FilterList): Observable<Request> {
val langFilter = filters.filterIsInstance<filterLang>().firstOrNull() val langFilter = filters.filterIsInstance<filterLang>().firstOrNull()
var langFilterString = "" var langFilterString = ""
if (langFilter != null) { if (langFilter != null) {
langFilterString = SOURCE_LANG_LIST.first {it.first == langFilter!!.values[langFilter!!.state]}.second langFilterString = SOURCE_LANG_LIST.first { it.first == langFilter!!.values[langFilter!!.state] }.second
} }
val uri = if(query.isNotBlank()) { val uri = if (query.isNotBlank()) {
Uri.parse("$baseUrl/search/").buildUpon().apply { Uri.parse("$baseUrl/search/").buildUpon().apply {
appendQueryParameter("q", query+langFilterString) appendQueryParameter("q", query + langFilterString)
} }
} else { } else {
Uri.parse(baseUrl).buildUpon() Uri.parse(baseUrl).buildUpon()
@ -73,12 +73,12 @@ 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!")
uri.appendQueryParameter("sort", "popular") uri.appendQueryParameter("sort", "popular")
} }
if(sortFilter.ascending) { if (sortFilter.ascending) {
return client.newCall(nhGet(uri.toString())) return client.newCall(nhGet(uri.toString()))
.asObservableSuccess() .asObservableSuccess()
.map { .map {
@ -103,11 +103,9 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
return Observable.just(nhGet(uri.toString(), page)) return Observable.just(nhGet(uri.toString(), page))
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = throw UnsupportedOperationException()
= throw UnsupportedOperationException()
override fun searchMangaParse(response: Response) override fun searchMangaParse(response: Response) = parseResultPage(response)
= parseResultPage(response)
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val uri = Uri.parse(baseUrl).buildUpon() val uri = Uri.parse(baseUrl).buildUpon()
@ -115,8 +113,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
return nhGet(uri.toString(), page) return nhGet(uri.toString(), page)
} }
override fun latestUpdatesParse(response: Response) override fun latestUpdatesParse(response: Response) = parseResultPage(response)
= parseResultPage(response)
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException() override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
@ -136,8 +133,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
} }
} }
override fun mangaDetailsRequest(manga: SManga) override fun mangaDetailsRequest(manga: SManga) = nhGet(baseUrl + manga.url)
= nhGet(baseUrl + manga.url)
fun parseResultPage(response: Response): MangasPage { fun parseResultPage(response: Response): MangasPage {
val doc = response.asJsoup() val doc = response.asJsoup()
@ -153,7 +149,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
// last() is a hack to ignore the lazy-loader placeholder image on the front page // last() is a hack to ignore the lazy-loader placeholder image on the front page
thumbnail_url = it.select("img").last().attr("src") thumbnail_url = it.select("img").last().attr("src")
// In some pages, the thumbnail url does not include the protocol // In some pages, the thumbnail url does not include the protocol
if(!thumbnail_url!!.startsWith("https:")) thumbnail_url = "https:$thumbnail_url" if (!thumbnail_url!!.startsWith("https:")) thumbnail_url = "https:$thumbnail_url"
} }
} }
@ -215,16 +211,14 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
.toSingle() .toSingle()
} }
override fun fetchChapterList(manga: SManga) override fun fetchChapterList(manga: SManga) = Observable.just(listOf(SChapter.create().apply {
= Observable.just(listOf(SChapter.create().apply {
url = manga.url url = manga.url
name = "Chapter" name = "Chapter"
chapter_number = 1f chapter_number = 1f
})) }))
override fun fetchPageList(chapter: SChapter) override fun fetchPageList(chapter: SChapter) = getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata ->
= getOrLoadMetadata(chapter.mangaId, NHentaiSearchMetadata.nhUrlToId(chapter.url)).map { metadata -> if (metadata.mediaId == null) emptyList()
if(metadata.mediaId == null) emptyList()
else 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)
@ -264,6 +258,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
val appName by lazy { val appName by lazy {
context.getString(R.string.app_name) context.getString(R.string.app_name)
} }
fun nhGet(url: String, tag: Any? = null) = GET(url) fun nhGet(url: String, tag: Any? = null) = GET(url)
.newBuilder() .newBuilder()
.header("User-Agent", .header("User-Agent",
@ -291,7 +286,7 @@ class NHentai(context: Context) : HttpSource(), LewdSource<NHentaiSearchMetadata
) )
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]}/"
@ -304,10 +299,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")
) )

View File

@ -73,7 +73,7 @@ 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/")
@ -106,7 +106,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
uri.appendQueryParameter("page", page.toString()) uri.appendQueryParameter("page", page.toString())
uri.appendQueryParameter("title", query) uri.appendQueryParameter("title", query)
filters.forEach { filters.forEach {
if(it is UriFilter) it.addToUri(uri) if (it is UriFilter) it.addToUri(uri)
} }
return GET(uri.toString()) return GET(uri.toString())
} }
@ -152,38 +152,38 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
tags.clear() tags.clear()
var inStatus: String? = null var inStatus: String? = null
rightBoxElement.childNodes().forEach { rightBoxElement.childNodes().forEach {
if(it is Element && it.tagName().toLowerCase() == "h4") { if (it is Element && it.tagName().toLowerCase() == "h4") {
inStatus = it.text().trim() inStatus = it.text().trim()
} else { } else {
when(inStatus) { when (inStatus) {
"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" -> {
if(it is Element && it.tagName() == "a") { if (it is Element && it.tagName() == "a") {
artist = it.text() artist = it.text()
tags += RaisedTag("artist", it.text().toLowerCase(), TAG_TYPE_VIRTUAL) tags += RaisedTag("artist", it.text().toLowerCase(), TAG_TYPE_VIRTUAL)
} }
} }
"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
} }
} }
@ -197,8 +197,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
} }
} }
override fun mangaDetailsParse(document: Document): SManga override fun mangaDetailsParse(document: Document): SManga = throw UnsupportedOperationException()
= throw UnsupportedOperationException()
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val num = when (lang) { val num = when (lang) {
@ -226,18 +225,17 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
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
} catch(ignored: Exception) {} } catch (ignored: Exception) {
}
} }
override fun pageListParse(document: Document) override fun pageListParse(document: Document) = document.getElementById("pageSelect").getElementsByTag("option").map {
= document.getElementById("pageSelect").getElementsByTag("option").map {
Page(it.attr("data-page").toInt() - 1, baseUrl + it.attr("value")) Page(it.attr("data-page").toInt() - 1, baseUrl + it.attr("value"))
} }
override fun imageUrlParse(document: Document) override fun imageUrlParse(document: Document) = "http:" + document.getElementById("mainImg").attr("src")!!
= "http:" + document.getElementById("mainImg").attr("src")!!
override fun getFilterList() = FilterList ( override fun getFilterList() = FilterList(
AuthorFilter(), AuthorFilter(),
ArtistFilter(), ArtistFilter(),
TypeFilterGroup(), TypeFilterGroup(),
@ -253,7 +251,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
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())
} }
} }
@ -302,7 +300,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
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())
} }
} }
@ -310,7 +308,7 @@ class PervEden(override val id: Long, val pvLang: PervEdenLang) : ParsedHttpSour
override val matchingHosts = listOf("www.perveden.com") override val matchingHosts = listOf("www.perveden.com")
override fun matchesUri(uri: Uri): Boolean { override fun matchesUri(uri: Uri): Boolean {
return super.matchesUri(uri) && uri.pathSegments.firstOrNull()?.toLowerCase() == when(pvLang) { return super.matchesUri(uri) && uri.pathSegments.firstOrNull()?.toLowerCase() == when (pvLang) {
PervEdenLang.en -> "en-manga" PervEdenLang.en -> "en-manga"
PervEdenLang.it -> "it-manga" PervEdenLang.it -> "it-manga"
} }

View File

@ -32,7 +32,7 @@ import rx.schedulers.Schedulers
typealias SiteMap = NakedTrie<Unit> typealias SiteMap = NakedTrie<Unit>
class EightMuses: HttpSource(), class EightMuses : HttpSource(),
LewdSource<EightMusesSearchMetadata, Document>, LewdSource<EightMusesSearchMetadata, Document>,
UrlImportableSource { UrlImportableSource {
override val id = EIGHTMUSES_SOURCE_ID override val id = EIGHTMUSES_SOURCE_ID
@ -118,7 +118,7 @@ class EightMuses: 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): 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)
@ -161,11 +161,9 @@ class EightMuses: HttpSource(),
throw UnsupportedOperationException("Should not be called!") throw UnsupportedOperationException("Should not be called!")
} }
override fun fetchLatestUpdates(page: Int) override fun fetchLatestUpdates(page: Int) = fetchListing(latestUpdatesRequest(page), false)
= fetchListing(latestUpdatesRequest(page), false)
override fun fetchPopularManga(page: Int) override fun fetchPopularManga(page: Int) = fetchListing(popularMangaRequest(page), false) // TODO Dig
= fetchListing(popularMangaRequest(page), false) // TODO Dig
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return urlImportFetchSearchManga(query) { return urlImportFetchSearchManga(query) {
@ -190,7 +188,7 @@ 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('/')
@ -265,14 +263,14 @@ class EightMuses: HttpSource(),
val contents = parseSelf(response.asJsoup()) val contents = parseSelf(response.asJsoup())
val out = mutableListOf<SChapter>() val out = mutableListOf<SChapter>()
if(contents.images.isNotEmpty()) { if (contents.images.isNotEmpty()) {
out += SChapter.create().apply { out += SChapter.create().apply {
this.url = url this.url = url
this.name = if(prefix.isBlank()) ">" else prefix this.name = if (prefix.isBlank()) ">" else prefix
} }
} }
val builtPrefix = if(prefix.isBlank()) "> " else "$prefix > " val builtPrefix = if (prefix.isBlank()) "> " else "$prefix > "
out + contents.albums.flatMap { ele -> out + contents.albums.flatMap { ele ->
fetchAndParseChapterList(builtPrefix + ele.selectFirst(".title-text").text(), ele.attr("href")) fetchAndParseChapterList(builtPrefix + ele.selectFirst(".title-text").text(), ele.attr("href"))
@ -281,6 +279,7 @@ class EightMuses: HttpSource(),
} }
data class SelfContents(val albums: List<Element>, val images: List<Element>) data class SelfContents(val albums: List<Element>, val images: List<Element>)
private fun parseSelf(doc: Document): SelfContents { private fun parseSelf(doc: Document): SelfContents {
// Parse self // Parse self
val gc = doc.select(".gallery .c-tile") val gc = doc.select(".gallery .c-tile")
@ -377,7 +376,7 @@ class EightMuses: HttpSource(),
override fun mapUrlToMangaUrl(uri: Uri): String? { override fun mapUrlToMangaUrl(uri: Uri): String? {
var path = uri.pathSegments.drop(2) var path = uri.pathSegments.drop(2)
if(uri.pathSegments[1].toLowerCase() == "picture") { if (uri.pathSegments[1].toLowerCase() == "picture") {
path = path.dropLast(1) path = path.dropLast(1)
} }
return "/comics/album/${path.joinToString("/")}" return "/comics/album/${path.joinToString("/")}"

View File

@ -74,8 +74,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
override fun popularMangaRequest(page: Int) override fun popularMangaRequest(page: Int) = GET("$baseUrl/browse/title/rank/DESC/$page", headers)
= GET("$baseUrl/browse/title/rank/DESC/$page", headers)
private fun parseListing(response: Response): MangasPage { private fun parseListing(response: Response): MangasPage {
val doc = response.asJsoup() val doc = response.asJsoup()
@ -125,8 +124,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
* @param query the search query. * @param query the search query.
* @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 not be called!")
= throw UnsupportedOperationException("Should not be called!")
private fun fetchSearchMangaInternal(page: Int, query: String, filters: FilterList): Observable<MangasPage> { private fun fetchSearchMangaInternal(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) { return RxJavaInterop.toV1Single(GlobalScope.async(Dispatchers.IO) {
@ -138,28 +136,28 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
// <NS, VALUE, EXCLUDED> // <NS, VALUE, EXCLUDED>
var tagQuery: List<Triple<String, String, Boolean>>? = null var tagQuery: List<Triple<String, String, Boolean>>? = null
if(sortFilter != null) { if (sortFilter != null) {
sortFilter.state?.let { state -> sortFilter.state?.let { state ->
if(query.isNotBlank()) { if (query.isNotBlank()) {
throw IllegalArgumentException("Cannot use sorting while text/tag search is active!") throw IllegalArgumentException("Cannot use sorting while text/tag search is active!")
} }
isSortFilter = true isSortFilter = true
base = "/browse/title/${SortFilter.SORT_OPTIONS[state.index].first}/${if(state.ascending) "ASC" else "DESC"}" base = "/browse/title/${SortFilter.SORT_OPTIONS[state.index].first}/${if (state.ascending) "ASC" else "DESC"}"
} }
} }
if(base == null) { if (base == null) {
base = if(modeFilter != null && modeFilter.state == 1) { base = if (modeFilter != null && modeFilter.state == 1) {
tagQuery = searchEngine.parseQuery(query, false).map { tagQuery = searchEngine.parseQuery(query, false).map {
when (it) { when (it) {
is Text -> { is Text -> {
var minDist = Int.MAX_VALUE.toDouble() var minDist = Int.MAX_VALUE.toDouble()
// ns, value // ns, value
var minContent: Pair<String, String> = "" to "" var minContent: Pair<String, String> = "" to ""
for(ns in ALL_TAGS) { for (ns in ALL_TAGS) {
val (v, d) = ns.value.nearest(it.rawTextOnly(), minDist) val (v, d) = ns.value.nearest(it.rawTextOnly(), minDist)
if(d < minDist) { if (d < minDist) {
minDist = d minDist = d
minContent = ns.key to v minContent = ns.key to v
} }
@ -171,7 +169,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
val mappedNs = NS_MAPPINGS[it.namespace] ?: it.namespace val mappedNs = NS_MAPPINGS[it.namespace] ?: it.namespace
var key = mappedNs var key = mappedNs
if(!ALL_TAGS.containsKey(key)) key = ALL_TAGS.keys.sorted().nearest(mappedNs).first if (!ALL_TAGS.containsKey(key)) key = ALL_TAGS.keys.sorted().nearest(mappedNs).first
// Find nearest NS // Find nearest NS
val nsContents = ALL_TAGS[key] val nsContents = ALL_TAGS[key]
@ -193,18 +191,18 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
base += "/$page" base += "/$page"
if(isSortFilter) { if (isSortFilter) {
parseListing(client.newCall(GET(baseUrl + base, headers)) parseListing(client.newCall(GET(baseUrl + base, headers))
.asObservableSuccess() .asObservableSuccess()
.toSingle() .toSingle()
.await(Schedulers.io())) .await(Schedulers.io()))
} else { } else {
val body = if(tagQuery != null) { val body = if (tagQuery != null) {
FormBody.Builder() FormBody.Builder()
.add("type", "advance") .add("type", "advance")
.apply { .apply {
tagQuery.forEach { tagQuery.forEach {
add(it.first + "_" + it.second, if(it.third) "n" else "y") add(it.first + "_" + it.second, if (it.third) "n" else "y")
} }
} }
} else { } else {
@ -222,7 +220,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
.toSingle() .toSingle()
.await(Schedulers.io()) .await(Schedulers.io())
if(!processResponse.isRedirect) if (!processResponse.isRedirect)
throw IllegalStateException("Unexpected process response code!") throw IllegalStateException("Unexpected process response code!")
val sessId = processResponse.headers("Set-Cookie").find { val sessId = processResponse.headers("Set-Cookie").find {
@ -258,13 +256,13 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
// Collection must be sorted and cannot be sorted // Collection must be sorted and cannot be sorted
private fun List<String>.nearest(string: String, maxDist: Double = Int.MAX_VALUE.toDouble()): Pair<String, Double> { private fun List<String>.nearest(string: String, maxDist: Double = Int.MAX_VALUE.toDouble()): Pair<String, Double> {
val idx = binarySearch(string) val idx = binarySearch(string)
return if(idx < 0) { return if (idx < 0) {
val l = Levenshtein() val l = Levenshtein()
var minSoFar = maxDist var minSoFar = maxDist
var minIndexSoFar = 0 var minIndexSoFar = 0
forEachIndexed { index, s -> forEachIndexed { index, s ->
val d = l.distance(string, s, ceil(minSoFar).toInt()) val d = l.distance(string, s, ceil(minSoFar).toInt())
if(d < minSoFar) { if (d < minSoFar) {
minSoFar = d minSoFar = d
minIndexSoFar = index minIndexSoFar = index
} }
@ -312,7 +310,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
tags.clear() tags.clear()
(tables[""]!! + tables["categories"]!!).forEach { (k, v) -> (tables[""]!! + tables["categories"]!!).forEach { (k, v) ->
when(val lowercaseNs = k.toLowerCase()) { when (val lowercaseNs = k.toLowerCase()) {
"title" -> title = v.text() "title" -> title = v.text()
"length" -> length = v.text().substringBefore(" ").toInt() "length" -> length = v.text().substringBefore(" ").toInt()
else -> { else -> {
@ -376,8 +374,9 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
val doc = response.asJsoup() val doc = response.asJsoup()
val basePath = listOf("data") + response.request.url.pathSegments val basePath = listOf("data") + response.request.url.pathSegments
val scripts = doc.getElementsByTag("script").map { it.data() } val scripts = doc.getElementsByTag("script").map { it.data() }
for(script in scripts) { for (script in scripts) {
val totalPages = TOTAL_PAGES_REGEX.find(script)?.groupValues?.getOrNull(1)?.toIntOrNull() ?: continue val totalPages = TOTAL_PAGES_REGEX.find(script)?.groupValues?.getOrNull(1)?.toIntOrNull()
?: continue
val pageList = PAGE_LIST_REGEX.find(script)?.groupValues?.getOrNull(1) ?: continue val pageList = PAGE_LIST_REGEX.find(script)?.groupValues?.getOrNull(1) ?: continue
return jsonParser.parse(pageList).array.take(totalPages).map { return jsonParser.parse(pageList).array.take(totalPages).map {
@ -956,6 +955,7 @@ class HBrowse : HttpSource(), LewdSource<HBrowseSearchMetadata, Document>, UrlIm
).mapValues { it.value.sorted() } ).mapValues { it.value.sorted() }
private val TAGS_AS_MARKDOWN = ALL_TAGS.map { (ns, values) -> private val TAGS_AS_MARKDOWN = ALL_TAGS.map { (ns, values) ->
"#### $ns\n" + values.map { "- $it" }.joinToString("\n") }.joinToString("\n\n") "#### $ns\n" + values.map { "- $it" }.joinToString("\n")
}.joinToString("\n\n")
} }
} }

View File

@ -98,7 +98,7 @@ class HentaiCafe(delegate: HttpSource) : DelegatedHttpSource(delegate),
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"

View File

@ -32,7 +32,7 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
//Support direct URL importing //Support direct URL importing
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
val trimmedIdQuery = query.trim().removePrefix("id:") val trimmedIdQuery = query.trim().removePrefix("id:")
val newQuery = if(trimmedIdQuery.toIntOrNull() ?: -1 >= 0) { val newQuery = if (trimmedIdQuery.toIntOrNull() ?: -1 >= 0) {
"$baseUrl/gallery/$trimmedIdQuery/-" "$baseUrl/gallery/$trimmedIdQuery/-"
} else query } else query
@ -68,7 +68,7 @@ class Pururin(delegate: HttpSource) : DelegatedHttpSource(delegate),
contentWrapper.select(".table-gallery-info > tbody > tr").forEach { ele -> contentWrapper.select(".table-gallery-info > tbody > tr").forEach { ele ->
val key = ele.child(0).text().toLowerCase() val key = ele.child(0).text().toLowerCase()
val value = ele.child(1) val value = ele.child(1)
when(key) { when (key) {
"pages" -> { "pages" -> {
val split = value.text().split("(").trimAll().dropBlank() val split = value.text().split("(").trimAll().dropBlank()

View File

@ -25,105 +25,107 @@ import java.text.SimpleDateFormat
import java.util.* import java.util.*
class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate), class Tsumino(delegate: HttpSource) : DelegatedHttpSource(delegate),
LewdSource<TsuminoSearchMetadata, Document>, UrlImportableSource { 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? {
val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
if(lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry")
return null
return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
}
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun mapUrlToMangaUrl(uri: Uri): String? {
return client.newCall(mangaDetailsRequest(manga)) val lcFirstPathSegment = uri.pathSegments.firstOrNull()?.toLowerCase() ?: return null
.asObservableSuccess() if (lcFirstPathSegment != "read" && lcFirstPathSegment != "book" && lcFirstPathSegment != "entry")
.flatMap { return null
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga)) return "https://tsumino.com/Book/Info/${uri.lastPathSegment}"
} }
}
override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
with(metadata) { return client.newCall(mangaDetailsRequest(manga))
tmId = TsuminoSearchMetadata.tmIdFromUrl(input.location()).toInt() .asObservableSuccess()
tags.clear() .flatMap {
parseToManga(manga, it.asJsoup()).andThen(Observable.just(manga))
}
}
input.getElementById("Title")?.text()?.let { override fun parseIntoMetadata(metadata: TsuminoSearchMetadata, input: Document) {
title = it.trim() with(metadata) {
} tmId = TsuminoSearchMetadata.tmIdFromUrl(input.location()).toInt()
tags.clear()
input.getElementById("Artist")?.children()?.first()?.text()?.trim()?.let { input.getElementById("Title")?.text()?.let {
tags.add(RaisedTag("artist", it, TAG_TYPE_VIRTUAL)) title = it.trim()
artist = it }
}
input.getElementById("Uploader")?.children()?.first()?.text()?.trim()?.let { input.getElementById("Artist")?.children()?.first()?.text()?.trim()?.let {
uploader = it tags.add(RaisedTag("artist", it, TAG_TYPE_VIRTUAL))
} artist = it
}
input.getElementById("Uploaded")?.text()?.let { input.getElementById("Uploader")?.children()?.first()?.text()?.trim()?.let {
uploadDate = TM_DATE_FORMAT.parse(it.trim()).time uploader = it
} }
input.getElementById("Pages")?.text()?.let { input.getElementById("Uploaded")?.text()?.let {
length = it.trim().toIntOrNull() uploadDate = TM_DATE_FORMAT.parse(it.trim()).time
} }
input.getElementById("Rating")?.text()?.let { input.getElementById("Pages")?.text()?.let {
ratingString = it.trim() length = it.trim().toIntOrNull()
} }
input.getElementById("Category")?.children()?.first()?.text()?.let { input.getElementById("Rating")?.text()?.let {
category = it.trim() ratingString = it.trim()
tags.add(RaisedTag("genre", it, TAG_TYPE_VIRTUAL)) }
}
input.getElementById("Collection")?.children()?.first()?.text()?.let { input.getElementById("Category")?.children()?.first()?.text()?.let {
collection = it.trim() category = it.trim()
} tags.add(RaisedTag("genre", it, TAG_TYPE_VIRTUAL))
}
input.getElementById("Group")?.children()?.first()?.text()?.let { input.getElementById("Collection")?.children()?.first()?.text()?.let {
group = it.trim() collection = it.trim()
tags.add(RaisedTag("group", it, TAG_TYPE_VIRTUAL)) }
}
val newParody = mutableListOf<String>() input.getElementById("Group")?.children()?.first()?.text()?.let {
input.getElementById("Parody")?.children()?.forEach { group = it.trim()
val entry = it.text().trim() tags.add(RaisedTag("group", it, TAG_TYPE_VIRTUAL))
newParody.add(entry) }
tags.add(RaisedTag("parody", entry, TAG_TYPE_VIRTUAL))
}
parody = newParody
val newCharacter = mutableListOf<String>() val newParody = mutableListOf<String>()
input.getElementById("Character")?.children()?.forEach { input.getElementById("Parody")?.children()?.forEach {
val entry = it.text().trim() val entry = it.text().trim()
newCharacter.add(entry) newParody.add(entry)
tags.add(RaisedTag("character", entry, TAG_TYPE_VIRTUAL)) tags.add(RaisedTag("parody", entry, TAG_TYPE_VIRTUAL))
} }
character = newCharacter parody = newParody
input.getElementById("Tag")?.children()?.let { val newCharacter = mutableListOf<String>()
tags.addAll(it.map { input.getElementById("Character")?.children()?.forEach {
RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT) val entry = it.text().trim()
}) newCharacter.add(entry)
} tags.add(RaisedTag("character", entry, TAG_TYPE_VIRTUAL))
} }
} character = newCharacter
override val matchingHosts = listOf(
"www.tsumino.com",
"tsumino.com"
)
companion object { input.getElementById("Tag")?.children()?.let {
val jsonParser by lazy {JsonParser()} tags.addAll(it.map {
val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US) RaisedTag(null, it.text().trim(), TAG_TYPE_DEFAULT)
private val ASP_NET_COOKIE_NAME = "ASP.NET_SessionId" })
} }
}
}
override val matchingHosts = listOf(
"www.tsumino.com",
"tsumino.com"
)
companion object {
val jsonParser by lazy { JsonParser() }
val TM_DATE_FORMAT = SimpleDateFormat("yyyy MMM dd", Locale.US)
private val ASP_NET_COOKIE_NAME = "ASP.NET_SessionId"
}
} }

View File

@ -19,13 +19,13 @@ class ExtensionFilterController : SettingsController() {
val activeLangs = preferences.enabledLanguages().get() val activeLangs = preferences.enabledLanguages().get()
val availableLangs = val availableLangs =
Injekt.get<ExtensionManager>().availableExtensions.groupBy { Injekt.get<ExtensionManager>().availableExtensions.groupBy {
it.lang it.lang
}.keys.minus("all").partition { }.keys.minus("all").partition {
it in activeLangs it in activeLangs
}.let { }.let {
it.first + it.second it.first + it.second
} }
availableLangs.forEach { availableLangs.forEach {
switchPreference { switchPreference {

View File

@ -427,14 +427,14 @@ open class BrowseSourcePresenter(
return loaded.map { return loaded.map {
try { try {
val id = it.substringBefore(':').toLong() val id = it.substringBefore(':').toLong()
if(id != source.id) return@map null if (id != source.id) return@map null
val content = jsonParser.parse(it.substringAfter(':')).obj val content = jsonParser.parse(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["name"].string,
content["query"].string, content["query"].string,
originalFilters) 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!")
t.printStackTrace() t.printStackTrace()

View File

@ -36,7 +36,9 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
// Keep compatibility as searchText field was replaced when we upgraded FlexibleAdapter // Keep compatibility as searchText field was replaced when we upgraded FlexibleAdapter
var searchText var searchText
get() = getFilter(String::class.java) ?: "" get() = getFilter(String::class.java) ?: ""
set(value) { setFilter(value) } set(value) {
setFilter(value)
}
// EXH <-- // EXH <--
/** /**
@ -73,7 +75,7 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
// we want to perform a no-op filter) // we want to perform a no-op filter)
suspend fun performFilter(cScope: CoroutineScope) { suspend fun performFilter(cScope: CoroutineScope) {
lastFilterJob?.cancel() lastFilterJob?.cancel()
if(mangas.isNotEmpty() && searchText.isNotBlank()) { if (mangas.isNotEmpty() && searchText.isNotBlank()) {
val savedSearchText = searchText val savedSearchText = searchText
val job = cScope.launch(Dispatchers.IO) { val job = cScope.launch(Dispatchers.IO) {
@ -90,7 +92,7 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await() val mangaWithMetaIdsQuery = db.getIdsOfFavoriteMangaWithMetadata().await()
val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count) val mangaWithMetaIds = LongArray(mangaWithMetaIdsQuery.count)
if(mangaWithMetaIds.isNotEmpty()) { if (mangaWithMetaIds.isNotEmpty()) {
val mangaIdCol = mangaWithMetaIdsQuery.getColumnIndex(MangaTable.COL_ID) val mangaIdCol = mangaWithMetaIdsQuery.getColumnIndex(MangaTable.COL_ID)
mangaWithMetaIdsQuery.moveToFirst() mangaWithMetaIdsQuery.moveToFirst()
while (!mangaWithMetaIdsQuery.isAfterLast) { while (!mangaWithMetaIdsQuery.isAfterLast) {
@ -104,7 +106,7 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
ensureActive() // Fail early when cancelled ensureActive() // Fail early when cancelled
val convertedResult = LongArray(queryResult.count) val convertedResult = LongArray(queryResult.count)
if(convertedResult.isNotEmpty()) { if (convertedResult.isNotEmpty()) {
val mangaIdCol = queryResult.getColumnIndex(SearchMetadataTable.COL_MANGA_ID) val mangaIdCol = queryResult.getColumnIndex(SearchMetadataTable.COL_MANGA_ID)
queryResult.moveToFirst() queryResult.moveToFirst()
while (!queryResult.isAfterLast) { while (!queryResult.isAfterLast) {
@ -119,11 +121,11 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
// Flow the mangas to allow cancellation of this filter operation // Flow the mangas to allow cancellation of this filter operation
mangas.asFlow().cancellable().filter { item -> mangas.asFlow().cancellable().filter { item ->
if(isLewdSource(item.manga.source)) { if (isLewdSource(item.manga.source)) {
val mangaId = item.manga.id ?: -1 val mangaId = item.manga.id ?: -1
if(convertedResult.binarySearch(mangaId) < 0) { if (convertedResult.binarySearch(mangaId) < 0) {
// Check if this manga even has metadata // Check if this manga even has metadata
if(mangaWithMetaIds.binarySearch(mangaId) < 0) { if (mangaWithMetaIds.binarySearch(mangaId) < 0) {
// No meta? Filter using title // No meta? Filter using title
item.filter(savedSearchText) item.filter(savedSearchText)
} else false } else false
@ -134,7 +136,7 @@ class LibraryCategoryAdapter(view: LibraryCategoryView) :
}.toList() }.toList()
} catch (e: Exception) { } catch (e: Exception) {
// Do not catch cancellations // Do not catch cancellations
if(e is CancellationException) throw e if (e is CancellationException) throw e
Timber.w(e, "Could not filter mangas!") Timber.w(e, "Could not filter mangas!")
mangas mangas

View File

@ -182,10 +182,10 @@ 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(
@ -216,9 +216,9 @@ class MangaInfoPresenter(
// Note that if the manga are merged in a different order, this won't trigger, but I don't care lol // Note that if the manga are merged in a different order, this won't trigger, but I don't care lol
val existingManga = db.getManga(toInsert.url, toInsert.source).await() val existingManga = db.getManga(toInsert.url, toInsert.source).await()
if(existingManga != null) { if (existingManga != null) {
withContext(NonCancellable) { withContext(NonCancellable) {
if(toInsert.id != null) { if (toInsert.id != null) {
db.deleteManga(toInsert).await() db.deleteManga(toInsert).await()
} }
} }
@ -230,7 +230,7 @@ class MangaInfoPresenter(
toInsert.initialized = false toInsert.initialized = false
val newId = db.insertManga(toInsert).await().insertedId() val newId = db.insertManga(toInsert).await().insertedId()
if(newId != null) toInsert.id = newId if (newId != null) toInsert.id = newId
return toInsert return toInsert
} }

View File

@ -37,12 +37,12 @@ class SaveImageNotifier(private val context: Context) {
*/ */
fun onComplete(file: File) { fun onComplete(file: File) {
val bitmap = GlideApp.with(context) val bitmap = GlideApp.with(context)
.asBitmap() .asBitmap()
.load(file) .load(file)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.skipMemoryCache(true) .skipMemoryCache(true)
.submit(720, 1280) .submit(720, 1280)
.get() .get()
if (bitmap != null) { if (bitmap != null) {
showCompleteNotification(file, bitmap) showCompleteNotification(file, bitmap)

View File

@ -19,16 +19,16 @@ class DirectoryPageLoader(val file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return file.listFiles() return file.listFiles()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { FileInputStream(it) } }
.sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<File> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, file -> .mapIndexed { i, file ->
val streamFn = { FileInputStream(file) } val streamFn = { FileInputStream(file) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -30,14 +30,14 @@ class EpubPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return epub.getImagesFromPages() return epub.getImagesFromPages()
.mapIndexed { i, path -> .mapIndexed { i, path ->
val streamFn = { epub.getInputStream(epub.getEntry(path)!!) } val streamFn = { epub.getInputStream(epub.getEntry(path)!!) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -43,17 +43,17 @@ class RarPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return archive.fileHeaders return archive.fileHeaders
.filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.fileNameString) { archive.getInputStream(it) } }
.sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) }) .sortedWith(Comparator<FileHeader> { f1, f2 -> f1.fileNameString.compareToCaseInsensitiveNaturalOrder(f2.fileNameString) })
.mapIndexed { i, header -> .mapIndexed { i, header ->
val streamFn = { getStream(header) } val streamFn = { getStream(header) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -33,16 +33,16 @@ class ZipPageLoader(file: File) : PageLoader() {
*/ */
override fun getPages(): Observable<List<ReaderPage>> { override fun getPages(): Observable<List<ReaderPage>> {
return zip.entries().toList() return zip.entries().toList()
.filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } } .filter { !it.isDirectory && ImageUtil.isImage(it.name) { zip.getInputStream(it) } }
.sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) }) .sortedWith(Comparator<ZipEntry> { f1, f2 -> f1.name.compareToCaseInsensitiveNaturalOrder(f2.name) })
.mapIndexed { i, entry -> .mapIndexed { i, entry ->
val streamFn = { zip.getInputStream(entry) } val streamFn = { zip.getInputStream(entry) }
ReaderPage(i).apply { ReaderPage(i).apply {
stream = streamFn stream = streamFn
status = Page.READY status = Page.READY
}
} }
} .let { Observable.just(it) }
.let { Observable.just(it) }
} }
/** /**

View File

@ -146,13 +146,13 @@ open class WebtoonRecyclerView @JvmOverloads constructor(
} }
animate() animate()
.apply { .apply {
newX?.let { x(it) } newX?.let { x(it) }
newY?.let { y(it) } newY?.let { y(it) }
} }
.setInterpolator(DecelerateInterpolator()) .setInterpolator(DecelerateInterpolator())
.setDuration(400) .setDuration(400)
.start() .start()
return true return true
} }

View File

@ -85,7 +85,7 @@ class SettingsEhController : SettingsController() {
onChange { newVal -> onChange { newVal ->
newVal as Boolean newVal as Boolean
if(!newVal) { if (!newVal) {
preferences.enableExhentai().set(false) preferences.enableExhentai().set(false)
true true
} else { } else {
@ -226,7 +226,7 @@ class SettingsEhController : SettingsController() {
defaultValue = "0" defaultValue = "0"
preferences.eh_autoUpdateFrequency().asObservable().subscribeUntilDestroy { newVal -> preferences.eh_autoUpdateFrequency().asObservable().subscribeUntilDestroy { newVal ->
summary = if(newVal == 0) { summary = if (newVal == 0) {
"${context.getString(R.string.app_name)} will currently never check galleries in your library for updates." "${context.getString(R.string.app_name)} will currently never check galleries in your library for updates."
} else { } else {
"${context.getString(R.string.app_name)} checks/updates galleries in batches. " + "${context.getString(R.string.app_name)} checks/updates galleries in batches. " +

View File

@ -124,9 +124,9 @@ fun syncChaptersWithSource(
} }
// --> EXH (carry over reading progress) // --> EXH (carry over reading progress)
if(manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) { if (manga.source == EH_SOURCE_ID || manga.source == EXH_SOURCE_ID) {
val finalAdded = toAdd.subtract(readded) val finalAdded = toAdd.subtract(readded)
if(finalAdded.isNotEmpty()) { if (finalAdded.isNotEmpty()) {
val max = dbChapters.maxBy { it.last_page_read } val max = dbChapters.maxBy { it.last_page_read }
if (max != null && max.last_page_read > 0) { if (max != null && max.last_page_read > 0) {
for (chapter in finalAdded) { for (chapter in finalAdded) {

View File

@ -139,8 +139,8 @@ class EpubFile(file: File) : Closeable {
*/ */
private fun getPagesFromDocument(document: Document): List<String> { private fun getPagesFromDocument(document: Document): List<String> {
val pages = document.select("manifest > item") val pages = document.select("manifest > item")
.filter { "application/xhtml+xml" == it.attr("media-type") } .filter { "application/xhtml+xml" == it.attr("media-type") }
.associateBy { it.attr("id") } .associateBy { it.attr("id") }
val spine = document.select("spine > itemref").map { it.attr("idref") } val spine = document.select("spine > itemref").map { it.attr("idref") }
return spine.mapNotNull { pages[it] }.map { it.attr("href") } return spine.mapNotNull { pages[it] }.map { it.attr("href") }