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
|
||||
}
|
||||
}
|