mirror of
				https://github.com/mihonapp/mihon.git
				synced 2025-10-31 22:37:56 +01:00 
			
		
		
		
	Downloading extensions from Github Repo. (#1101)
Downloading extensions from Github Repo.
This commit is contained in:
		| @@ -0,0 +1,8 @@ | ||||
| package eu.kanade.tachiyomi.source | ||||
|  | ||||
| import android.support.v7.preference.PreferenceScreen | ||||
|  | ||||
| interface ConfigurableSource : Source { | ||||
|  | ||||
|     fun setupPreferenceScreen(screen: PreferenceScreen) | ||||
| } | ||||
| @@ -1,30 +1,19 @@ | ||||
| package eu.kanade.tachiyomi.source | ||||
|  | ||||
| import android.Manifest.permission.READ_EXTERNAL_STORAGE | ||||
| import android.content.Context | ||||
| import android.content.pm.ApplicationInfo | ||||
| import android.content.pm.PackageManager | ||||
| import android.os.Environment | ||||
| import dalvik.system.PathClassLoader | ||||
| import eu.kanade.tachiyomi.R | ||||
| import eu.kanade.tachiyomi.source.online.HttpSource | ||||
| import eu.kanade.tachiyomi.source.online.YamlHttpSource | ||||
| import eu.kanade.tachiyomi.source.online.english.* | ||||
| import eu.kanade.tachiyomi.source.online.german.WieManga | ||||
| import eu.kanade.tachiyomi.source.online.russian.Mangachan | ||||
| import eu.kanade.tachiyomi.source.online.russian.Mintmanga | ||||
| import eu.kanade.tachiyomi.source.online.russian.Readmanga | ||||
| import eu.kanade.tachiyomi.util.hasPermission | ||||
| import org.yaml.snakeyaml.Yaml | ||||
| import timber.log.Timber | ||||
| import java.io.File | ||||
|  | ||||
| open class SourceManager(private val context: Context) { | ||||
|  | ||||
|     private val sourcesMap = mutableMapOf<Long, Source>() | ||||
|  | ||||
|     init { | ||||
|         createSources() | ||||
|         createInternalSources().forEach { registerSource(it) } | ||||
|     } | ||||
|  | ||||
|     open fun get(sourceKey: Long): Source? { | ||||
| @@ -35,18 +24,16 @@ open class SourceManager(private val context: Context) { | ||||
|  | ||||
|     fun getCatalogueSources() = sourcesMap.values.filterIsInstance<CatalogueSource>() | ||||
|  | ||||
|     private fun createSources() { | ||||
|         createExtensionSources().forEach { registerSource(it) } | ||||
|         createYamlSources().forEach { registerSource(it) } | ||||
|         createInternalSources().forEach { registerSource(it) } | ||||
|     } | ||||
|  | ||||
|     private fun registerSource(source: Source, overwrite: Boolean = false) { | ||||
|     internal fun registerSource(source: Source, overwrite: Boolean = false) { | ||||
|         if (overwrite || !sourcesMap.containsKey(source.id)) { | ||||
|             sourcesMap.put(source.id, source) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     internal fun unregisterSource(source: Source) { | ||||
|         sourcesMap.remove(source.id) | ||||
|     } | ||||
|  | ||||
|     private fun createInternalSources(): List<Source> = listOf( | ||||
|             LocalSource(context), | ||||
|             Batoto(), | ||||
| @@ -60,92 +47,4 @@ open class SourceManager(private val context: Context) { | ||||
|             Mangasee(), | ||||
|             WieManga() | ||||
|     ) | ||||
|  | ||||
|     private fun createYamlSources(): List<Source> { | ||||
|         val sources = mutableListOf<Source>() | ||||
|  | ||||
|         val parsersDir = File(Environment.getExternalStorageDirectory().absolutePath + | ||||
|                 File.separator + context.getString(R.string.app_name), "parsers") | ||||
|  | ||||
|         if (parsersDir.exists() && context.hasPermission(READ_EXTERNAL_STORAGE)) { | ||||
|             val yaml = Yaml() | ||||
|             for (file in parsersDir.listFiles().filter { it.extension == "yml" }) { | ||||
|                 try { | ||||
|                     val map = file.inputStream().use { yaml.loadAs(it, Map::class.java) } | ||||
|                     sources.add(YamlHttpSource(map)) | ||||
|                 } catch (e: Exception) { | ||||
|                     Timber.e("Error loading source from file. Bad format?", e) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
|  | ||||
|     private fun createExtensionSources(): List<Source> { | ||||
|         val pkgManager = context.packageManager | ||||
|         val flags = PackageManager.GET_CONFIGURATIONS or PackageManager.GET_SIGNATURES | ||||
|         val installedPkgs = pkgManager.getInstalledPackages(flags) | ||||
|         val extPkgs = installedPkgs.filter { it.reqFeatures.orEmpty().any { it.name == EXTENSION_FEATURE } } | ||||
|  | ||||
|         val sources = mutableListOf<Source>() | ||||
|         for (pkgInfo in extPkgs) { | ||||
|             val appInfo = pkgManager.getApplicationInfo(pkgInfo.packageName, | ||||
|                     PackageManager.GET_META_DATA) ?: continue | ||||
|  | ||||
|             val extName = pkgManager.getApplicationLabel(appInfo).toString() | ||||
|                     .substringAfter("Tachiyomi: ") | ||||
|             val version = pkgInfo.versionName | ||||
|             val sourceClasses = appInfo.metaData.getString(METADATA_SOURCE_CLASS) | ||||
|                     .split(";") | ||||
|                     .map { | ||||
|                         val sourceClass = it.trim() | ||||
|                         if(sourceClass.startsWith(".")) | ||||
|                             pkgInfo.packageName + sourceClass | ||||
|                         else | ||||
|                             sourceClass | ||||
|                     } | ||||
|  | ||||
|             val extension = Extension(extName, appInfo, version, sourceClasses) | ||||
|             try { | ||||
|                 sources += loadExtension(extension) | ||||
|             } catch (e: Exception) { | ||||
|                 Timber.e("Extension load error: $extName.", e) | ||||
|             } catch (e: LinkageError) { | ||||
|                 Timber.e("Extension load error: $extName.", e) | ||||
|             } | ||||
|         } | ||||
|         return sources | ||||
|     } | ||||
|  | ||||
|     private fun loadExtension(ext: Extension): List<Source> { | ||||
|         // Validate lib version | ||||
|         val majorLibVersion = ext.version.substringBefore('.').toInt() | ||||
|         if (majorLibVersion < LIB_VERSION_MIN || majorLibVersion > LIB_VERSION_MAX) { | ||||
|             throw Exception("Lib version is $majorLibVersion, while only versions " | ||||
|                     + "$LIB_VERSION_MIN to $LIB_VERSION_MAX are allowed") | ||||
|         } | ||||
|  | ||||
|         val classLoader = PathClassLoader(ext.appInfo.sourceDir, null, context.classLoader) | ||||
|         return ext.sourceClasses.flatMap { | ||||
|             val obj = Class.forName(it, false, classLoader).newInstance() | ||||
|             when(obj) { | ||||
|                 is Source -> listOf(obj) | ||||
|                 is SourceFactory -> obj.createSources() | ||||
|                 else -> throw Exception("Unknown source class type!") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     class Extension(val name: String, | ||||
|                     val appInfo: ApplicationInfo, | ||||
|                     val version: String, | ||||
|                     val sourceClasses: List<String>) | ||||
|  | ||||
|     private companion object { | ||||
|         const val EXTENSION_FEATURE = "tachiyomi.extension" | ||||
|         const val METADATA_SOURCE_CLASS = "tachiyomi.extension.class" | ||||
|         const val LIB_VERSION_MIN = 1 | ||||
|         const val LIB_VERSION_MAX = 1 | ||||
|     } | ||||
|  | ||||
| } | ||||
|   | ||||
| @@ -1,6 +1,5 @@ | ||||
| package eu.kanade.tachiyomi.source.online | ||||
|  | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.NetworkHelper | ||||
| import eu.kanade.tachiyomi.network.asObservableSuccess | ||||
| @@ -28,10 +27,12 @@ abstract class HttpSource : CatalogueSource { | ||||
|      */ | ||||
|     protected val network: NetworkHelper by injectLazy() | ||||
|  | ||||
|     /** | ||||
|      * Preferences helper. | ||||
|      */ | ||||
|     protected val preferences: PreferencesHelper by injectLazy() | ||||
| //    /** | ||||
| //     * Preferences that a source may need. | ||||
| //     */ | ||||
| //    val preferences: SharedPreferences by lazy { | ||||
| //        Injekt.get<Application>().getSharedPreferences("source_$id", Context.MODE_PRIVATE) | ||||
| //    } | ||||
|  | ||||
|     /** | ||||
|      * Base url of the website without the trailing slash, like: http://mysite.com | ||||
|   | ||||
| @@ -1,231 +0,0 @@ | ||||
| package eu.kanade.tachiyomi.source.online | ||||
|  | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.POST | ||||
| import eu.kanade.tachiyomi.source.model.* | ||||
| import eu.kanade.tachiyomi.util.asJsoup | ||||
| import eu.kanade.tachiyomi.util.attrOrText | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import org.jsoup.Jsoup | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| class YamlHttpSource(mappings: Map<*, *>) : HttpSource() { | ||||
|  | ||||
|     val map = YamlSourceNode(mappings) | ||||
|  | ||||
|     override val name: String | ||||
|         get() = map.name | ||||
|  | ||||
|     override val baseUrl = map.host.let { | ||||
|         if (it.endsWith("/")) it.dropLast(1) else it | ||||
|     } | ||||
|  | ||||
|     override val lang = map.lang.toLowerCase() | ||||
|  | ||||
|     override val supportsLatest = map.latestupdates != null | ||||
|  | ||||
|     override val client = when (map.client) { | ||||
|         "cloudflare" -> network.cloudflareClient | ||||
|         else -> network.client | ||||
|     } | ||||
|  | ||||
|     override val id = map.id.let { | ||||
|         (it as? Int ?: (lang.toUpperCase().hashCode() + 31 * it.hashCode()) and 0x7fffffff).toLong() | ||||
|     } | ||||
|  | ||||
|     // Ugly, but needed after the changes | ||||
|     var popularNextPage: String? = null | ||||
|     var searchNextPage: String? = null | ||||
|     var latestNextPage: String? = null | ||||
|  | ||||
|     override fun popularMangaRequest(page: Int): Request { | ||||
|         val url = if (page == 1) { | ||||
|             popularNextPage = null | ||||
|             map.popular.url | ||||
|         } else { | ||||
|             popularNextPage!! | ||||
|         } | ||||
|         return when (map.popular.method?.toLowerCase()) { | ||||
|             "post" -> POST(url, headers, map.popular.createForm()) | ||||
|             else -> GET(url, headers) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun popularMangaParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         val mangas = document.select(map.popular.manga_css).map { element -> | ||||
|             SManga.create().apply { | ||||
|                 title = element.text() | ||||
|                 setUrlWithoutDomain(element.attr("href")) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         popularNextPage = map.popular.next_url_css?.let { selector -> | ||||
|              document.select(selector).first()?.absUrl("href") | ||||
|         } | ||||
|  | ||||
|         return MangasPage(mangas, popularNextPage != null) | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { | ||||
|         val url = if (page == 1) { | ||||
|             searchNextPage = null | ||||
|             map.search.url.replace("\$query", query) | ||||
|         } else { | ||||
|             searchNextPage!! | ||||
|         } | ||||
|         return when (map.search.method?.toLowerCase()) { | ||||
|             "post" -> POST(url, headers, map.search.createForm()) | ||||
|             else -> GET(url, headers) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun searchMangaParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         val mangas = document.select(map.search.manga_css).map { element -> | ||||
|             SManga.create().apply { | ||||
|                 title = element.text() | ||||
|                 setUrlWithoutDomain(element.attr("href")) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         searchNextPage = map.search.next_url_css?.let { selector -> | ||||
|             document.select(selector).first()?.absUrl("href") | ||||
|         } | ||||
|  | ||||
|         return MangasPage(mangas, searchNextPage != null) | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesRequest(page: Int): Request { | ||||
|         val url = if (page == 1) { | ||||
|             latestNextPage = null | ||||
|             map.latestupdates!!.url | ||||
|         } else { | ||||
|             latestNextPage!! | ||||
|         } | ||||
|         return when (map.latestupdates!!.method?.toLowerCase()) { | ||||
|             "post" -> POST(url, headers, map.latestupdates.createForm()) | ||||
|             else -> GET(url, headers) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     override fun latestUpdatesParse(response: Response): MangasPage { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         val mangas = document.select(map.latestupdates!!.manga_css).map { element -> | ||||
|             SManga.create().apply { | ||||
|                 title = element.text() | ||||
|                 setUrlWithoutDomain(element.attr("href")) | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         popularNextPage = map.latestupdates.next_url_css?.let { selector -> | ||||
|             document.select(selector).first()?.absUrl("href") | ||||
|         } | ||||
|  | ||||
|         return MangasPage(mangas, popularNextPage != null) | ||||
|     } | ||||
|  | ||||
|     override fun mangaDetailsParse(response: Response): SManga { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         val manga = SManga.create() | ||||
|         with(map.manga) { | ||||
|             val pool = parts.get(document) | ||||
|  | ||||
|             manga.author = author?.process(document, pool) | ||||
|             manga.artist = artist?.process(document, pool) | ||||
|             manga.description = summary?.process(document, pool) | ||||
|             manga.thumbnail_url = cover?.process(document, pool) | ||||
|             manga.genre = genres?.process(document, pool) | ||||
|             manga.status = status?.getStatus(document, pool) ?: SManga.UNKNOWN | ||||
|         } | ||||
|         return manga | ||||
|     } | ||||
|  | ||||
|     override fun chapterListParse(response: Response): List<SChapter> { | ||||
|         val document = response.asJsoup() | ||||
|  | ||||
|         val chapters = mutableListOf<SChapter>() | ||||
|         with(map.chapters) { | ||||
|             val pool = emptyMap<String, Element>() | ||||
|             val dateFormat = SimpleDateFormat(date?.format, Locale.ENGLISH) | ||||
|  | ||||
|             for (element in document.select(chapter_css)) { | ||||
|                 val chapter = SChapter.create() | ||||
|                 element.select(title).first().let { | ||||
|                     chapter.name = it.text() | ||||
|                     chapter.setUrlWithoutDomain(it.attr("href")) | ||||
|                 } | ||||
|                 val dateElement = element.select(date?.select).first() | ||||
|                 chapter.date_upload = date?.getDate(dateElement, pool, dateFormat)?.time ?: 0 | ||||
|                 chapters.add(chapter) | ||||
|             } | ||||
|         } | ||||
|         return chapters | ||||
|     } | ||||
|  | ||||
|     override fun pageListParse(response: Response): List<Page> { | ||||
|         val body = response.body()!!.string() | ||||
|         val url = response.request().url().toString() | ||||
|  | ||||
|         val pages = mutableListOf<Page>() | ||||
|  | ||||
|         val document by lazy { Jsoup.parse(body, url) } | ||||
|  | ||||
|         with(map.pages) { | ||||
|             // Capture a list of values where page urls will be resolved. | ||||
|             val capturedPages = if (pages_regex != null) | ||||
|                 pages_regex!!.toRegex().findAll(body).map { it.value }.toList() | ||||
|             else if (pages_css != null) | ||||
|                 document.select(pages_css).map { it.attrOrText(pages_attr!!) } | ||||
|             else | ||||
|                 null | ||||
|  | ||||
|             // For each captured value, obtain the url and create a new page. | ||||
|             capturedPages?.forEach { value -> | ||||
|                 // If the captured value isn't an url, we have to use replaces with the chapter url. | ||||
|                 val pageUrl = if (replace != null && replacement != null) | ||||
|                     url.replace(replace!!.toRegex(), replacement!!.replace("\$value", value)) | ||||
|                 else | ||||
|                     value | ||||
|  | ||||
|                 pages.add(Page(pages.size, pageUrl)) | ||||
|             } | ||||
|  | ||||
|             // Capture a list of images. | ||||
|             val capturedImages = if (image_regex != null) | ||||
|                 image_regex!!.toRegex().findAll(body).map { it.groups[1]?.value }.toList() | ||||
|             else if (image_css != null) | ||||
|                 document.select(image_css).map { it.absUrl(image_attr) } | ||||
|             else | ||||
|                 null | ||||
|  | ||||
|             // Assign the image url to each page | ||||
|             capturedImages?.forEachIndexed { i, url -> | ||||
|                 val page = pages.getOrElse(i) { Page(i, "").apply { pages.add(this) } } | ||||
|                 page.imageUrl = url | ||||
|             } | ||||
|         } | ||||
|         return pages | ||||
|     } | ||||
|  | ||||
|     override fun imageUrlParse(response: Response): String { | ||||
|         val body = response.body()!!.string() | ||||
|         val url = response.request().url().toString() | ||||
|  | ||||
|         with(map.pages) { | ||||
|             return if (image_regex != null) | ||||
|                 image_regex!!.toRegex().find(body)!!.groups[1]!!.value | ||||
|             else if (image_css != null) | ||||
|                 Jsoup.parse(body, url).select(image_css).first().absUrl(image_attr) | ||||
|             else | ||||
|                 throw Exception("image_regex and image_css are null") | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,234 +0,0 @@ | ||||
| @file:Suppress("UNCHECKED_CAST") | ||||
|  | ||||
| package eu.kanade.tachiyomi.source.online | ||||
|  | ||||
| import eu.kanade.tachiyomi.source.model.SManga | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.RequestBody | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import java.text.ParseException | ||||
| import java.text.SimpleDateFormat | ||||
| import java.util.* | ||||
|  | ||||
| private fun toMap(map: Any?) = map as? Map<String, Any?> | ||||
|  | ||||
| class YamlSourceNode(uncheckedMap: Map<*, *>) { | ||||
|  | ||||
|     val map = toMap(uncheckedMap)!! | ||||
|  | ||||
|     val id: Any by map | ||||
|  | ||||
|     val name: String by map | ||||
|  | ||||
|     val host: String by map | ||||
|  | ||||
|     val lang: String by map | ||||
|  | ||||
|     val client: String? | ||||
|         get() = map["client"] as? String | ||||
|  | ||||
|     val popular = PopularNode(toMap(map["popular"])!!) | ||||
|  | ||||
|     val latestupdates = toMap(map["latest_updates"])?.let { LatestUpdatesNode(it) } | ||||
|  | ||||
|     val search = SearchNode(toMap(map["search"])!!) | ||||
|  | ||||
|     val manga = MangaNode(toMap(map["manga"])!!) | ||||
|  | ||||
|     val chapters = ChaptersNode(toMap(map["chapters"])!!) | ||||
|  | ||||
|     val pages = PagesNode(toMap(map["pages"])!!) | ||||
| } | ||||
|  | ||||
| interface RequestableNode { | ||||
|  | ||||
|     val map: Map<String, Any?> | ||||
|  | ||||
|     val url: String | ||||
|         get() = map["url"] as String | ||||
|  | ||||
|     val method: String? | ||||
|         get() = map["method"] as? String | ||||
|  | ||||
|     val payload: Map<String, String>? | ||||
|         get() = map["payload"] as? Map<String, String> | ||||
|  | ||||
|     fun createForm(): RequestBody { | ||||
|         return FormBody.Builder().apply { | ||||
|             payload?.let { | ||||
|                 for ((key, value) in it) { | ||||
|                     add(key, value) | ||||
|                 } | ||||
|             } | ||||
|         }.build() | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| class PopularNode(override val map: Map<String, Any?>): RequestableNode { | ||||
|  | ||||
|     val manga_css: String by map | ||||
|  | ||||
|     val next_url_css: String? | ||||
|         get() = map["next_url_css"] as? String | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class LatestUpdatesNode(override val map: Map<String, Any?>): RequestableNode { | ||||
|  | ||||
|     val manga_css: String by map | ||||
|  | ||||
|     val next_url_css: String? | ||||
|         get() = map["next_url_css"] as? String | ||||
|  | ||||
| } | ||||
|  | ||||
|  | ||||
| class SearchNode(override val map: Map<String, Any?>): RequestableNode { | ||||
|  | ||||
|     val manga_css: String by map | ||||
|  | ||||
|     val next_url_css: String? | ||||
|         get() = map["next_url_css"] as? String | ||||
| } | ||||
|  | ||||
| class MangaNode(private val map: Map<String, Any?>) { | ||||
|  | ||||
|     val parts = CacheNode(toMap(map["parts"]) ?: emptyMap()) | ||||
|  | ||||
|     val artist = toMap(map["artist"])?.let { SelectableNode(it) } | ||||
|  | ||||
|     val author = toMap(map["author"])?.let { SelectableNode(it) } | ||||
|  | ||||
|     val summary = toMap(map["summary"])?.let { SelectableNode(it) } | ||||
|  | ||||
|     val status = toMap(map["status"])?.let { StatusNode(it) } | ||||
|  | ||||
|     val genres = toMap(map["genres"])?.let { SelectableNode(it) } | ||||
|  | ||||
|     val cover = toMap(map["cover"])?.let { CoverNode(it) } | ||||
|  | ||||
| } | ||||
|  | ||||
| class ChaptersNode(private val map: Map<String, Any?>) { | ||||
|  | ||||
|     val chapter_css: String by map | ||||
|  | ||||
|     val title: String by map | ||||
|  | ||||
|     val date = toMap(toMap(map["date"]))?.let { DateNode(it) } | ||||
| } | ||||
|  | ||||
| class CacheNode(private val map: Map<String, Any?>) { | ||||
|  | ||||
|     fun get(document: Document) = map.mapValues { document.select(it.value as String).first() } | ||||
| } | ||||
|  | ||||
| open class SelectableNode(private val map: Map<String, Any?>) { | ||||
|  | ||||
|     val select: String by map | ||||
|  | ||||
|     val from: String? | ||||
|         get() = map["from"] as? String | ||||
|  | ||||
|     open val attr: String? | ||||
|         get() = map["attr"] as? String | ||||
|  | ||||
|     val capture: String? | ||||
|         get() = map["capture"] as? String | ||||
|  | ||||
|     fun process(document: Element, cache: Map<String, Element>): String { | ||||
|         val parent = from?.let { cache[it] } ?: document | ||||
|         val node = parent.select(select).first() | ||||
|         var text = attr?.let { node.attr(it) } ?: node.text() | ||||
|         capture?.let { | ||||
|             text = Regex(it).find(text)?.groupValues?.get(1) ?: text | ||||
|         } | ||||
|         return text | ||||
|     } | ||||
| } | ||||
|  | ||||
| class StatusNode(private val map: Map<String, Any?>) : SelectableNode(map) { | ||||
|  | ||||
|     val complete: String? | ||||
|         get() = map["complete"] as? String | ||||
|  | ||||
|     val ongoing: String? | ||||
|         get() = map["ongoing"] as? String | ||||
|  | ||||
|     val licensed: String? | ||||
|         get() = map["licensed"] as? String | ||||
|  | ||||
|     fun getStatus(document: Element, cache: Map<String, Element>): Int { | ||||
|         val text = process(document, cache) | ||||
|         complete?.let { | ||||
|             if (text.contains(it)) return SManga.COMPLETED | ||||
|         } | ||||
|         ongoing?.let { | ||||
|             if (text.contains(it)) return SManga.ONGOING | ||||
|         } | ||||
|         licensed?.let { | ||||
|             if (text.contains(it)) return SManga.LICENSED | ||||
|         } | ||||
|         return SManga.UNKNOWN | ||||
|     } | ||||
| } | ||||
|  | ||||
| class CoverNode(private val map: Map<String, Any?>) : SelectableNode(map) { | ||||
|  | ||||
|     override val attr: String? | ||||
|         get() = map["attr"] as? String ?: "src" | ||||
| } | ||||
|  | ||||
| class DateNode(private val map: Map<String, Any?>) : SelectableNode(map) { | ||||
|  | ||||
|     val format: String by map | ||||
|  | ||||
|     fun getDate(document: Element, cache: Map<String, Element>, formatter: SimpleDateFormat): Date { | ||||
|         val text = process(document, cache) | ||||
|         try { | ||||
|             return formatter.parse(text) | ||||
|         } catch (exception: ParseException) {} | ||||
|  | ||||
|         for (i in 0..7) { | ||||
|             (map["day$i"] as? List<String>)?.let { | ||||
|                 it.find { it.toRegex().containsMatchIn(text) }?.let { | ||||
|                     return Calendar.getInstance().apply { add(Calendar.DATE, -i) }.time | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         return Date(0) | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| class PagesNode(private val map: Map<String, Any?>) { | ||||
|  | ||||
|     val pages_regex: String? | ||||
|         get() = map["pages_regex"] as? String | ||||
|  | ||||
|     val pages_css: String? | ||||
|         get() = map["pages_css"] as? String | ||||
|  | ||||
|     val pages_attr: String? | ||||
|         get() = map["pages_attr"] as? String ?: "value" | ||||
|  | ||||
|     val replace: String? | ||||
|         get() = map["url_replace"] as? String | ||||
|  | ||||
|     val replacement: String? | ||||
|         get() = map["url_replacement"] as? String | ||||
|  | ||||
|     val image_regex: String? | ||||
|         get() = map["image_regex"] as? String | ||||
|  | ||||
|     val image_css: String? | ||||
|         get() = map["image_css"] as? String | ||||
|  | ||||
|     val image_attr: String | ||||
|         get() = map["image_attr"] as? String ?: "src" | ||||
|  | ||||
| } | ||||
| @@ -1,6 +1,7 @@ | ||||
| package eu.kanade.tachiyomi.source.online.english | ||||
|  | ||||
| import android.text.Html | ||||
| import eu.kanade.tachiyomi.data.preference.PreferencesHelper | ||||
| import eu.kanade.tachiyomi.network.GET | ||||
| import eu.kanade.tachiyomi.network.POST | ||||
| import eu.kanade.tachiyomi.network.asObservable | ||||
| @@ -9,14 +10,11 @@ import eu.kanade.tachiyomi.source.online.LoginSource | ||||
| import eu.kanade.tachiyomi.source.online.ParsedHttpSource | ||||
| import eu.kanade.tachiyomi.util.asJsoup | ||||
| import eu.kanade.tachiyomi.util.selectText | ||||
| import okhttp3.FormBody | ||||
| import okhttp3.HttpUrl | ||||
| import okhttp3.OkHttpClient | ||||
| import okhttp3.Request | ||||
| import okhttp3.Response | ||||
| import okhttp3.* | ||||
| import org.jsoup.nodes.Document | ||||
| import org.jsoup.nodes.Element | ||||
| import rx.Observable | ||||
| import uy.kohesive.injekt.injectLazy | ||||
| import java.net.URI | ||||
| import java.text.ParseException | ||||
| import java.text.SimpleDateFormat | ||||
| @@ -25,6 +23,9 @@ import java.util.regex.Pattern | ||||
|  | ||||
| class Batoto : ParsedHttpSource(), LoginSource { | ||||
|  | ||||
|     // TODO remove | ||||
|     private val preferences: PreferencesHelper by injectLazy() | ||||
|  | ||||
|     override val id: Long = 1 | ||||
|  | ||||
|     override val name = "Batoto" | ||||
|   | ||||
		Reference in New Issue
	
	Block a user