| 
		 Before Width: | Height: | Size: 4.7 KiB  | 
| 
		 Before Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 7.3 KiB  | 
| 
		 Before Width: | Height: | Size: 15 KiB  | 
| 
		 Before Width: | Height: | Size: 24 KiB  | 
| 
		 Before Width: | Height: | Size: 164 KiB  | 
| 
		 Before Width: | Height: | Size: 2.3 KiB  | 
| 
		 Before Width: | Height: | Size: 1.2 KiB  | 
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 6.1 KiB  | 
| 
		 Before Width: | Height: | Size: 8.3 KiB  | 
| 
		 Before Width: | Height: | Size: 32 KiB  | 
@ -1,10 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.ar.gemanga
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
 | 
			
		||||
 | 
			
		||||
class Gemanga : Madara("Gemanga", "https://gemanga.com", "ar") {
 | 
			
		||||
 | 
			
		||||
    // The website does not flag the content.
 | 
			
		||||
    override val useLoadMoreSearch = false
 | 
			
		||||
    override val filterNonMangaItems = false
 | 
			
		||||
}
 | 
			
		||||
@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.all.flamescans
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceFactory
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import java.util.concurrent.TimeUnit
 | 
			
		||||
 | 
			
		||||
@ -13,21 +12,6 @@ class FlameScansFactory : SourceFactory {
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FlameScansAr : FlameScans("https://ar.flamescans.org", "ar", "/series") {
 | 
			
		||||
    override val client: OkHttpClient = super.client.newBuilder()
 | 
			
		||||
        .rateLimit(1, 1, TimeUnit.SECONDS)
 | 
			
		||||
        .build()
 | 
			
		||||
 | 
			
		||||
    override val id: Long = 6053688312544266540
 | 
			
		||||
 | 
			
		||||
    override fun String?.parseStatus() = when {
 | 
			
		||||
        this == null -> SManga.UNKNOWN
 | 
			
		||||
        this.contains("مستمر") -> SManga.ONGOING
 | 
			
		||||
        this.contains("مكتمل") -> SManga.COMPLETED
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class FlameScansEn : FlameScans("https://flamescans.org", "en", "/series") {
 | 
			
		||||
    override val client: OkHttpClient = super.client.newBuilder()
 | 
			
		||||
        .rateLimit(2, 7, TimeUnit.SECONDS)
 | 
			
		||||
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.0 KiB  | 
| 
		 Before Width: | Height: | Size: 2.7 KiB  | 
| 
		 Before Width: | Height: | Size: 7.1 KiB  | 
| 
		 Before Width: | Height: | Size: 13 KiB  | 
| 
		 Before Width: | Height: | Size: 19 KiB  | 
| 
		 Before Width: | Height: | Size: 118 KiB  | 
@ -1,33 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.id.gabutscans
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import kotlinx.serialization.json.jsonArray
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
class GabutScans : MangaThemesia(
 | 
			
		||||
    "Gabut Scans", "https://gabutscans.com", "id",
 | 
			
		||||
    dateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale("id"))
 | 
			
		||||
) {
 | 
			
		||||
 | 
			
		||||
    override val hasProjectPage = true
 | 
			
		||||
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        // Prefer using sources loaded from javascript
 | 
			
		||||
        // because current CDN (Statically) can't load old images.
 | 
			
		||||
        // Example: https://gabutscans.com/cinderella-wa-sagasanai-chapter-41-end-bahasa-indonesia/
 | 
			
		||||
 | 
			
		||||
        val docString = document.toString()
 | 
			
		||||
        val imageListRegex = Regex("\"images.*?:.*?(\\[.*?])")
 | 
			
		||||
        val (imageListJson) = imageListRegex.find(docString)!!.destructured
 | 
			
		||||
 | 
			
		||||
        val imageList = json.parseToJsonElement(imageListJson).jsonArray
 | 
			
		||||
 | 
			
		||||
        return imageList.mapIndexed { i, jsonEl ->
 | 
			
		||||
            Page(i, "", jsonEl.jsonPrimitive.content)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -34,7 +34,6 @@ class MadaraGenerator : ThemeSourceGenerator {
 | 
			
		||||
        SingleLang("Akuzenai Arts", "https://akuzenaiarts.org", "en"),
 | 
			
		||||
        SingleLang("Aleatória Scan", "https://aleatoriascan.xyz", "pt-BR", className = "AleatoriaScan"),
 | 
			
		||||
        SingleLang("AllPornComic", "https://allporncomic.com", "en", isNsfw = true),
 | 
			
		||||
        SingleLang("AllTopManga", "https://alltopmanga.com", "en", isNsfw = true),
 | 
			
		||||
        SingleLang("Aln Scans", "https://alnscans.com", "en"),
 | 
			
		||||
        SingleLang("Amuy", "https://amuyscans.com", "pt-BR", isNsfw = true),
 | 
			
		||||
        SingleLang("Anikiga", "https://anikiga.com", "tr"),
 | 
			
		||||
@ -113,7 +112,6 @@ class MadaraGenerator : ThemeSourceGenerator {
 | 
			
		||||
        SingleLang("GalaxyDegenScans", "https://gdscans.com", "en", overrideVersionCode = 4),
 | 
			
		||||
        SingleLang("Gatemanga", "https://gatemanga.com", "ar", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("GeassToon", "https://geasstoon.com", "tr"),
 | 
			
		||||
        SingleLang("Gemanga", "https://gemanga.com", "ar", overrideVersionCode = 2),
 | 
			
		||||
        SingleLang("Glorious Scan", "https://gloriousscan.com", "pt-BR"),
 | 
			
		||||
        SingleLang("Glory Manga", "https://glorymanga.com", "tr"),
 | 
			
		||||
        SingleLang("Glory Scans", "https://gloryscans.com", "tr", isNsfw = true),
 | 
			
		||||
 | 
			
		||||
@ -15,7 +15,7 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
 | 
			
		||||
 | 
			
		||||
    override val sources = listOf(
 | 
			
		||||
        MultiLang("Asura Scans", "https://www.asurascans.com", listOf("en", "tr"), className = "AsuraScansFactory", pkgName = "asurascans", overrideVersionCode = 18),
 | 
			
		||||
        MultiLang("Flame Scans", "https://flamescans.org", listOf("ar", "en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 3),
 | 
			
		||||
        MultiLang("Flame Scans", "https://flamescans.org", listOf("en"), className = "FlameScansFactory", pkgName = "flamescans", overrideVersionCode = 4),
 | 
			
		||||
        MultiLang("Komik Lab", "https://komiklab.com", listOf("en", "id"), className = "KomikLabFactory", pkgName = "komiklab", overrideVersionCode = 1),
 | 
			
		||||
        MultiLang("Miau Scan", "https://miauscan.com", listOf("es", "pt-BR")),
 | 
			
		||||
        SingleLang("Animated Glitched Scans", "https://anigliscans.com", "en"),
 | 
			
		||||
@ -32,7 +32,6 @@ class MangaThemesiaGenerator : ThemeSourceGenerator {
 | 
			
		||||
        SingleLang("DuniaKomik.id", "https://duniakomik.id", "id", className = "DuniaKomikId"),
 | 
			
		||||
        SingleLang("FlameScans.fr", "https://flamescans.fr", "fr", className = "FlameScansFR"),
 | 
			
		||||
        SingleLang("Franxx Mangás", "https://franxxmangas.net", "pt-BR", className = "FranxxMangas", isNsfw = true),
 | 
			
		||||
        SingleLang("Gabut Scans", "https://gabutscans.com", "id", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("Gecenin Lordu", "https://geceninlordu.com", "tr", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("GoGoManga", "https://gogomanga.fun", "en", overrideVersionCode = 1),
 | 
			
		||||
        SingleLang("Gremory Mangas", "https://gremorymangas.com", "es"),
 | 
			
		||||
 | 
			
		||||
@ -1,24 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
 | 
			
		||||
    package="eu.kanade.tachiyomi.extension">
 | 
			
		||||
 | 
			
		||||
    <application>
 | 
			
		||||
        <activity
 | 
			
		||||
            android:name=".all.genkanio.GenkanIOUrlActivity"
 | 
			
		||||
            android:excludeFromRecents="true"
 | 
			
		||||
            android:exported="true"
 | 
			
		||||
            android:theme="@android:style/Theme.NoDisplay">
 | 
			
		||||
            <intent-filter>
 | 
			
		||||
                <action android:name="android.intent.action.VIEW" />
 | 
			
		||||
 | 
			
		||||
                <category android:name="android.intent.category.DEFAULT" />
 | 
			
		||||
                <category android:name="android.intent.category.BROWSABLE" />
 | 
			
		||||
                <data
 | 
			
		||||
                    android:host="genkan.io"
 | 
			
		||||
                    android:pathPattern="/manga/..*"
 | 
			
		||||
                    android:scheme="https" />
 | 
			
		||||
            </intent-filter>
 | 
			
		||||
 | 
			
		||||
        </activity>
 | 
			
		||||
    </application>
 | 
			
		||||
</manifest>
 | 
			
		||||
@ -1,12 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
apply plugin: 'kotlinx-serialization'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'Genkan.io'
 | 
			
		||||
    pkgNameSuffix = "all.genkanio"
 | 
			
		||||
    extClass = '.GenkanIOFactory'
 | 
			
		||||
    extVersionCode = 5
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 3.4 KiB  | 
| 
		 Before Width: | Height: | Size: 1.9 KiB  | 
| 
		 Before Width: | Height: | Size: 4.6 KiB  | 
| 
		 Before Width: | Height: | Size: 8.6 KiB  | 
| 
		 Before Width: | Height: | Size: 12 KiB  | 
| 
		 Before Width: | Height: | Size: 58 KiB  | 
@ -1,348 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.genkanio
 | 
			
		||||
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.network.POST
 | 
			
		||||
import eu.kanade.tachiyomi.network.asObservableSuccess
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import kotlinx.serialization.json.Json
 | 
			
		||||
import kotlinx.serialization.json.JsonElement
 | 
			
		||||
import kotlinx.serialization.json.JsonNull
 | 
			
		||||
import kotlinx.serialization.json.JsonObject
 | 
			
		||||
import kotlinx.serialization.json.JsonPrimitive
 | 
			
		||||
import kotlinx.serialization.json.boolean
 | 
			
		||||
import kotlinx.serialization.json.buildJsonArray
 | 
			
		||||
import kotlinx.serialization.json.buildJsonObject
 | 
			
		||||
import kotlinx.serialization.json.int
 | 
			
		||||
import kotlinx.serialization.json.jsonObject
 | 
			
		||||
import kotlinx.serialization.json.jsonPrimitive
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.Interceptor
 | 
			
		||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.RequestBody.Companion.toRequestBody
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import okhttp3.ResponseBody.Companion.toResponseBody
 | 
			
		||||
import okio.Buffer
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import rx.Observable
 | 
			
		||||
import uy.kohesive.injekt.injectLazy
 | 
			
		||||
import java.util.Calendar
 | 
			
		||||
 | 
			
		||||
open class GenkanIO(override val lang: String) : ParsedHttpSource() {
 | 
			
		||||
    final override val name = "Genkan.io"
 | 
			
		||||
    final override val baseUrl = "https://genkan.io"
 | 
			
		||||
    final override val supportsLatest = false
 | 
			
		||||
 | 
			
		||||
    private val json: Json by injectLazy()
 | 
			
		||||
 | 
			
		||||
    /** An interceptor which encapsulates the logic needed to interoperate with Genkan.io's
 | 
			
		||||
     *  livewire server, which uses a form a Remote Procedure call
 | 
			
		||||
     */
 | 
			
		||||
    private val livewireInterceptor = object : Interceptor {
 | 
			
		||||
        private lateinit var fingerprint: JsonElement
 | 
			
		||||
        lateinit var serverMemo: JsonObject
 | 
			
		||||
        private lateinit var csrf: String
 | 
			
		||||
        var initialized = false
 | 
			
		||||
        val serverUrl = "$baseUrl/livewire/message/manga.list-all-manga"
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Given a string encoded with html entities and escape sequences, makes an attempt to decode
 | 
			
		||||
         * and returns decoded string
 | 
			
		||||
         *
 | 
			
		||||
         * Warning: This is not all all exhaustive, and probably misses edge cases
 | 
			
		||||
         *
 | 
			
		||||
         * @Returns decoded string
 | 
			
		||||
         */
 | 
			
		||||
        private fun htmlDecode(html: String): String {
 | 
			
		||||
            return html.replace(Regex("&([A-Za-z]+);")) { match ->
 | 
			
		||||
                mapOf(
 | 
			
		||||
                    "raquo" to "»",
 | 
			
		||||
                    "laquo" to "«",
 | 
			
		||||
                    "amp" to "&",
 | 
			
		||||
                    "lt" to "<",
 | 
			
		||||
                    "gt" to ">",
 | 
			
		||||
                    "quot" to "\""
 | 
			
		||||
                )[match.groups[1]!!.value] ?: match.groups[0]!!.value
 | 
			
		||||
            }.replace(Regex("\\\\(.)")) { match ->
 | 
			
		||||
                mapOf(
 | 
			
		||||
                    "t" to "\t",
 | 
			
		||||
                    "n" to "\n",
 | 
			
		||||
                    "r" to "\r",
 | 
			
		||||
                    "b" to "\b"
 | 
			
		||||
                )[match.groups[1]!!.value] ?: match.groups[1]!!.value
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Recursively merges j2 onto j1 in place
 | 
			
		||||
         * If j1 and j2 both contain keys whose values aren't both jsonObjects, j2's value overwrites j1's
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        private fun mergeLeft(j1: JsonObject, j2: JsonObject): JsonObject = buildJsonObject {
 | 
			
		||||
            j1.keys.forEach { put(it, j1[it]!!) }
 | 
			
		||||
            j2.keys.forEach { k ->
 | 
			
		||||
                when {
 | 
			
		||||
                    j1[k] !is JsonObject -> put(k, j2[k]!!)
 | 
			
		||||
                    j1[k] is JsonObject && j2[k] is JsonObject -> put(k, mergeLeft(j1[k]!!.jsonObject, j2[k]!!.jsonObject))
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Initializes lateinit member vars
 | 
			
		||||
         */
 | 
			
		||||
        private fun initLivewire(chain: Interceptor.Chain) {
 | 
			
		||||
            val response = chain.proceed(GET("$baseUrl/manga", headers))
 | 
			
		||||
            val soup = response.asJsoup()
 | 
			
		||||
            response.body?.close()
 | 
			
		||||
            val csrfToken = soup.selectFirst("meta[name=csrf-token]")?.attr("content")
 | 
			
		||||
 | 
			
		||||
            val initialProps = soup.selectFirst("div[wire:initial-data]")?.attr("wire:initial-data")?.let {
 | 
			
		||||
                json.parseToJsonElement(htmlDecode(it))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (csrfToken != null && initialProps is JsonObject) {
 | 
			
		||||
                csrf = csrfToken
 | 
			
		||||
                serverMemo = initialProps["serverMemo"]!!.jsonObject
 | 
			
		||||
                fingerprint = initialProps["fingerprint"]!!
 | 
			
		||||
                initialized = true
 | 
			
		||||
            } else {
 | 
			
		||||
                Log.e("GenkanIo", soup.selectFirst("div[wire:initial-data]")?.toString() ?: "null")
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Builds a request for livewire, augmenting the request with required body fields and headers
 | 
			
		||||
         *
 | 
			
		||||
         * @param req: Request - A request with a json encoded body, which represent the updates sent to server
 | 
			
		||||
         *
 | 
			
		||||
         */
 | 
			
		||||
        private fun livewireRequest(req: Request): Request {
 | 
			
		||||
            val payload = buildJsonObject {
 | 
			
		||||
                put("fingerprint", fingerprint)
 | 
			
		||||
                put("serverMemo", serverMemo)
 | 
			
		||||
                put("updates", json.parseToJsonElement(Buffer().apply { req.body!!.writeTo(this) }.readUtf8()))
 | 
			
		||||
            }.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
 | 
			
		||||
 | 
			
		||||
            return req.newBuilder()
 | 
			
		||||
                .method(req.method, payload)
 | 
			
		||||
                .addHeader("x-csrf-token", csrf)
 | 
			
		||||
                .addHeader("x-livewire", "true")
 | 
			
		||||
                .build()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Transforms  json response from livewire server into a response which returns html
 | 
			
		||||
         *
 | 
			
		||||
         * @param response: Response - The response of sending a message to genkan's livewire server
 | 
			
		||||
         *
 | 
			
		||||
         * @return HTML Response - The html embedded within the provided response
 | 
			
		||||
         */
 | 
			
		||||
        private fun livewireResponse(response: Response): Response {
 | 
			
		||||
            if (!response.isSuccessful) return response
 | 
			
		||||
            val body = response.body!!.string()
 | 
			
		||||
            val responseJson = json.parseToJsonElement(body).jsonObject
 | 
			
		||||
 | 
			
		||||
            // response contains state that we need to preserve
 | 
			
		||||
            serverMemo = mergeLeft(serverMemo, responseJson["serverMemo"]!!.jsonObject)
 | 
			
		||||
 | 
			
		||||
            // this seems to be an error  state, so reset everything
 | 
			
		||||
            if (responseJson["effects"]?.jsonObject?.get("html") is JsonNull) {
 | 
			
		||||
                initialized = false
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // Build html response
 | 
			
		||||
            return response.newBuilder()
 | 
			
		||||
                .body(htmlDecode("${responseJson["effects"]?.jsonObject?.get("html")}").toResponseBody("Content-Type: text/html; charset=UTF-8".toMediaTypeOrNull()))
 | 
			
		||||
                .build()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        override fun intercept(chain: Interceptor.Chain): Response {
 | 
			
		||||
            if (chain.request().url.toString() != serverUrl)
 | 
			
		||||
                return chain.proceed(chain.request())
 | 
			
		||||
 | 
			
		||||
            if (!initialized) initLivewire(chain)
 | 
			
		||||
            return livewireResponse(chain.proceed(livewireRequest(chain.request())))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override val client = super.client.newBuilder().addInterceptor(livewireInterceptor).build()
 | 
			
		||||
 | 
			
		||||
    // popular manga
 | 
			
		||||
 | 
			
		||||
    override fun fetchPopularManga(page: Int) = fetchSearchManga(page, "", FilterList(emptyList()))
 | 
			
		||||
    override fun popularMangaRequest(page: Int) = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun popularMangaFromElement(element: Element) = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun popularMangaSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun popularMangaNextPageSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    // latest
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element) = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun latestUpdatesSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun latestUpdatesNextPageSelector() = throw UnsupportedOperationException("Not used")
 | 
			
		||||
 | 
			
		||||
    // search
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        val data = if (livewireInterceptor.initialized) livewireInterceptor.serverMemo["data"]!!.jsonObject else buildJsonObject {
 | 
			
		||||
            put("readyToLoad", JsonPrimitive(false))
 | 
			
		||||
            put("page", JsonPrimitive(1))
 | 
			
		||||
            put("search", JsonPrimitive(""))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val updates = buildJsonArray {
 | 
			
		||||
            if (data["readyToLoad"]?.jsonPrimitive?.boolean == false) {
 | 
			
		||||
                add(json.parseToJsonElement("""{"type":"callMethod","payload":{"method":"loadManga","params":[]}}"""))
 | 
			
		||||
            }
 | 
			
		||||
            val isNewQuery = query != data["search"]?.jsonPrimitive?.content
 | 
			
		||||
            if (isNewQuery) {
 | 
			
		||||
                add(json.parseToJsonElement("""{"type": "syncInput", "payload": {"name": "search", "value": "$query"}}"""))
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val currPage = if (isNewQuery) 1 else data["page"]!!.jsonPrimitive.int
 | 
			
		||||
 | 
			
		||||
            for (i in (currPage + 1)..page)
 | 
			
		||||
                add(json.parseToJsonElement("""{"type":"callMethod","payload":{"method":"nextPage","params":[]}}"""))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return POST(
 | 
			
		||||
            livewireInterceptor.serverUrl,
 | 
			
		||||
            headers,
 | 
			
		||||
            updates.toString().toRequestBody("application/json; charset=utf-8".toMediaTypeOrNull())
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaFromElement(element: Element): SManga {
 | 
			
		||||
        val manga = SManga.create()
 | 
			
		||||
        element.select("a").let {
 | 
			
		||||
            manga.url = it.attr("href").substringAfter(baseUrl)
 | 
			
		||||
            manga.title = it.text()
 | 
			
		||||
        }
 | 
			
		||||
        manga.thumbnail_url = element.select("img").attr("src")
 | 
			
		||||
        return manga
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaSelector() = "ul[role=list]:has(a)> li"
 | 
			
		||||
    override fun searchMangaNextPageSelector() = "button[rel=next]"
 | 
			
		||||
 | 
			
		||||
    // chapter list (is paginated),
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    override fun chapterListRequest(manga: SManga): Request = throw UnsupportedOperationException("Not used")
 | 
			
		||||
    data class ChapterPage(val chapters: List<SChapter>, val hasnext: Boolean)
 | 
			
		||||
 | 
			
		||||
    override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
 | 
			
		||||
        return if (manga.status != SManga.LICENSED) {
 | 
			
		||||
            // Returns an observable which emits the list of chapters found on a page,
 | 
			
		||||
            // for every page starting from specified page
 | 
			
		||||
            fun getAllPagesFrom(page: Int, pred: Observable<List<SChapter>> = Observable.just(emptyList())): Observable<List<SChapter>> =
 | 
			
		||||
                client.newCall(chapterListRequest(manga, page))
 | 
			
		||||
                    .asObservableSuccess()
 | 
			
		||||
                    .concatMap { response ->
 | 
			
		||||
                        val cp = chapterPageParse(response)
 | 
			
		||||
                        if (cp.hasnext)
 | 
			
		||||
                            getAllPagesFrom(page + 1, pred = pred.concatWith(Observable.just(cp.chapters))) // tail call to avoid blowing the stack
 | 
			
		||||
                        else
 | 
			
		||||
                            pred.concatWith(Observable.just(cp.chapters))
 | 
			
		||||
                    }
 | 
			
		||||
            getAllPagesFrom(1).reduce(List<SChapter>::plus)
 | 
			
		||||
        } else {
 | 
			
		||||
            Observable.error(Exception("Licensed - No chapters to show"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun chapterPageParse(response: Response): ChapterPage {
 | 
			
		||||
        val document = response.asJsoup()
 | 
			
		||||
 | 
			
		||||
        val manga = document.select(chapterListSelector()).map { element ->
 | 
			
		||||
            chapterFromElement(element)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        val hasNextPage = chapterListNextPageSelector()?.let { selector ->
 | 
			
		||||
            document.select(selector).first()
 | 
			
		||||
        } != null
 | 
			
		||||
 | 
			
		||||
        return ChapterPage(manga, hasNextPage)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun chapterListRequest(manga: SManga, page: Int): Request {
 | 
			
		||||
        val url = "$baseUrl${manga.url}".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
            .addQueryParameter("page", "$page")
 | 
			
		||||
        return GET("$url", headers)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element): SChapter = element.children().let { tableRow ->
 | 
			
		||||
        val isTitleBlank: (String) -> Boolean = { s: String -> s == "-" || s.isBlank() }
 | 
			
		||||
        val (numElem, nameElem, languageElem, groupElem, viewsElem) = tableRow
 | 
			
		||||
        val (releasedElem, urlElem) = Pair(tableRow[5], tableRow[6])
 | 
			
		||||
        SChapter.create().apply {
 | 
			
		||||
            name = if (isTitleBlank(nameElem.text())) "Chapter ${numElem.text()}" else "Ch. ${numElem.text()}: ${nameElem.text()}"
 | 
			
		||||
            url = urlElem.select("a").attr("href").substringAfter(baseUrl)
 | 
			
		||||
            date_upload = parseRelativeDate(releasedElem.text())
 | 
			
		||||
            scanlator = groupElem.text()
 | 
			
		||||
            chapter_number = numElem.text().toFloat()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterListSelector() = when (lang) {
 | 
			
		||||
        "ar" -> "tbody > tr:contains(Arabic)"
 | 
			
		||||
        "en" -> "tbody > tr:contains(English)"
 | 
			
		||||
        "fr" -> "tbody > tr:contains(French)"
 | 
			
		||||
        "pl" -> "tbody > tr:contains(Polish)"
 | 
			
		||||
        "pt-BR" -> "tbody > tr:contains(Portuguese)"
 | 
			
		||||
        "ru" -> "tbody > tr:contains(Russian)"
 | 
			
		||||
        "es" -> "tbody > tr:contains(Spanish)"
 | 
			
		||||
        "tr" -> "tbody > tr:contains(Turkish)"
 | 
			
		||||
        else -> "tbody > tr"
 | 
			
		||||
    }
 | 
			
		||||
    private fun chapterListNextPageSelector() = "a[rel=next]"
 | 
			
		||||
 | 
			
		||||
    // manga
 | 
			
		||||
 | 
			
		||||
    override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
 | 
			
		||||
        thumbnail_url = document.selectFirst("section > div > img").attr("src")
 | 
			
		||||
        status = SManga.UNKNOWN // unreported
 | 
			
		||||
        artist = null // unreported
 | 
			
		||||
        author = null // unreported
 | 
			
		||||
        description = document.selectFirst("h2").nextElementSibling().text()
 | 
			
		||||
            .plus("\n\n\n")
 | 
			
		||||
            // Add additional details from info table
 | 
			
		||||
            .plus(
 | 
			
		||||
                document.select("ul.mt-1").joinToString("\n") {
 | 
			
		||||
                    "${it.previousElementSibling().text()}: ${it.text()}"
 | 
			
		||||
                }
 | 
			
		||||
            )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseRelativeDate(date: String): Long {
 | 
			
		||||
        val trimmedDate = date.substringBefore(" ago").removeSuffix("s").split(" ")
 | 
			
		||||
 | 
			
		||||
        val calendar = Calendar.getInstance()
 | 
			
		||||
        when (trimmedDate[1]) {
 | 
			
		||||
            "year" -> calendar.apply { add(Calendar.YEAR, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "month" -> calendar.apply { add(Calendar.MONTH, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "week" -> calendar.apply { add(Calendar.WEEK_OF_MONTH, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "day" -> calendar.apply { add(Calendar.DAY_OF_MONTH, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "hour" -> calendar.apply { add(Calendar.HOUR_OF_DAY, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "minute" -> calendar.apply { add(Calendar.MINUTE, -trimmedDate[0].toInt()) }
 | 
			
		||||
            "second" -> calendar.apply { add(Calendar.SECOND, 0) }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return calendar.timeInMillis
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // Pages
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> = document.select("main > div > img").mapIndexed { index, img ->
 | 
			
		||||
        Page(index, "", img.attr("src"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used")
 | 
			
		||||
}
 | 
			
		||||
@ -1,17 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.genkanio
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.source.SourceFactory
 | 
			
		||||
 | 
			
		||||
class GenkanIOFactory : SourceFactory {
 | 
			
		||||
    override fun createSources() = listOf(
 | 
			
		||||
        GenkanIO("all"),
 | 
			
		||||
        GenkanIO("ar"),
 | 
			
		||||
        GenkanIO("en"),
 | 
			
		||||
        GenkanIO("fr"),
 | 
			
		||||
        GenkanIO("pl"),
 | 
			
		||||
        GenkanIO("pt-BR"),
 | 
			
		||||
        GenkanIO("ru"),
 | 
			
		||||
        GenkanIO("es"),
 | 
			
		||||
        GenkanIO("tr"),
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
@ -1,44 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.all.genkanio
 | 
			
		||||
 | 
			
		||||
import android.app.Activity
 | 
			
		||||
import android.content.ActivityNotFoundException
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
 | 
			
		||||
class GenkanIOUrlActivity : Activity() {
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
        val pathSegments = intent?.data?.pathSegments
 | 
			
		||||
        if (pathSegments != null && pathSegments.size >= 2) {
 | 
			
		||||
            // url scheme is of the form "/manga/ID-MANGANAME"
 | 
			
		||||
            val (_, titleComponent) = pathSegments
 | 
			
		||||
 | 
			
		||||
            // This is essentially substringBefore(titleComponent, '-'), don't have access to stdlib
 | 
			
		||||
            var titleId = ""
 | 
			
		||||
            for (i in 0 until titleComponent.length) {
 | 
			
		||||
                if (titleComponent[i] == '-') break
 | 
			
		||||
                titleId = titleId.plus(titleComponent[i])
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            val mainIntent = Intent().apply {
 | 
			
		||||
                action = "eu.kanade.tachiyomi.SEARCH"
 | 
			
		||||
                putExtra("query", titleId)
 | 
			
		||||
                putExtra("filter", packageName)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                startActivity(mainIntent)
 | 
			
		||||
            } catch (e: ActivityNotFoundException) {
 | 
			
		||||
                Log.e("GenkanIOUrlActivity", e.toString())
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            Log.e("GenkanIOUrlActivity", "could not parse uri from intent $intent")
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        finish()
 | 
			
		||||
        exitProcess(0)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,2 +0,0 @@
 | 
			
		||||
<?xml version="1.0" encoding="utf-8"?>
 | 
			
		||||
<manifest package="eu.kanade.tachiyomi.extension" />
 | 
			
		||||
@ -1,11 +0,0 @@
 | 
			
		||||
apply plugin: 'com.android.application'
 | 
			
		||||
apply plugin: 'kotlin-android'
 | 
			
		||||
 | 
			
		||||
ext {
 | 
			
		||||
    extName = 'ManhuaID'
 | 
			
		||||
    pkgNameSuffix = 'id.manhuaid'
 | 
			
		||||
    extClass = '.ManhuaID'
 | 
			
		||||
    extVersionCode = 8
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
apply from: "$rootDir/common.gradle"
 | 
			
		||||
| 
		 Before Width: | Height: | Size: 5.9 KiB  | 
| 
		 Before Width: | Height: | Size: 3.2 KiB  | 
| 
		 Before Width: | Height: | Size: 8.4 KiB  | 
| 
		 Before Width: | Height: | Size: 16 KiB  | 
| 
		 Before Width: | Height: | Size: 24 KiB  | 
| 
		 Before Width: | Height: | Size: 150 KiB  | 
@ -1,151 +0,0 @@
 | 
			
		||||
package eu.kanade.tachiyomi.extension.id.manhuaid
 | 
			
		||||
 | 
			
		||||
import eu.kanade.tachiyomi.network.GET
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Filter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.FilterList
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.Page
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SChapter
 | 
			
		||||
import eu.kanade.tachiyomi.source.model.SManga
 | 
			
		||||
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
 | 
			
		||||
import eu.kanade.tachiyomi.util.asJsoup
 | 
			
		||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
 | 
			
		||||
import okhttp3.OkHttpClient
 | 
			
		||||
import okhttp3.Request
 | 
			
		||||
import okhttp3.Response
 | 
			
		||||
import org.jsoup.nodes.Document
 | 
			
		||||
import org.jsoup.nodes.Element
 | 
			
		||||
import java.text.SimpleDateFormat
 | 
			
		||||
import java.util.Locale
 | 
			
		||||
 | 
			
		||||
class ManhuaID : ParsedHttpSource() {
 | 
			
		||||
 | 
			
		||||
    override val name = "ManhuaID"
 | 
			
		||||
 | 
			
		||||
    override val baseUrl = "https://manhuaid.com"
 | 
			
		||||
 | 
			
		||||
    override val lang = "id"
 | 
			
		||||
 | 
			
		||||
    override val supportsLatest = true
 | 
			
		||||
 | 
			
		||||
    override val client: OkHttpClient = network.cloudflareClient
 | 
			
		||||
 | 
			
		||||
    // popular
 | 
			
		||||
    override fun popularMangaSelector() = "a:has(img.card-img)"
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaRequest(page: Int) = GET("$baseUrl/index.php/project?page_project=$page", headers)
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaFromElement(element: Element) = SManga.create().apply {
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
        title = element.select("img").attr("alt")
 | 
			
		||||
        thumbnail_url = element.select("img").attr("abs:src")
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun popularMangaNextPageSelector() = "[rel=nofollow]"
 | 
			
		||||
 | 
			
		||||
    // latest
 | 
			
		||||
    override fun latestUpdatesSelector() = popularMangaSelector()
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/index.php/project?page_project=$page", headers)
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesFromElement(element: Element) = popularMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
 | 
			
		||||
 | 
			
		||||
    // search
 | 
			
		||||
    override fun searchMangaSelector() = popularMangaSelector()
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
 | 
			
		||||
        var url = "$baseUrl/search?".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
        url.addQueryParameter("q", query)
 | 
			
		||||
        filters.forEach { filter ->
 | 
			
		||||
            when (filter) {
 | 
			
		||||
                is ProjectFilter -> {
 | 
			
		||||
                    if (filter.toUriPart() == "project-filter-on") {
 | 
			
		||||
                        url = "$baseUrl/project?page_project=$page".toHttpUrlOrNull()!!.newBuilder()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        return GET(url.toString(), headers)
 | 
			
		||||
    }
 | 
			
		||||
    override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
 | 
			
		||||
 | 
			
		||||
    override fun searchMangaNextPageSelector() = "li:last-child:not(.active) [rel=nofollow]"
 | 
			
		||||
 | 
			
		||||
    // manga details
 | 
			
		||||
    override fun mangaDetailsParse(document: Document) = SManga.create().apply {
 | 
			
		||||
        author = document.select("table").first().select("td")[3].text()
 | 
			
		||||
        title = document.select("h1").text()
 | 
			
		||||
        description = document.select(".text-justify").text()
 | 
			
		||||
        genre = document.select("span.badge.badge-success.mr-1.mb-1").joinToString { it.text() }
 | 
			
		||||
        status = document.select("td > span.badge.badge-success").text().let {
 | 
			
		||||
            parseStatus(it)
 | 
			
		||||
        }
 | 
			
		||||
        thumbnail_url = document.select("img.img-fluid").attr("abs:src")
 | 
			
		||||
 | 
			
		||||
        // add series type(manga/manhwa/manhua/other) thinggy to genre
 | 
			
		||||
        document.select("table tr:contains(Type) a, table a[href*=type]").firstOrNull()?.ownText()?.let {
 | 
			
		||||
            if (it.isEmpty().not() && genre!!.contains(it, true).not()) {
 | 
			
		||||
                genre += if (genre!!.isEmpty()) it else ", $it"
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseStatus(status: String) = when {
 | 
			
		||||
        status.contains("Ongoing") -> SManga.ONGOING
 | 
			
		||||
        status.contains("Completed") -> SManga.COMPLETED
 | 
			
		||||
        else -> SManga.UNKNOWN
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // chapters
 | 
			
		||||
    override fun chapterListSelector() = "table.table tr td:first-of-type a"
 | 
			
		||||
 | 
			
		||||
    override fun chapterListParse(response: Response): List<SChapter> {
 | 
			
		||||
        val document = response.asJsoup()
 | 
			
		||||
        val chapters = document.select(chapterListSelector()).map { chapterFromElement(it) }
 | 
			
		||||
 | 
			
		||||
        // Add timestamp to latest chapter, taken from "Updated On". so source which not provide chapter timestamp will have atleast one
 | 
			
		||||
        val date = document.select("table tr:contains(update) td").text()
 | 
			
		||||
        val checkChapter = document.select(chapterListSelector()).firstOrNull()
 | 
			
		||||
        if (date != "" && checkChapter != null) chapters[0].date_upload = parseDate(date)
 | 
			
		||||
 | 
			
		||||
        return chapters
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun parseDate(date: String): Long {
 | 
			
		||||
        return SimpleDateFormat("dd/MM/yyyy", Locale.ENGLISH).parse(date)?.time ?: 0L
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun chapterFromElement(element: Element) = SChapter.create().apply {
 | 
			
		||||
        setUrlWithoutDomain(element.attr("href"))
 | 
			
		||||
        name = element.text()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // pages
 | 
			
		||||
    override fun pageListParse(document: Document): List<Page> {
 | 
			
		||||
        return document.select("img.img-fluid.mb-0.mt-0").mapIndexed { i, element ->
 | 
			
		||||
            Page(i, "", element.attr("src"))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used")
 | 
			
		||||
 | 
			
		||||
    override fun getFilterList() = FilterList(
 | 
			
		||||
        Filter.Header("NOTE: cant be used with search or other filter!"),
 | 
			
		||||
        Filter.Header("$name Project List page"),
 | 
			
		||||
        ProjectFilter(),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private class ProjectFilter : UriPartFilter(
 | 
			
		||||
        "Filter Project",
 | 
			
		||||
        arrayOf(
 | 
			
		||||
            Pair("Show all manga", ""),
 | 
			
		||||
            Pair("Show only project manga", "project-filter-on")
 | 
			
		||||
        )
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
 | 
			
		||||
        Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
 | 
			
		||||
        fun toUriPart() = vals[state].second
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||