Move source and network outside data

This commit is contained in:
len 2017-01-20 21:24:31 +01:00
parent a4c145c1ef
commit 706163e7a6
95 changed files with 4082 additions and 4082 deletions

View File

@ -6,9 +6,9 @@ import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import uy.kohesive.injekt.api.InjektModule import uy.kohesive.injekt.api.InjektModule
import uy.kohesive.injekt.api.InjektRegistrar import uy.kohesive.injekt.api.InjektRegistrar

View File

@ -6,7 +6,7 @@ import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson import com.google.gson.Gson
import com.jakewharton.disklrucache.DiskLruCache import com.jakewharton.disklrucache.DiskLruCache
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.DiskUtil
import eu.kanade.tachiyomi.util.saveTo import eu.kanade.tachiyomi.util.saveTo
import okhttp3.Response import okhttp3.Response

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import java.io.Serializable import java.io.Serializable
interface Chapter : SChapter, Serializable { interface Chapter : SChapter, Serializable {

View File

@ -1,6 +1,6 @@
package eu.kanade.tachiyomi.data.database.models package eu.kanade.tachiyomi.data.database.models
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
interface Manga : SManga { interface Manga : SManga {

View File

@ -6,8 +6,8 @@ import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import rx.Observable import rx.Observable
/** /**

View File

@ -7,7 +7,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.util.DiskUtil import eu.kanade.tachiyomi.util.DiskUtil
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View File

@ -5,8 +5,8 @@ import com.google.gson.Gson
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
/** /**

View File

@ -10,10 +10,10 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.download.model.DownloadQueue import eu.kanade.tachiyomi.data.download.model.DownloadQueue
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.data.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator
import eu.kanade.tachiyomi.util.RetryWithDelay import eu.kanade.tachiyomi.util.RetryWithDelay
import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.plusAssign

View File

@ -2,8 +2,8 @@ package eu.kanade.tachiyomi.data.download.model
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
class Download(val source: OnlineSource, val manga: Manga, val chapter: Chapter) { class Download(val source: OnlineSource, val manga: Manga, val chapter: Chapter) {

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.download.model
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.download.DownloadStore import eu.kanade.tachiyomi.data.download.DownloadStore
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import rx.Observable import rx.Observable
import rx.subjects.PublishSubject import rx.subjects.PublishSubject
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList

View File

@ -8,7 +8,7 @@ import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory
import com.bumptech.glide.load.model.GlideUrl import com.bumptech.glide.load.model.GlideUrl
import com.bumptech.glide.module.GlideModule import com.bumptech.glide.module.GlideModule
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.InputStream import java.io.InputStream

View File

@ -8,8 +8,8 @@ import com.bumptech.glide.load.model.*
import com.bumptech.glide.load.model.stream.StreamModelLoader import com.bumptech.glide.load.model.stream.StreamModelLoader
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream

View File

@ -20,9 +20,9 @@ import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
import eu.kanade.tachiyomi.data.notification.NotificationReceiver import eu.kanade.tachiyomi.data.notification.NotificationReceiver
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.* import eu.kanade.tachiyomi.util.*
import rx.Observable import rx.Observable

View File

@ -7,7 +7,7 @@ import android.preference.PreferenceManager
import com.f2prateek.rx.preferences.Preference import com.f2prateek.rx.preferences.Preference
import com.f2prateek.rx.preferences.RxSharedPreferences import com.f2prateek.rx.preferences.RxSharedPreferences
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.data.track.TrackService
import java.io.File import java.io.File

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track
import android.support.annotation.CallSuper import android.support.annotation.CallSuper
import android.support.annotation.DrawableRes import android.support.annotation.DrawableRes
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import rx.Completable import rx.Completable

View File

@ -5,7 +5,7 @@ import com.github.salomonbrys.kotson.int
import com.github.salomonbrys.kotson.string import com.github.salomonbrys.kotson.string
import com.google.gson.JsonObject import com.google.gson.JsonObject
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.ResponseBody import okhttp3.ResponseBody

View File

@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.data.track.kitsu
import com.github.salomonbrys.kotson.* import com.github.salomonbrys.kotson.*
import com.google.gson.JsonObject import com.google.gson.JsonObject
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import retrofit2.Retrofit import retrofit2.Retrofit

View File

@ -3,10 +3,10 @@ package eu.kanade.tachiyomi.data.track.myanimelist
import android.net.Uri import android.net.Uri
import android.util.Xml import android.util.Xml
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.network.asObservable import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.data.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.util.selectInt import eu.kanade.tachiyomi.util.selectInt
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText

View File

@ -8,10 +8,10 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import android.os.Build import android.os.Build
import eu.kanade.tachiyomi.BuildConfig import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.data.network.ProgressListener import eu.kanade.tachiyomi.network.ProgressListener
import eu.kanade.tachiyomi.data.network.newCallWithProgress import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.util.registerLocalReceiver import eu.kanade.tachiyomi.util.registerLocalReceiver
import eu.kanade.tachiyomi.util.saveTo import eu.kanade.tachiyomi.util.saveTo
import eu.kanade.tachiyomi.util.sendLocalBroadcastSync import eu.kanade.tachiyomi.util.sendLocalBroadcastSync

View File

@ -1,80 +1,80 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import com.squareup.duktape.Duktape import com.squareup.duktape.Duktape
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Interceptor import okhttp3.Interceptor
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interceptor { class CloudflareInterceptor(private val cookies: PersistentCookieStore) : Interceptor {
//language=RegExp //language=RegExp
private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""") private val operationPattern = Regex("""setTimeout\(function\(\)\{\s+(var (?:\w,)+f.+?\r?\n[\s\S]+?a\.value =.+?)\r?\n""")
//language=RegExp //language=RegExp
private val passPattern = Regex("""name="pass" value="(.+?)"""") private val passPattern = Regex("""name="pass" value="(.+?)"""")
//language=RegExp //language=RegExp
private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""") private val challengePattern = Regex("""name="jschl_vc" value="(\w+)"""")
override fun intercept(chain: Interceptor.Chain): Response { override fun intercept(chain: Interceptor.Chain): Response {
val response = chain.proceed(chain.request()) val response = chain.proceed(chain.request())
// Check if we already solved a challenge // Check if we already solved a challenge
if (response.code() != 503 && if (response.code() != 503 &&
cookies.get(response.request().url()).any { it.name() == "cf_clearance" }) { cookies.get(response.request().url()).any { it.name() == "cf_clearance" }) {
return response return response
} }
// Check if Cloudflare anti-bot is on // Check if Cloudflare anti-bot is on
if ("URL=/cdn-cgi/" in response.header("Refresh", "") if ("URL=/cdn-cgi/" in response.header("Refresh", "")
&& response.header("Server", "") == "cloudflare-nginx") { && response.header("Server", "") == "cloudflare-nginx") {
return chain.proceed(resolveChallenge(response)) return chain.proceed(resolveChallenge(response))
} }
return response return response
} }
private fun resolveChallenge(response: Response): Request { private fun resolveChallenge(response: Response): Request {
val duktape = Duktape.create() val duktape = Duktape.create()
try { try {
val originalRequest = response.request() val originalRequest = response.request()
val domain = originalRequest.url().host() val domain = originalRequest.url().host()
val content = response.body().string() val content = response.body().string()
// CloudFlare requires waiting 4 seconds before resolving the challenge // CloudFlare requires waiting 4 seconds before resolving the challenge
Thread.sleep(4000) Thread.sleep(4000)
val operation = operationPattern.find(content)?.groups?.get(1)?.value val operation = operationPattern.find(content)?.groups?.get(1)?.value
val challenge = challengePattern.find(content)?.groups?.get(1)?.value val challenge = challengePattern.find(content)?.groups?.get(1)?.value
val pass = passPattern.find(content)?.groups?.get(1)?.value val pass = passPattern.find(content)?.groups?.get(1)?.value
if (operation == null || challenge == null || pass == null) { if (operation == null || challenge == null || pass == null) {
throw RuntimeException("Failed resolving Cloudflare challenge") throw RuntimeException("Failed resolving Cloudflare challenge")
} }
val js = operation val js = operation
//language=RegExp //language=RegExp
.replace(Regex("""a\.value =(.+?) \+.*"""), "$1") .replace(Regex("""a\.value =(.+?) \+.*"""), "$1")
//language=RegExp //language=RegExp
.replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "") .replace(Regex("""\s{3,}[a-z](?: = |\.).+"""), "")
.replace("\n", "") .replace("\n", "")
val result = (duktape.evaluate(js) as Double).toInt() val result = (duktape.evaluate(js) as Double).toInt()
val answer = "${result + domain.length}" val answer = "${result + domain.length}"
val url = HttpUrl.parse("http://$domain/cdn-cgi/l/chk_jschl").newBuilder() val url = HttpUrl.parse("http://$domain/cdn-cgi/l/chk_jschl").newBuilder()
.addQueryParameter("jschl_vc", challenge) .addQueryParameter("jschl_vc", challenge)
.addQueryParameter("pass", pass) .addQueryParameter("pass", pass)
.addQueryParameter("jschl_answer", answer) .addQueryParameter("jschl_answer", answer)
.toString() .toString()
val referer = originalRequest.url().toString() val referer = originalRequest.url().toString()
return GET(url, originalRequest.headers().newBuilder().add("Referer", referer).build()) return GET(url, originalRequest.headers().newBuilder().add("Referer", referer).build())
} finally { } finally {
duktape.close() duktape.close()
} }
} }
} }

View File

@ -1,38 +1,38 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import okhttp3.Cache import okhttp3.Cache
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import java.io.File import java.io.File
class NetworkHelper(context: Context) { class NetworkHelper(context: Context) {
private val cacheDir = File(context.cacheDir, "network_cache") private val cacheDir = File(context.cacheDir, "network_cache")
private val cacheSize = 5L * 1024 * 1024 // 5 MiB private val cacheSize = 5L * 1024 * 1024 // 5 MiB
private val cookieManager = PersistentCookieJar(context) private val cookieManager = PersistentCookieJar(context)
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.cookieJar(cookieManager) .cookieJar(cookieManager)
.cache(Cache(cacheDir, cacheSize)) .cache(Cache(cacheDir, cacheSize))
.build() .build()
val forceCacheClient = client.newBuilder() val forceCacheClient = client.newBuilder()
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder() originalResponse.newBuilder()
.removeHeader("Pragma") .removeHeader("Pragma")
.header("Cache-Control", "max-age=600") .header("Cache-Control", "max-age=600")
.build() .build()
} }
.build() .build()
val cloudflareClient = client.newBuilder() val cloudflareClient = client.newBuilder()
.addInterceptor(CloudflareInterceptor(cookies)) .addInterceptor(CloudflareInterceptor(cookies))
.build() .build()
val cookies: PersistentCookieStore val cookies: PersistentCookieStore
get() = cookieManager.store get() = cookieManager.store
} }

View File

@ -1,70 +1,70 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import okhttp3.Call import okhttp3.Call
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import rx.Producer import rx.Producer
import rx.Subscription import rx.Subscription
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
fun Call.asObservable(): Observable<Response> { fun Call.asObservable(): Observable<Response> {
return Observable.create { subscriber -> return Observable.create { subscriber ->
// Since Call is a one-shot type, clone it for each new subscriber. // Since Call is a one-shot type, clone it for each new subscriber.
val call = clone() val call = clone()
// Wrap the call in a helper which handles both unsubscription and backpressure. // Wrap the call in a helper which handles both unsubscription and backpressure.
val requestArbiter = object : AtomicBoolean(), Producer, Subscription { val requestArbiter = object : AtomicBoolean(), Producer, Subscription {
override fun request(n: Long) { override fun request(n: Long) {
if (n == 0L || !compareAndSet(false, true)) return if (n == 0L || !compareAndSet(false, true)) return
try { try {
val response = call.execute() val response = call.execute()
if (!subscriber.isUnsubscribed) { if (!subscriber.isUnsubscribed) {
subscriber.onNext(response) subscriber.onNext(response)
subscriber.onCompleted() subscriber.onCompleted()
} }
} catch (error: Exception) { } catch (error: Exception) {
if (!subscriber.isUnsubscribed) { if (!subscriber.isUnsubscribed) {
subscriber.onError(error) subscriber.onError(error)
} }
} }
} }
override fun unsubscribe() { override fun unsubscribe() {
call.cancel() call.cancel()
} }
override fun isUnsubscribed(): Boolean { override fun isUnsubscribed(): Boolean {
return call.isCanceled return call.isCanceled
} }
} }
subscriber.add(requestArbiter) subscriber.add(requestArbiter)
subscriber.setProducer(requestArbiter) subscriber.setProducer(requestArbiter)
} }
} }
fun Call.asObservableSuccess(): Observable<Response> { fun Call.asObservableSuccess(): Observable<Response> {
return asObservable().doOnNext { response -> return asObservable().doOnNext { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
response.close() response.close()
throw Exception("HTTP error ${response.code()}") throw Exception("HTTP error ${response.code()}")
} }
} }
} }
fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call { fun OkHttpClient.newCallWithProgress(request: Request, listener: ProgressListener): Call {
val progressClient = newBuilder() val progressClient = newBuilder()
.cache(null) .cache(null)
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalResponse = chain.proceed(chain.request()) val originalResponse = chain.proceed(chain.request())
originalResponse.newBuilder() originalResponse.newBuilder()
.body(ProgressResponseBody(originalResponse.body(), listener)) .body(ProgressResponseBody(originalResponse.body(), listener))
.build() .build()
} }
.build() .build()
return progressClient.newCall(request) return progressClient.newCall(request)
} }

View File

@ -1,19 +1,19 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.CookieJar import okhttp3.CookieJar
import okhttp3.HttpUrl import okhttp3.HttpUrl
class PersistentCookieJar(context: Context) : CookieJar { class PersistentCookieJar(context: Context) : CookieJar {
val store = PersistentCookieStore(context) val store = PersistentCookieStore(context)
override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) { override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {
store.addAll(url, cookies) store.addAll(url, cookies)
} }
override fun loadForRequest(url: HttpUrl): List<Cookie> { override fun loadForRequest(url: HttpUrl): List<Cookie> {
return store.get(url) return store.get(url)
} }
} }

View File

@ -1,75 +1,75 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import android.content.Context import android.content.Context
import okhttp3.Cookie import okhttp3.Cookie
import okhttp3.HttpUrl import okhttp3.HttpUrl
import java.net.URI import java.net.URI
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
class PersistentCookieStore(context: Context) { class PersistentCookieStore(context: Context) {
private val cookieMap = ConcurrentHashMap<String, List<Cookie>>() private val cookieMap = ConcurrentHashMap<String, List<Cookie>>()
private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE) private val prefs = context.getSharedPreferences("cookie_store", Context.MODE_PRIVATE)
init { init {
for ((key, value) in prefs.all) { for ((key, value) in prefs.all) {
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
val cookies = value as? Set<String> val cookies = value as? Set<String>
if (cookies != null) { if (cookies != null) {
try { try {
val url = HttpUrl.parse("http://$key") val url = HttpUrl.parse("http://$key")
val nonExpiredCookies = cookies.map { Cookie.parse(url, it) } val nonExpiredCookies = cookies.map { Cookie.parse(url, it) }
.filter { !it.hasExpired() } .filter { !it.hasExpired() }
cookieMap.put(key, nonExpiredCookies) cookieMap.put(key, nonExpiredCookies)
} catch (e: Exception) { } catch (e: Exception) {
// Ignore // Ignore
} }
} }
} }
} }
fun addAll(url: HttpUrl, cookies: List<Cookie>) { fun addAll(url: HttpUrl, cookies: List<Cookie>) {
synchronized(this) { synchronized(this) {
val key = url.uri().host val key = url.uri().host
// Append or replace the cookies for this domain. // Append or replace the cookies for this domain.
val cookiesForDomain = cookieMap[key].orEmpty().toMutableList() val cookiesForDomain = cookieMap[key].orEmpty().toMutableList()
for (cookie in cookies) { for (cookie in cookies) {
// Find a cookie with the same name. Replace it if found, otherwise add a new one. // Find a cookie with the same name. Replace it if found, otherwise add a new one.
val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() } val pos = cookiesForDomain.indexOfFirst { it.name() == cookie.name() }
if (pos == -1) { if (pos == -1) {
cookiesForDomain.add(cookie) cookiesForDomain.add(cookie)
} else { } else {
cookiesForDomain[pos] = cookie cookiesForDomain[pos] = cookie
} }
} }
cookieMap.put(key, cookiesForDomain) cookieMap.put(key, cookiesForDomain)
// Get cookies to be stored in disk // Get cookies to be stored in disk
val newValues = cookiesForDomain.asSequence() val newValues = cookiesForDomain.asSequence()
.filter { it.persistent() && !it.hasExpired() } .filter { it.persistent() && !it.hasExpired() }
.map { it.toString() } .map { it.toString() }
.toSet() .toSet()
prefs.edit().putStringSet(key, newValues).apply() prefs.edit().putStringSet(key, newValues).apply()
} }
} }
fun removeAll() { fun removeAll() {
synchronized(this) { synchronized(this) {
prefs.edit().clear().apply() prefs.edit().clear().apply()
cookieMap.clear() cookieMap.clear()
} }
} }
fun get(url: HttpUrl) = get(url.uri().host) fun get(url: HttpUrl) = get(url.uri().host)
fun get(uri: URI) = get(uri.host) fun get(uri: URI) = get(uri.host)
private fun get(url: String): List<Cookie> { private fun get(url: String): List<Cookie> {
return cookieMap[url].orEmpty().filter { !it.hasExpired() } return cookieMap[url].orEmpty().filter { !it.hasExpired() }
} }
private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt() private fun Cookie.hasExpired() = System.currentTimeMillis() >= expiresAt()
} }

View File

@ -1,5 +1,5 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
interface ProgressListener { interface ProgressListener {
fun update(bytesRead: Long, contentLength: Long, done: Boolean) fun update(bytesRead: Long, contentLength: Long, done: Boolean)
} }

View File

@ -1,40 +1,40 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.ResponseBody import okhttp3.ResponseBody
import okio.* import okio.*
import java.io.IOException import java.io.IOException
class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() { class ProgressResponseBody(private val responseBody: ResponseBody, private val progressListener: ProgressListener) : ResponseBody() {
private val bufferedSource: BufferedSource by lazy { private val bufferedSource: BufferedSource by lazy {
Okio.buffer(source(responseBody.source())) Okio.buffer(source(responseBody.source()))
} }
override fun contentType(): MediaType { override fun contentType(): MediaType {
return responseBody.contentType() return responseBody.contentType()
} }
override fun contentLength(): Long { override fun contentLength(): Long {
return responseBody.contentLength() return responseBody.contentLength()
} }
override fun source(): BufferedSource { override fun source(): BufferedSource {
return bufferedSource return bufferedSource
} }
private fun source(source: Source): Source { private fun source(source: Source): Source {
return object : ForwardingSource(source) { return object : ForwardingSource(source) {
internal var totalBytesRead = 0L internal var totalBytesRead = 0L
@Throws(IOException::class) @Throws(IOException::class)
override fun read(sink: Buffer, byteCount: Long): Long { override fun read(sink: Buffer, byteCount: Long): Long {
val bytesRead = super.read(sink, byteCount) val bytesRead = super.read(sink, byteCount)
// read() returns the number of bytes read, or -1 if this source is exhausted. // read() returns the number of bytes read, or -1 if this source is exhausted.
totalBytesRead += if (bytesRead != -1L) bytesRead else 0 totalBytesRead += if (bytesRead != -1L) bytesRead else 0
progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L) progressListener.update(totalBytesRead, responseBody.contentLength(), bytesRead == -1L)
return bytesRead return bytesRead
} }
} }
} }
} }

View File

@ -1,32 +1,32 @@
package eu.kanade.tachiyomi.data.network package eu.kanade.tachiyomi.network
import okhttp3.* import okhttp3.*
import java.util.concurrent.TimeUnit.MINUTES import java.util.concurrent.TimeUnit.MINUTES
private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build() private val DEFAULT_CACHE_CONTROL = CacheControl.Builder().maxAge(10, MINUTES).build()
private val DEFAULT_HEADERS = Headers.Builder().build() private val DEFAULT_HEADERS = Headers.Builder().build()
private val DEFAULT_BODY: RequestBody = FormBody.Builder().build() private val DEFAULT_BODY: RequestBody = FormBody.Builder().build()
fun GET(url: String, fun GET(url: String,
headers: Headers = DEFAULT_HEADERS, headers: Headers = DEFAULT_HEADERS,
cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { cache: CacheControl = DEFAULT_CACHE_CONTROL): Request {
return Request.Builder() return Request.Builder()
.url(url) .url(url)
.headers(headers) .headers(headers)
.cacheControl(cache) .cacheControl(cache)
.build() .build()
} }
fun POST(url: String, fun POST(url: String,
headers: Headers = DEFAULT_HEADERS, headers: Headers = DEFAULT_HEADERS,
body: RequestBody = DEFAULT_BODY, body: RequestBody = DEFAULT_BODY,
cache: CacheControl = DEFAULT_CACHE_CONTROL): Request { cache: CacheControl = DEFAULT_CACHE_CONTROL): Request {
return Request.Builder() return Request.Builder()
.url(url) .url(url)
.post(body) .post(body)
.headers(headers) .headers(headers)
.cacheControl(cache) .cacheControl(cache)
.build() .build()
} }

View File

@ -1,46 +1,46 @@
package eu.kanade.tachiyomi.data.source package eu.kanade.tachiyomi.source
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import rx.Observable import rx.Observable
interface CatalogueSource : Source { interface CatalogueSource : Source {
/** /**
* An ISO 639-1 compliant language code (two letters in lower case). * An ISO 639-1 compliant language code (two letters in lower case).
*/ */
val lang: String val lang: String
/** /**
* Whether the source has support for latest updates. * Whether the source has support for latest updates.
*/ */
val supportsLatest: Boolean val supportsLatest: Boolean
/** /**
* Returns an observable containing a page with a list of manga. * Returns an observable containing a page with a list of manga.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
fun fetchPopularManga(page: Int): Observable<MangasPage> fun fetchPopularManga(page: Int): Observable<MangasPage>
/** /**
* Returns an observable containing a page with a list of manga. * Returns an observable containing a page with a list of manga.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
* @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.
*/ */
fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage>
/** /**
* Returns an observable containing a page with a list of latest manga updates. * Returns an observable containing a page with a list of latest manga updates.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
fun fetchLatestUpdates(page: Int): Observable<MangasPage> fun fetchLatestUpdates(page: Int): Observable<MangasPage>
/** /**
* Returns the list of filters for the source. * Returns the list of filters for the source.
*/ */
fun getFilterList(): FilterList fun getFilterList(): FilterList
} }

View File

@ -1,44 +1,44 @@
package eu.kanade.tachiyomi.data.source package eu.kanade.tachiyomi.source
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import rx.Observable import rx.Observable
/** /**
* A basic interface for creating a source. It could be an online source, a local source, etc... * A basic interface for creating a source. It could be an online source, a local source, etc...
*/ */
interface Source { interface Source {
/** /**
* Id for the source. Must be unique. * Id for the source. Must be unique.
*/ */
val id: Long val id: Long
/** /**
* Name of the source. * Name of the source.
*/ */
val name: String val name: String
/** /**
* Returns an observable with the updated details for a manga. * Returns an observable with the updated details for a manga.
* *
* @param manga the manga to update. * @param manga the manga to update.
*/ */
fun fetchMangaDetails(manga: SManga): Observable<SManga> fun fetchMangaDetails(manga: SManga): Observable<SManga>
/** /**
* Returns an observable with all the available chapters for a manga. * Returns an observable with all the available chapters for a manga.
* *
* @param manga the manga to update. * @param manga the manga to update.
*/ */
fun fetchChapterList(manga: SManga): Observable<List<SChapter>> fun fetchChapterList(manga: SManga): Observable<List<SChapter>>
/** /**
* Returns an observable with the list of pages a chapter has. * Returns an observable with the list of pages a chapter has.
* *
* @param chapter the chapter. * @param chapter the chapter.
*/ */
fun fetchPageList(chapter: SChapter): Observable<List<Page>> fun fetchPageList(chapter: SChapter): Observable<List<Page>>
} }

View File

@ -1,159 +1,159 @@
package eu.kanade.tachiyomi.data.source package eu.kanade.tachiyomi.source
import android.Manifest.permission.READ_EXTERNAL_STORAGE import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.content.Context import android.content.Context
import android.content.pm.ApplicationInfo import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.os.Environment import android.os.Environment
import dalvik.system.PathClassLoader import dalvik.system.PathClassLoader
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.data.source.online.YamlOnlineSource import eu.kanade.tachiyomi.source.online.YamlOnlineSource
import eu.kanade.tachiyomi.data.source.online.english.* import eu.kanade.tachiyomi.source.online.english.*
import eu.kanade.tachiyomi.data.source.online.german.WieManga import eu.kanade.tachiyomi.source.online.german.WieManga
import eu.kanade.tachiyomi.data.source.online.russian.Mangachan import eu.kanade.tachiyomi.source.online.russian.Mangachan
import eu.kanade.tachiyomi.data.source.online.russian.Mintmanga import eu.kanade.tachiyomi.source.online.russian.Mintmanga
import eu.kanade.tachiyomi.data.source.online.russian.Readmanga import eu.kanade.tachiyomi.source.online.russian.Readmanga
import eu.kanade.tachiyomi.util.hasPermission import eu.kanade.tachiyomi.util.hasPermission
import org.yaml.snakeyaml.Yaml import org.yaml.snakeyaml.Yaml
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
open class SourceManager(private val context: Context) { open class SourceManager(private val context: Context) {
private val sourcesMap = mutableMapOf<Long, Source>() private val sourcesMap = mutableMapOf<Long, Source>()
init { init {
createSources() createSources()
} }
open fun get(sourceKey: Long): Source? { open fun get(sourceKey: Long): Source? {
return sourcesMap[sourceKey] return sourcesMap[sourceKey]
} }
fun getOnlineSources() = sourcesMap.values.filterIsInstance<OnlineSource>() fun getOnlineSources() = sourcesMap.values.filterIsInstance<OnlineSource>()
fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>() fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>()
private fun createSources() { private fun createSources() {
createExtensionSources().forEach { registerSource(it) } createExtensionSources().forEach { registerSource(it) }
createYamlSources().forEach { registerSource(it) } createYamlSources().forEach { registerSource(it) }
createInternalSources().forEach { registerSource(it) } createInternalSources().forEach { registerSource(it) }
} }
private fun registerSource(source: Source, overwrite: Boolean = false) { private fun registerSource(source: Source, overwrite: Boolean = false) {
if (overwrite || !sourcesMap.containsKey(source.id)) { if (overwrite || !sourcesMap.containsKey(source.id)) {
sourcesMap.put(source.id, source) sourcesMap.put(source.id, source)
} }
} }
private fun createInternalSources(): List<Source> = listOf( private fun createInternalSources(): List<Source> = listOf(
Batoto(), Batoto(),
Mangahere(), Mangahere(),
Mangafox(), Mangafox(),
Kissmanga(), Kissmanga(),
Readmanga(), Readmanga(),
Mintmanga(), Mintmanga(),
Mangachan(), Mangachan(),
Readmangatoday(), Readmangatoday(),
Mangasee(), Mangasee(),
WieManga() WieManga()
) )
private fun createYamlSources(): List<Source> { private fun createYamlSources(): List<Source> {
val sources = mutableListOf<Source>() val sources = mutableListOf<Source>()
val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath + val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath +
File.separator + context.getString(R.string.app_name), "parsers") File.separator + context.getString(R.string.app_name), "parsers")
if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) { if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) {
val yaml = Yaml() val yaml = Yaml()
for (file in parsersDir.listFiles().filter { it.extension == "yml" }) { for (file in parsersDir.listFiles().filter { it.extension == "yml" }) {
try { try {
val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) } val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) }
sources.add(YamlOnlineSource(map)) sources.add(YamlOnlineSource(map))
} catch (e: Exception) { } catch (e: Exception) {
Timber.e("Error loading source from file. Bad format?") Timber.e("Error loading source from file. Bad format?")
} }
} }
} }
return sources return sources
} }
private fun createExtensionSources(): List<OnlineSource> { private fun createExtensionSources(): List<OnlineSource> {
val pkgManager = context.packageManager val pkgManager = context.packageManager
val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES
val installedPkgs = pkgManager.getInstalledPackages(flags) val installedPkgs = pkgManager.getInstalledPackages(flags)
val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == FEATURE } } val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == FEATURE } }
val sources = mutableListOf<OnlineSource>() val sources = mutableListOf<OnlineSource>()
for (pkgInfo in extPkgs) { for (pkgInfo in extPkgs) {
val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName, val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName,
PackageManager.GET_META_DATA) ?: continue PackageManager.GET_META_DATA) ?: continue
val data = appInfo.metaData val data = appInfo.metaData
val extName = data.getString(NAME) val extName = data.getString(NAME)
val version = data.getInt(VERSION) val version = data.getInt(VERSION)
val sourceClass = extendClassName(data.getString(SOURCE), pkgInfo.packageName) val sourceClass = extendClassName(data.getString(SOURCE), pkgInfo.packageName)
val ext = Extension(extName, appInfo, version, sourceClass) val ext = Extension(extName, appInfo, version, sourceClass)
if (!validateExtension(ext)) { if (!validateExtension(ext)) {
continue continue
} }
val instance = loadExtension(ext, pkgManager) val instance = loadExtension(ext, pkgManager)
if (instance == null) { if (instance == null) {
Timber.e("Extension error: failed to instance $extName") Timber.e("Extension error: failed to instance $extName")
continue continue
} }
sources.add(instance) sources.add(instance)
} }
return sources return sources
} }
private fun validateExtension(ext: Extension): Boolean { private fun validateExtension(ext: Extension): Boolean {
if (ext.version < LIB_VERSION_MIN || ext.version > LIB_VERSION_MAX) { if (ext.version < LIB_VERSION_MIN || ext.version > LIB_VERSION_MAX) {
Timber.e("Extension error: ${ext.name} has version ${ext.version}, while only versions " Timber.e("Extension error: ${ext.name} has version ${ext.version}, while only versions "
+ "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed")
return false return false
} }
return true return true
} }
private fun loadExtension(ext: Extension, pkgManager: PackageManager): OnlineSource? { private fun loadExtension(ext: Extension, pkgManager: PackageManager): OnlineSource? {
return try { return try {
val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader) val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader)
val resources = pkgManager.getResourcesForApplication(ext.appInfo) val resources = pkgManager.getResourcesForApplication(ext.appInfo)
Class.forName(ext.sourceClass, false, classLoader).newInstance() as? OnlineSource Class.forName(ext.sourceClass, false, classLoader).newInstance() as? OnlineSource
} catch (e: Exception) { } catch (e: Exception) {
null null
} catch (e: LinkageError) { } catch (e: LinkageError) {
null null
} }
} }
private fun extendClassName(className: String, packageName: String): String { private fun extendClassName(className: String, packageName: String): String {
return if (className.startsWith(".")) { return if (className.startsWith(".")) {
packageName + className packageName + className
} else { } else {
className className
} }
} }
class Extension(val name: String, class Extension(val name: String,
val appInfo: ApplicationInfo, val appInfo: ApplicationInfo,
val version: Int, val version: Int,
val sourceClass: String) val sourceClass: String)
private companion object { private companion object {
const val FEATURE = "tachiyomi.extension" const val FEATURE = "tachiyomi.extension"
const val NAME = "tachiyomi.extension.name" const val NAME = "tachiyomi.extension.name"
const val VERSION = "tachiyomi.extension.version" const val VERSION = "tachiyomi.extension.version"
const val SOURCE = "tachiyomi.extension.source" const val SOURCE = "tachiyomi.extension.source"
const val LIB_VERSION_MIN = 1 const val LIB_VERSION_MIN = 1
const val LIB_VERSION_MAX = 1 const val LIB_VERSION_MAX = 1
} }
} }

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
sealed class Filter<T>(val name: String, var state: T) { sealed class Filter<T>(val name: String, var state: T) {
open class Header(name: String) : Filter<Any>(name, 0) open class Header(name: String) : Filter<Any>(name, 0)

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list { data class FilterList(val list: List<Filter<*>>) : List<Filter<*>> by list {
constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList()) constructor(vararg fs: Filter<*>) : this(if (fs.isNotEmpty()) fs.asList() else emptyList())
} }

View File

@ -1,3 +1,3 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
data class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean) data class MangasPage(val mangas: List<SManga>, val hasNextPage: Boolean)

View File

@ -1,47 +1,47 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.data.network.ProgressListener import eu.kanade.tachiyomi.network.ProgressListener
import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.ReaderChapter
import rx.subjects.Subject import rx.subjects.Subject
class Page( class Page(
val index: Int, val index: Int,
val url: String = "", val url: String = "",
var imageUrl: String? = null, var imageUrl: String? = null,
@Transient var uri: Uri? = null @Transient var uri: Uri? = null
) : ProgressListener { ) : ProgressListener {
val number: Int val number: Int
get() = index + 1 get() = index + 1
@Transient lateinit var chapter: ReaderChapter @Transient lateinit var chapter: ReaderChapter
@Transient @Volatile var status: Int = 0 @Transient @Volatile var status: Int = 0
set(value) { set(value) {
field = value field = value
statusSubject?.onNext(value) statusSubject?.onNext(value)
} }
@Transient @Volatile var progress: Int = 0 @Transient @Volatile var progress: Int = 0
@Transient private var statusSubject: Subject<Int, Int>? = null @Transient private var statusSubject: Subject<Int, Int>? = null
override fun update(bytesRead: Long, contentLength: Long, done: Boolean) { override fun update(bytesRead: Long, contentLength: Long, done: Boolean) {
progress = (100 * bytesRead / contentLength).toInt() progress = (100 * bytesRead / contentLength).toInt()
} }
fun setStatusSubject(subject: Subject<Int, Int>?) { fun setStatusSubject(subject: Subject<Int, Int>?) {
this.statusSubject = subject this.statusSubject = subject
} }
companion object { companion object {
const val QUEUE = 0 const val QUEUE = 0
const val LOAD_PAGE = 1 const val LOAD_PAGE = 1
const val DOWNLOAD_IMAGE = 2 const val DOWNLOAD_IMAGE = 2
const val READY = 3 const val READY = 3
const val ERROR = 4 const val ERROR = 4
} }
} }

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
import java.io.Serializable import java.io.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
class SChapterImpl : SChapter { class SChapterImpl : SChapter {

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
import java.io.Serializable import java.io.Serializable

View File

@ -1,4 +1,4 @@
package eu.kanade.tachiyomi.data.source.model package eu.kanade.tachiyomi.source.model
class SMangaImpl : SManga { class SMangaImpl : SManga {

View File

@ -1,15 +1,15 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
interface LoginSource : Source { interface LoginSource : Source {
fun isLogged(): Boolean fun isLogged(): Boolean
fun login(username: String, password: String): Observable<Boolean> fun login(username: String, password: String): Observable<Boolean>
fun isAuthenticationSuccessful(response: Response): Boolean fun isAuthenticationSuccessful(response: Response): Boolean
} }

View File

@ -1,361 +1,361 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.data.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.data.network.newCallWithProgress import eu.kanade.tachiyomi.network.newCallWithProgress
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.net.URI import java.net.URI
import java.net.URISyntaxException import java.net.URISyntaxException
import java.security.MessageDigest import java.security.MessageDigest
/** /**
* A simple implementation for sources from a website. * A simple implementation for sources from a website.
*/ */
abstract class OnlineSource : CatalogueSource { abstract class OnlineSource : CatalogueSource {
/** /**
* Network service. * Network service.
*/ */
val network: NetworkHelper by injectLazy() val network: NetworkHelper by injectLazy()
/** /**
* Preferences helper. * Preferences helper.
*/ */
val preferences: PreferencesHelper by injectLazy() val preferences: PreferencesHelper by injectLazy()
/** /**
* Base url of the website without the trailing slash, like: http://mysite.com * Base url of the website without the trailing slash, like: http://mysite.com
*/ */
abstract val baseUrl: String abstract val baseUrl: String
/** /**
* Version id used to generate the source id. If the site completely changes and urls are * Version id used to generate the source id. If the site completely changes and urls are
* incompatible, you may increase this value and it'll be considered as a new source. * incompatible, you may increase this value and it'll be considered as a new source.
*/ */
open val versionId = 1 open val versionId = 1
/** /**
* Id of the source. By default it uses a generated id using the first 16 characters (64 bits) * Id of the source. By default it uses a generated id using the first 16 characters (64 bits)
* of the MD5 of the string: sourcename/language/versionId * of the MD5 of the string: sourcename/language/versionId
* Note the generated id sets the sign bit to 0. * Note the generated id sets the sign bit to 0.
*/ */
override val id by lazy { override val id by lazy {
val key = "${name.toLowerCase()}/$lang/$versionId" val key = "${name.toLowerCase()}/$lang/$versionId"
val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray()) val bytes = MessageDigest.getInstance("MD5").digest(key.toByteArray())
(0..7).map { bytes[it].toLong() and 0xff shl 8*(7-it) }.reduce(Long::or) and Long.MAX_VALUE (0..7).map { bytes[it].toLong() and 0xff shl 8*(7-it) }.reduce(Long::or) and Long.MAX_VALUE
} }
/** /**
* Headers used for requests. * Headers used for requests.
*/ */
val headers: Headers by lazy { headersBuilder().build() } val headers: Headers by lazy { headersBuilder().build() }
/** /**
* Default network client for doing requests. * Default network client for doing requests.
*/ */
open val client: OkHttpClient open val client: OkHttpClient
get() = network.client get() = network.client
/** /**
* Headers builder for requests. Implementations can override this method for custom headers. * Headers builder for requests. Implementations can override this method for custom headers.
*/ */
open protected fun headersBuilder() = Headers.Builder().apply { open protected fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
} }
/** /**
* Visible name of the source. * Visible name of the source.
*/ */
override fun toString() = "$name (${lang.toUpperCase()})" override fun toString() = "$name (${lang.toUpperCase()})"
/** /**
* Returns an observable containing a page with a list of manga. Normally it's not needed to * Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method. * override this method.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
override fun fetchPopularManga(page: Int): Observable<MangasPage> { override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return client.newCall(popularMangaRequest(page)) return client.newCall(popularMangaRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
popularMangaParse(response) popularMangaParse(response)
} }
} }
/** /**
* Returns the request for the popular manga given the page. * Returns the request for the popular manga given the page.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
abstract protected fun popularMangaRequest(page: Int): Request abstract protected fun popularMangaRequest(page: Int): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun popularMangaParse(response: Response): MangasPage abstract protected fun popularMangaParse(response: Response): MangasPage
/** /**
* Returns an observable containing a page with a list of manga. Normally it's not needed to * Returns an observable containing a page with a list of manga. Normally it's not needed to
* override this method. * override this method.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
* @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 fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters)) return client.newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
searchMangaParse(response) searchMangaParse(response)
} }
} }
/** /**
* Returns the request for the search manga given the page. * Returns the request for the search manga given the page.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
* @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.
*/ */
abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request abstract protected fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun searchMangaParse(response: Response): MangasPage abstract protected fun searchMangaParse(response: Response): MangasPage
/** /**
* Returns an observable containing a page with a list of latest manga updates. * Returns an observable containing a page with a list of latest manga updates.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return client.newCall(latestUpdatesRequest(page)) return client.newCall(latestUpdatesRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
latestUpdatesParse(response) latestUpdatesParse(response)
} }
} }
/** /**
* Returns the request for latest manga given the page. * Returns the request for latest manga given the page.
* *
* @param page the page number to retrieve. * @param page the page number to retrieve.
*/ */
abstract protected fun latestUpdatesRequest(page: Int): Request abstract protected fun latestUpdatesRequest(page: Int): Request
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun latestUpdatesParse(response: Response): MangasPage abstract protected fun latestUpdatesParse(response: Response): MangasPage
/** /**
* Returns an observable with the updated details for a manga. Normally it's not needed to * Returns an observable with the updated details for a manga. Normally it's not needed to
* override this method. * override this method.
* *
* @param manga the manga to be updated. * @param manga the manga to be updated.
*/ */
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return client.newCall(mangaDetailsRequest(manga)) return client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
mangaDetailsParse(response).apply { initialized = true } mangaDetailsParse(response).apply { initialized = true }
} }
} }
/** /**
* Returns the request for the details of a manga. Override only if it's needed to change the * Returns the request for the details of a manga. Override only if it's needed to change the
* url, send different headers or request method like POST. * url, send different headers or request method like POST.
* *
* @param manga the manga to be updated. * @param manga the manga to be updated.
*/ */
open fun mangaDetailsRequest(manga: SManga): Request { open fun mangaDetailsRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers) return GET(baseUrl + manga.url, headers)
} }
/** /**
* Parses the response from the site and returns the details of a manga. * Parses the response from the site and returns the details of a manga.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun mangaDetailsParse(response: Response): SManga abstract protected fun mangaDetailsParse(response: Response): SManga
/** /**
* Returns an observable with the updated chapter list for a manga. Normally it's not needed to * Returns an observable with the updated chapter list for a manga. Normally it's not needed to
* override this method. * override this method.
* *
* @param manga the manga to look for chapters. * @param manga the manga to look for chapters.
*/ */
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return client.newCall(chapterListRequest(manga)) return client.newCall(chapterListRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
chapterListParse(response) chapterListParse(response)
} }
} }
/** /**
* Returns the request for updating the chapter list. Override only if it's needed to override * Returns the request for updating the chapter list. Override only if it's needed to override
* the url, send different headers or request method like POST. * the url, send different headers or request method like POST.
* *
* @param manga the manga to look for chapters. * @param manga the manga to look for chapters.
*/ */
open protected fun chapterListRequest(manga: SManga): Request { open protected fun chapterListRequest(manga: SManga): Request {
return GET(baseUrl + manga.url, headers) return GET(baseUrl + manga.url, headers)
} }
/** /**
* Parses the response from the site and returns a list of chapters. * Parses the response from the site and returns a list of chapters.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun chapterListParse(response: Response): List<SChapter> abstract protected fun chapterListParse(response: Response): List<SChapter>
/** /**
* Returns an observable with the page list for a chapter. * Returns an observable with the page list for a chapter.
* *
* @param chapter the chapter whose page list has to be fetched. * @param chapter the chapter whose page list has to be fetched.
*/ */
override fun fetchPageList(chapter: SChapter): Observable<List<Page>> { override fun fetchPageList(chapter: SChapter): Observable<List<Page>> {
return client.newCall(pageListRequest(chapter)) return client.newCall(pageListRequest(chapter))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
pageListParse(response) pageListParse(response)
} }
} }
/** /**
* Returns the request for getting the page list. Override only if it's needed to override the * Returns the request for getting the page list. Override only if it's needed to override the
* url, send different headers or request method like POST. * url, send different headers or request method like POST.
* *
* @param chapter the chapter whose page list has to be fetched. * @param chapter the chapter whose page list has to be fetched.
*/ */
open protected fun pageListRequest(chapter: SChapter): Request { open protected fun pageListRequest(chapter: SChapter): Request {
return GET(baseUrl + chapter.url, headers) return GET(baseUrl + chapter.url, headers)
} }
/** /**
* Parses the response from the site and returns a list of pages. * Parses the response from the site and returns a list of pages.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun pageListParse(response: Response): List<Page> abstract protected fun pageListParse(response: Response): List<Page>
/** /**
* Returns an observable with the page containing the source url of the image. If there's any * Returns an observable with the page containing the source url of the image. If there's any
* error, it will return null instead of throwing an exception. * error, it will return null instead of throwing an exception.
* *
* @param page the page whose source image has to be fetched. * @param page the page whose source image has to be fetched.
*/ */
open fun fetchImageUrl(page: Page): Observable<String> { open fun fetchImageUrl(page: Page): Observable<String> {
return client.newCall(imageUrlRequest(page)) return client.newCall(imageUrlRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { imageUrlParse(it) } .map { imageUrlParse(it) }
} }
/** /**
* Returns the request for getting the url to the source image. Override only if it's needed to * Returns the request for getting the url to the source image. Override only if it's needed to
* override the url, send different headers or request method like POST. * override the url, send different headers or request method like POST.
* *
* @param page the chapter whose page list has to be fetched * @param page the chapter whose page list has to be fetched
*/ */
open protected fun imageUrlRequest(page: Page): Request { open protected fun imageUrlRequest(page: Page): Request {
return GET(page.url, headers) return GET(page.url, headers)
} }
/** /**
* Parses the response from the site and returns the absolute url to the source image. * Parses the response from the site and returns the absolute url to the source image.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
abstract protected fun imageUrlParse(response: Response): String abstract protected fun imageUrlParse(response: Response): String
/** /**
* Returns an observable with the response of the source image. * Returns an observable with the response of the source image.
* *
* @param page the page whose source image has to be downloaded. * @param page the page whose source image has to be downloaded.
*/ */
fun fetchImage(page: Page): Observable<Response> { fun fetchImage(page: Page): Observable<Response> {
return client.newCallWithProgress(imageRequest(page), page) return client.newCallWithProgress(imageRequest(page), page)
.asObservableSuccess() .asObservableSuccess()
} }
/** /**
* Returns the request for getting the source image. Override only if it's needed to override * Returns the request for getting the source image. Override only if it's needed to override
* the url, send different headers or request method like POST. * the url, send different headers or request method like POST.
* *
* @param page the chapter whose page list has to be fetched * @param page the chapter whose page list has to be fetched
*/ */
open protected fun imageRequest(page: Page): Request { open protected fun imageRequest(page: Page): Request {
return GET(page.imageUrl!!, headers) return GET(page.imageUrl!!, headers)
} }
/** /**
* Assigns the url of the chapter without the scheme and domain. It saves some redundancy from * Assigns the url of the chapter without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change. * database and the urls could still work after a domain change.
* *
* @param url the full url to the chapter. * @param url the full url to the chapter.
*/ */
fun SChapter.setUrlWithoutDomain(url: String) { fun SChapter.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url) this.url = getUrlWithoutDomain(url)
} }
/** /**
* Assigns the url of the manga without the scheme and domain. It saves some redundancy from * Assigns the url of the manga without the scheme and domain. It saves some redundancy from
* database and the urls could still work after a domain change. * database and the urls could still work after a domain change.
* *
* @param url the full url to the manga. * @param url the full url to the manga.
*/ */
fun SManga.setUrlWithoutDomain(url: String) { fun SManga.setUrlWithoutDomain(url: String) {
this.url = getUrlWithoutDomain(url) this.url = getUrlWithoutDomain(url)
} }
/** /**
* Returns the url of the given string without the scheme and domain. * Returns the url of the given string without the scheme and domain.
* *
* @param orig the full url. * @param orig the full url.
*/ */
private fun getUrlWithoutDomain(orig: String): String { private fun getUrlWithoutDomain(orig: String): String {
try { try {
val uri = URI(orig) val uri = URI(orig)
var out = uri.path var out = uri.path
if (uri.query != null) if (uri.query != null)
out += "?" + uri.query out += "?" + uri.query
if (uri.fragment != null) if (uri.fragment != null)
out += "#" + uri.fragment out += "#" + uri.fragment
return out return out
} catch (e: URISyntaxException) { } catch (e: URISyntaxException) {
return orig return orig
} }
} }
/** /**
* Called before inserting a new chapter into database. Use it if you need to override chapter * Called before inserting a new chapter into database. Use it if you need to override chapter
* fields, like the title or the chapter number. Do not change anything to [manga]. * fields, like the title or the chapter number. Do not change anything to [manga].
* *
* @param chapter the chapter to be added. * @param chapter the chapter to be added.
* @param manga the manga of the chapter. * @param manga the manga of the chapter.
*/ */
open fun prepareNewChapter(chapter: SChapter, manga: SManga) { open fun prepareNewChapter(chapter: SChapter, manga: SManga) {
} }
/** /**
* Returns the list of filters for the source. * Returns the list of filters for the source.
*/ */
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
} }

View File

@ -1,98 +1,98 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import rx.Observable import rx.Observable
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
// TODO: this should be handled with a different approach. // TODO: this should be handled with a different approach.
/** /**
* Chapter cache. * Chapter cache.
*/ */
private val chapterCache: ChapterCache by injectLazy() private val chapterCache: ChapterCache by injectLazy()
/** /**
* Returns an observable with the page list for a chapter. It tries to return the page list from * Returns an observable with the page list for a chapter. It tries to return the page list from
* the local cache, otherwise fallbacks to network. * the local cache, otherwise fallbacks to network.
* *
* @param chapter the chapter whose page list has to be fetched. * @param chapter the chapter whose page list has to be fetched.
*/ */
fun OnlineSource.fetchPageListFromCacheThenNet(chapter: Chapter): Observable<List<Page>> { fun OnlineSource.fetchPageListFromCacheThenNet(chapter: Chapter): Observable<List<Page>> {
return chapterCache return chapterCache
.getPageListFromCache(chapter) .getPageListFromCache(chapter)
.onErrorResumeNext { fetchPageList(chapter) } .onErrorResumeNext { fetchPageList(chapter) }
} }
/** /**
* Returns an observable of the page with the downloaded image. * Returns an observable of the page with the downloaded image.
* *
* @param page the page whose source image has to be downloaded. * @param page the page whose source image has to be downloaded.
*/ */
fun OnlineSource.fetchImageFromCacheThenNet(page: Page): Observable<Page> { fun OnlineSource.fetchImageFromCacheThenNet(page: Page): Observable<Page> {
return if (page.imageUrl.isNullOrEmpty()) return if (page.imageUrl.isNullOrEmpty())
getImageUrl(page).flatMap { getCachedImage(it) } getImageUrl(page).flatMap { getCachedImage(it) }
else else
getCachedImage(page) getCachedImage(page)
} }
fun OnlineSource.getImageUrl(page: Page): Observable<Page> { fun OnlineSource.getImageUrl(page: Page): Observable<Page> {
page.status = Page.LOAD_PAGE page.status = Page.LOAD_PAGE
return fetchImageUrl(page) return fetchImageUrl(page)
.doOnError { page.status = Page.ERROR } .doOnError { page.status = Page.ERROR }
.onErrorReturn { null } .onErrorReturn { null }
.doOnNext { page.imageUrl = it } .doOnNext { page.imageUrl = it }
.map { page } .map { page }
} }
/** /**
* Returns an observable of the page that gets the image from the chapter or fallbacks to * Returns an observable of the page that gets the image from the chapter or fallbacks to
* network and copies it to the cache calling [cacheImage]. * network and copies it to the cache calling [cacheImage].
* *
* @param page the page. * @param page the page.
*/ */
fun OnlineSource.getCachedImage(page: Page): Observable<Page> { fun OnlineSource.getCachedImage(page: Page): Observable<Page> {
val imageUrl = page.imageUrl ?: return Observable.just(page) val imageUrl = page.imageUrl ?: return Observable.just(page)
return Observable.just(page) return Observable.just(page)
.flatMap { .flatMap {
if (!chapterCache.isImageInCache(imageUrl)) { if (!chapterCache.isImageInCache(imageUrl)) {
cacheImage(page) cacheImage(page)
} else { } else {
Observable.just(page) Observable.just(page)
} }
} }
.doOnNext { .doOnNext {
page.uri = Uri.fromFile(chapterCache.getImageFile(imageUrl)) page.uri = Uri.fromFile(chapterCache.getImageFile(imageUrl))
page.status = Page.READY page.status = Page.READY
} }
.doOnError { page.status = Page.ERROR } .doOnError { page.status = Page.ERROR }
.onErrorReturn { page } .onErrorReturn { page }
} }
/** /**
* Returns an observable of the page that downloads the image to [ChapterCache]. * Returns an observable of the page that downloads the image to [ChapterCache].
* *
* @param page the page. * @param page the page.
*/ */
private fun OnlineSource.cacheImage(page: Page): Observable<Page> { private fun OnlineSource.cacheImage(page: Page): Observable<Page> {
page.status = Page.DOWNLOAD_IMAGE page.status = Page.DOWNLOAD_IMAGE
return fetchImage(page) return fetchImage(page)
.doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) } .doOnNext { chapterCache.putImageToCache(page.imageUrl!!, it) }
.map { page } .map { page }
} }
fun OnlineSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> { fun OnlineSource.fetchAllImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages) return Observable.from(pages)
.filter { !it.imageUrl.isNullOrEmpty() } .filter { !it.imageUrl.isNullOrEmpty() }
.mergeWith(fetchRemainingImageUrlsFromPageList(pages)) .mergeWith(fetchRemainingImageUrlsFromPageList(pages))
} }
fun OnlineSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> { fun OnlineSource.fetchRemainingImageUrlsFromPageList(pages: List<Page>): Observable<Page> {
return Observable.from(pages) return Observable.from(pages)
.filter { it.imageUrl.isNullOrEmpty() } .filter { it.imageUrl.isNullOrEmpty() }
.concatMap { getImageUrl(it) } .concatMap { getImageUrl(it) }
} }

View File

@ -1,200 +1,200 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
/** /**
* A simple implementation for sources from a website using Jsoup, an HTML parser. * A simple implementation for sources from a website using Jsoup, an HTML parser.
*/ */
abstract class ParsedOnlineSource : OnlineSource() { abstract class ParsedOnlineSource : OnlineSource() {
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(popularMangaSelector()).map { element -> val mangas = document.select(popularMangaSelector()).map { element ->
popularMangaFromElement(element) popularMangaFromElement(element)
} }
val hasNextPage = popularMangaNextPageSelector()?.let { selector -> val hasNextPage = popularMangaNextPageSelector()?.let { selector ->
document.select(selector).first() document.select(selector).first()
} != null } != null
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun popularMangaSelector(): String abstract protected fun popularMangaSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values. * totally fine to fill only those two values.
* *
* @param element an element obtained from [popularMangaSelector]. * @param element an element obtained from [popularMangaSelector].
*/ */
abstract protected fun popularMangaFromElement(element: Element): SManga abstract protected fun popularMangaFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun popularMangaNextPageSelector(): String? abstract protected fun popularMangaNextPageSelector(): String?
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(searchMangaSelector()).map { element -> val mangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element) searchMangaFromElement(element)
} }
val hasNextPage = searchMangaNextPageSelector()?.let { selector -> val hasNextPage = searchMangaNextPageSelector()?.let { selector ->
document.select(selector).first() document.select(selector).first()
} != null } != null
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun searchMangaSelector(): String abstract protected fun searchMangaSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values. * totally fine to fill only those two values.
* *
* @param element an element obtained from [searchMangaSelector]. * @param element an element obtained from [searchMangaSelector].
*/ */
abstract protected fun searchMangaFromElement(element: Element): SManga abstract protected fun searchMangaFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun searchMangaNextPageSelector(): String? abstract protected fun searchMangaNextPageSelector(): String?
/** /**
* Parses the response from the site and returns a [MangasPage] object. * Parses the response from the site and returns a [MangasPage] object.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(latestUpdatesSelector()).map { element -> val mangas = document.select(latestUpdatesSelector()).map { element ->
latestUpdatesFromElement(element) latestUpdatesFromElement(element)
} }
val hasNextPage = latestUpdatesNextPageSelector()?.let { selector -> val hasNextPage = latestUpdatesNextPageSelector()?.let { selector ->
document.select(selector).first() document.select(selector).first()
} != null } != null
return MangasPage(mangas, hasNextPage) return MangasPage(mangas, hasNextPage)
} }
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each manga. * Returns the Jsoup selector that returns a list of [Element] corresponding to each manga.
*/ */
abstract protected fun latestUpdatesSelector(): String abstract protected fun latestUpdatesSelector(): String
/** /**
* Returns a manga from the given [element]. Most sites only show the title and the url, it's * Returns a manga from the given [element]. Most sites only show the title and the url, it's
* totally fine to fill only those two values. * totally fine to fill only those two values.
* *
* @param element an element obtained from [latestUpdatesSelector]. * @param element an element obtained from [latestUpdatesSelector].
*/ */
abstract protected fun latestUpdatesFromElement(element: Element): SManga abstract protected fun latestUpdatesFromElement(element: Element): SManga
/** /**
* Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if * Returns the Jsoup selector that returns the <a> tag linking to the next page, or null if
* there's no next page. * there's no next page.
*/ */
abstract protected fun latestUpdatesNextPageSelector(): String? abstract protected fun latestUpdatesNextPageSelector(): String?
/** /**
* Parses the response from the site and returns the details of a manga. * Parses the response from the site and returns the details of a manga.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
return mangaDetailsParse(response.asJsoup()) return mangaDetailsParse(response.asJsoup())
} }
/** /**
* Returns the details of the manga from the given [document]. * Returns the details of the manga from the given [document].
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun mangaDetailsParse(document: Document): SManga abstract protected fun mangaDetailsParse(document: Document): SManga
/** /**
* Parses the response from the site and returns a list of chapters. * Parses the response from the site and returns a list of chapters.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()
return document.select(chapterListSelector()).map { chapterFromElement(it) } return document.select(chapterListSelector()).map { chapterFromElement(it) }
} }
/** /**
* Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter. * Returns the Jsoup selector that returns a list of [Element] corresponding to each chapter.
*/ */
abstract protected fun chapterListSelector(): String abstract protected fun chapterListSelector(): String
/** /**
* Returns a chapter from the given element. * Returns a chapter from the given element.
* *
* @param element an element obtained from [chapterListSelector]. * @param element an element obtained from [chapterListSelector].
*/ */
abstract protected fun chapterFromElement(element: Element): SChapter abstract protected fun chapterFromElement(element: Element): SChapter
/** /**
* Parses the response from the site and returns the page list. * Parses the response from the site and returns the page list.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
return pageListParse(response.asJsoup()) return pageListParse(response.asJsoup())
} }
/** /**
* Returns a page list from the given document. * Returns a page list from the given document.
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun pageListParse(document: Document): List<Page> abstract protected fun pageListParse(document: Document): List<Page>
/** /**
* Parse the response from the site and returns the absolute url to the source image. * Parse the response from the site and returns the absolute url to the source image.
* *
* @param response the response from the site. * @param response the response from the site.
*/ */
override fun imageUrlParse(response: Response): String { override fun imageUrlParse(response: Response): String {
return imageUrlParse(response.asJsoup()) return imageUrlParse(response.asJsoup())
} }
/** /**
* Returns the absolute url to the source image from the document. * Returns the absolute url to the source image from the document.
* *
* @param document the parsed document. * @param document the parsed document.
*/ */
abstract protected fun imageUrlParse(document: Document): String abstract protected fun imageUrlParse(document: Document): String
} }

View File

@ -1,232 +1,232 @@
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.attrOrText import eu.kanade.tachiyomi.util.attrOrText
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.Jsoup import org.jsoup.Jsoup
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() { class YamlOnlineSource(mappings: Map<*, *>) : OnlineSource() {
val map = YamlSourceNode(mappings) val map = YamlSourceNode(mappings)
override val name: String override val name: String
get() = map.name get() = map.name
override val baseUrl = map.host.let { override val baseUrl = map.host.let {
if (it.endsWith("/")) it.dropLast(1) else it if (it.endsWith("/")) it.dropLast(1) else it
} }
override val lang = map.lang.toLowerCase() override val lang = map.lang.toLowerCase()
override val supportsLatest = map.latestupdates != null override val supportsLatest = map.latestupdates != null
override val client = when (map.client) { override val client = when (map.client) {
"cloudflare" -> network.cloudflareClient "cloudflare" -> network.cloudflareClient
else -> network.client else -> network.client
} }
override val id = map.id.let { override val id = map.id.let {
(it as? Int ?: (lang.toUpperCase().hashCode() + 31 * it.hashCode()) and 0x7fffffff).toLong() (it as? Int ?: (lang.toUpperCase().hashCode() + 31 * it.hashCode()) and 0x7fffffff).toLong()
} }
// Ugly, but needed after the changes // Ugly, but needed after the changes
var popularNextPage: String? = null var popularNextPage: String? = null
var searchNextPage: String? = null var searchNextPage: String? = null
var latestNextPage: String? = null var latestNextPage: String? = null
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val url = if (page == 1) { val url = if (page == 1) {
popularNextPage = null popularNextPage = null
map.popular.url map.popular.url
} else { } else {
popularNextPage!! popularNextPage!!
} }
return when (map.popular.method?.toLowerCase()) { return when (map.popular.method?.toLowerCase()) {
"post" -> POST(url, headers, map.popular.createForm()) "post" -> POST(url, headers, map.popular.createForm())
else -> GET(url, headers) else -> GET(url, headers)
} }
} }
override fun popularMangaParse(response: Response): MangasPage { override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(map.popular.manga_css).map { element -> val mangas = document.select(map.popular.manga_css).map { element ->
SManga.create().apply { SManga.create().apply {
title = element.text() title = element.text()
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
} }
} }
popularNextPage = map.popular.next_url_css?.let { selector -> popularNextPage = map.popular.next_url_css?.let { selector ->
document.select(selector).first()?.absUrl("href") document.select(selector).first()?.absUrl("href")
} }
return MangasPage(mangas, popularNextPage != null) return MangasPage(mangas, popularNextPage != null)
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = if (page == 1) { val url = if (page == 1) {
searchNextPage = null searchNextPage = null
map.search.url.replace("\$query", query) map.search.url.replace("\$query", query)
} else { } else {
searchNextPage!! searchNextPage!!
} }
return when (map.search.method?.toLowerCase()) { return when (map.search.method?.toLowerCase()) {
"post" -> POST(url, headers, map.search.createForm()) "post" -> POST(url, headers, map.search.createForm())
else -> GET(url, headers) else -> GET(url, headers)
} }
} }
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(map.search.manga_css).map { element -> val mangas = document.select(map.search.manga_css).map { element ->
SManga.create().apply { SManga.create().apply {
title = element.text() title = element.text()
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
} }
} }
searchNextPage = map.search.next_url_css?.let { selector -> searchNextPage = map.search.next_url_css?.let { selector ->
document.select(selector).first()?.absUrl("href") document.select(selector).first()?.absUrl("href")
} }
return MangasPage(mangas, searchNextPage != null) return MangasPage(mangas, searchNextPage != null)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val url = if (page == 1) { val url = if (page == 1) {
latestNextPage = null latestNextPage = null
map.latestupdates!!.url map.latestupdates!!.url
} else { } else {
latestNextPage!! latestNextPage!!
} }
return when (map.latestupdates!!.method?.toLowerCase()) { return when (map.latestupdates!!.method?.toLowerCase()) {
"post" -> POST(url, headers, map.latestupdates.createForm()) "post" -> POST(url, headers, map.latestupdates.createForm())
else -> GET(url, headers) else -> GET(url, headers)
} }
} }
override fun latestUpdatesParse(response: Response): MangasPage { override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(map.latestupdates!!.manga_css).map { element -> val mangas = document.select(map.latestupdates!!.manga_css).map { element ->
SManga.create().apply { SManga.create().apply {
title = element.text() title = element.text()
setUrlWithoutDomain(element.attr("href")) setUrlWithoutDomain(element.attr("href"))
} }
} }
popularNextPage = map.latestupdates.next_url_css?.let { selector -> popularNextPage = map.latestupdates.next_url_css?.let { selector ->
document.select(selector).first()?.absUrl("href") document.select(selector).first()?.absUrl("href")
} }
return MangasPage(mangas, popularNextPage != null) return MangasPage(mangas, popularNextPage != null)
} }
override fun mangaDetailsParse(response: Response): SManga { override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup() val document = response.asJsoup()
val manga = SManga.create() val manga = SManga.create()
with(map.manga) { with(map.manga) {
val pool = parts.get(document) val pool = parts.get(document)
manga.author = author?.process(document, pool) manga.author = author?.process(document, pool)
manga.artist = artist?.process(document, pool) manga.artist = artist?.process(document, pool)
manga.description = summary?.process(document, pool) manga.description = summary?.process(document, pool)
manga.thumbnail_url = cover?.process(document, pool) manga.thumbnail_url = cover?.process(document, pool)
manga.genre = genres?.process(document, pool) manga.genre = genres?.process(document, pool)
manga.status = status?.getStatus(document, pool) ?: SManga.UNKNOWN manga.status = status?.getStatus(document, pool) ?: SManga.UNKNOWN
} }
return manga return manga
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup() val document = response.asJsoup()
val chapters = mutableListOf<SChapter>() val chapters = mutableListOf<SChapter>()
with(map.chapters) { with(map.chapters) {
val pool = emptyMap<String, Element>() val pool = emptyMap<String, Element>()
val dateFormat = SimpleDateFormat(date?.format, Locale.ENGLISH) val dateFormat = SimpleDateFormat(date?.format, Locale.ENGLISH)
for (element in document.select(chapter_css)) { for (element in document.select(chapter_css)) {
val chapter = SChapter.create() val chapter = SChapter.create()
element.select(title).first().let { element.select(title).first().let {
chapter.name = it.text() chapter.name = it.text()
chapter.setUrlWithoutDomain(it.attr("href")) chapter.setUrlWithoutDomain(it.attr("href"))
} }
val dateElement = element.select(date?.select).first() val dateElement = element.select(date?.select).first()
chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0
chapters.add(chapter) chapters.add(chapter)
} }
} }
return chapters return chapters
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val body = response.body().string() val body = response.body().string()
val url = response.request().url().toString() val url = response.request().url().toString()
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
// TODO lazy initialization in Kotlin 1.1 // TODO lazy initialization in Kotlin 1.1
val document = Jsoup.parse(body, url) val document = Jsoup.parse(body, url)
with(map.pages) { with(map.pages) {
// Capture a list of values where page urls will be resolved. // Capture a list of values where page urls will be resolved.
val capturedPages = if (pages_regex != null) val capturedPages = if (pages_regex != null)
pages_regex!!.toRegex().findAll(body).map { it.value }.toList() pages_regex!!.toRegex().findAll(body).map { it.value }.toList()
else if (pages_css != null) else if (pages_css != null)
document.select(pages_css).map { it.attrOrText(pages_attr!!) } document.select(pages_css).map { it.attrOrText(pages_attr!!) }
else else
null null
// For each captured value, obtain the url and create a new page. // For each captured value, obtain the url and create a new page.
capturedPages?.forEach { value -> capturedPages?.forEach { value ->
// If the captured value isn't an url, we have to use replaces with the chapter url. // If the captured value isn't an url, we have to use replaces with the chapter url.
val pageUrl = if (replace != null && replacement != null) val pageUrl = if (replace != null && replacement != null)
url.replace(replace!!.toRegex(), replacement!!.replace("\$value", value)) url.replace(replace!!.toRegex(), replacement!!.replace("\$value", value))
else else
value value
pages.add(Page(pages.size, pageUrl)) pages.add(Page(pages.size, pageUrl))
} }
// Capture a list of images. // Capture a list of images.
val capturedImages = if (image_regex != null) val capturedImages = if (image_regex != null)
image_regex!!.toRegex().findAll(body).map { it.groups[1]?.value }.toList() image_regex!!.toRegex().findAll(body).map { it.groups[1]?.value }.toList()
else if (image_css != null) else if (image_css != null)
document.select(image_css).map { it.absUrl(image_attr) } document.select(image_css).map { it.absUrl(image_attr) }
else else
null null
// Assign the image url to each page // Assign the image url to each page
capturedImages?.forEachIndexed { i, url -> capturedImages?.forEachIndexed { i, url ->
val page = pages.getOrElse(i) { Page(i, "").apply { pages.add(this) } } val page = pages.getOrElse(i) { Page(i, "").apply { pages.add(this) } }
page.imageUrl = url page.imageUrl = url
} }
} }
return pages return pages
} }
override fun imageUrlParse(response: Response): String { override fun imageUrlParse(response: Response): String {
val body = response.body().string() val body = response.body().string()
val url = response.request().url().toString() val url = response.request().url().toString()
with(map.pages) { with(map.pages) {
return if (image_regex != null) return if (image_regex != null)
image_regex!!.toRegex().find(body)!!.groups[1]!!.value image_regex!!.toRegex().find(body)!!.groups[1]!!.value
else if (image_css != null) else if (image_css != null)
Jsoup.parse(body, url).select(image_css).first().absUrl(image_attr) Jsoup.parse(body, url).select(image_css).first().absUrl(image_attr)
else else
throw Exception("image_regex and image_css are null") throw Exception("image_regex and image_css are null")
} }
} }
} }

View File

@ -1,234 +1,234 @@
@file:Suppress("UNCHECKED_CAST") @file:Suppress("UNCHECKED_CAST")
package eu.kanade.tachiyomi.data.source.online package eu.kanade.tachiyomi.source.online
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.RequestBody import okhttp3.RequestBody
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
private fun toMap(map: Any?) = map as? Map<String, Any?> private fun toMap(map: Any?) = map as? Map<String, Any?>
class YamlSourceNode(uncheckedMap: Map<*, *>) { class YamlSourceNode(uncheckedMap: Map<*, *>) {
val map = toMap(uncheckedMap)!! val map = toMap(uncheckedMap)!!
val id: Any by map val id: Any by map
val name: String by map val name: String by map
val host: String by map val host: String by map
val lang: String by map val lang: String by map
val client: String? val client: String?
get() = map["client"] as? String get() = map["client"] as? String
val popular = PopularNode(toMap(map["popular"])!!) val popular = PopularNode(toMap(map["popular"])!!)
val latestupdates = toMap(map["latest_updates"])?.let { LatestUpdatesNode(it) } val latestupdates = toMap(map["latest_updates"])?.let { LatestUpdatesNode(it) }
val search = SearchNode(toMap(map["search"])!!) val search = SearchNode(toMap(map["search"])!!)
val manga = MangaNode(toMap(map["manga"])!!) val manga = MangaNode(toMap(map["manga"])!!)
val chapters = ChaptersNode(toMap(map["chapters"])!!) val chapters = ChaptersNode(toMap(map["chapters"])!!)
val pages = PagesNode(toMap(map["pages"])!!) val pages = PagesNode(toMap(map["pages"])!!)
} }
interface RequestableNode { interface RequestableNode {
val map: Map<String, Any?> val map: Map<String, Any?>
val url: String val url: String
get() = map["url"] as String get() = map["url"] as String
val method: String? val method: String?
get() = map["method"] as? String get() = map["method"] as? String
val payload: Map<String, String>? val payload: Map<String, String>?
get() = map["payload"] as? Map<String, String> get() = map["payload"] as? Map<String, String>
fun createForm(): RequestBody { fun createForm(): RequestBody {
return FormBody.Builder().apply { return FormBody.Builder().apply {
payload?.let { payload?.let {
for ((key, value) in it) { for ((key, value) in it) {
add(key, value) add(key, value)
} }
} }
}.build() }.build()
} }
} }
class PopularNode(override val map: Map<String, Any?>): RequestableNode { class PopularNode(override val map: Map<String, Any?>): RequestableNode {
val manga_css: String by map val manga_css: String by map
val next_url_css: String? val next_url_css: String?
get() = map["next_url_css"] as? String get() = map["next_url_css"] as? String
} }
class LatestUpdatesNode(override val map: Map<String, Any?>): RequestableNode { class LatestUpdatesNode(override val map: Map<String, Any?>): RequestableNode {
val manga_css: String by map val manga_css: String by map
val next_url_css: String? val next_url_css: String?
get() = map["next_url_css"] as? String get() = map["next_url_css"] as? String
} }
class SearchNode(override val map: Map<String, Any?>): RequestableNode { class SearchNode(override val map: Map<String, Any?>): RequestableNode {
val manga_css: String by map val manga_css: String by map
val next_url_css: String? val next_url_css: String?
get() = map["next_url_css"] as? String get() = map["next_url_css"] as? String
} }
class MangaNode(private val map: Map<String, Any?>) { class MangaNode(private val map: Map<String, Any?>) {
val parts = CacheNode(toMap(map["parts"]) ?: emptyMap()) val parts = CacheNode(toMap(map["parts"]) ?: emptyMap())
val artist = toMap(map["artist"])?.let { SelectableNode(it) } val artist = toMap(map["artist"])?.let { SelectableNode(it) }
val author = toMap(map["author"])?.let { SelectableNode(it) } val author = toMap(map["author"])?.let { SelectableNode(it) }
val summary = toMap(map["summary"])?.let { SelectableNode(it) } val summary = toMap(map["summary"])?.let { SelectableNode(it) }
val status = toMap(map["status"])?.let { StatusNode(it) } val status = toMap(map["status"])?.let { StatusNode(it) }
val genres = toMap(map["genres"])?.let { SelectableNode(it) } val genres = toMap(map["genres"])?.let { SelectableNode(it) }
val cover = toMap(map["cover"])?.let { CoverNode(it) } val cover = toMap(map["cover"])?.let { CoverNode(it) }
} }
class ChaptersNode(private val map: Map<String, Any?>) { class ChaptersNode(private val map: Map<String, Any?>) {
val chapter_css: String by map val chapter_css: String by map
val title: String by map val title: String by map
val date = toMap(toMap(map["date"]))?.let { DateNode(it) } val date = toMap(toMap(map["date"]))?.let { DateNode(it) }
} }
class CacheNode(private val map: Map<String, Any?>) { class CacheNode(private val map: Map<String, Any?>) {
fun get(document: Document) = map.mapValues { document.select(it.value as String).first() } fun get(document: Document) = map.mapValues { document.select(it.value as String).first() }
} }
open class SelectableNode(private val map: Map<String, Any?>) { open class SelectableNode(private val map: Map<String, Any?>) {
val select: String by map val select: String by map
val from: String? val from: String?
get() = map["from"] as? String get() = map["from"] as? String
open val attr: String? open val attr: String?
get() = map["attr"] as? String get() = map["attr"] as? String
val capture: String? val capture: String?
get() = map["capture"] as? String get() = map["capture"] as? String
fun process(document: Element, cache: Map<String, Element>): String { fun process(document: Element, cache: Map<String, Element>): String {
val parent = from?.let { cache[it] } ?: document val parent = from?.let { cache[it] } ?: document
val node = parent.select(select).first() val node = parent.select(select).first()
var text = attr?.let { node.attr(it) } ?: node.text() var text = attr?.let { node.attr(it) } ?: node.text()
capture?.let { capture?.let {
text = Regex(it).find(text)?.groupValues?.get(1) ?: text text = Regex(it).find(text)?.groupValues?.get(1) ?: text
} }
return text return text
} }
} }
class StatusNode(private val map: Map<String, Any?>) : SelectableNode(map) { class StatusNode(private val map: Map<String, Any?>) : SelectableNode(map) {
val complete: String? val complete: String?
get() = map["complete"] as? String get() = map["complete"] as? String
val ongoing: String? val ongoing: String?
get() = map["ongoing"] as? String get() = map["ongoing"] as? String
val licensed: String? val licensed: String?
get() = map["licensed"] as? String get() = map["licensed"] as? String
fun getStatus(document: Element, cache: Map<String, Element>): Int { fun getStatus(document: Element, cache: Map<String, Element>): Int {
val text = process(document, cache) val text = process(document, cache)
complete?.let { complete?.let {
if (text.contains(it)) return SManga.COMPLETED if (text.contains(it)) return SManga.COMPLETED
} }
ongoing?.let { ongoing?.let {
if (text.contains(it)) return SManga.ONGOING if (text.contains(it)) return SManga.ONGOING
} }
licensed?.let { licensed?.let {
if (text.contains(it)) return SManga.LICENSED if (text.contains(it)) return SManga.LICENSED
} }
return SManga.UNKNOWN return SManga.UNKNOWN
} }
} }
class CoverNode(private val map: Map<String, Any?>) : SelectableNode(map) { class CoverNode(private val map: Map<String, Any?>) : SelectableNode(map) {
override val attr: String? override val attr: String?
get() = map["attr"] as? String ?: "src" get() = map["attr"] as? String ?: "src"
} }
class DateNode(private val map: Map<String, Any?>) : SelectableNode(map) { class DateNode(private val map: Map<String, Any?>) : SelectableNode(map) {
val format: String by map val format: String by map
fun getDate(document: Element, cache: Map<String, Element>, formatter: SimpleDateFormat): Date { fun getDate(document: Element, cache: Map<String, Element>, formatter: SimpleDateFormat): Date {
val text = process(document, cache) val text = process(document, cache)
try { try {
return formatter.parse(text) return formatter.parse(text)
} catch (exception: ParseException) {} } catch (exception: ParseException) {}
for (i in 0..7) { for (i in 0..7) {
(map["day$i"] as? List<String>)?.let { (map["day$i"] as? List<String>)?.let {
it.find { it.toRegex().containsMatchIn(text) }?.let { it.find { it.toRegex().containsMatchIn(text) }?.let {
return Calendar.getInstance().apply { add(Calendar.DATE, -i) }.time return Calendar.getInstance().apply { add(Calendar.DATE, -i) }.time
} }
} }
} }
return Date(0) return Date(0)
} }
} }
class PagesNode(private val map: Map<String, Any?>) { class PagesNode(private val map: Map<String, Any?>) {
val pages_regex: String? val pages_regex: String?
get() = map["pages_regex"] as? String get() = map["pages_regex"] as? String
val pages_css: String? val pages_css: String?
get() = map["pages_css"] as? String get() = map["pages_css"] as? String
val pages_attr: String? val pages_attr: String?
get() = map["pages_attr"] as? String ?: "value" get() = map["pages_attr"] as? String ?: "value"
val replace: String? val replace: String?
get() = map["url_replace"] as? String get() = map["url_replace"] as? String
val replacement: String? val replacement: String?
get() = map["url_replacement"] as? String get() = map["url_replacement"] as? String
val image_regex: String? val image_regex: String?
get() = map["image_regex"] as? String get() = map["image_regex"] as? String
val image_css: String? val image_css: String?
get() = map["image_css"] as? String get() = map["image_css"] as? String
val image_attr: String val image_attr: String
get() = map["image_attr"] as? String ?: "src" get() = map["image_attr"] as? String ?: "src"
} }

View File

@ -1,366 +1,366 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import android.text.Html import android.text.Html
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.network.asObservable import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import eu.kanade.tachiyomi.util.selectText import eu.kanade.tachiyomi.util.selectText
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.net.URI import java.net.URI
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
class Batoto : ParsedOnlineSource(), LoginSource { class Batoto : ParsedOnlineSource(), LoginSource {
override val id: Long = 1 override val id: Long = 1
override val name = "Batoto" override val name = "Batoto"
override val baseUrl = "http://bato.to" override val baseUrl = "http://bato.to"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*") private val datePattern = Pattern.compile("(\\d+|A|An)\\s+(.*?)s? ago.*")
private val dateFields = HashMap<String, Int>().apply { private val dateFields = HashMap<String, Int>().apply {
put("second", Calendar.SECOND) put("second", Calendar.SECOND)
put("minute", Calendar.MINUTE) put("minute", Calendar.MINUTE)
put("hour", Calendar.HOUR) put("hour", Calendar.HOUR)
put("day", Calendar.DATE) put("day", Calendar.DATE)
put("week", Calendar.WEEK_OF_YEAR) put("week", Calendar.WEEK_OF_YEAR)
put("month", Calendar.MONTH) put("month", Calendar.MONTH)
put("year", Calendar.YEAR) put("year", Calendar.YEAR)
} }
private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE) private val staffNotice = Pattern.compile("=+Batoto Staff Notice=+([^=]+)==+", Pattern.CASE_INSENSITIVE)
override fun headersBuilder() = super.headersBuilder() override fun headersBuilder() = super.headersBuilder()
.add("Cookie", "lang_option=English") .add("Cookie", "lang_option=English")
private val pageHeaders = super.headersBuilder() private val pageHeaders = super.headersBuilder()
.add("Referer", "http://bato.to/reader") .add("Referer", "http://bato.to/reader")
.build() .build()
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/search_ajax?order_cond=views&order=desc&p=$page", headers) return GET("$baseUrl/search_ajax?order_cond=views&order=desc&p=$page", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/search_ajax?order_cond=update&order=desc&p=$page", headers) return GET("$baseUrl/search_ajax?order_cond=update&order=desc&p=$page", headers)
} }
override fun popularMangaSelector() = "tr:has(a)" override fun popularMangaSelector() = "tr:has(a)"
override fun latestUpdatesSelector() = "tr:has(a)" override fun latestUpdatesSelector() = "tr:has(a)"
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a[href^=http://bato.to]").first().let { element.select("a[href^=http://bato.to]").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text().trim() manga.title = it.text().trim()
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "#show_more_row" override fun popularMangaNextPageSelector() = "#show_more_row"
override fun latestUpdatesNextPageSelector() = "#show_more_row" override fun latestUpdatesNextPageSelector() = "#show_more_row"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder() val url = HttpUrl.parse("$baseUrl/search_ajax").newBuilder()
if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c") if (!query.isEmpty()) url.addQueryParameter("name", query).addQueryParameter("name_cond", "c")
var genres = "" var genres = ""
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is Status -> if (!filter.isIgnored()) { is Status -> if (!filter.isIgnored()) {
url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c") url.addQueryParameter("completed", if (filter.isExcluded()) "i" else "c")
} }
is GenreList -> { is GenreList -> {
filter.state.forEach { filter -> filter.state.forEach { filter ->
when (filter) { when (filter) {
is Genre -> if (!filter.isIgnored()) { is Genre -> if (!filter.isIgnored()) {
genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id genres += (if (filter.isExcluded()) ";e" else ";i") + filter.id
} }
is SelectField -> { is SelectField -> {
val sel = filter.values[filter.state].value val sel = filter.values[filter.state].value
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
} }
} }
} }
} }
is TextField -> { is TextField -> {
if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
} }
is SelectField -> { is SelectField -> {
val sel = filter.values[filter.state].value val sel = filter.values[filter.state].value
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
} }
is Flag -> { is Flag -> {
val sel = if (filter.state) filter.valTrue else filter.valFalse val sel = if (filter.state) filter.valTrue else filter.valFalse
if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel) if (!sel.isEmpty()) url.addQueryParameter(filter.key, sel)
} }
is OrderBy -> { is OrderBy -> {
url.addQueryParameter("order_cond", arrayOf("title", "author", "artist", "rating", "views", "update")[filter.state!!.index]) url.addQueryParameter("order_cond", arrayOf("title", "author", "artist", "rating", "views", "update")[filter.state!!.index])
url.addQueryParameter("order", if (filter.state?.ascending == true) "asc" else "desc") url.addQueryParameter("order", if (filter.state?.ascending == true) "asc" else "desc")
} }
} }
} }
if (!genres.isEmpty()) url.addQueryParameter("genres", genres) if (!genres.isEmpty()) url.addQueryParameter("genres", genres)
url.addQueryParameter("p", page.toString()) url.addQueryParameter("p", page.toString())
return GET(url.toString(), headers) return GET(url.toString(), headers)
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsRequest(manga: SManga): Request { override fun mangaDetailsRequest(manga: SManga): Request {
val mangaId = manga.url.substringAfterLast("r") val mangaId = manga.url.substringAfterLast("r")
return GET("$baseUrl/comic_pop?id=$mangaId", headers) return GET("$baseUrl/comic_pop?id=$mangaId", headers)
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val tbody = document.select("tbody").first() val tbody = document.select("tbody").first()
val artistElement = tbody.select("tr:contains(Author/Artist:)").first() val artistElement = tbody.select("tr:contains(Author/Artist:)").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = artistElement.selectText("td:eq(1)") manga.author = artistElement.selectText("td:eq(1)")
manga.artist = artistElement.selectText("td:eq(2)") ?: manga.author manga.artist = artistElement.selectText("td:eq(2)") ?: manga.author
manga.description = tbody.selectText("tr:contains(Description:) > td:eq(1)") manga.description = tbody.selectText("tr:contains(Description:) > td:eq(1)")
manga.thumbnail_url = document.select("img[src^=http://img.bato.to/forums/uploads/]").first()?.attr("src") manga.thumbnail_url = document.select("img[src^=http://img.bato.to/forums/uploads/]").first()?.attr("src")
manga.status = parseStatus(document.selectText("tr:contains(Status:) > td:eq(1)")) manga.status = parseStatus(document.selectText("tr:contains(Status:) > td:eq(1)"))
manga.genre = tbody.select("tr:contains(Genres:) img").map { it.attr("alt") }.joinToString(", ") manga.genre = tbody.select("tr:contains(Genres:) img").map { it.attr("alt") }.joinToString(", ")
return manga return manga
} }
private fun parseStatus(status: String?) = when (status) { private fun parseStatus(status: String?) = when (status) {
"Ongoing" -> SManga.ONGOING "Ongoing" -> SManga.ONGOING
"Complete" -> SManga.COMPLETED "Complete" -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val body = response.body().string() val body = response.body().string()
val matcher = staffNotice.matcher(body) val matcher = staffNotice.matcher(body)
if (matcher.find()) { if (matcher.find()) {
@Suppress("DEPRECATION") @Suppress("DEPRECATION")
val notice = Html.fromHtml(matcher.group(1)).toString().trim() val notice = Html.fromHtml(matcher.group(1)).toString().trim()
throw Exception(notice) throw Exception(notice)
} }
val document = response.asJsoup(body) val document = response.asJsoup(body)
return document.select(chapterListSelector()).map { chapterFromElement(it) } return document.select(chapterListSelector()).map { chapterFromElement(it) }
} }
override fun chapterListSelector() = "tr.row.lang_English.chapter_row" override fun chapterListSelector() = "tr.row.lang_English.chapter_row"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a[href^=http://bato.to/reader").first() val urlElement = element.select("a[href^=http://bato.to/reader").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("td").getOrNull(4)?.let { chapter.date_upload = element.select("td").getOrNull(4)?.let {
parseDateFromElement(it) parseDateFromElement(it)
} ?: 0 } ?: 0
return chapter return chapter
} }
private fun parseDateFromElement(dateElement: Element): Long { private fun parseDateFromElement(dateElement: Element): Long {
val dateAsString = dateElement.text() val dateAsString = dateElement.text()
var date: Date var date: Date
try { try {
date = SimpleDateFormat("dd MMMMM yyyy - hh:mm a", Locale.ENGLISH).parse(dateAsString) date = SimpleDateFormat("dd MMMMM yyyy - hh:mm a", Locale.ENGLISH).parse(dateAsString)
} catch (e: ParseException) { } catch (e: ParseException) {
val m = datePattern.matcher(dateAsString) val m = datePattern.matcher(dateAsString)
if (m.matches()) { if (m.matches()) {
val number = m.group(1) val number = m.group(1)
val amount = if (number.contains("A")) 1 else Integer.parseInt(m.group(1)) val amount = if (number.contains("A")) 1 else Integer.parseInt(m.group(1))
val unit = m.group(2) val unit = m.group(2)
date = Calendar.getInstance().apply { date = Calendar.getInstance().apply {
add(dateFields[unit]!!, -amount) add(dateFields[unit]!!, -amount)
}.time }.time
} else { } else {
return 0 return 0
} }
} }
return date.time return date.time
} }
override fun pageListRequest(chapter: SChapter): Request { override fun pageListRequest(chapter: SChapter): Request {
val id = chapter.url.substringAfterLast("#") val id = chapter.url.substringAfterLast("#")
return GET("$baseUrl/areader?id=$id&p=1", pageHeaders) return GET("$baseUrl/areader?id=$id&p=1", pageHeaders)
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
val selectElement = document.select("#page_select").first() val selectElement = document.select("#page_select").first()
if (selectElement != null) { if (selectElement != null) {
for ((i, element) in selectElement.select("option").withIndex()) { for ((i, element) in selectElement.select("option").withIndex()) {
pages.add(Page(i, element.attr("value"))) pages.add(Page(i, element.attr("value")))
} }
pages.getOrNull(0)?.imageUrl = imageUrlParse(document) pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
} else { } else {
// For webtoons in one page // For webtoons in one page
for ((i, element) in document.select("div > img").withIndex()) { for ((i, element) in document.select("div > img").withIndex()) {
pages.add(Page(i, "", element.attr("src"))) pages.add(Page(i, "", element.attr("src")))
} }
} }
return pages return pages
} }
override fun imageUrlRequest(page: Page): Request { override fun imageUrlRequest(page: Page): Request {
val pageUrl = page.url val pageUrl = page.url
val start = pageUrl.indexOf("#") + 1 val start = pageUrl.indexOf("#") + 1
val end = pageUrl.indexOf("_", start) val end = pageUrl.indexOf("_", start)
val id = pageUrl.substring(start, end) val id = pageUrl.substring(start, end)
return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end + 1)}", pageHeaders) return GET("$baseUrl/areader?id=$id&p=${pageUrl.substring(end + 1)}", pageHeaders)
} }
override fun imageUrlParse(document: Document): String { override fun imageUrlParse(document: Document): String {
return document.select("#comic_page").first().attr("src") return document.select("#comic_page").first().attr("src")
} }
override fun login(username: String, password: String) = override fun login(username: String, password: String) =
client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global&section=login", headers)) client.newCall(GET("$baseUrl/forums/index.php?app=core&module=global&section=login", headers))
.asObservable() .asObservable()
.flatMap { doLogin(it, username, password) } .flatMap { doLogin(it, username, password) }
.map { isAuthenticationSuccessful(it) } .map { isAuthenticationSuccessful(it) }
private fun doLogin(response: Response, username: String, password: String): Observable<Response> { private fun doLogin(response: Response, username: String, password: String): Observable<Response> {
val doc = response.asJsoup() val doc = response.asJsoup()
val form = doc.select("#login").first() val form = doc.select("#login").first()
val url = form.attr("action") val url = form.attr("action")
val authKey = form.select("input[name=auth_key]").first() val authKey = form.select("input[name=auth_key]").first()
val payload = FormBody.Builder().apply { val payload = FormBody.Builder().apply {
add(authKey.attr("name"), authKey.attr("value")) add(authKey.attr("name"), authKey.attr("value"))
add("ips_username", username) add("ips_username", username)
add("ips_password", password) add("ips_password", password)
add("invisible", "1") add("invisible", "1")
add("rememberMe", "1") add("rememberMe", "1")
}.build() }.build()
return client.newCall(POST(url, headers, payload)).asObservable() return client.newCall(POST(url, headers, payload)).asObservable()
} }
override fun isAuthenticationSuccessful(response: Response) = override fun isAuthenticationSuccessful(response: Response) =
response.priorResponse() != null && response.priorResponse().code() == 302 response.priorResponse() != null && response.priorResponse().code() == 302
override fun isLogged(): Boolean { override fun isLogged(): Boolean {
return network.cookies.get(URI(baseUrl)).any { it.name() == "pass_hash" } return network.cookies.get(URI(baseUrl)).any { it.name() == "pass_hash" }
} }
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
if (!isLogged()) { if (!isLogged()) {
val username = preferences.sourceUsername(this) val username = preferences.sourceUsername(this)
val password = preferences.sourcePassword(this) val password = preferences.sourcePassword(this)
if (username.isNullOrEmpty() || password.isNullOrEmpty()) { if (username.isNullOrEmpty() || password.isNullOrEmpty()) {
return Observable.error(Exception("User not logged")) return Observable.error(Exception("User not logged"))
} else { } else {
return login(username, password).flatMap { super.fetchChapterList(manga) } return login(username, password).flatMap { super.fetchChapterList(manga) }
} }
} else { } else {
return super.fetchChapterList(manga) return super.fetchChapterList(manga)
} }
} }
private data class ListValue(val name: String, val value: String) { private data class ListValue(val name: String, val value: String) {
override fun toString(): String = name override fun toString(): String = name
} }
private class Status : Filter.TriState("Completed") private class Status : Filter.TriState("Completed")
private class Genre(name: String, val id: Int) : Filter.TriState(name) private class Genre(name: String, val id: Int) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class SelectField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.Select<ListValue>(name, values, state) private class SelectField(name: String, val key: String, values: Array<ListValue>, state: Int = 0) : Filter.Select<ListValue>(name, values, state)
private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name) private class Flag(name: String, val key: String, val valTrue: String, val valFalse: String) : Filter.CheckBox(name)
private class GenreList(genres: List<Filter<*>>) : Filter.Group<Filter<*>>("Genres", genres) private class GenreList(genres: List<Filter<*>>) : Filter.Group<Filter<*>>("Genres", genres)
private class OrderBy : Filter.Sort("Order by", private class OrderBy : Filter.Sort("Order by",
arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"), arrayOf("Title", "Author", "Artist", "Rating", "Views", "Last Update"),
Filter.Sort.Selection(4, false)) Filter.Sort.Selection(4, false))
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "artist_name"), TextField("Author", "artist_name"),
SelectField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))), SelectField("Type", "type", arrayOf(ListValue("Any", ""), ListValue("Manga (Jp)", "jp"), ListValue("Manhwa (Kr)", "kr"), ListValue("Manhua (Cn)", "cn"), ListValue("Artbook", "ar"), ListValue("Other", "ot"))),
Status(), Status(),
Flag("Exclude mature", "mature", "m", ""), Flag("Exclude mature", "mature", "m", ""),
OrderBy(), OrderBy(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => { // [...document.querySelectorAll("#advanced_options div.genre_buttons")].map((el,i) => {
// const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})` // const onClick=el.getAttribute('onclick');const id=onClick.substr(14,onClick.length-16);return `Genre("${el.textContent.trim()}", ${id})`
// }).join(',\n') // }).join(',\n')
// on https://bato.to/search // on https://bato.to/search
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
SelectField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))), SelectField("Inclusion mode", "genre_cond", arrayOf(ListValue("And (all selected genres)", "and"), ListValue("Or (any selected genres) ", "or"))),
Genre("4-Koma", 40), Genre("4-Koma", 40),
Genre("Action", 1), Genre("Action", 1),
Genre("Adventure", 2), Genre("Adventure", 2),
Genre("Award Winning", 39), Genre("Award Winning", 39),
Genre("Comedy", 3), Genre("Comedy", 3),
Genre("Cooking", 41), Genre("Cooking", 41),
Genre("Doujinshi", 9), Genre("Doujinshi", 9),
Genre("Drama", 10), Genre("Drama", 10),
Genre("Ecchi", 12), Genre("Ecchi", 12),
Genre("Fantasy", 13), Genre("Fantasy", 13),
Genre("Gender Bender", 15), Genre("Gender Bender", 15),
Genre("Harem", 17), Genre("Harem", 17),
Genre("Historical", 20), Genre("Historical", 20),
Genre("Horror", 22), Genre("Horror", 22),
Genre("Josei", 34), Genre("Josei", 34),
Genre("Martial Arts", 27), Genre("Martial Arts", 27),
Genre("Mecha", 30), Genre("Mecha", 30),
Genre("Medical", 42), Genre("Medical", 42),
Genre("Music", 37), Genre("Music", 37),
Genre("Mystery", 4), Genre("Mystery", 4),
Genre("Oneshot", 38), Genre("Oneshot", 38),
Genre("Psychological", 5), Genre("Psychological", 5),
Genre("Romance", 6), Genre("Romance", 6),
Genre("School Life", 7), Genre("School Life", 7),
Genre("Sci-fi", 8), Genre("Sci-fi", 8),
Genre("Seinen", 32), Genre("Seinen", 32),
Genre("Shoujo", 35), Genre("Shoujo", 35),
Genre("Shoujo Ai", 16), Genre("Shoujo Ai", 16),
Genre("Shounen", 33), Genre("Shounen", 33),
Genre("Shounen Ai", 19), Genre("Shounen Ai", 19),
Genre("Slice of Life", 21), Genre("Slice of Life", 21),
Genre("Smut", 23), Genre("Smut", 23),
Genre("Sports", 25), Genre("Sports", 25),
Genre("Supernatural", 26), Genre("Supernatural", 26),
Genre("Tragedy", 28), Genre("Tragedy", 28),
Genre("Webtoon", 36), Genre("Webtoon", 36),
Genre("Yaoi", 29), Genre("Yaoi", 29),
Genre("Yuri", 31), Genre("Yuri", 31),
Genre("[no chapters]", 44) Genre("[no chapters]", 44)
) )
} }

View File

@ -1,197 +1,197 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.regex.Pattern import java.util.regex.Pattern
class Kissmanga : ParsedOnlineSource() { class Kissmanga : ParsedOnlineSource() {
override val id: Long = 4 override val id: Long = 4
override val name = "Kissmanga" override val name = "Kissmanga"
override val baseUrl = "http://kissmanga.com" override val baseUrl = "http://kissmanga.com"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient override val client: OkHttpClient = network.cloudflareClient
override fun popularMangaSelector() = "table.listing tr:gt(1)" override fun popularMangaSelector() = "table.listing tr:gt(1)"
override fun latestUpdatesSelector() = "table.listing tr:gt(1)" override fun latestUpdatesSelector() = "table.listing tr:gt(1)"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/MangaList/MostPopular?page=$page", headers) return GET("$baseUrl/MangaList/MostPopular?page=$page", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers) return GET("http://kissmanga.com/MangaList/LatestUpdate?page=$page", headers)
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("td a:eq(0)").first().let { element.select("td a:eq(0)").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "li > a:contains( Next)" override fun popularMangaNextPageSelector() = "li > a:contains( Next)"
override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)" override fun latestUpdatesNextPageSelector(): String = "ul.pager > li > a:contains(Next)"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val form = FormBody.Builder().apply { val form = FormBody.Builder().apply {
add("mangaName", query) add("mangaName", query)
for (filter in if (filters.isEmpty()) getFilterList() else filters) { for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when (filter) { when (filter) {
is Author -> add("authorArtist", filter.state) is Author -> add("authorArtist", filter.state)
is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state]) is Status -> add("status", arrayOf("", "Completed", "Ongoing")[filter.state])
is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) } is GenreList -> filter.state.forEach { genre -> add("genres", genre.state.toString()) }
} }
} }
} }
return POST("$baseUrl/AdvanceSearch", headers, form.build()) return POST("$baseUrl/AdvanceSearch", headers, form.build())
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun searchMangaNextPageSelector() = null override fun searchMangaNextPageSelector() = null
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.barContent").first() val infoElement = document.select("div.barContent").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text() manga.author = infoElement.select("p:has(span:contains(Author:)) > a").first()?.text()
manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text() manga.genre = infoElement.select("p:has(span:contains(Genres:)) > *:gt(0)").text()
manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text() manga.description = infoElement.select("p:has(span:contains(Summary:)) ~ p").text()
manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) } manga.status = infoElement.select("p:has(span:contains(Status:))").first()?.text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src") manga.thumbnail_url = document.select(".rightBox:eq(0) img").first()?.attr("src")
return manga return manga
} }
fun parseStatus(status: String) = when { fun parseStatus(status: String) = when {
status.contains("Ongoing") -> SManga.ONGOING status.contains("Ongoing") -> SManga.ONGOING
status.contains("Completed") -> SManga.COMPLETED status.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = "table.listing tr:gt(1)" override fun chapterListSelector() = "table.listing tr:gt(1)"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("MM/dd/yyyy").parse(it).time SimpleDateFormat("MM/dd/yyyy").parse(it).time
} ?: 0 } ?: 0
return chapter return chapter
} }
override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers) override fun pageListRequest(chapter: SChapter) = POST(baseUrl + chapter.url, headers)
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
//language=RegExp //language=RegExp
val p = Pattern.compile("""lstImages.push\("(.+?)"""") val p = Pattern.compile("""lstImages.push\("(.+?)"""")
val m = p.matcher(response.body().string()) val m = p.matcher(response.body().string())
var i = 0 var i = 0
while (m.find()) { while (m.find()) {
pages.add(Page(i++, "", m.group(1))) pages.add(Page(i++, "", m.group(1)))
} }
return pages return pages
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
throw Exception("Not used") throw Exception("Not used")
} }
override fun imageUrlRequest(page: Page) = GET(page.url) override fun imageUrlRequest(page: Page) = GET(page.url)
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
private class Status : Filter.TriState("Completed") private class Status : Filter.TriState("Completed")
private class Author : Filter.Text("Author") private class Author : Filter.Text("Author")
private class Genre(name: String) : Filter.TriState(name) private class Genre(name: String) : Filter.TriState(name)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Author(), Author(),
Status(), Status(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n') // $("select[name=\"genres\"]").map((i,el) => `Genre("${$(el).next().text().trim()}", ${i})`).get().join(',\n')
// on http://kissmanga.com/AdvanceSearch // on http://kissmanga.com/AdvanceSearch
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("4-Koma"), Genre("4-Koma"),
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),
Genre("Adventure"), Genre("Adventure"),
Genre("Comedy"), Genre("Comedy"),
Genre("Comic"), Genre("Comic"),
Genre("Cooking"), Genre("Cooking"),
Genre("Doujinshi"), Genre("Doujinshi"),
Genre("Drama"), Genre("Drama"),
Genre("Ecchi"), Genre("Ecchi"),
Genre("Fantasy"), Genre("Fantasy"),
Genre("Gender Bender"), Genre("Gender Bender"),
Genre("Harem"), Genre("Harem"),
Genre("Historical"), Genre("Historical"),
Genre("Horror"), Genre("Horror"),
Genre("Josei"), Genre("Josei"),
Genre("Lolicon"), Genre("Lolicon"),
Genre("Manga"), Genre("Manga"),
Genre("Manhua"), Genre("Manhua"),
Genre("Manhwa"), Genre("Manhwa"),
Genre("Martial Arts"), Genre("Martial Arts"),
Genre("Mature"), Genre("Mature"),
Genre("Mecha"), Genre("Mecha"),
Genre("Medical"), Genre("Medical"),
Genre("Music"), Genre("Music"),
Genre("Mystery"), Genre("Mystery"),
Genre("One shot"), Genre("One shot"),
Genre("Psychological"), Genre("Psychological"),
Genre("Romance"), Genre("Romance"),
Genre("School Life"), Genre("School Life"),
Genre("Sci-fi"), Genre("Sci-fi"),
Genre("Seinen"), Genre("Seinen"),
Genre("Shotacon"), Genre("Shotacon"),
Genre("Shoujo"), Genre("Shoujo"),
Genre("Shoujo Ai"), Genre("Shoujo Ai"),
Genre("Shounen"), Genre("Shounen"),
Genre("Shounen Ai"), Genre("Shounen Ai"),
Genre("Slice of Life"), Genre("Slice of Life"),
Genre("Smut"), Genre("Smut"),
Genre("Sports"), Genre("Sports"),
Genre("Supernatural"), Genre("Supernatural"),
Genre("Tragedy"), Genre("Tragedy"),
Genre("Webtoon"), Genre("Webtoon"),
Genre("Yaoi"), Genre("Yaoi"),
Genre("Yuri") Genre("Yuri")
) )
} }

View File

@ -1,223 +1,223 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class Mangafox : ParsedOnlineSource() { class Mangafox : ParsedOnlineSource() {
override val id: Long = 3 override val id: Long = 3
override val name = "Mangafox" override val name = "Mangafox"
override val baseUrl = "http://mangafox.me" override val baseUrl = "http://mangafox.me"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaSelector() = "div#mangalist > ul.list > li" override fun popularMangaSelector() = "div#mangalist > ul.list > li"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val pageStr = if (page != 1) "$page.htm" else "" val pageStr = if (page != 1) "$page.htm" else ""
return GET("$baseUrl/directory/$pageStr", headers) return GET("$baseUrl/directory/$pageStr", headers)
} }
override fun latestUpdatesSelector() = "div#mangalist > ul.list > li" override fun latestUpdatesSelector() = "div#mangalist > ul.list > li"
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val pageStr = if (page != 1) "$page.htm" else "" val pageStr = if (page != 1) "$page.htm" else ""
return GET("$baseUrl/directory/$pageStr?latest") return GET("$baseUrl/directory/$pageStr?latest")
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a.title").first().let { element.select("a.title").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "a:has(span.next)" override fun popularMangaNextPageSelector() = "a:has(span.next)"
override fun latestUpdatesNextPageSelector() = "a:has(span.next)" override fun latestUpdatesNextPageSelector() = "a:has(span.next)"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is Status -> url.addQueryParameter(filter.id, filter.state.toString()) is Status -> url.addQueryParameter(filter.id, filter.state.toString())
is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) }
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString()) is Type -> url.addQueryParameter("type", if(filter.state == 0) "" else filter.state.toString())
is OrderBy -> { is OrderBy -> {
url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index])
url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za")
} }
} }
} }
url.addQueryParameter("page", page.toString()) url.addQueryParameter("page", page.toString())
return GET(url.toString(), headers) return GET(url.toString(), headers)
} }
override fun searchMangaSelector() = "div#mangalist > ul.list > li" override fun searchMangaSelector() = "div#mangalist > ul.list > li"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a.title").first().let { element.select("a.title").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun searchMangaNextPageSelector() = "a:has(span.next)" override fun searchMangaNextPageSelector() = "a:has(span.next)"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div#title").first() val infoElement = document.select("div#title").first()
val rowElement = infoElement.select("table > tbody > tr:eq(1)").first() val rowElement = infoElement.select("table > tbody > tr:eq(1)").first()
val sideInfoElement = document.select("#series_info").first() val sideInfoElement = document.select("#series_info").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = rowElement.select("td:eq(1)").first()?.text() manga.author = rowElement.select("td:eq(1)").first()?.text()
manga.artist = rowElement.select("td:eq(2)").first()?.text() manga.artist = rowElement.select("td:eq(2)").first()?.text()
manga.genre = rowElement.select("td:eq(3)").first()?.text() manga.genre = rowElement.select("td:eq(3)").first()?.text()
manga.description = infoElement.select("p.summary").first()?.text() manga.description = infoElement.select("p.summary").first()?.text()
manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) } manga.status = sideInfoElement.select(".data").first()?.text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src") manga.thumbnail_url = sideInfoElement.select("div.cover > img").first()?.attr("src")
return manga return manga
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
status.contains("Ongoing") -> SManga.ONGOING status.contains("Ongoing") -> SManga.ONGOING
status.contains("Completed") -> SManga.COMPLETED status.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = "div#chapters li div" override fun chapterListSelector() = "div#chapters li div"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a.tips").first() val urlElement = element.select("a.tips").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.date").first()?.text()?.let { parseChapterDate(it) } ?: 0
return chapter return chapter
} }
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String): Long {
return if ("Today" in date || " ago" in date) { return if ("Today" in date || " ago" in date) {
Calendar.getInstance().apply { Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
} else if ("Yesterday" in date) { } else if ("Yesterday" in date) {
Calendar.getInstance().apply { Calendar.getInstance().apply {
add(Calendar.DATE, -1) add(Calendar.DATE, -1)
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
} else { } else {
try { try {
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time
} catch (e: ParseException) { } catch (e: ParseException) {
0L 0L
} }
} }
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val url = document.baseUri().substringBeforeLast('/') val url = document.baseUri().substringBeforeLast('/')
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("select.m").first()?.select("option:not([value=0])")?.forEach { document.select("select.m").first()?.select("option:not([value=0])")?.forEach {
pages.add(Page(pages.size, "$url/${it.attr("value")}.html")) pages.add(Page(pages.size, "$url/${it.attr("value")}.html"))
} }
return pages return pages
} }
override fun imageUrlParse(document: Document): String { override fun imageUrlParse(document: Document): String {
val url = document.getElementById("image").attr("src") val url = document.getElementById("image").attr("src")
return if ("compressed?token=" !in url) { return if ("compressed?token=" !in url) {
url url
} else { } else {
"http://mangafox.me/media/logo.png" "http://mangafox.me/media/logo.png"
} }
} }
private class Status(val id: String = "is_completed") : Filter.TriState("Completed") private class Status(val id: String = "is_completed") : Filter.TriState("Completed")
private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
private class OrderBy : Filter.Sort("Order by", private class OrderBy : Filter.Sort("Order by",
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
Filter.Sort.Selection(2, false)) Filter.Sort.Selection(2, false))
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), TextField("Artist", "artist"),
Type(), Type(),
Status(), Status(),
OrderBy(), OrderBy(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n') // $('select.genres').map((i,el)=>`Genre("${$(el).next().text().trim()}", "${$(el).attr('name')}")`).get().join(',\n')
// on http://mangafox.me/search.php // on http://mangafox.me/search.php
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),
Genre("Adventure"), Genre("Adventure"),
Genre("Comedy"), Genre("Comedy"),
Genre("Doujinshi"), Genre("Doujinshi"),
Genre("Drama"), Genre("Drama"),
Genre("Ecchi"), Genre("Ecchi"),
Genre("Fantasy"), Genre("Fantasy"),
Genre("Gender Bender"), Genre("Gender Bender"),
Genre("Harem"), Genre("Harem"),
Genre("Historical"), Genre("Historical"),
Genre("Horror"), Genre("Horror"),
Genre("Josei"), Genre("Josei"),
Genre("Martial Arts"), Genre("Martial Arts"),
Genre("Mature"), Genre("Mature"),
Genre("Mecha"), Genre("Mecha"),
Genre("Mystery"), Genre("Mystery"),
Genre("One Shot"), Genre("One Shot"),
Genre("Psychological"), Genre("Psychological"),
Genre("Romance"), Genre("Romance"),
Genre("School Life"), Genre("School Life"),
Genre("Sci-fi"), Genre("Sci-fi"),
Genre("Seinen"), Genre("Seinen"),
Genre("Shoujo"), Genre("Shoujo"),
Genre("Shoujo Ai"), Genre("Shoujo Ai"),
Genre("Shounen"), Genre("Shounen"),
Genre("Shounen Ai"), Genre("Shounen Ai"),
Genre("Slice of Life"), Genre("Slice of Life"),
Genre("Smut"), Genre("Smut"),
Genre("Sports"), Genre("Sports"),
Genre("Supernatural"), Genre("Supernatural"),
Genre("Tragedy"), Genre("Tragedy"),
Genre("Webtoons"), Genre("Webtoons"),
Genre("Yaoi"), Genre("Yaoi"),
Genre("Yuri") Genre("Yuri")
) )
} }

View File

@ -1,220 +1,220 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class Mangahere : ParsedOnlineSource() { class Mangahere : ParsedOnlineSource() {
override val id: Long = 2 override val id: Long = 2
override val name = "Mangahere" override val name = "Mangahere"
override val baseUrl = "http://www.mangahere.co" override val baseUrl = "http://www.mangahere.co"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaSelector() = "div.directory_list > ul > li" override fun popularMangaSelector() = "div.directory_list > ul > li"
override fun latestUpdatesSelector() = "div.directory_list > ul > li" override fun latestUpdatesSelector() = "div.directory_list > ul > li"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/directory/$page.htm?views.za", headers) return GET("$baseUrl/directory/$page.htm?views.za", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers) return GET("$baseUrl/directory/$page.htm?last_chapter_time.za", headers)
} }
private fun mangaFromElement(query: String, element: Element): SManga { private fun mangaFromElement(query: String, element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select(query).first().let { element.select(query).first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text() manga.title = if (it.hasAttr("title")) it.attr("title") else if (it.hasAttr("rel")) it.attr("rel") else it.text()
} }
return manga return manga
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
return mangaFromElement("div.title > a", element) return mangaFromElement("div.title > a", element)
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "div.next-page > a.next" override fun popularMangaNextPageSelector() = "div.next-page > a.next"
override fun latestUpdatesNextPageSelector() = "div.next-page > a.next" override fun latestUpdatesNextPageSelector() = "div.next-page > a.next"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query) val url = HttpUrl.parse("$baseUrl/search.php?name_method=cw&author_method=cw&artist_method=cw&advopts=1").newBuilder().addQueryParameter("name", query)
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state]) is Status -> url.addQueryParameter("is_completed", arrayOf("", "1", "0")[filter.state])
is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) } is GenreList -> filter.state.forEach { genre -> url.addQueryParameter(genre.id, genre.state.toString()) }
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state]) is Type -> url.addQueryParameter("direction", arrayOf("", "rl", "lr")[filter.state])
is OrderBy -> { is OrderBy -> {
url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index]) url.addQueryParameter("sort", arrayOf("name", "rating", "views", "total_chapters", "last_chapter_time")[filter.state!!.index])
url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za") url.addQueryParameter("order", if (filter.state?.ascending == true) "az" else "za")
} }
} }
} }
url.addQueryParameter("page", page.toString()) url.addQueryParameter("page", page.toString())
return GET(url.toString(), headers) return GET(url.toString(), headers)
} }
override fun searchMangaSelector() = "div.result_search > dl:has(dt)" override fun searchMangaSelector() = "div.result_search > dl:has(dt)"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return mangaFromElement("a.manga_info", element) return mangaFromElement("a.manga_info", element)
} }
override fun searchMangaNextPageSelector() = "div.next-page > a.next" override fun searchMangaNextPageSelector() = "div.next-page > a.next"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val detailElement = document.select(".manga_detail_top").first() val detailElement = document.select(".manga_detail_top").first()
val infoElement = detailElement.select(".detail_topText").first() val infoElement = detailElement.select(".detail_topText").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("a[href^=http://www.mangahere.co/author/]").first()?.text() manga.author = infoElement.select("a[href^=http://www.mangahere.co/author/]").first()?.text()
manga.artist = infoElement.select("a[href^=http://www.mangahere.co/artist/]").first()?.text() manga.artist = infoElement.select("a[href^=http://www.mangahere.co/artist/]").first()?.text()
manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):") manga.genre = infoElement.select("li:eq(3)").first()?.text()?.substringAfter("Genre(s):")
manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less") manga.description = infoElement.select("#show").first()?.text()?.substringBeforeLast("Show less")
manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) } manga.status = infoElement.select("li:eq(6)").first()?.text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src") manga.thumbnail_url = detailElement.select("img.img").first()?.attr("src")
return manga return manga
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
status.contains("Ongoing") -> SManga.ONGOING status.contains("Ongoing") -> SManga.ONGOING
status.contains("Completed") -> SManga.COMPLETED status.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = ".detail_list > ul:not([class]) > li" override fun chapterListSelector() = ".detail_list > ul:not([class]) > li"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val parentEl = element.select("span.left").first() val parentEl = element.select("span.left").first()
val urlElement = parentEl.select("a").first() val urlElement = parentEl.select("a").first()
var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: "" var volume = parentEl.select("span.mr6")?.first()?.text()?.trim() ?: ""
if (volume.length > 0) { if (volume.length > 0) {
volume = " - " + volume volume = " - " + volume
} }
var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: "" var title = parentEl?.textNodes()?.last()?.text()?.trim() ?: ""
if (title.length > 0) { if (title.length > 0) {
title = " - " + title title = " - " + title
} }
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() + volume + title chapter.name = urlElement.text() + volume + title
chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.right").first()?.text()?.let { parseChapterDate(it) } ?: 0
return chapter return chapter
} }
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String): Long {
return if ("Today" in date) { return if ("Today" in date) {
Calendar.getInstance().apply { Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
} else if ("Yesterday" in date) { } else if ("Yesterday" in date) {
Calendar.getInstance().apply { Calendar.getInstance().apply {
add(Calendar.DATE, -1) add(Calendar.DATE, -1)
set(Calendar.HOUR_OF_DAY, 0) set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0) set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0) set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0) set(Calendar.MILLISECOND, 0)
}.timeInMillis }.timeInMillis
} else { } else {
try { try {
SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH).parse(date).time
} catch (e: ParseException) { } catch (e: ParseException) {
0L 0L
} }
} }
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("select.wid60").first()?.getElementsByTag("option")?.forEach { document.select("select.wid60").first()?.getElementsByTag("option")?.forEach {
pages.add(Page(pages.size, it.attr("value"))) pages.add(Page(pages.size, it.attr("value")))
} }
pages.getOrNull(0)?.imageUrl = imageUrlParse(document) pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
return pages return pages
} }
override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src") override fun imageUrlParse(document: Document) = document.getElementById("image").attr("src")
private class Status : Filter.TriState("Completed") private class Status : Filter.TriState("Completed")
private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name) private class Genre(name: String, val id: String = "genres[$name]") : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)")) private class Type : Filter.Select<String>("Type", arrayOf("Any", "Japanese Manga (read from right to left)", "Korean Manhwa (read from left to right)"))
private class OrderBy : Filter.Sort("Order by", private class OrderBy : Filter.Sort("Order by",
arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"), arrayOf("Series name", "Rating", "Views", "Total chapters", "Last chapter"),
Filter.Sort.Selection(2, false)) Filter.Sort.Selection(2, false))
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), TextField("Artist", "artist"),
Type(), Type(),
Status(), Status(),
OrderBy(), OrderBy(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n') // [...document.querySelectorAll("select[id^='genres'")].map((el,i) => `Genre("${el.nextSibling.nextSibling.textContent.trim()}", "${el.getAttribute('name')}")`).join(',\n')
// http://www.mangahere.co/advsearch.htm // http://www.mangahere.co/advsearch.htm
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Action"), Genre("Action"),
Genre("Adventure"), Genre("Adventure"),
Genre("Comedy"), Genre("Comedy"),
Genre("Doujinshi"), Genre("Doujinshi"),
Genre("Drama"), Genre("Drama"),
Genre("Ecchi"), Genre("Ecchi"),
Genre("Fantasy"), Genre("Fantasy"),
Genre("Gender Bender"), Genre("Gender Bender"),
Genre("Harem"), Genre("Harem"),
Genre("Historical"), Genre("Historical"),
Genre("Horror"), Genre("Horror"),
Genre("Josei"), Genre("Josei"),
Genre("Martial Arts"), Genre("Martial Arts"),
Genre("Mature"), Genre("Mature"),
Genre("Mecha"), Genre("Mecha"),
Genre("Mystery"), Genre("Mystery"),
Genre("One Shot"), Genre("One Shot"),
Genre("Psychological"), Genre("Psychological"),
Genre("Romance"), Genre("Romance"),
Genre("School Life"), Genre("School Life"),
Genre("Sci-fi"), Genre("Sci-fi"),
Genre("Seinen"), Genre("Seinen"),
Genre("Shoujo"), Genre("Shoujo"),
Genre("Shoujo Ai"), Genre("Shoujo Ai"),
Genre("Shounen"), Genre("Shounen"),
Genre("Shounen Ai"), Genre("Shounen Ai"),
Genre("Slice of Life"), Genre("Slice of Life"),
Genre("Sports"), Genre("Sports"),
Genre("Supernatural"), Genre("Supernatural"),
Genre("Tragedy"), Genre("Tragedy"),
Genre("Yaoi"), Genre("Yaoi"),
Genre("Yuri") Genre("Yuri")
) )
} }

View File

@ -1,243 +1,243 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.regex.Pattern import java.util.regex.Pattern
class Mangasee : ParsedOnlineSource() { class Mangasee : ParsedOnlineSource() {
override val id: Long = 9 override val id: Long = 9
override val name = "Mangasee" override val name = "Mangasee"
override val baseUrl = "http://mangaseeonline.net" override val baseUrl = "http://mangaseeonline.net"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?") private val recentUpdatesPattern = Pattern.compile("(.*?)\\s(\\d+\\.?\\d*)\\s?(Completed)?")
private val indexPattern = Pattern.compile("-index-(.*?)-") private val indexPattern = Pattern.compile("-index-(.*?)-")
override fun popularMangaSelector() = "div.requested > div.row" override fun popularMangaSelector() = "div.requested > div.row"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending") val (body, requestUrl) = convertQueryToPost(page, "$baseUrl/search/request.php?sortBy=popularity&sortOrder=descending")
return POST(requestUrl, headers, body.build()) return POST(requestUrl, headers, body.build())
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a.resultLink").first().let { element.select("a.resultLink").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun popularMangaNextPageSelector() = "button.requestMore" override fun popularMangaNextPageSelector() = "button.requestMore"
override fun searchMangaSelector() = "div.requested > div.row" override fun searchMangaSelector() = "div.requested > div.row"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder() val url = HttpUrl.parse("$baseUrl/search/request.php").newBuilder()
if (!query.isEmpty()) url.addQueryParameter("keyword", query) if (!query.isEmpty()) url.addQueryParameter("keyword", query)
val genres = mutableListOf<String>() val genres = mutableListOf<String>()
val genresNo = mutableListOf<String>() val genresNo = mutableListOf<String>()
for (filter in if (filters.isEmpty()) getFilterList() else filters) { for (filter in if (filters.isEmpty()) getFilterList() else filters) {
when (filter) { when (filter) {
is Sort -> { is Sort -> {
if (filter.state?.index != 0) if (filter.state?.index != 0)
url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity") url.addQueryParameter("sortBy", if (filter.state?.index == 1) "dateUpdated" else "popularity")
if (filter.state?.ascending != true) if (filter.state?.ascending != true)
url.addQueryParameter("sortOrder", "descending") url.addQueryParameter("sortOrder", "descending")
} }
is SelectField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state]) is SelectField -> if (filter.state != 0) url.addQueryParameter(filter.key, filter.values[filter.state])
is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state) is TextField -> if (!filter.state.isEmpty()) url.addQueryParameter(filter.key, filter.state)
is GenreList -> filter.state.forEach { genre -> is GenreList -> filter.state.forEach { genre ->
when (genre.state) { when (genre.state) {
Filter.TriState.STATE_INCLUDE -> genres.add(genre.name) Filter.TriState.STATE_INCLUDE -> genres.add(genre.name)
Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name) Filter.TriState.STATE_EXCLUDE -> genresNo.add(genre.name)
} }
} }
} }
} }
if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(",")) if (genres.isNotEmpty()) url.addQueryParameter("genre", genres.joinToString(","))
if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(",")) if (genresNo.isNotEmpty()) url.addQueryParameter("genreNo", genresNo.joinToString(","))
val (body, requestUrl) = convertQueryToPost(page, url.toString()) val (body, requestUrl) = convertQueryToPost(page, url.toString())
return POST(requestUrl, headers, body.build()) return POST(requestUrl, headers, body.build())
} }
private fun convertQueryToPost(page: Int, url: String): Pair<FormBody.Builder, String> { private fun convertQueryToPost(page: Int, url: String): Pair<FormBody.Builder, String> {
val url = HttpUrl.parse(url) val url = HttpUrl.parse(url)
val body = FormBody.Builder().add("page", page.toString()) val body = FormBody.Builder().add("page", page.toString())
for (i in 0..url.querySize() - 1) { for (i in 0..url.querySize() - 1) {
body.add(url.queryParameterName(i), url.queryParameterValue(i)) body.add(url.queryParameterName(i), url.queryParameterValue(i))
} }
val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath() val requestUrl = url.scheme() + "://" + url.host() + url.encodedPath()
return Pair(body, requestUrl) return Pair(body, requestUrl)
} }
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a.resultLink").first().let { element.select("a.resultLink").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun searchMangaNextPageSelector() = "button.requestMore" override fun searchMangaNextPageSelector() = "button.requestMore"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val detailElement = document.select("div.well > div.row").first() val detailElement = document.select("div.well > div.row").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text() manga.author = detailElement.select("a[href^=/search/?author=]").first()?.text()
manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString() manga.genre = detailElement.select("span.details > div.row > div:has(b:contains(Genre(s))) > a").map { it.text() }.joinToString()
manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text() manga.description = detailElement.select("strong:contains(Description:) + div").first()?.text()
manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) } manga.status = detailElement.select("a[href^=/search/?status=]").first()?.text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src") manga.thumbnail_url = detailElement.select("div > img").first()?.absUrl("src")
return manga return manga
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
status.contains("Ongoing (Scan)") -> SManga.ONGOING status.contains("Ongoing (Scan)") -> SManga.ONGOING
status.contains("Complete (Scan)") -> SManga.COMPLETED status.contains("Complete (Scan)") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = "div.chapter-list > a" override fun chapterListSelector() = "div.chapter-list > a"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: "" chapter.name = element.select("span.chapterLabel").first().text()?.let { it } ?: ""
chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("time").first()?.attr("datetime")?.let { parseChapterDate(it) } ?: 0
return chapter return chapter
} }
private fun parseChapterDate(dateAsString: String): Long { private fun parseChapterDate(dateAsString: String): Long {
return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time return SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(dateAsString).time
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val fullUrl = document.baseUri() val fullUrl = document.baseUri()
val url = fullUrl.substringBeforeLast('/') val url = fullUrl.substringBeforeLast('/')
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
val series = document.select("input.IndexName").first().attr("value") val series = document.select("input.IndexName").first().attr("value")
val chapter = document.select("span.CurChapter").first().text() val chapter = document.select("span.CurChapter").first().text()
var index = "" var index = ""
val m = indexPattern.matcher(fullUrl) val m = indexPattern.matcher(fullUrl)
if (m.find()) { if (m.find()) {
val indexNumber = m.group(1) val indexNumber = m.group(1)
index = "-index-$indexNumber" index = "-index-$indexNumber"
} }
document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach { document.select("div.ContainerNav").first().select("select.PageSelect > option").forEach {
pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html")) pages.add(Page(pages.size, "$url/$series-chapter-$chapter$index-page-${pages.size + 1}.html"))
} }
pages.getOrNull(0)?.imageUrl = imageUrlParse(document) pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
return pages return pages
} }
override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src") override fun imageUrlParse(document: Document): String = document.select("img.CurImage").attr("src")
override fun latestUpdatesNextPageSelector() = "button.requestMore" override fun latestUpdatesNextPageSelector() = "button.requestMore"
override fun latestUpdatesSelector(): String = "a.latestSeries" override fun latestUpdatesSelector(): String = "a.latestSeries"
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val url = "http://mangaseeonline.net/home/latest.request.php" val url = "http://mangaseeonline.net/home/latest.request.php"
val (body, requestUrl) = convertQueryToPost(page, url) val (body, requestUrl) = convertQueryToPost(page, url)
return POST(requestUrl, headers, body.build()) return POST(requestUrl, headers, body.build())
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a.latestSeries").first().let { element.select("a.latestSeries").first().let {
val chapterUrl = it.attr("href") val chapterUrl = it.attr("href")
val indexOfMangaUrl = chapterUrl.indexOf("-chapter-") val indexOfMangaUrl = chapterUrl.indexOf("-chapter-")
val indexOfLastPath = chapterUrl.lastIndexOf("/") val indexOfLastPath = chapterUrl.lastIndexOf("/")
val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl) val mangaUrl = chapterUrl.substring(indexOfLastPath, indexOfMangaUrl)
val defaultText = it.select("p.clamp2").text() val defaultText = it.select("p.clamp2").text()
val m = recentUpdatesPattern.matcher(defaultText) val m = recentUpdatesPattern.matcher(defaultText)
val title = if (m.matches()) m.group(1) else defaultText val title = if (m.matches()) m.group(1) else defaultText
manga.setUrlWithoutDomain("/manga" + mangaUrl) manga.setUrlWithoutDomain("/manga" + mangaUrl)
manga.title = title manga.title = title
} }
return manga return manga
} }
private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false)) private class Sort : Filter.Sort("Sort", arrayOf("Alphabetically", "Date updated", "Popularity"), Filter.Sort.Selection(2, false))
private class Genre(name: String) : Filter.TriState(name) private class Genre(name: String) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state) private class SelectField(name: String, val key: String, values: Array<String>, state: Int = 0) : Filter.Select<String>(name, values, state)
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Years", "year"), TextField("Years", "year"),
TextField("Author", "author"), TextField("Author", "author"),
SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")), SelectField("Scan Status", "status", arrayOf("Any", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing")),
SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")), SelectField("Publish Status", "pstatus", arrayOf("Any", "Cancelled", "Complete", "Discontinued", "Hiatus", "Incomplete", "Ongoing", "Unfinished")),
SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")), SelectField("Type", "type", arrayOf("Any", "Doujinshi", "Manga", "Manhua", "Manhwa", "OEL", "One-shot")),
Sort(), Sort(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n') // [...document.querySelectorAll("label.triStateCheckBox input")].map(el => `Filter("${el.getAttribute('name')}", "${el.nextSibling.textContent.trim()}")`).join(',\n')
// http://mangasee.co/advanced-search/ // http://mangasee.co/advanced-search/
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Action"), Genre("Action"),
Genre("Adult"), Genre("Adult"),
Genre("Adventure"), Genre("Adventure"),
Genre("Comedy"), Genre("Comedy"),
Genre("Doujinshi"), Genre("Doujinshi"),
Genre("Drama"), Genre("Drama"),
Genre("Ecchi"), Genre("Ecchi"),
Genre("Fantasy"), Genre("Fantasy"),
Genre("Gender Bender"), Genre("Gender Bender"),
Genre("Harem"), Genre("Harem"),
Genre("Hentai"), Genre("Hentai"),
Genre("Historical"), Genre("Historical"),
Genre("Horror"), Genre("Horror"),
Genre("Josei"), Genre("Josei"),
Genre("Lolicon"), Genre("Lolicon"),
Genre("Martial Arts"), Genre("Martial Arts"),
Genre("Mature"), Genre("Mature"),
Genre("Mecha"), Genre("Mecha"),
Genre("Mystery"), Genre("Mystery"),
Genre("Psychological"), Genre("Psychological"),
Genre("Romance"), Genre("Romance"),
Genre("School Life"), Genre("School Life"),
Genre("Sci-fi"), Genre("Sci-fi"),
Genre("Seinen"), Genre("Seinen"),
Genre("Shotacon"), Genre("Shotacon"),
Genre("Shoujo"), Genre("Shoujo"),
Genre("Shoujo Ai"), Genre("Shoujo Ai"),
Genre("Shounen"), Genre("Shounen"),
Genre("Shounen Ai"), Genre("Shounen Ai"),
Genre("Slice of Life"), Genre("Slice of Life"),
Genre("Smut"), Genre("Smut"),
Genre("Sports"), Genre("Sports"),
Genre("Supernatural"), Genre("Supernatural"),
Genre("Tragedy"), Genre("Tragedy"),
Genre("Yaoi"), Genre("Yaoi"),
Genre("Yuri") Genre("Yuri")
) )
} }

View File

@ -1,219 +1,219 @@
package eu.kanade.tachiyomi.data.source.online.english package eu.kanade.tachiyomi.source.online.english
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.Headers import okhttp3.Headers
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.util.* import java.util.*
class Readmangatoday : ParsedOnlineSource() { class Readmangatoday : ParsedOnlineSource() {
override val id: Long = 8 override val id: Long = 8
override val name = "ReadMangaToday" override val name = "ReadMangaToday"
override val baseUrl = "http://www.readmanga.today" override val baseUrl = "http://www.readmanga.today"
override val lang = "en" override val lang = "en"
override val supportsLatest = true override val supportsLatest = true
override val client: OkHttpClient get() = network.cloudflareClient override val client: OkHttpClient get() = network.cloudflareClient
/** /**
* Search only returns data with this set * Search only returns data with this set
*/ */
override fun headersBuilder() = Headers.Builder().apply { override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)") add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)")
add("X-Requested-With", "XMLHttpRequest") add("X-Requested-With", "XMLHttpRequest")
} }
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/hot-manga/$page", headers) return GET("$baseUrl/hot-manga/$page", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/latest-releases/$page", headers) return GET("$baseUrl/latest-releases/$page", headers)
} }
override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box" override fun popularMangaSelector() = "div.hot-manga > div.style-list > div.box"
override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box" override fun latestUpdatesSelector() = "div.hot-manga > div.style-grid > div.box"
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("div.title > h2 > a").first().let { element.select("div.title > h2 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" override fun popularMangaNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)"
override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)" override fun latestUpdatesNextPageSelector() = "div.hot-manga > ul.pagination > li > a:contains(»)"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val builder = okhttp3.FormBody.Builder() val builder = okhttp3.FormBody.Builder()
builder.add("manga-name", query) builder.add("manga-name", query)
(if (filters.isEmpty()) getFilterList() else filters).forEach { filter -> (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
when (filter) { when (filter) {
is TextField -> builder.add(filter.key, filter.state) is TextField -> builder.add(filter.key, filter.state)
is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state]) is Type -> builder.add("type", arrayOf("all", "japanese", "korean", "chinese")[filter.state])
is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state]) is Status -> builder.add("status", arrayOf("both", "completed", "ongoing")[filter.state])
is GenreList -> filter.state.forEach { genre -> is GenreList -> filter.state.forEach { genre ->
when (genre.state) { when (genre.state) {
Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString()) Filter.TriState.STATE_INCLUDE -> builder.add("include[]", genre.id.toString())
Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString()) Filter.TriState.STATE_EXCLUDE -> builder.add("exclude[]", genre.id.toString())
} }
} }
} }
} }
return POST("$baseUrl/service/advanced_search", headers, builder.build()) return POST("$baseUrl/service/advanced_search", headers, builder.build())
} }
override fun searchMangaSelector() = "div.style-list > div.box" override fun searchMangaSelector() = "div.style-list > div.box"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("div.title > h2 > a").first().let { element.select("div.title > h2 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
return manga return manga
} }
override fun searchMangaNextPageSelector() = "div.next-page > a.next" override fun searchMangaNextPageSelector() = "div.next-page > a.next"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val detailElement = document.select("div.movie-meta").first() val detailElement = document.select("div.movie-meta").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = document.select("ul.cast-list li.director > ul a").first()?.text() manga.author = document.select("ul.cast-list li.director > ul a").first()?.text()
manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text() manga.artist = document.select("ul.cast-list li:not(.director) > ul a").first()?.text()
manga.genre = detailElement.select("dl.dl-horizontal > dd:eq(5)").first()?.text() manga.genre = detailElement.select("dl.dl-horizontal > dd:eq(5)").first()?.text()
manga.description = detailElement.select("li.movie-detail").first()?.text() manga.description = detailElement.select("li.movie-detail").first()?.text()
manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) } manga.status = detailElement.select("dl.dl-horizontal > dd:eq(3)").first()?.text().orEmpty().let { parseStatus(it) }
manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src") manga.thumbnail_url = detailElement.select("img.img-responsive").first()?.attr("src")
return manga return manga
} }
private fun parseStatus(status: String) = when { private fun parseStatus(status: String) = when {
status.contains("Ongoing") -> SManga.ONGOING status.contains("Ongoing") -> SManga.ONGOING
status.contains("Completed") -> SManga.COMPLETED status.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN else -> SManga.UNKNOWN
} }
override fun chapterListSelector() = "ul.chp_lst > li" override fun chapterListSelector() = "ul.chp_lst > li"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.select("span.val").text() chapter.name = urlElement.select("span.val").text()
chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = element.select("span.dte").first()?.text()?.let { parseChapterDate(it) } ?: 0
return chapter return chapter
} }
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String): Long {
val dateWords: List<String> = date.split(" ") val dateWords: List<String> = date.split(" ")
if (dateWords.size == 3) { if (dateWords.size == 3) {
val timeAgo = Integer.parseInt(dateWords[0]) val timeAgo = Integer.parseInt(dateWords[0])
val date: Calendar = Calendar.getInstance() val date: Calendar = Calendar.getInstance()
if (dateWords[1].contains("Minute")) { if (dateWords[1].contains("Minute")) {
date.add(Calendar.MINUTE, -timeAgo) date.add(Calendar.MINUTE, -timeAgo)
} else if (dateWords[1].contains("Hour")) { } else if (dateWords[1].contains("Hour")) {
date.add(Calendar.HOUR_OF_DAY, -timeAgo) date.add(Calendar.HOUR_OF_DAY, -timeAgo)
} else if (dateWords[1].contains("Day")) { } else if (dateWords[1].contains("Day")) {
date.add(Calendar.DAY_OF_YEAR, -timeAgo) date.add(Calendar.DAY_OF_YEAR, -timeAgo)
} else if (dateWords[1].contains("Week")) { } else if (dateWords[1].contains("Week")) {
date.add(Calendar.WEEK_OF_YEAR, -timeAgo) date.add(Calendar.WEEK_OF_YEAR, -timeAgo)
} else if (dateWords[1].contains("Month")) { } else if (dateWords[1].contains("Month")) {
date.add(Calendar.MONTH, -timeAgo) date.add(Calendar.MONTH, -timeAgo)
} else if (dateWords[1].contains("Year")) { } else if (dateWords[1].contains("Year")) {
date.add(Calendar.YEAR, -timeAgo) date.add(Calendar.YEAR, -timeAgo)
} }
return date.timeInMillis return date.timeInMillis
} }
return 0L return 0L
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach { document.select("ul.list-switcher-2 > li > select.jump-menu").first().getElementsByTag("option").forEach {
pages.add(Page(pages.size, it.attr("value"))) pages.add(Page(pages.size, it.attr("value")))
} }
pages.getOrNull(0)?.imageUrl = imageUrlParse(document) pages.getOrNull(0)?.imageUrl = imageUrlParse(document)
return pages return pages
} }
override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src") override fun imageUrlParse(document: Document) = document.select("img.img-responsive-2").first().attr("src")
private class Status : Filter.TriState("Completed") private class Status : Filter.TriState("Completed")
private class Genre(name: String, val id: Int) : Filter.TriState(name) private class Genre(name: String, val id: Int) : Filter.TriState(name)
private class TextField(name: String, val key: String) : Filter.Text(name) private class TextField(name: String, val key: String) : Filter.Text(name)
private class Type : Filter.Select<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua")) private class Type : Filter.Select<String>("Type", arrayOf("All", "Japanese Manga", "Korean Manhwa", "Chinese Manhua"))
private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres) private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author-name"), TextField("Author", "author-name"),
TextField("Artist", "artist-name"), TextField("Artist", "artist-name"),
Type(), Type(),
Status(), Status(),
GenreList(getGenreList()) GenreList(getGenreList())
) )
// [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n') // [...document.querySelectorAll("ul.manga-cat span")].map(el => `Genre("${el.nextSibling.textContent.trim()}", ${el.getAttribute('data-id')})`).join(',\n')
// http://www.readmanga.today/advanced-search // http://www.readmanga.today/advanced-search
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Genre("Action", 2), Genre("Action", 2),
Genre("Adventure", 4), Genre("Adventure", 4),
Genre("Comedy", 5), Genre("Comedy", 5),
Genre("Doujinshi", 6), Genre("Doujinshi", 6),
Genre("Drama", 7), Genre("Drama", 7),
Genre("Ecchi", 8), Genre("Ecchi", 8),
Genre("Fantasy", 9), Genre("Fantasy", 9),
Genre("Gender Bender", 10), Genre("Gender Bender", 10),
Genre("Harem", 11), Genre("Harem", 11),
Genre("Historical", 12), Genre("Historical", 12),
Genre("Horror", 13), Genre("Horror", 13),
Genre("Josei", 14), Genre("Josei", 14),
Genre("Lolicon", 15), Genre("Lolicon", 15),
Genre("Martial Arts", 16), Genre("Martial Arts", 16),
Genre("Mature", 17), Genre("Mature", 17),
Genre("Mecha", 18), Genre("Mecha", 18),
Genre("Mystery", 19), Genre("Mystery", 19),
Genre("One shot", 20), Genre("One shot", 20),
Genre("Psychological", 21), Genre("Psychological", 21),
Genre("Romance", 22), Genre("Romance", 22),
Genre("School Life", 23), Genre("School Life", 23),
Genre("Sci-fi", 24), Genre("Sci-fi", 24),
Genre("Seinen", 25), Genre("Seinen", 25),
Genre("Shotacon", 26), Genre("Shotacon", 26),
Genre("Shoujo", 27), Genre("Shoujo", 27),
Genre("Shoujo Ai", 28), Genre("Shoujo Ai", 28),
Genre("Shounen", 29), Genre("Shounen", 29),
Genre("Shounen Ai", 30), Genre("Shounen Ai", 30),
Genre("Slice of Life", 31), Genre("Slice of Life", 31),
Genre("Smut", 32), Genre("Smut", 32),
Genre("Sports", 33), Genre("Sports", 33),
Genre("Supernatural", 34), Genre("Supernatural", 34),
Genre("Tragedy", 35), Genre("Tragedy", 35),
Genre("Yaoi", 36), Genre("Yaoi", 36),
Genre("Yuri", 37) Genre("Yuri", 37)
) )
} }

View File

@ -1,122 +1,122 @@
package eu.kanade.tachiyomi.data.source.online.german package eu.kanade.tachiyomi.source.online.german
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.Request import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
class WieManga : ParsedOnlineSource() { class WieManga : ParsedOnlineSource() {
override val id: Long = 10 override val id: Long = 10
override val name = "Wie Manga!" override val name = "Wie Manga!"
override val baseUrl = "http://www.wiemanga.com" override val baseUrl = "http://www.wiemanga.com"
override val lang = "de" override val lang = "de"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaSelector() = ".booklist td > div" override fun popularMangaSelector() = ".booklist td > div"
override fun latestUpdatesSelector() = ".booklist td > div" override fun latestUpdatesSelector() = ".booklist td > div"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/list/Hot-Book/", headers) return GET("$baseUrl/list/Hot-Book/", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/list/New-Update/", headers) return GET("$baseUrl/list/New-Update/", headers)
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val image = element.select("dt img") val image = element.select("dt img")
val title = element.select("dd a:first-child") val title = element.select("dd a:first-child")
val manga = SManga.create() val manga = SManga.create()
manga.setUrlWithoutDomain(title.attr("href")) manga.setUrlWithoutDomain(title.attr("href"))
manga.title = title.text() manga.title = title.text()
manga.thumbnail_url = image.attr("src") manga.thumbnail_url = image.attr("src")
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = null override fun popularMangaNextPageSelector() = null
override fun latestUpdatesNextPageSelector() = null override fun latestUpdatesNextPageSelector() = null
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
return GET("$baseUrl/search/?wd=$query", headers) return GET("$baseUrl/search/?wd=$query", headers)
} }
override fun searchMangaSelector() = ".searchresult td > div" override fun searchMangaSelector() = ".searchresult td > div"
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val image = element.select(".resultimg img") val image = element.select(".resultimg img")
val title = element.select(".resultbookname") val title = element.select(".resultbookname")
val manga = SManga.create() val manga = SManga.create()
manga.setUrlWithoutDomain(title.attr("href")) manga.setUrlWithoutDomain(title.attr("href"))
manga.title = title.text() manga.title = title.text()
manga.thumbnail_url = image.attr("src") manga.thumbnail_url = image.attr("src")
return manga return manga
} }
override fun searchMangaNextPageSelector() = ".pagetor a.l" override fun searchMangaNextPageSelector() = ".pagetor a.l"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first() val imageElement = document.select(".bookmessgae tr > td:nth-child(1)").first()
val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first() val infoElement = document.select(".bookmessgae tr > td:nth-child(2)").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text() manga.author = infoElement.select("dd:nth-of-type(2) a").first()?.text()
manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text() manga.artist = infoElement.select("dd:nth-of-type(3) a").first()?.text()
manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "") manga.description = infoElement.select("dl > dt:last-child").first()?.text()?.replaceFirst("Beschreibung", "")
manga.thumbnail_url = imageElement.select("img").first()?.attr("src") manga.thumbnail_url = imageElement.select("img").first()?.attr("src")
if (manga.author == "RSS") if (manga.author == "RSS")
manga.author = null manga.author = null
if (manga.artist == "RSS") if (manga.artist == "RSS")
manga.artist = null manga.artist = null
return manga return manga
} }
override fun chapterListSelector() = ".chapterlist tr:not(:first-child)" override fun chapterListSelector() = ".chapterlist tr:not(:first-child)"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select(".col1 a").first() val urlElement = element.select(".col1 a").first()
val dateElement = element.select(".col3 a").first() val dateElement = element.select(".col3 a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0 chapter.date_upload = dateElement?.text()?.let { parseChapterDate(it) } ?: 0
return chapter return chapter
} }
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String): Long {
return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time return SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(date).time
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("select#page").first().select("option").forEach { document.select("select#page").first().select("option").forEach {
pages.add(Page(pages.size, it.attr("value"))) pages.add(Page(pages.size, it.attr("value")))
} }
return pages return pages
} }
override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src") override fun imageUrlParse(document: Document) = document.select("img#comicpic").first().attr("src")
} }

View File

@ -1,230 +1,230 @@
package eu.kanade.tachiyomi.data.source.online.russian package eu.kanade.tachiyomi.source.online.russian
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
class Mangachan : ParsedOnlineSource() { class Mangachan : ParsedOnlineSource() {
override val id: Long = 7 override val id: Long = 7
override val name = "Mangachan" override val name = "Mangachan"
override val baseUrl = "http://mangachan.me" override val baseUrl = "http://mangachan.me"
override val lang = "ru" override val lang = "ru"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers) return GET("$baseUrl/mostfavorites?offset=${20 * (page - 1)}", headers)
} }
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = if (query.isNotEmpty()) { val url = if (query.isNotEmpty()) {
"$baseUrl/?do=search&subaction=search&story=$query" "$baseUrl/?do=search&subaction=search&story=$query"
} else { } else {
val filt = filters.filterIsInstance<Genre>().filter { !it.isIgnored() } val filt = filters.filterIsInstance<Genre>().filter { !it.isIgnored() }
if (filt.isNotEmpty()) { if (filt.isNotEmpty()) {
var genres = "" var genres = ""
filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' } filt.forEach { genres += (if (it.isExcluded()) "-" else "") + it.id + '+' }
"$baseUrl/tags/${genres.dropLast(1)}" "$baseUrl/tags/${genres.dropLast(1)}"
} else { } else {
"$baseUrl/?do=search&subaction=search&story=$query" "$baseUrl/?do=search&subaction=search&story=$query"
} }
} }
return GET(url, headers) return GET(url, headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/newestch?page=$page") return GET("$baseUrl/newestch?page=$page")
} }
override fun popularMangaSelector() = "div.content_row" override fun popularMangaSelector() = "div.content_row"
override fun latestUpdatesSelector() = "ul.area_rightNews li" override fun latestUpdatesSelector() = "ul.area_rightNews li"
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("h2 > a").first().let { element.select("h2 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("a:nth-child(1)").first().let { element.select("a:nth-child(1)").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text() manga.title = it.text()
} }
return manga return manga
} }
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "a:contains(Вперед)" override fun popularMangaNextPageSelector() = "a:contains(Вперед)"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaNextPageSelector() = "a:contains(Далее)" override fun searchMangaNextPageSelector() = "a:contains(Далее)"
private fun searchGenresNextPageSelector() = popularMangaNextPageSelector() private fun searchGenresNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup() val document = response.asJsoup()
val mangas = document.select(searchMangaSelector()).map { element -> val mangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element) searchMangaFromElement(element)
} }
// FIXME // FIXME
// val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE } // val allIgnore = filters.all { it.state == Filter.TriState.STATE_IGNORE }
// searchMangaNextPageSelector().let { selector -> // searchMangaNextPageSelector().let { selector ->
// if (page.nextPageUrl.isNullOrEmpty() && allIgnore) { // if (page.nextPageUrl.isNullOrEmpty() && allIgnore) {
// val onClick = document.select(selector).first()?.attr("onclick") // val onClick = document.select(selector).first()?.attr("onclick")
// val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)")) // val pageNum = onClick?.substring(23, onClick.indexOf("); return(false)"))
// page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum // page.nextPageUrl = searchMangaInitialUrl(query, emptyList()) + "&search_start=" + pageNum
// } // }
// } // }
// //
// searchGenresNextPageSelector().let { selector -> // searchGenresNextPageSelector().let { selector ->
// if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) { // if (page.nextPageUrl.isNullOrEmpty() && !allIgnore) {
// val url = document.select(selector).first()?.attr("href") // val url = document.select(selector).first()?.attr("href")
// page.nextPageUrl = searchMangaInitialUrl(query, filters) + url // page.nextPageUrl = searchMangaInitialUrl(query, filters) + url
// } // }
// } // }
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("table.mangatitle").first() val infoElement = document.select("table.mangatitle").first()
val descElement = document.select("div#description").first() val descElement = document.select("div#description").first()
val imgElement = document.select("img#cover").first() val imgElement = document.select("img#cover").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text() manga.author = infoElement.select("tr:eq(2) > td:eq(1)").text()
manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text() manga.genre = infoElement.select("tr:eq(5) > td:eq(1)").text()
manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text()) manga.status = parseStatus(infoElement.select("tr:eq(4) > td:eq(1)").text())
manga.description = descElement.textNodes().first().text() manga.description = descElement.textNodes().first().text()
manga.thumbnail_url = baseUrl + imgElement.attr("src") manga.thumbnail_url = baseUrl + imgElement.attr("src")
return manga return manga
} }
private fun parseStatus(element: String): Int { private fun parseStatus(element: String): Int {
when { when {
element.contains("перевод завершен") -> return SManga.COMPLETED element.contains("перевод завершен") -> return SManga.COMPLETED
element.contains("перевод продолжается") -> return SManga.ONGOING element.contains("перевод продолжается") -> return SManga.ONGOING
else -> return SManga.UNKNOWN else -> return SManga.UNKNOWN
} }
} }
override fun chapterListSelector() = "table.table_cha tr:gt(1)" override fun chapterListSelector() = "table.table_cha tr:gt(1)"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = element.select("div.date").first()?.text()?.let { chapter.date_upload = element.select("div.date").first()?.text()?.let {
SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time SimpleDateFormat("yyyy-MM-dd", Locale.US).parse(it).time
} ?: 0 } ?: 0
return chapter return chapter
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val html = response.body().string() val html = response.body().string()
val beginIndex = html.indexOf("fullimg\":[") + 10 val beginIndex = html.indexOf("fullimg\":[") + 10
val endIndex = html.indexOf(",]", beginIndex) val endIndex = html.indexOf(",]", beginIndex)
val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "") val trimmedHtml = html.substring(beginIndex, endIndex).replace("\"", "")
val pageUrls = trimmedHtml.split(',') val pageUrls = trimmedHtml.split(',')
return pageUrls.mapIndexed { i, url -> Page(i, "", url) } return pageUrls.mapIndexed { i, url -> Page(i, "", url) }
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
throw Exception("Not used") throw Exception("Not used")
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name) private class Genre(name: String, val id: String = name.replace(' ', '_')) : Filter.TriState(name)
/* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) => /* [...document.querySelectorAll("li.sidetag > a:nth-child(1)")].map((el,i) =>
* { const link=el.getAttribute('href');const id=link.substr(6,link.length); * { const link=el.getAttribute('href');const id=link.substr(6,link.length);
* return `Genre("${id.replace("_", " ")}")` }).join(',\n') * return `Genre("${id.replace("_", " ")}")` }).join(',\n')
* on http://mangachan.me/ * on http://mangachan.me/
*/ */
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Genre("18 плюс"), Genre("18 плюс"),
Genre("bdsm"), Genre("bdsm"),
Genre("арт"), Genre("арт"),
Genre("биография"), Genre("биография"),
Genre("боевик"), Genre("боевик"),
Genre("боевые искусства"), Genre("боевые искусства"),
Genre("вампиры"), Genre("вампиры"),
Genre("веб"), Genre("веб"),
Genre("гарем"), Genre("гарем"),
Genre("гендерная интрига"), Genre("гендерная интрига"),
Genre("героическое фэнтези"), Genre("героическое фэнтези"),
Genre("детектив"), Genre("детектив"),
Genre("дзёсэй"), Genre("дзёсэй"),
Genre("додзинси"), Genre("додзинси"),
Genre("драма"), Genre("драма"),
Genre("игра"), Genre("игра"),
Genre("инцест"), Genre("инцест"),
Genre("искусство"), Genre("искусство"),
Genre("история"), Genre("история"),
Genre("киберпанк"), Genre("киберпанк"),
Genre("кодомо"), Genre("кодомо"),
Genre("комедия"), Genre("комедия"),
Genre("литРПГ"), Genre("литРПГ"),
Genre("магия"), Genre("магия"),
Genre("махо-сёдзё"), Genre("махо-сёдзё"),
Genre("меха"), Genre("меха"),
Genre("мистика"), Genre("мистика"),
Genre("музыка"), Genre("музыка"),
Genre("научная фантастика"), Genre("научная фантастика"),
Genre("повседневность"), Genre("повседневность"),
Genre("постапокалиптика"), Genre("постапокалиптика"),
Genre("приключения"), Genre("приключения"),
Genre("психология"), Genre("психология"),
Genre("романтика"), Genre("романтика"),
Genre("самурайский боевик"), Genre("самурайский боевик"),
Genre("сборник"), Genre("сборник"),
Genre("сверхъестественное"), Genre("сверхъестественное"),
Genre("сказка"), Genre("сказка"),
Genre("спорт"), Genre("спорт"),
Genre("супергерои"), Genre("супергерои"),
Genre("сэйнэн"), Genre("сэйнэн"),
Genre("сёдзё"), Genre("сёдзё"),
Genre("сёдзё-ай"), Genre("сёдзё-ай"),
Genre("сёнэн"), Genre("сёнэн"),
Genre("сёнэн-ай"), Genre("сёнэн-ай"),
Genre("тентакли"), Genre("тентакли"),
Genre("трагедия"), Genre("трагедия"),
Genre("триллер"), Genre("триллер"),
Genre("ужасы"), Genre("ужасы"),
Genre("фантастика"), Genre("фантастика"),
Genre("фурри"), Genre("фурри"),
Genre("фэнтези"), Genre("фэнтези"),
Genre("школа"), Genre("школа"),
Genre("эротика"), Genre("эротика"),
Genre("юри"), Genre("юри"),
Genre("яой"), Genre("яой"),
Genre("ёнкома") Genre("ёнкома")
) )
} }

View File

@ -1,184 +1,184 @@
package eu.kanade.tachiyomi.data.source.online.russian package eu.kanade.tachiyomi.source.online.russian
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
class Mintmanga : ParsedOnlineSource() { class Mintmanga : ParsedOnlineSource() {
override val id: Long = 6 override val id: Long = 6
override val name = "Mintmanga" override val name = "Mintmanga"
override val baseUrl = "http://mintmanga.com" override val baseUrl = "http://mintmanga.com"
override val lang = "ru" override val lang = "ru"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers)
} }
override fun popularMangaSelector() = "div.desc" override fun popularMangaSelector() = "div.desc"
override fun latestUpdatesSelector() = "div.desc" override fun latestUpdatesSelector() = "div.desc"
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "a.nextLink" override fun popularMangaNextPageSelector() = "a.nextLink"
override fun latestUpdatesNextPageSelector() = "a.nextLink" override fun latestUpdatesNextPageSelector() = "a.nextLink"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")
return GET("$baseUrl/search?q=$query&$genres", headers) return GET("$baseUrl/search?q=$query&$genres", headers)
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
// max 200 results // max 200 results
override fun searchMangaNextPageSelector() = null override fun searchMangaNextPageSelector() = null
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.leftContent").first() val infoElement = document.select("div.leftContent").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("span.elem_author").first()?.text() manga.author = infoElement.select("span.elem_author").first()?.text()
manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",")
manga.description = infoElement.select("div.manga-description").text() manga.description = infoElement.select("div.manga-description").text()
manga.status = parseStatus(infoElement.html()) manga.status = parseStatus(infoElement.html())
manga.thumbnail_url = infoElement.select("img").attr("data-full") manga.thumbnail_url = infoElement.select("img").attr("data-full")
return manga return manga
} }
private fun parseStatus(element: String): Int { private fun parseStatus(element: String): Int {
when { when {
element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED
element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED
element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING
else -> return SManga.UNKNOWN else -> return SManga.UNKNOWN
} }
} }
override fun chapterListSelector() = "div.chapters-link tbody tr" override fun chapterListSelector() = "div.chapters-link tbody tr"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
chapter.name = urlElement.text().replace(" новое", "") chapter.name = urlElement.text().replace(" новое", "")
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
} ?: 0 } ?: 0
return chapter return chapter
} }
override fun prepareNewChapter(chapter: SChapter, manga: SManga) { override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
chapter.chapter_number = -2f chapter.chapter_number = -2f
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val html = response.body().string() val html = response.body().string()
val beginIndex = html.indexOf("rm_h.init( [") val beginIndex = html.indexOf("rm_h.init( [")
val endIndex = html.indexOf("], 0, false);", beginIndex) val endIndex = html.indexOf("], 0, false);", beginIndex)
val trimmedHtml = html.substring(beginIndex, endIndex) val trimmedHtml = html.substring(beginIndex, endIndex)
val p = Pattern.compile("'.+?','.+?',\".+?\"") val p = Pattern.compile("'.+?','.+?',\".+?\"")
val m = p.matcher(trimmedHtml) val m = p.matcher(trimmedHtml)
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
var i = 0 var i = 0
while (m.find()) { while (m.find()) {
val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',')
pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2]))
} }
return pages return pages
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
throw Exception("Not used") throw Exception("Not used")
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
private class Genre(name: String, val id: String) : Filter.TriState(name) private class Genre(name: String, val id: String) : Filter.TriState(name)
/* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => {
* const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33);
* return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n')
* on http://mintmanga.com/search * on http://mintmanga.com/search
*/ */
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Genre("арт", "el_2220"), Genre("арт", "el_2220"),
Genre("бара", "el_1353"), Genre("бара", "el_1353"),
Genre("боевик", "el_1346"), Genre("боевик", "el_1346"),
Genre("боевые искусства", "el_1334"), Genre("боевые искусства", "el_1334"),
Genre("вампиры", "el_1339"), Genre("вампиры", "el_1339"),
Genre("гарем", "el_1333"), Genre("гарем", "el_1333"),
Genre("гендерная интрига", "el_1347"), Genre("гендерная интрига", "el_1347"),
Genre("героическое фэнтези", "el_1337"), Genre("героическое фэнтези", "el_1337"),
Genre("детектив", "el_1343"), Genre("детектив", "el_1343"),
Genre("дзёсэй", "el_1349"), Genre("дзёсэй", "el_1349"),
Genre("додзинси", "el_1332"), Genre("додзинси", "el_1332"),
Genre("драма", "el_1310"), Genre("драма", "el_1310"),
Genre("игра", "el_5229"), Genre("игра", "el_5229"),
Genre("история", "el_1311"), Genre("история", "el_1311"),
Genre("киберпанк", "el_1351"), Genre("киберпанк", "el_1351"),
Genre("комедия", "el_1328"), Genre("комедия", "el_1328"),
Genre("меха", "el_1318"), Genre("меха", "el_1318"),
Genre("мистика", "el_1324"), Genre("мистика", "el_1324"),
Genre("научная фантастика", "el_1325"), Genre("научная фантастика", "el_1325"),
Genre("повседневность", "el_1327"), Genre("повседневность", "el_1327"),
Genre("постапокалиптика", "el_1342"), Genre("постапокалиптика", "el_1342"),
Genre("приключения", "el_1322"), Genre("приключения", "el_1322"),
Genre("психология", "el_1335"), Genre("психология", "el_1335"),
Genre("романтика", "el_1313"), Genre("романтика", "el_1313"),
Genre("самурайский боевик", "el_1316"), Genre("самурайский боевик", "el_1316"),
Genre("сверхъестественное", "el_1350"), Genre("сверхъестественное", "el_1350"),
Genre("сёдзё", "el_1314"), Genre("сёдзё", "el_1314"),
Genre("сёдзё-ай", "el_1320"), Genre("сёдзё-ай", "el_1320"),
Genre("сёнэн", "el_1326"), Genre("сёнэн", "el_1326"),
Genre("сёнэн-ай", "el_1330"), Genre("сёнэн-ай", "el_1330"),
Genre("спорт", "el_1321"), Genre("спорт", "el_1321"),
Genre("сэйнэн", "el_1329"), Genre("сэйнэн", "el_1329"),
Genre("трагедия", "el_1344"), Genre("трагедия", "el_1344"),
Genre("триллер", "el_1341"), Genre("триллер", "el_1341"),
Genre("ужасы", "el_1317"), Genre("ужасы", "el_1317"),
Genre("фантастика", "el_1331"), Genre("фантастика", "el_1331"),
Genre("фэнтези", "el_1323"), Genre("фэнтези", "el_1323"),
Genre("школа", "el_1319"), Genre("школа", "el_1319"),
Genre("эротика", "el_1340"), Genre("эротика", "el_1340"),
Genre("этти", "el_1354"), Genre("этти", "el_1354"),
Genre("юри", "el_1315"), Genre("юри", "el_1315"),
Genre("яой", "el_1336") Genre("яой", "el_1336")
) )
} }

View File

@ -1,183 +1,183 @@
package eu.kanade.tachiyomi.data.source.online.russian package eu.kanade.tachiyomi.source.online.russian
import eu.kanade.tachiyomi.data.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.data.source.model.* import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.data.source.online.ParsedOnlineSource import eu.kanade.tachiyomi.source.online.ParsedOnlineSource
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*
import java.util.regex.Pattern import java.util.regex.Pattern
class Readmanga : ParsedOnlineSource() { class Readmanga : ParsedOnlineSource() {
override val id: Long = 5 override val id: Long = 5
override val name = "Readmanga" override val name = "Readmanga"
override val baseUrl = "http://readmanga.me" override val baseUrl = "http://readmanga.me"
override val lang = "ru" override val lang = "ru"
override val supportsLatest = true override val supportsLatest = true
override fun popularMangaSelector() = "div.desc" override fun popularMangaSelector() = "div.desc"
override fun latestUpdatesSelector() = "div.desc" override fun latestUpdatesSelector() = "div.desc"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers) return GET("$baseUrl/list?sortType=rate&offset=${70 * (page - 1)}&max=70", headers)
} }
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers) return GET("$baseUrl/list?sortType=updated&offset=${70 * (page - 1)}&max=70", headers)
} }
override fun popularMangaFromElement(element: Element): SManga { override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
element.select("h3 > a").first().let { element.select("h3 > a").first().let {
manga.setUrlWithoutDomain(it.attr("href")) manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.attr("title") manga.title = it.attr("title")
} }
return manga return manga
} }
override fun latestUpdatesFromElement(element: Element): SManga { override fun latestUpdatesFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
override fun popularMangaNextPageSelector() = "a.nextLink" override fun popularMangaNextPageSelector() = "a.nextLink"
override fun latestUpdatesNextPageSelector() = "a.nextLink" override fun latestUpdatesNextPageSelector() = "a.nextLink"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&") val genres = filters.filterIsInstance<Genre>().map { it.id + arrayOf("=", "=in", "=ex")[it.state] }.joinToString("&")
return GET("$baseUrl/search?q=$query&$genres", headers) return GET("$baseUrl/search?q=$query&$genres", headers)
} }
override fun searchMangaSelector() = popularMangaSelector() override fun searchMangaSelector() = popularMangaSelector()
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
return popularMangaFromElement(element) return popularMangaFromElement(element)
} }
// max 200 results // max 200 results
override fun searchMangaNextPageSelector() = null override fun searchMangaNextPageSelector() = null
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select("div.leftContent").first() val infoElement = document.select("div.leftContent").first()
val manga = SManga.create() val manga = SManga.create()
manga.author = infoElement.select("span.elem_author").first()?.text() manga.author = infoElement.select("span.elem_author").first()?.text()
manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",") manga.genre = infoElement.select("span.elem_genre").text().replace(" ,", ",")
manga.description = infoElement.select("div.manga-description").text() manga.description = infoElement.select("div.manga-description").text()
manga.status = parseStatus(infoElement.html()) manga.status = parseStatus(infoElement.html())
manga.thumbnail_url = infoElement.select("img").attr("data-full") manga.thumbnail_url = infoElement.select("img").attr("data-full")
return manga return manga
} }
private fun parseStatus(element: String): Int { private fun parseStatus(element: String): Int {
when { when {
element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED element.contains("<h3>Запрещена публикация произведения по копирайту</h3>") -> return SManga.LICENSED
element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED element.contains("<h1 class=\"names\"> Сингл") || element.contains("<b>Перевод:</b> завершен") -> return SManga.COMPLETED
element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING element.contains("<b>Перевод:</b> продолжается") -> return SManga.ONGOING
else -> return SManga.UNKNOWN else -> return SManga.UNKNOWN
} }
} }
override fun chapterListSelector() = "div.chapters-link tbody tr" override fun chapterListSelector() = "div.chapters-link tbody tr"
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first() val urlElement = element.select("a").first()
val chapter = SChapter.create() val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1") chapter.setUrlWithoutDomain(urlElement.attr("href") + "?mature=1")
chapter.name = urlElement.text().replace(" новое", "") chapter.name = urlElement.text().replace(" новое", "")
chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let { chapter.date_upload = element.select("td:eq(1)").first()?.text()?.let {
SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time SimpleDateFormat("dd/MM/yy", Locale.US).parse(it).time
} ?: 0 } ?: 0
return chapter return chapter
} }
override fun prepareNewChapter(chapter: SChapter, manga: SManga) { override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
chapter.chapter_number = -2f chapter.chapter_number = -2f
} }
override fun pageListParse(response: Response): List<Page> { override fun pageListParse(response: Response): List<Page> {
val html = response.body().string() val html = response.body().string()
val beginIndex = html.indexOf("rm_h.init( [") val beginIndex = html.indexOf("rm_h.init( [")
val endIndex = html.indexOf("], 0, false);", beginIndex) val endIndex = html.indexOf("], 0, false);", beginIndex)
val trimmedHtml = html.substring(beginIndex, endIndex) val trimmedHtml = html.substring(beginIndex, endIndex)
val p = Pattern.compile("'.+?','.+?',\".+?\"") val p = Pattern.compile("'.+?','.+?',\".+?\"")
val m = p.matcher(trimmedHtml) val m = p.matcher(trimmedHtml)
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
var i = 0 var i = 0
while (m.find()) { while (m.find()) {
val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',') val urlParts = m.group().replace("[\"\']+".toRegex(), "").split(',')
pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2])) pages.add(Page(i++, "", urlParts[1] + urlParts[0] + urlParts[2]))
} }
return pages return pages
} }
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
throw Exception("Not used") throw Exception("Not used")
} }
override fun imageUrlParse(document: Document) = "" override fun imageUrlParse(document: Document) = ""
private class Genre(name: String, val id: String) : Filter.TriState(name) private class Genre(name: String, val id: String) : Filter.TriState(name)
/* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => { /* [...document.querySelectorAll("tr.advanced_option:nth-child(1) > td:nth-child(3) span.js-link")].map((el,i) => {
* const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33); * const onClick=el.getAttribute('onclick');const id=onClick.substr(31,onClick.length-33);
* return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n') * return `Genre("${el.textContent.trim()}", "${id}")` }).join(',\n')
* on http://readmanga.me/search * on http://readmanga.me/search
*/ */
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Genre("арт", "el_5685"), Genre("арт", "el_5685"),
Genre("боевик", "el_2155"), Genre("боевик", "el_2155"),
Genre("боевые искусства", "el_2143"), Genre("боевые искусства", "el_2143"),
Genre("вампиры", "el_2148"), Genre("вампиры", "el_2148"),
Genre("гарем", "el_2142"), Genre("гарем", "el_2142"),
Genre("гендерная интрига", "el_2156"), Genre("гендерная интрига", "el_2156"),
Genre("героическое фэнтези", "el_2146"), Genre("героическое фэнтези", "el_2146"),
Genre("детектив", "el_2152"), Genre("детектив", "el_2152"),
Genre("дзёсэй", "el_2158"), Genre("дзёсэй", "el_2158"),
Genre("додзинси", "el_2141"), Genre("додзинси", "el_2141"),
Genre("драма", "el_2118"), Genre("драма", "el_2118"),
Genre("игра", "el_2154"), Genre("игра", "el_2154"),
Genre("история", "el_2119"), Genre("история", "el_2119"),
Genre("киберпанк", "el_8032"), Genre("киберпанк", "el_8032"),
Genre("кодомо", "el_2137"), Genre("кодомо", "el_2137"),
Genre("комедия", "el_2136"), Genre("комедия", "el_2136"),
Genre("махо-сёдзё", "el_2147"), Genre("махо-сёдзё", "el_2147"),
Genre("меха", "el_2126"), Genre("меха", "el_2126"),
Genre("мистика", "el_2132"), Genre("мистика", "el_2132"),
Genre("научная фантастика", "el_2133"), Genre("научная фантастика", "el_2133"),
Genre("повседневность", "el_2135"), Genre("повседневность", "el_2135"),
Genre("постапокалиптика", "el_2151"), Genre("постапокалиптика", "el_2151"),
Genre("приключения", "el_2130"), Genre("приключения", "el_2130"),
Genre("психология", "el_2144"), Genre("психология", "el_2144"),
Genre("романтика", "el_2121"), Genre("романтика", "el_2121"),
Genre("самурайский боевик", "el_2124"), Genre("самурайский боевик", "el_2124"),
Genre("сверхъестественное", "el_2159"), Genre("сверхъестественное", "el_2159"),
Genre("сёдзё", "el_2122"), Genre("сёдзё", "el_2122"),
Genre("сёдзё-ай", "el_2128"), Genre("сёдзё-ай", "el_2128"),
Genre("сёнэн", "el_2134"), Genre("сёнэн", "el_2134"),
Genre("сёнэн-ай", "el_2139"), Genre("сёнэн-ай", "el_2139"),
Genre("спорт", "el_2129"), Genre("спорт", "el_2129"),
Genre("сэйнэн", "el_2138"), Genre("сэйнэн", "el_2138"),
Genre("трагедия", "el_2153"), Genre("трагедия", "el_2153"),
Genre("триллер", "el_2150"), Genre("триллер", "el_2150"),
Genre("ужасы", "el_2125"), Genre("ужасы", "el_2125"),
Genre("фантастика", "el_2140"), Genre("фантастика", "el_2140"),
Genre("фэнтези", "el_2131"), Genre("фэнтези", "el_2131"),
Genre("школа", "el_2127"), Genre("школа", "el_2127"),
Genre("этти", "el_2149"), Genre("этти", "el_2149"),
Genre("юри", "el_2123") Genre("юри", "el_2123")
) )
} }

View File

@ -15,8 +15,8 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.IFlexible import eu.davidea.flexibleadapter.items.IFlexible
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers import rx.schedulers.Schedulers

View File

@ -8,13 +8,13 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.catalogue.filter.* import eu.kanade.tachiyomi.ui.catalogue.filter.*
import rx.Observable import rx.Observable

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.catalogue package eu.kanade.tachiyomi.ui.catalogue
import com.jakewharton.rxrelay.PublishRelay import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import rx.Observable import rx.Observable
/** /**

View File

@ -8,7 +8,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<CheckboxItem.Holder>() { open class CheckboxItem(val filter: Filter.CheckBox) : AbstractFlexibleItem<CheckboxItem.Holder>() {

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.flexibleadapter.items.ISectionable
import eu.davidea.viewholders.ExpandableViewHolder import eu.davidea.viewholders.ExpandableViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.setVectorCompat import eu.kanade.tachiyomi.util.setVectorCompat
class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() { class GroupItem(val filter: Filter.Group<*>) : AbstractExpandableHeaderItem<GroupItem.Holder, ISectionable<*, *>>() {

View File

@ -9,7 +9,7 @@ import android.widget.TextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() { class HeaderItem(val filter: Filter.Header) : AbstractHeaderItem<HeaderItem.Holder>() {

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.catalogue.filter package eu.kanade.tachiyomi.ui.catalogue.filter
import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable<TriStateItem.Holder, GroupItem> { class TriStateSectionItem(filter: Filter.TriState) : TriStateItem(filter), ISectionable<TriStateItem.Holder, GroupItem> {

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener import eu.kanade.tachiyomi.widget.IgnoreFirstSpinnerListener
open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() { open class SelectItem(val filter: Filter.Select<*>) : AbstractFlexibleItem<SelectItem.Holder>() {

View File

@ -8,7 +8,7 @@ import android.view.ViewGroup
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractHeaderItem import eu.davidea.flexibleadapter.items.AbstractHeaderItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<SeparatorItem.Holder>() { class SeparatorItem(val filter: Filter.Separator) : AbstractHeaderItem<SeparatorItem.Holder>() {

View File

@ -7,7 +7,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem import eu.davidea.flexibleadapter.items.AbstractExpandableHeaderItem
import eu.davidea.flexibleadapter.items.ISectionable import eu.davidea.flexibleadapter.items.ISectionable
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.setVectorCompat import eu.kanade.tachiyomi.util.setVectorCompat
class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() { class SortGroup(val filter: Filter.Sort) : AbstractExpandableHeaderItem<SortGroup.Holder, ISectionable<*, *>>() {

View File

@ -10,7 +10,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractSectionableItem import eu.davidea.flexibleadapter.items.AbstractSectionableItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) { class SortItem(val name: String, val group: SortGroup) : AbstractSectionableItem<SortItem.Holder, SortGroup>(group) {

View File

@ -9,7 +9,7 @@ import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.widget.SimpleTextWatcher import eu.kanade.tachiyomi.widget.SimpleTextWatcher
open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() { open class TextItem(val filter: Filter.Text) : AbstractFlexibleItem<TextItem.Holder>() {

View File

@ -9,7 +9,7 @@ import android.widget.CheckedTextView
import eu.davidea.flexibleadapter.FlexibleAdapter import eu.davidea.flexibleadapter.FlexibleAdapter
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem import eu.davidea.flexibleadapter.items.AbstractFlexibleItem
import eu.davidea.viewholders.FlexibleViewHolder import eu.davidea.viewholders.FlexibleViewHolder
import eu.kanade.tachiyomi.data.source.model.Filter import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.util.dpToPx import eu.kanade.tachiyomi.util.dpToPx
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.R as TR import eu.kanade.tachiyomi.R as TR

View File

@ -7,7 +7,7 @@ import android.view.MenuItem
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.plusAssign
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.latest_updates package eu.kanade.tachiyomi.ui.latest_updates
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.model.MangasPage import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.ui.catalogue.Pager import eu.kanade.tachiyomi.ui.catalogue.Pager
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers

View File

@ -1,8 +1,8 @@
package eu.kanade.tachiyomi.ui.latest_updates package eu.kanade.tachiyomi.ui.latest_updates
import eu.kanade.tachiyomi.data.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.model.FilterList import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter import eu.kanade.tachiyomi.ui.catalogue.CataloguePresenter
import eu.kanade.tachiyomi.ui.catalogue.Pager import eu.kanade.tachiyomi.ui.catalogue.Pager

View File

@ -13,7 +13,7 @@ import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.combineLatest import eu.kanade.tachiyomi.util.combineLatest
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.isNullOrUnsubscribed

View File

@ -9,8 +9,8 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.manga.MangaEvent
import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent import eu.kanade.tachiyomi.ui.manga.info.ChapterCountEvent

View File

@ -14,9 +14,9 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.CenterCrop import com.bumptech.glide.load.resource.bitmap.CenterCrop
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment import eu.kanade.tachiyomi.ui.base.fragment.BaseRxFragment
import eu.kanade.tachiyomi.ui.manga.MangaActivity import eu.kanade.tachiyomi.ui.manga.MangaActivity
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor

View File

@ -4,8 +4,8 @@ import android.os.Bundle
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.manga.MangaEvent import eu.kanade.tachiyomi.ui.manga.MangaEvent
import eu.kanade.tachiyomi.util.SharedData import eu.kanade.tachiyomi.util.SharedData

View File

@ -2,11 +2,11 @@ package eu.kanade.tachiyomi.ui.reader
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.data.source.online.fetchImageFromCacheThenNet import eu.kanade.tachiyomi.source.online.fetchImageFromCacheThenNet
import eu.kanade.tachiyomi.data.source.online.fetchPageListFromCacheThenNet import eu.kanade.tachiyomi.source.online.fetchPageListFromCacheThenNet
import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.plusAssign
import rx.Observable import rx.Observable
import rx.schedulers.Schedulers import rx.schedulers.Schedulers

View File

@ -20,7 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity import eu.kanade.tachiyomi.ui.base.activity.BaseRxActivity
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader

View File

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.ui.reader package eu.kanade.tachiyomi.ui.reader
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
class ReaderChapter(c: Chapter) : Chapter by c { class ReaderChapter(c: Chapter) : Chapter by c {

View File

@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.data.track.TrackUpdateService import eu.kanade.tachiyomi.data.track.TrackUpdateService
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter

View File

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.reader.viewer.base
import com.davemorrissey.labs.subscaleview.decoder.* import com.davemorrissey.labs.subscaleview.decoder.*
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment import eu.kanade.tachiyomi.ui.base.fragment.BaseFragment
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.ReaderChapter

View File

@ -4,7 +4,7 @@ import android.net.Uri
import android.support.v4.content.ContextCompat import android.support.v4.content.ContextCompat
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import kotlinx.android.synthetic.main.page_decode_error.view.* import kotlinx.android.synthetic.main.page_decode_error.view.*

View File

@ -10,7 +10,7 @@ import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.RightToLeftReader

View File

@ -6,7 +6,7 @@ import android.view.MotionEvent
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader import eu.kanade.tachiyomi.ui.reader.viewer.pager.horizontal.LeftToRightReader

View File

@ -4,7 +4,7 @@ import android.support.v4.view.PagerAdapter
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import eu.kanade.tachiyomi.widget.ViewPagerAdapter import eu.kanade.tachiyomi.widget.ViewPagerAdapter

View File

@ -4,7 +4,7 @@ import android.support.v7.widget.RecyclerView
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
/** /**

View File

@ -8,7 +8,7 @@ import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
import com.hippo.unifile.UniFile import com.hippo.unifile.UniFile
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.ReaderActivity import eu.kanade.tachiyomi.ui.reader.ReaderActivity
import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout import eu.kanade.tachiyomi.ui.reader.viewer.base.PageDecodeErrorLayout
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate

View File

@ -5,7 +5,7 @@ import android.support.v7.widget.RecyclerView
import android.view.* import android.view.*
import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import eu.kanade.tachiyomi.data.source.model.Page import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.ui.reader.ReaderChapter import eu.kanade.tachiyomi.ui.reader.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader import eu.kanade.tachiyomi.ui.reader.viewer.base.BaseReader
import eu.kanade.tachiyomi.widget.PreCachingLayoutManager import eu.kanade.tachiyomi.widget.PreCachingLayoutManager

View File

@ -8,7 +8,7 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import rx.Observable import rx.Observable
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers

View File

@ -4,7 +4,7 @@ import android.view.ViewGroup
import eu.davidea.flexibleadapter4.FlexibleAdapter import eu.davidea.flexibleadapter4.FlexibleAdapter
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory import eu.kanade.tachiyomi.data.database.models.MangaChapterHistory
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.util.inflate import eu.kanade.tachiyomi.util.inflate
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy

View File

@ -9,7 +9,7 @@ import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.library.LibraryUpdateService import eu.kanade.tachiyomi.data.library.LibraryUpdateService
import eu.kanade.tachiyomi.data.network.NetworkHelper import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.util.plusAssign import eu.kanade.tachiyomi.util.plusAssign
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import rx.Observable import rx.Observable

View File

@ -7,8 +7,8 @@ import android.support.v7.preference.XpPreferenceFragment
import android.view.View import android.view.View
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference import eu.kanade.tachiyomi.widget.preference.LoginCheckBoxPreference
import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog import eu.kanade.tachiyomi.widget.preference.SourceLoginDialog
import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory import eu.kanade.tachiyomi.widget.preference.SwitchPreferenceCategory

View File

@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.util
import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import java.util.* import java.util.*
/** /**

View File

@ -6,8 +6,8 @@ import android.support.v7.preference.PreferenceViewHolder
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import eu.kanade.tachiyomi.util.getResourceColor import eu.kanade.tachiyomi.util.getResourceColor
import eu.kanade.tachiyomi.util.setVectorCompat import eu.kanade.tachiyomi.util.setVectorCompat
import kotlinx.android.synthetic.main.pref_item_source.view.* import kotlinx.android.synthetic.main.pref_item_source.view.*

View File

@ -3,9 +3,9 @@ package eu.kanade.tachiyomi.widget.preference
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.online.LoginSource import eu.kanade.tachiyomi.source.online.LoginSource
import eu.kanade.tachiyomi.util.toast import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.pref_account_login.view.* import kotlinx.android.synthetic.main.pref_account_login.view.*
import rx.android.schedulers.AndroidSchedulers import rx.android.schedulers.AndroidSchedulers

View File

@ -8,9 +8,9 @@ import eu.kanade.tachiyomi.BuildConfig
import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner import eu.kanade.tachiyomi.CustomRobolectricGradleTestRunner
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.data.source.model.SChapter import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.data.source.online.OnlineSource import eu.kanade.tachiyomi.source.online.OnlineSource
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Before import org.junit.Before
import org.junit.Test import org.junit.Test