Compare commits
54 Commits
d61cbfc0c1
...
eb4c7f00be
Author | SHA1 | Date |
---|---|---|
Sofie | eb4c7f00be | |
Yush0DAN | 53dd6255f2 | |
beerpsi | 54e5dd9469 | |
Deivid Gabriel Pereira de Oliveira | ee11eaa24f | |
Mike | 11e66824c4 | |
Mike | 640344e5e2 | |
Claudemirovsky | f5d9d57008 | |
AwkwardPeak7 | d49c20a530 | |
beerpsi | 93df09d758 | |
Luqman | 40fa020e0e | |
beerpsi | f9a2e87f6b | |
Draff | 34dbd2d59a | |
beerpsi | 19a1dd9133 | |
beerpsi | 198c7f053f | |
beerpsi | 1549a6ad52 | |
beerpsi | 5fa08faf08 | |
beerpsi | b377580e3b | |
bapeey | 15b9714c54 | |
beerpsi | e96323105e | |
AwkwardPeak7 | 77a1336c15 | |
AwkwardPeak7 | b49c4c5378 | |
Mike | 846e783195 | |
Secozzi | c3f277e342 | |
Eshlender | a14c354a5b | |
Luqman | d51448d967 | |
KirinRaikage | 537adadea0 | |
Secozzi | 544d28e4c4 | |
AwkwardPeak7 | 1f1753d047 | |
KirinRaikage | f45bc4cc4d | |
Claudemirovsky | 3d1006e244 | |
bapeey | bcb3c31237 | |
Mike | 3922547fa3 | |
AwkwardPeak7 | b26afd2269 | |
Secozzi | 64d59377c6 | |
Mike | 23f8d95b1d | |
Secozzi | 29697c086a | |
Vetle Ledaal | 27bed17520 | |
Vetle Ledaal | 8a55ca5d6f | |
bapeey | b6e923ac49 | |
Secozzi | 8194dd0096 | |
NotBlankyu | af69ff235f | |
NotBlankyu | cf699e5331 | |
bapeey | f9aa8a1c93 | |
bapeey | 39431c31b1 | |
bapeey | 7390f88f2c | |
beerpsi | 0d92662eec | |
Claudemirovsky | 3616d62946 | |
NotBlankyu | 9a571a9625 | |
AwkwardPeak7 | 2829141f0f | |
NotBlankyu | 602493510f | |
Umair Yousif | f3f816f956 | |
Claudemirovsky | 2e410da03b | |
AwkwardPeak7 | 598baf532c | |
Fermín Cirella | c311614a2e |
|
@ -769,9 +769,11 @@ Please **do test your changes by compiling it through Android Studio** before su
|
||||||
|
|
||||||
### Pull Request checklist
|
### Pull Request checklist
|
||||||
|
|
||||||
- Update `extVersionCode` value in `build.gradle` for individual extensions
|
- Updated `extVersionCode` value in `build.gradle` for individual extensions
|
||||||
- Update `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
|
- Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
|
||||||
- Reference all related issues in the PR body (e.g. "Closes #xyz")
|
- Referenced all related issues in the PR body (e.g. "Closes #xyz")
|
||||||
- Add the `isNsfw = true` flag in `build.gradle` when appropriate
|
- Added the `isNsfw = true` flag in `build.gradle` when appropriate
|
||||||
- Explicitly kept the `id` if a source's name or language were changed
|
- Have not changed source names
|
||||||
- Test the modifications by compiling and running the extension through Android Studio
|
- Have explicitly kept the `id` if a source's name or language were changed
|
||||||
|
- Have tested the modifications by compiling and running the extension through Android Studio
|
||||||
|
- Have removed `web_hi_res_512.png` when adding a new extension
|
||||||
|
|
|
@ -85,7 +85,7 @@ private class RandomUserAgentInterceptor(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
|
private const val UA_DB_URL = "https://keiyoushi.github.io/user-agents/user-agents.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 2.6 KiB |
Before Width: | Height: | Size: 4.4 KiB After Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 8.0 KiB After Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 17 KiB |
|
@ -1,5 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.flixscans
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans
|
|
||||||
|
|
||||||
class FlixScansNet : FlixScans("Flix Scans", "https://flixscans.org", "en", cdnUrl = "https://media.flixscans.org/")
|
|
|
@ -1,13 +1,7 @@
|
||||||
package eu.kanade.tachiyomi.extension.ar.galaxymanga
|
package eu.kanade.tachiyomi.extension.ar.galaxymanga
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans
|
import eu.kanade.tachiyomi.multisrc.flixscans.FlixScans
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import okhttp3.Request
|
|
||||||
|
|
||||||
class GalaxyManga : FlixScans("جالاكسي مانجا", "https://flixscans.com", "ar") {
|
class GalaxyManga : FlixScans("جالاكسي مانجا", "https://flixscans.com", "ar") {
|
||||||
override val versionId = 2
|
override val versionId = 2
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
|
||||||
return GET("$apiUrl/webtoon/pages/home/action", headers)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 4.4 KiB |
After Width: | Height: | Size: 8.4 KiB |
After Width: | Height: | Size: 13 KiB |
|
@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class CeriseScan : Madara(
|
class CeriseScan : Madara(
|
||||||
"Cerise Scan",
|
"Cerise Scan",
|
||||||
"https://cerisescan.com",
|
"https://cerisescan.net",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
||||||
) {
|
) {
|
||||||
|
|
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 25 KiB |
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.darkscan
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
|
class DarkScan : Madara("Dark-scan", "https://dark-scan.com", "en") {
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
|
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
||||||
|
}
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -0,0 +1,19 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.ezmanga
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class EZmanga : Madara(
|
||||||
|
"EZmanga",
|
||||||
|
"https://ezmanga.net",
|
||||||
|
"en",
|
||||||
|
dateFormat = SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH),
|
||||||
|
) {
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
|
override val client = super.client.newBuilder()
|
||||||
|
.rateLimit(1)
|
||||||
|
.build()
|
||||||
|
}
|
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 23 KiB |
After Width: | Height: | Size: 38 KiB |
|
@ -0,0 +1,112 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.gourmetscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
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.SChapter
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import okhttp3.Request
|
||||||
|
import org.jsoup.nodes.Document
|
||||||
|
import org.jsoup.nodes.Element
|
||||||
|
|
||||||
|
class GourmetScans : Madara(
|
||||||
|
"Gourmet Scans",
|
||||||
|
"https://gourmetsupremacy.com",
|
||||||
|
"en",
|
||||||
|
) {
|
||||||
|
override val mangaSubString = "project"
|
||||||
|
|
||||||
|
override val useNewChapterEndpoint = false
|
||||||
|
|
||||||
|
// Search
|
||||||
|
|
||||||
|
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
||||||
|
|
||||||
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
|
val url = baseUrl.toHttpUrl().newBuilder()
|
||||||
|
|
||||||
|
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||||
|
val yearFilter = filterList.find { it is YearFilter } as YearFilter
|
||||||
|
val orderByFilter = filterList.find { it is OrderByFilter } as OrderByFilter
|
||||||
|
val genreFilter = filterList.find { it is GenreFilter } as? GenreFilter
|
||||||
|
|
||||||
|
when {
|
||||||
|
yearFilter.state.isNotBlank() -> {
|
||||||
|
url.addPathSegment("release-year")
|
||||||
|
url.addPathSegment(yearFilter.state)
|
||||||
|
}
|
||||||
|
genreFilter?.state?.equals(0)?.not() ?: false -> {
|
||||||
|
url.addPathSegment("genre")
|
||||||
|
url.addPathSegment(genreFilter!!.toUriPart())
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
url.addPathSegment(mangaSubString)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (orderByFilter.toUriPart().isNotBlank()) {
|
||||||
|
url.addQueryParameter("m_orderby", orderByFilter.toUriPart())
|
||||||
|
}
|
||||||
|
|
||||||
|
url.addPathSegments(searchPage(page))
|
||||||
|
|
||||||
|
return GET(url.build(), headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun searchMangaSelector(): String = ".page-listing-item .page-item-detail"
|
||||||
|
|
||||||
|
override fun searchMangaNextPageSelector(): String = ".navigation-ajax > #navigation-ajax"
|
||||||
|
|
||||||
|
// Filters
|
||||||
|
|
||||||
|
override fun genresRequest(): Request = GET("$baseUrl/$mangaSubString", headers)
|
||||||
|
|
||||||
|
override fun parseGenres(document: Document): List<Genre> {
|
||||||
|
genresList = document.select("div.row.genres ul li a")
|
||||||
|
.orEmpty()
|
||||||
|
.map { li ->
|
||||||
|
Pair(
|
||||||
|
li.text(),
|
||||||
|
li.attr("href").split("/").last { it.isNotBlank() },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private var genresList: List<Pair<String, String>> = emptyList()
|
||||||
|
|
||||||
|
class GenreFilter(val vals: List<Pair<String, String>>) :
|
||||||
|
UriPartFilter("Genre", vals.toTypedArray())
|
||||||
|
|
||||||
|
override fun getFilterList(): FilterList {
|
||||||
|
val filters = buildList(4) {
|
||||||
|
add(YearFilter(yearFilterTitle))
|
||||||
|
add(
|
||||||
|
OrderByFilter(
|
||||||
|
title = orderByFilterTitle,
|
||||||
|
options = orderByFilterOptions.zip(orderByFilterOptionsValues),
|
||||||
|
state = 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
add(Filter.Separator())
|
||||||
|
|
||||||
|
if (genresList.isEmpty()) {
|
||||||
|
add(Filter.Header(genresMissingWarning))
|
||||||
|
} else {
|
||||||
|
add(GenreFilter(listOf(Pair("<select>", "")) + genresList))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FilterList(filters)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Chapters
|
||||||
|
|
||||||
|
override fun chapterFromElement(element: Element): SChapter {
|
||||||
|
return super.chapterFromElement(element).apply {
|
||||||
|
url = this.url.substringBefore("?style=list")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 7.1 KiB After Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 8.5 KiB |
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 13 KiB |
|
@ -6,8 +6,14 @@ import org.jsoup.nodes.Document
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class HentaiCB : Madara("Hentai CB", "https://hentaicube.net", "vi", SimpleDateFormat("dd/MM/yyyy", Locale("vi"))) {
|
class HentaiCB : Madara("CBHentai", "https://cbhentai.net", "vi", SimpleDateFormat("dd/MM/yyyy", Locale("vi"))) {
|
||||||
|
|
||||||
override val id: Long = 823638192569572166
|
override val id: Long = 823638192569572166
|
||||||
|
|
||||||
|
override val mangaSubString = "read"
|
||||||
|
|
||||||
|
override val filterNonMangaItems = false
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
override fun pageListParse(document: Document): List<Page> {
|
||||||
return super.pageListParse(document).distinctBy { it.imageUrl }
|
return super.pageListParse(document).distinctBy { it.imageUrl }
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class Hipercool : Madara("HipercooL", "https://hipercool.xyz", "pt-BR") {
|
class Hipercool : Madara("HipercooL", "https://hiper.cool", "pt-BR") {
|
||||||
|
|
||||||
// Migrated from a custom CMS to Madara.
|
// Migrated from a custom CMS to Madara.
|
||||||
override val versionId = 2
|
override val versionId = 2
|
||||||
|
|
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 8.5 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,59 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.pt.huntersscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class HuntersScans : Madara(
|
||||||
|
"Hunters Scan",
|
||||||
|
"https://huntersscan.xyz/",
|
||||||
|
"pt-BR",
|
||||||
|
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
||||||
|
) {
|
||||||
|
|
||||||
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
|
.rateLimit(1, 2, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
|
private fun loadMoreRequest(page: Int, metaKey: String): Request {
|
||||||
|
val formBody = FormBody.Builder().apply {
|
||||||
|
add("action", "madara_load_more")
|
||||||
|
add("page", page.toString())
|
||||||
|
add("template", "madara-core/content/content-archive")
|
||||||
|
add("vars[paged]", "1")
|
||||||
|
add("vars[orderby]", "meta_value_num")
|
||||||
|
add("vars[template]", "archive")
|
||||||
|
add("vars[sidebar]", "right")
|
||||||
|
add("vars[post_type]", "wp-manga")
|
||||||
|
add("vars[post_status]", "publish")
|
||||||
|
add("vars[meta_key]", metaKey)
|
||||||
|
add("vars[order]", "desc")
|
||||||
|
add("vars[meta_query][relation]", "AND")
|
||||||
|
add("vars[manga_archives_item_layout]", "default")
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val xhrHeaders = headersBuilder()
|
||||||
|
.add("Content-Length", formBody.contentLength().toString())
|
||||||
|
.add("Content-Type", formBody.contentType().toString())
|
||||||
|
.add("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return loadMoreRequest(page - 1, "_wp_manga_views")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return loadMoreRequest(page - 1, "_latest_update")
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@ import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import okhttp3.FormBody
|
import okhttp3.FormBody
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
@ -62,14 +61,4 @@ class LectorMangaLat : Madara(
|
||||||
}
|
}
|
||||||
|
|
||||||
override val pageListParseSelector = "div.reading-content div.page-break > img"
|
override val pageListParseSelector = "div.reading-content div.page-break > img"
|
||||||
|
|
||||||
override fun imageFromElement(element: Element): String? {
|
|
||||||
return when {
|
|
||||||
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
|
|
||||||
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
|
||||||
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
|
||||||
element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ")
|
|
||||||
else -> element.attr("abs:src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.extension.en.mangareadorg
|
package eu.kanade.tachiyomi.extension.en.mangareadorg
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ -10,14 +9,4 @@ class MangaReadOrg : Madara(
|
||||||
"https://www.mangaread.org",
|
"https://www.mangaread.org",
|
||||||
"en",
|
"en",
|
||||||
SimpleDateFormat("dd.MM.yyy", Locale.US),
|
SimpleDateFormat("dd.MM.yyy", Locale.US),
|
||||||
) {
|
)
|
||||||
override fun imageFromElement(element: Element): String? {
|
|
||||||
return when {
|
|
||||||
element.hasAttr("data-cfsrc") -> element.attr("abs:data-cfsrc")
|
|
||||||
element.hasAttr("data-src") -> element.attr("abs:data-src")
|
|
||||||
element.hasAttr("data-lazy-src") -> element.attr("abs:data-lazy-src")
|
|
||||||
element.hasAttr("srcset") -> element.attr("abs:srcset").substringBefore(" ")
|
|
||||||
else -> element.attr("abs:src")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -2,11 +2,16 @@ package eu.kanade.tachiyomi.extension.es.mangasnosekai
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.CacheControl
|
import okhttp3.CacheControl
|
||||||
|
import okhttp3.FormBody
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -72,13 +77,14 @@ class MangasNoSekai : Madara(
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "nav.navigation a.next"
|
override fun searchMangaNextPageSelector() = "nav.navigation a.next"
|
||||||
|
|
||||||
override val mangaDetailsSelectorTitle = "div.summary-content h1.titleManga"
|
override val mangaDetailsSelectorTitle = "div.thumble-container p.titleMangaSingle"
|
||||||
override val mangaDetailsSelectorThumbnail = "div.tab-summary img.img-responsive"
|
override val mangaDetailsSelectorThumbnail = "div.thumble-container img.img-responsive"
|
||||||
override val mangaDetailsSelectorDescription = "div.summary-content div.artist-content"
|
override val mangaDetailsSelectorDescription = "section#section-sinopsis > p"
|
||||||
override val mangaDetailsSelectorStatus = "div.summary-content ul.general-List li:has(span:contains(Estado))"
|
override val mangaDetailsSelectorStatus = "section#section-sinopsis div.d-flex:has(div:contains(Estado)) p"
|
||||||
override val mangaDetailsSelectorAuthor = "div.summary-content ul.general-List li:has(span:contains(Autor))"
|
override val mangaDetailsSelectorAuthor = "section#section-sinopsis div.d-flex:has(div:contains(Autor)) p"
|
||||||
override val mangaDetailsSelectorArtist = "div.summary-content ul.general-List li:has(span:contains(Dibujante))"
|
override val mangaDetailsSelectorGenre = "section#section-sinopsis div.d-flex:has(div:contains(Generos)) p a"
|
||||||
override val seriesTypeSelector = "div.summary-content ul.general-List li:has(span:contains(Tipo))"
|
override val altNameSelector = "section#section-sinopsis div.d-flex:has(div:contains(Otros nombres)) p"
|
||||||
|
override val altName = "Otros nombres: "
|
||||||
|
|
||||||
override fun mangaDetailsParse(document: Document): SManga {
|
override fun mangaDetailsParse(document: Document): SManga {
|
||||||
val manga = SManga.create()
|
val manga = SManga.create()
|
||||||
|
@ -89,9 +95,6 @@ class MangasNoSekai : Madara(
|
||||||
selectFirst(mangaDetailsSelectorAuthor)?.ownText()?.let {
|
selectFirst(mangaDetailsSelectorAuthor)?.ownText()?.let {
|
||||||
manga.author = it
|
manga.author = it
|
||||||
}
|
}
|
||||||
selectFirst(mangaDetailsSelectorArtist)?.ownText()?.let {
|
|
||||||
manga.artist = it
|
|
||||||
}
|
|
||||||
select(mangaDetailsSelectorDescription).let {
|
select(mangaDetailsSelectorDescription).let {
|
||||||
manga.description = it.text()
|
manga.description = it.text()
|
||||||
}
|
}
|
||||||
|
@ -111,13 +114,6 @@ class MangasNoSekai : Madara(
|
||||||
.map { element -> element.text().lowercase(Locale.ROOT) }
|
.map { element -> element.text().lowercase(Locale.ROOT) }
|
||||||
.toMutableSet()
|
.toMutableSet()
|
||||||
|
|
||||||
// add manga/manhwa/manhua thinggy to genre
|
|
||||||
document.select(seriesTypeSelector).firstOrNull()?.ownText()?.let {
|
|
||||||
if (it.isEmpty().not() && it.notUpdating() && it != "-" && genres.contains(it).not()) {
|
|
||||||
genres.add(it.lowercase(Locale.ROOT))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
manga.genre = genres.toList().joinToString(", ") { genre ->
|
manga.genre = genres.toList().joinToString(", ") { genre ->
|
||||||
genre.replaceFirstChar {
|
genre.replaceFirstChar {
|
||||||
if (it.isLowerCase()) {
|
if (it.isLowerCase()) {
|
||||||
|
@ -130,7 +126,6 @@ class MangasNoSekai : Madara(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// add alternative name to manga description
|
|
||||||
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
|
document.select(altNameSelector).firstOrNull()?.ownText()?.let {
|
||||||
if (it.isBlank().not() && it.notUpdating()) {
|
if (it.isBlank().not() && it.notUpdating()) {
|
||||||
manga.description = when {
|
manga.description = when {
|
||||||
|
@ -153,4 +148,64 @@ class MangasNoSekai : Madara(
|
||||||
"views2",
|
"views2",
|
||||||
"new-manga",
|
"new-manga",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private fun altChapterRequest(mangaId: String, page: Int): Request {
|
||||||
|
val form = FormBody.Builder()
|
||||||
|
.add("action", "load_chapters")
|
||||||
|
.add("mangaid", mangaId)
|
||||||
|
.add("page", page.toString())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
val xhrHeaders = headersBuilder()
|
||||||
|
.add("Content-Length", form.contentLength().toString())
|
||||||
|
.add("Content-Type", form.contentType().toString())
|
||||||
|
.add("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, form)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val altChapterListSelector = "div.wp-manga-chapter"
|
||||||
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
val document = response.asJsoup()
|
||||||
|
|
||||||
|
val mangaUrl = document.location().removeSuffix("/")
|
||||||
|
|
||||||
|
var xhrRequest = xhrChaptersRequest(mangaUrl)
|
||||||
|
var xhrResponse = client.newCall(xhrRequest).execute()
|
||||||
|
|
||||||
|
val chapterElements = xhrResponse.asJsoup().select(chapterListSelector())
|
||||||
|
if (chapterElements.isEmpty()) {
|
||||||
|
val mangaId = document.selectFirst("div.tab-summary > script:containsData(manga_id)")?.data()
|
||||||
|
?.let { MANGA_ID_REGEX.find(it)?.groupValues?.get(1) }
|
||||||
|
?: throw Exception("No se pudo obtener el id del manga")
|
||||||
|
|
||||||
|
var page = 1
|
||||||
|
do {
|
||||||
|
xhrRequest = altChapterRequest(mangaId, page)
|
||||||
|
xhrResponse = client.newCall(xhrRequest).execute()
|
||||||
|
val xhrDocument = xhrResponse.asJsoup()
|
||||||
|
chapterElements.addAll(xhrDocument.select(altChapterListSelector))
|
||||||
|
page++
|
||||||
|
} while (xhrDocument.select(altChapterListSelector).isNotEmpty())
|
||||||
|
|
||||||
|
countViews(document)
|
||||||
|
return chapterElements.map(::altChapterFromElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
countViews(document)
|
||||||
|
return chapterElements.map(::chapterFromElement)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun altChapterFromElement(element: Element) = SChapter.create().apply {
|
||||||
|
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
|
||||||
|
name = element.select("div.text-sm").text()
|
||||||
|
date_upload = element.select("time").firstOrNull()?.text()?.let {
|
||||||
|
parseChapterDate(it)
|
||||||
|
} ?: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val MANGA_ID_REGEX = """manga_id\s*=\s*(.*)\s*;""".toRegex()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,46 +1,148 @@
|
||||||
package eu.kanade.tachiyomi.extension.id.mgkomik
|
package eu.kanade.tachiyomi.extension.id.mgkomik
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.util.Base64
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.preference.EditTextPreference
|
||||||
|
import androidx.preference.PreferenceScreen
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.GET
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.decodeFromString
|
||||||
import okhttp3.Headers
|
import okhttp3.Headers
|
||||||
|
import okhttp3.Interceptor
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Response
|
||||||
|
import uy.kohesive.injekt.Injekt
|
||||||
|
import uy.kohesive.injekt.api.get
|
||||||
|
import java.io.IOException
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import kotlin.random.Random
|
|
||||||
|
|
||||||
class MGKomik : Madara("MG Komik", "https://mgkomik.id", "id", SimpleDateFormat("dd MMM yy", Locale.US)) {
|
class MGKomik : Madara("MG Komik", "https://mgkomik.id", "id", SimpleDateFormat("dd MMM yy", Locale.US)) {
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
private val preferences: SharedPreferences by lazy {
|
||||||
.rateLimit(20, 5, TimeUnit.SECONDS)
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
|
}
|
||||||
|
private val encodedString = "AAAAaAAA AHQAAAG 0AAAAcAAAAHM AAAA6AAA ALwAAAC8 AAAByAAAAYQAA AHcAQuAZwAA" + "AGkAAAG0AA AAaAAAAHUAAA BiAAAAdQAAAHMAA ABlyAtAAAcgAAAG MAAABvAAAAbgAA AHQAAABlAAAA bgAAAHQAAAAuA " + "AAAYwAAAG 8AAABtAAAALwAAA GsAAABlyAtAAA aQAAAHkAAABv AAAAdQAAAHMAAAD oAiBuAAAaQAAAC8 AAAB1AAAAcwA" + " AAGUAAABy AAAALQAAAGE AAABnAAA AZQAAAG 4AAAG0AAAAe gAAAC8AAABnAAAAaA AAAC0AAABwAAAA YQAAAGcAAABlAA " + " AAegAAAC8AAAB 1AAAAcwAAAGU AAAByAAAALQA AAGEAAABnA AAAZQAAAG 4AAAG0A AAAegAAAC4A AABtAAAAa QAAAG4AQ " + " uAagAAAHM AAABvAAAAbg=="
|
||||||
|
|
||||||
|
private val keiListUaUrl = Base64.decode(encodedString.replace("\\s".toRegex(), "").replace("DoAiBu", "BoA").replace("G0A", "B0A").replace("BlyAt", "BlA").replace("AQuA", "AAAAuAAAA"), Base64.DEFAULT).toString(Charsets.UTF_32).replace("z", "s")
|
||||||
|
|
||||||
|
private var secChUaMP: List<String>? = null
|
||||||
|
private var userAgent: String? = null
|
||||||
|
private var checkedUa = false
|
||||||
|
|
||||||
|
private val uaIntercept = object : Interceptor {
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "")
|
||||||
|
try {
|
||||||
|
if (customUa!!.isNotBlank()) userAgent = customUa
|
||||||
|
|
||||||
|
if (userAgent.isNullOrBlank() && checkedUa.not()) {
|
||||||
|
val uaResponse = chain.proceed(GET(keiListUaUrl))
|
||||||
|
if (uaResponse.isSuccessful) {
|
||||||
|
val parseTachiUa = uaResponse.use { json.decodeFromString<TachiUaResponse>(it.body.string()) }
|
||||||
|
|
||||||
|
var listUserAgentString = parseTachiUa.desktop + parseTachiUa.mobile
|
||||||
|
|
||||||
|
listUserAgentString = listUserAgentString!!.filter {
|
||||||
|
listOf("windows", "android").any { filter ->
|
||||||
|
it.contains(filter, ignoreCase = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
userAgent = listUserAgentString!!.random()
|
||||||
|
checkedUa = true
|
||||||
|
}
|
||||||
|
uaResponse.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userAgent.isNullOrBlank().not()) {
|
||||||
|
secChUaMP = if (userAgent!!.contains("Windows")) {
|
||||||
|
listOf("?0", "Windows")
|
||||||
|
} else {
|
||||||
|
listOf("?1", "Android")
|
||||||
|
}
|
||||||
|
|
||||||
|
val newRequest = chain.request().newBuilder()
|
||||||
|
.header("User-Agent", userAgent!!.trim())
|
||||||
|
.header("Sec-CH-UA-Mobile", secChUaMP!![0])
|
||||||
|
.header("Sec-CH-UA-Platform", secChUaMP!![1])
|
||||||
|
.removeHeader("X-Requested-With")
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
return chain.proceed(newRequest)
|
||||||
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
|
}
|
||||||
.add("Accept-Language", "en-US,en;q=0.9,id;q=0.8")
|
return chain.proceed(chain.request())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IOException(e.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class TachiUaResponse(
|
||||||
|
val desktop: List<String> = emptyList(),
|
||||||
|
val mobile: List<String> = emptyList(),
|
||||||
|
)
|
||||||
|
|
||||||
|
// disable random ua in ext setting from multisrc (.setRandomUserAgent)
|
||||||
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
|
.addInterceptor(uaIntercept)
|
||||||
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override fun headersBuilder(): Headers.Builder {
|
||||||
|
val builder = super.headersBuilder()
|
||||||
.add("Sec-Fetch-Dest", "document")
|
.add("Sec-Fetch-Dest", "document")
|
||||||
.add("Sec-Fetch-Mode", "navigate")
|
.add("Sec-Fetch-Mode", "navigate")
|
||||||
.add("Sec-Fetch-Site", "same-origin")
|
.add("Sec-Fetch-Site", "same-origin")
|
||||||
.add("Sec-Fetch-User", "?1")
|
|
||||||
.add("Upgrade-Insecure-Requests", "1")
|
.add("Upgrade-Insecure-Requests", "1")
|
||||||
.add("X-Requested-With", randomString)
|
.add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
|
||||||
|
|
||||||
private fun generateRandomString(length: Int): String {
|
// used to flush tachi custom ua in webview and use system ua instead
|
||||||
val charset = "HALOGaES.BCDFHIJKMNPQRTUVWXYZ.bcdefghijklmnopqrstuvwxyz0123456789"
|
if (userAgent.isNullOrBlank()) builder.removeAll("User-Agent")
|
||||||
return (1..length)
|
|
||||||
.map { charset.random() }
|
return builder
|
||||||
.joinToString("")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page > 1) "page/$page/" else ""
|
override fun searchPage(page: Int): String = if (page > 1) "page/$page/" else ""
|
||||||
|
|
||||||
private val randomLength = Random.Default.nextInt(13, 21)
|
|
||||||
|
|
||||||
private val randomString = generateRandomString(randomLength)
|
|
||||||
|
|
||||||
override val mangaSubString = "komik"
|
override val mangaSubString = "komik"
|
||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "a.page.larger"
|
override fun searchMangaNextPageSelector() = "a.page.larger"
|
||||||
|
|
||||||
override val chapterUrlSuffix = ""
|
override val chapterUrlSuffix = ""
|
||||||
|
|
||||||
|
// remove random ua in setting ext from multisrc and use custom one
|
||||||
|
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||||
|
val prefCustomUserAgent = EditTextPreference(screen.context).apply {
|
||||||
|
key = PREF_KEY_CUSTOM_UA
|
||||||
|
title = TITLE_CUSTOM_UA
|
||||||
|
summary = (preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim() + SUMMARY_STRING_CUSTOM_UA).trim()
|
||||||
|
setOnPreferenceChangeListener { _, newValue ->
|
||||||
|
val customUa = newValue as String
|
||||||
|
preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply()
|
||||||
|
if (customUa.isNullOrBlank()) {
|
||||||
|
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
|
||||||
|
} else {
|
||||||
|
userAgent = null
|
||||||
|
}
|
||||||
|
summary = (customUa.trim() + SUMMARY_STRING2_CUSTOM_UA).trim()
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
screen.addPreference(prefCustomUserAgent)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TITLE_CUSTOM_UA = "Custom User-Agent"
|
||||||
|
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua"
|
||||||
|
const val SUMMARY_STRING_CUSTOM_UA = "\n\nBiarkan kosong untuk menggunakan User-Agent secara random" // leave empty to use random UA
|
||||||
|
const val SUMMARY_STRING2_CUSTOM_UA = "\n\nKosongkan untuk menggunakan User-Agent secara random" // make it blank/empty to use random UA
|
||||||
|
|
||||||
|
const val RESTART_APP_STRING = "Restart Aplikasi untuk menggunakan pengaturan baru." // restart app to use new settings
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 19 KiB |
After Width: | Height: | Size: 29 KiB |
|
@ -0,0 +1,60 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.es.mhscans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
import eu.kanade.tachiyomi.network.POST
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
|
import okhttp3.FormBody
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import okhttp3.Request
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class MHScans : Madara(
|
||||||
|
"MHScans",
|
||||||
|
"https://mhscans.com",
|
||||||
|
"es",
|
||||||
|
dateFormat = SimpleDateFormat("dd 'de' MMMM 'de' yyyy", Locale("es")),
|
||||||
|
) {
|
||||||
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
|
.rateLimit(1, 3, TimeUnit.SECONDS)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
|
override fun popularMangaNextPageSelector() = "body:not(:has(.no-posts))"
|
||||||
|
|
||||||
|
private fun loadMoreRequest(page: Int, metaKey: String): Request {
|
||||||
|
val formBody = FormBody.Builder().apply {
|
||||||
|
add("action", "madara_load_more")
|
||||||
|
add("page", page.toString())
|
||||||
|
add("template", "madara-core/content/content-archive")
|
||||||
|
add("vars[paged]", "1")
|
||||||
|
add("vars[orderby]", "meta_value_num")
|
||||||
|
add("vars[template]", "archive")
|
||||||
|
add("vars[sidebar]", "full")
|
||||||
|
add("vars[post_type]", "wp-manga")
|
||||||
|
add("vars[post_status]", "publish")
|
||||||
|
add("vars[meta_key]", metaKey)
|
||||||
|
add("vars[order]", "desc")
|
||||||
|
add("vars[meta_query][relation]", "AND")
|
||||||
|
add("vars[manga_archives_item_layout]", "big_thumbnail")
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
val xhrHeaders = headersBuilder()
|
||||||
|
.add("Content-Length", formBody.contentLength().toString())
|
||||||
|
.add("Content-Type", formBody.contentType().toString())
|
||||||
|
.add("X-Requested-With", "XMLHttpRequest")
|
||||||
|
.build()
|
||||||
|
|
||||||
|
return POST("$baseUrl/wp-admin/admin-ajax.php", xhrHeaders, formBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
|
return loadMoreRequest(page - 1, "_wp_manga_views")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
|
return loadMoreRequest(page - 1, "_latest_update")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,28 +1,17 @@
|
||||||
package eu.kanade.tachiyomi.extension.id.shinigami
|
package eu.kanade.tachiyomi.extension.id.shinigami
|
||||||
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.util.Base64
|
import android.util.Base64
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.preference.EditTextPreference
|
|
||||||
import androidx.preference.PreferenceScreen
|
|
||||||
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
import eu.kanade.tachiyomi.lib.cryptoaes.CryptoAES
|
||||||
import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator
|
import eu.kanade.tachiyomi.lib.synchrony.Deobfuscator
|
||||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
import eu.kanade.tachiyomi.source.model.SChapter
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import okhttp3.Headers
|
|
||||||
import okhttp3.Interceptor
|
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
import org.jsoup.nodes.Element
|
||||||
import uy.kohesive.injekt.Injekt
|
|
||||||
import uy.kohesive.injekt.api.get
|
|
||||||
import java.io.IOException
|
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
|
class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
|
||||||
|
@ -33,97 +22,37 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
|
||||||
|
|
||||||
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
override fun headersBuilder() = super.headersBuilder().apply {
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
add("Sec-Fetch-Dest", "document")
|
||||||
|
add("Sec-Fetch-Mode", "navigate")
|
||||||
|
add("Sec-Fetch-Site", "same-origin")
|
||||||
|
add("Upgrade-Insecure-Requests", "1")
|
||||||
|
add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
|
||||||
}
|
}
|
||||||
|
|
||||||
private val encodedString = "AAA AaAAAAH QAAAB0 AAAAcA AAAHMAA AA6AAA ALwAAAC8AA " + "AB0AAAAYQA AAGM AAADoAAAAaQAAAH kAAABvAA AAbQAAA GkAAABvAAAA cgAAAGcAAAAuAAA AZwAAAGk " + "AAAB0AAAA aAAAAHUAA ABiAAAALgAAAGkAA ABvAAAAL wAAAHUAAABzA AAAZQAAAHIAAAAtA AAAYQAAAGcA " + "AABlyAtAAAbgA AAHQAAAB6AAAA LwAAAHUAAA BcAAAAZQ AAAHIAAAAtAAA AYQAAAGcAAABl AAAAbgAA AHQAAAB6AAAALgAAAG" + " oAhAntUAABzAA AAbwAAAG4="
|
|
||||||
|
|
||||||
private val tachiUaUrl = Base64.decode(encodedString.replace("\\s".toRegex(), "").replace("DoA", "BoA").replace("GoAhAntU", "GoA").replace("BlyAt", "BlA").replace("BcA", "BzA"), Base64.DEFAULT).toString(Charsets.UTF_32).replace("z", "s")
|
|
||||||
|
|
||||||
private var secChUaMP: List<String>? = null
|
|
||||||
private var userAgent: String? = null
|
|
||||||
private var checkedUa = false
|
|
||||||
|
|
||||||
private val uaIntercept = object : Interceptor {
|
|
||||||
override fun intercept(chain: Interceptor.Chain): Response {
|
|
||||||
val customUa = preferences.getString(PREF_KEY_CUSTOM_UA, "")
|
|
||||||
try {
|
|
||||||
if (customUa!!.isNotBlank()) userAgent = customUa
|
|
||||||
|
|
||||||
if (userAgent.isNullOrBlank() && checkedUa.not()) {
|
|
||||||
val uaResponse = chain.proceed(GET(tachiUaUrl))
|
|
||||||
if (uaResponse.isSuccessful) {
|
|
||||||
val parseTachiUa = uaResponse.use { json.decodeFromString<TachiUaResponse>(it.body.string()) }
|
|
||||||
|
|
||||||
var listUserAgentString = parseTachiUa.desktop + parseTachiUa.mobile
|
|
||||||
|
|
||||||
listUserAgentString = listUserAgentString!!.filter {
|
|
||||||
listOf("windows", "android").any { filter ->
|
|
||||||
it.contains(filter, ignoreCase = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
userAgent = listUserAgentString!!.random()
|
|
||||||
checkedUa = true
|
|
||||||
}
|
|
||||||
uaResponse.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userAgent.isNullOrBlank().not()) {
|
|
||||||
secChUaMP = if (userAgent!!.contains("Windows")) {
|
|
||||||
listOf("?0", "Windows")
|
|
||||||
} else {
|
|
||||||
listOf("?1", "Android")
|
|
||||||
}
|
|
||||||
|
|
||||||
val newRequest = chain.request().newBuilder()
|
|
||||||
.header("User-Agent", userAgent!!.trim())
|
|
||||||
.header("Sec-CH-UA-Mobile", secChUaMP!![0])
|
|
||||||
.header("Sec-CH-UA-Platform", secChUaMP!![1])
|
|
||||||
.removeHeader("X-Requested-With")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return chain.proceed(newRequest)
|
|
||||||
}
|
|
||||||
return chain.proceed(chain.request())
|
|
||||||
} catch (e: Exception) {
|
|
||||||
throw IOException(e.message)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TachiUaResponse(
|
|
||||||
val desktop: List<String> = emptyList(),
|
|
||||||
val mobile: List<String> = emptyList(),
|
|
||||||
)
|
|
||||||
|
|
||||||
// disable random ua in ext setting from multisrc (.setRandomUserAgent)
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||||
.addInterceptor(uaIntercept)
|
.addInterceptor { chain ->
|
||||||
|
val request = chain.request()
|
||||||
|
val headers = request.headers.newBuilder().apply {
|
||||||
|
if (request.header("X-Requested-With")?.isBlank() == true) {
|
||||||
|
removeAll("X-Requested-With")
|
||||||
|
}
|
||||||
|
}.build()
|
||||||
|
|
||||||
|
chain.proceed(request.newBuilder().headers(headers).build())
|
||||||
|
}
|
||||||
.connectTimeout(10, TimeUnit.SECONDS)
|
.connectTimeout(10, TimeUnit.SECONDS)
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
.readTimeout(30, TimeUnit.SECONDS)
|
||||||
|
.rateLimit(2)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
|
||||||
val builder = super.headersBuilder()
|
|
||||||
.add("Sec-Fetch-Dest", "document")
|
|
||||||
.add("Sec-Fetch-Mode", "navigate")
|
|
||||||
.add("Sec-Fetch-Site", "same-origin")
|
|
||||||
.add("Upgrade-Insecure-Requests", "1")
|
|
||||||
.add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
|
|
||||||
|
|
||||||
// used to flush tachi custom ua in webview and use system ua instead
|
|
||||||
if (userAgent.isNullOrBlank()) builder.removeAll("User-Agent")
|
|
||||||
|
|
||||||
return builder
|
|
||||||
}
|
|
||||||
|
|
||||||
override val mangaSubString = "semua-series"
|
override val mangaSubString = "semua-series"
|
||||||
|
|
||||||
// Tags are useless as they are just SEO keywords.
|
// Tags are useless as they are just SEO keywords.
|
||||||
override val mangaDetailsSelectorTag = ""
|
override val mangaDetailsSelectorTag = ""
|
||||||
|
|
||||||
|
override val chapterUrlSelector = "a:not([href*=troll-page])"
|
||||||
|
|
||||||
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||||
val urlElement = element.selectFirst(chapterUrlSelector)!!
|
val urlElement = element.selectFirst(chapterUrlSelector)!!
|
||||||
|
|
||||||
|
@ -148,13 +77,15 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
|
||||||
val deobfuscated = Deobfuscator.deobfuscateScript(script)
|
val deobfuscated = Deobfuscator.deobfuscateScript(script)
|
||||||
?: throw Exception("Unable to deobfuscate chapter_data script")
|
?: throw Exception("Unable to deobfuscate chapter_data script")
|
||||||
|
|
||||||
val postId = script.substringAfter("var post_id = '").substringBefore("'")
|
val keyMatch = KEY_REGEX.find(deobfuscated)?.groupValues
|
||||||
val chapterData = json.decodeFromString<CDT>(
|
?: throw Exception("Unable to find key")
|
||||||
script.substringAfter("var chapter_data = '").substringBefore("'"),
|
|
||||||
)
|
|
||||||
|
|
||||||
val keyMatch = KEY_REGEX.find(deobfuscated)!!.groupValues
|
val chapterData = json.decodeFromString<CDT>(
|
||||||
val key = postId + keyMatch[1] + postId + keyMatch[2] + postId
|
CHAPTER_DATA_REGEX.find(script)?.groupValues?.get(1) ?: throw Exception("Unable to get chapter data"),
|
||||||
|
)
|
||||||
|
val postId = POST_ID_REGEX.find(script)?.groupValues?.get(1) ?: throw Exception("Unable to get post_id")
|
||||||
|
val otherId = OTHER_ID_REGEX.findAll(script).firstOrNull { it.groupValues[1] != "post" }?.groupValues?.get(2) ?: throw Exception("Unable to get other id")
|
||||||
|
val key = otherId + keyMatch[1] + postId + keyMatch[2] + postId
|
||||||
val salt = chapterData.s.decodeHex()
|
val salt = chapterData.s.decodeHex()
|
||||||
|
|
||||||
val unsaltedCiphertext = Base64.decode(chapterData.ct, Base64.DEFAULT)
|
val unsaltedCiphertext = Base64.decode(chapterData.ct, Base64.DEFAULT)
|
||||||
|
@ -176,36 +107,10 @@ class Shinigami : Madara("Shinigami", "https://shinigamitoon.com", "id") {
|
||||||
.toByteArray()
|
.toByteArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove random ua in setting ext from multisrc and use custom one
|
|
||||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
|
||||||
val prefCustomUserAgent = EditTextPreference(screen.context).apply {
|
|
||||||
key = PREF_KEY_CUSTOM_UA
|
|
||||||
title = TITLE_CUSTOM_UA
|
|
||||||
summary = (preferences.getString(PREF_KEY_CUSTOM_UA, "")!!.trim() + SUMMARY_STRING_CUSTOM_UA).trim()
|
|
||||||
setOnPreferenceChangeListener { _, newValue ->
|
|
||||||
val customUa = newValue as String
|
|
||||||
preferences.edit().putString(PREF_KEY_CUSTOM_UA, customUa).apply()
|
|
||||||
if (customUa.isNullOrBlank()) {
|
|
||||||
Toast.makeText(screen.context, RESTART_APP_STRING, Toast.LENGTH_LONG).show()
|
|
||||||
} else {
|
|
||||||
userAgent = null
|
|
||||||
}
|
|
||||||
summary = (customUa.trim() + SUMMARY_STRING2_CUSTOM_UA).trim()
|
|
||||||
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
screen.addPreference(prefCustomUserAgent)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
const val TITLE_CUSTOM_UA = "Custom User-Agent"
|
private val KEY_REGEX by lazy { Regex("""_id\s+\+\s+'(.*?)'\s+\+\s+post_id\s+\+\s+'(.*?)'\s+\+\s+post_id""") }
|
||||||
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua"
|
private val CHAPTER_DATA_REGEX by lazy { Regex("""var chapter_data\s*=\s*'(.*?)'""") }
|
||||||
const val SUMMARY_STRING_CUSTOM_UA = "\n\nBiarkan kosong untuk menggunakan User-Agent secara random"
|
private val POST_ID_REGEX by lazy { Regex("""var post_id\s*=\s*'(.*?)'""") }
|
||||||
const val SUMMARY_STRING2_CUSTOM_UA = "\n\nKosongkan untuk menggunakan User-Agent secara random"
|
private val OTHER_ID_REGEX by lazy { Regex("""var (\w+)_id\s*=\s*'(.*?)'""") }
|
||||||
|
|
||||||
const val RESTART_APP_STRING = "Restart Tachiyomi untuk menggunakan pengaturan baru."
|
|
||||||
|
|
||||||
private val KEY_REGEX by lazy { Regex("""post_id\s+\+\s+'(.*?)'\s+\+\s+post_id\s+\+\s+'(.*?)'\s+\+\s+post_id""") }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
After Width: | Height: | Size: 4.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 6.9 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 18 KiB |
|
@ -0,0 +1,9 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.en.whalemanga
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||||
|
|
||||||
|
class WhaleManga : Madara("WhaleManga", "https://whalemanga.com", "en") {
|
||||||
|
override val useNewChapterEndpoint = true
|
||||||
|
|
||||||
|
override fun searchPage(page: Int): String = if (page == 1) "" else "page/$page/"
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.arkhamscan
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class ArkhamScan : MangaThemesia(
|
|
||||||
"Arkham Scan",
|
|
||||||
"https://arkhamscan.com",
|
|
||||||
"pt-BR",
|
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
|
||||||
) {
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.rateLimit(1, 2, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override val altNamePrefix = "Nomes alternativos: "
|
|
||||||
}
|
|
|
@ -1,4 +1,4 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.asurascans
|
package eu.kanade.tachiyomi.extension.en.asurascans
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
|
@ -18,7 +18,6 @@ import okhttp3.OkHttpClient
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.Response
|
import okhttp3.Response
|
||||||
import org.jsoup.nodes.Document
|
import org.jsoup.nodes.Document
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
|
@ -27,7 +26,7 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class AsuraScansEn : MangaThemesia(
|
class AsuraScans : MangaThemesia(
|
||||||
"Asura Scans",
|
"Asura Scans",
|
||||||
"https://asuratoon.com",
|
"https://asuratoon.com",
|
||||||
"en",
|
"en",
|
||||||
|
@ -115,13 +114,6 @@ class AsuraScansEn : MangaThemesia(
|
||||||
.mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) }
|
.mapIndexed { i, img -> Page(i, document.location(), img.attr("abs:src")) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun Element.imgAttr(): String = when {
|
|
||||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
|
||||||
hasAttr("data-src") -> attr("abs:data-src")
|
|
||||||
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
|
|
||||||
else -> attr("abs:src")
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
|
private fun Observable<MangasPage>.tempUrlToPermIfNeeded(): Observable<MangasPage> {
|
||||||
return this.map { mangasPage ->
|
return this.map { mangasPage ->
|
||||||
MangasPage(
|
MangasPage(
|
|
@ -1,10 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.asurascans
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.source.SourceFactory
|
|
||||||
|
|
||||||
class AsuraScansFactory : SourceFactory {
|
|
||||||
override fun createSources() = listOf(
|
|
||||||
AsuraScansEn(),
|
|
||||||
AsuraScansTr(),
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,66 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.all.asurascans
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.decodeFromString
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import org.jsoup.nodes.Document
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class AsuraScansTr : MangaThemesia(
|
|
||||||
"Asura Scans",
|
|
||||||
"https://armoniscans.com",
|
|
||||||
"tr",
|
|
||||||
dateFormat = SimpleDateFormat("MMM d, yyyy", Locale("tr")),
|
|
||||||
) {
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.rateLimit(1, 3, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override val seriesArtistSelector = ".fmed b:contains(Çizer)+span"
|
|
||||||
override val seriesAuthorSelector = ".fmed b:contains(Yazar)+span"
|
|
||||||
override val seriesStatusSelector = ".imptdt:contains(Durum) i"
|
|
||||||
override val seriesTypeSelector = ".imptdt:contains(Tür) a"
|
|
||||||
|
|
||||||
override val altNamePrefix: String = "Alternatif isim: "
|
|
||||||
|
|
||||||
override fun String?.parseStatus(): Int = when {
|
|
||||||
this == null -> SManga.UNKNOWN
|
|
||||||
this.contains("Devam Ediyor", ignoreCase = true) -> SManga.ONGOING
|
|
||||||
this.contains("Tamamlandı", ignoreCase = true) -> SManga.COMPLETED
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun Element.imgAttr(): String = when {
|
|
||||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
|
||||||
hasAttr("data-src") -> attr("abs:data-src")
|
|
||||||
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
|
|
||||||
else -> attr("abs:src")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun pageListParse(document: Document): List<Page> {
|
|
||||||
val scriptContent = document.selectFirst("script:containsData(ts_reader)")?.data()
|
|
||||||
?: return super.pageListParse(document)
|
|
||||||
val jsonString = scriptContent.substringAfter("ts_reader.run(").substringBefore(");")
|
|
||||||
val tsReader = json.decodeFromString<TSReader>(jsonString)
|
|
||||||
val imageUrls = tsReader.sources.firstOrNull()?.images ?: return emptyList()
|
|
||||||
return imageUrls.mapIndexed { index, imageUrl -> Page(index, document.location(), imageUrl) }
|
|
||||||
}
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class TSReader(
|
|
||||||
val sources: List<ReaderImageSource>,
|
|
||||||
)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class ReaderImageSource(
|
|
||||||
val source: String,
|
|
||||||
val images: List<String>,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.extension.es.carteldemanhwas
|
package eu.kanade.tachiyomi.extension.es.carteldemanhwas
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
@ -15,13 +14,6 @@ class CarteldeManhwas : MangaThemesia(
|
||||||
override val hasProjectPage = true
|
override val hasProjectPage = true
|
||||||
override val projectPageString = "/proyectos"
|
override val projectPageString = "/proyectos"
|
||||||
|
|
||||||
override fun Element.imgAttr(): String = when {
|
|
||||||
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
|
|
||||||
hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
|
|
||||||
hasAttr("data-src") -> attr("abs:data-src")
|
|
||||||
else -> attr("abs:src")
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun searchMangaSelector() = ".utao .uta .imgu:not(:has(span.novelabel)), " +
|
override fun searchMangaSelector() = ".utao .uta .imgu:not(:has(span.novelabel)), " +
|
||||||
".listupd .bs .bsx:not(:has(span.novelabel)), " +
|
".listupd .bs .bsx:not(:has(span.novelabel)), " +
|
||||||
".listo .bs .bsx:not(:has(span.novelabel))"
|
".listo .bs .bsx:not(:has(span.novelabel))"
|
||||||
|
|
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 6.0 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,22 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.pt.hikariscan
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class HikariScan : MangaThemesia(
|
||||||
|
"Hikari Scan",
|
||||||
|
"https://hikariscan.org",
|
||||||
|
"pt-BR",
|
||||||
|
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
||||||
|
) {
|
||||||
|
override val client = super.client.newBuilder()
|
||||||
|
.rateLimitHost(baseUrl.toHttpUrl(), 1, 2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// =========================== Manga Details ============================
|
||||||
|
override val altNamePrefix = "Títulos alternativos: "
|
||||||
|
override val seriesAuthorSelector = ".tsinfo .imptdt:contains(autor) i"
|
||||||
|
}
|
|
@ -1,11 +1,47 @@
|
||||||
package eu.kanade.tachiyomi.extension.id.komiktap
|
package eu.kanade.tachiyomi.extension.id.komiktap
|
||||||
|
|
||||||
|
import app.cash.quickjs.QuickJs
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.util.asJsoup
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.Cookie
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
class Komiktap : MangaThemesia("Komiktap", "https://komiktap.me", "id") {
|
class Komiktap : MangaThemesia("Komiktap", "https://komiktap.me", "id") {
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
override val client = super.client.newBuilder().addInterceptor(::sucuriInterceptor).build()
|
||||||
.rateLimit(4)
|
|
||||||
.build()
|
// Taken from es/ManhwasNet
|
||||||
|
private fun sucuriInterceptor(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
val url = request.url
|
||||||
|
val response = try {
|
||||||
|
chain.proceed(request)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Try to clear cookies and retry
|
||||||
|
client.cookieJar.saveFromResponse(url, emptyList())
|
||||||
|
val clearHeaders = request.headers.newBuilder().removeAll("Cookie").build()
|
||||||
|
chain.proceed(request.newBuilder().headers(clearHeaders).build())
|
||||||
|
}
|
||||||
|
if (response.headers["x-sucuri-cache"].isNullOrEmpty() && response.headers["x-sucuri-id"] != null && url.toString().startsWith(baseUrl)) {
|
||||||
|
val script = response.use { it.asJsoup() }.selectFirst("script")?.data()
|
||||||
|
|
||||||
|
if (script != null) {
|
||||||
|
val patchedScript = script.split("(r)")[0].dropLast(1) + "r=r.replace('document.cookie','cookie');"
|
||||||
|
QuickJs.create().use {
|
||||||
|
val result = (it.evaluate(patchedScript) as String)
|
||||||
|
.replace("location.", "")
|
||||||
|
.replace("reload();", "")
|
||||||
|
val sucuriCookie = (it.evaluate(result) as String).split("=", limit = 2)
|
||||||
|
val cookieName = sucuriCookie.first()
|
||||||
|
val cookieValue = sucuriCookie.last().replace(";path", "")
|
||||||
|
client.cookieJar.saveFromResponse(url, listOf(Cookie.parse(url, "$cookieName=$cookieValue")!!))
|
||||||
|
}
|
||||||
|
val newResponse = chain.proceed(request)
|
||||||
|
if (!newResponse.headers["x-sucuri-cache"].isNullOrEmpty()) return newResponse
|
||||||
|
}
|
||||||
|
throw IOException("Situs yang dilindungi - Buka di WebView untuk mencoba membuka blokir.")
|
||||||
|
}
|
||||||
|
return response
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Before Width: | Height: | Size: 25 KiB |
|
@ -1,36 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.ssshentais
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Request
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class SssHentais : MangaThemesia(
|
|
||||||
"SSS Hentais",
|
|
||||||
"https://hentais.sssscanlator.com",
|
|
||||||
"pt-BR",
|
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
|
||||||
) {
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.rateLimit(1, 2, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
override fun imageRequest(page: Page): Request {
|
|
||||||
val newHeaders = headersBuilder()
|
|
||||||
.set("Referer", page.url)
|
|
||||||
.set("Accept", "image/avif,image/webp,*/*")
|
|
||||||
.set("Accept-Language", "pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3")
|
|
||||||
.set("Sec-Fetch-Dest", "image")
|
|
||||||
.set("Sec-Fetch-Mode", "no-cors")
|
|
||||||
.set("Sec-Fetch-Site", "same-origin")
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return GET(page.imageUrl!!, newHeaders)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -12,7 +12,7 @@ import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class SSSScanlator : MangaThemesia(
|
class SSSScanlator : MangaThemesia(
|
||||||
"SSSScanlator",
|
"SSSScanlator",
|
||||||
"https://sssscanlator.com",
|
"https://sssscanlator.com.br",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
||||||
) {
|
) {
|
||||||
|
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 5.5 KiB |
After Width: | Height: | Size: 11 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,27 @@
|
||||||
|
package eu.kanade.tachiyomi.extension.tr.tempestfansub
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||||
|
import eu.kanade.tachiyomi.network.interceptor.rateLimitHost
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
|
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||||
|
|
||||||
|
class TempestFansub : MangaThemesia(
|
||||||
|
"Tempest Fansub",
|
||||||
|
"https://tempestfansub.com",
|
||||||
|
"tr",
|
||||||
|
) {
|
||||||
|
override val client = super.client.newBuilder()
|
||||||
|
.rateLimitHost(baseUrl.toHttpUrl(), 2)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
// =========================== Manga Details ============================
|
||||||
|
override val seriesArtistSelector = ".tsinfo .imptdt:contains(İllüstratör) i"
|
||||||
|
override val seriesAuthorSelector = ".tsinfo .imptdt:contains(Yazar) i"
|
||||||
|
override val seriesStatusSelector = ".tsinfo .imptdt:contains(Seri Durumu) i"
|
||||||
|
|
||||||
|
override fun String?.parseStatus(): Int = when (this?.trim()?.lowercase()) {
|
||||||
|
"devam ediyor" -> SManga.ONGOING
|
||||||
|
"bitti" -> SManga.COMPLETED
|
||||||
|
else -> SManga.UNKNOWN
|
||||||
|
}
|
||||||
|
}
|
Before Width: | Height: | Size: 9.2 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 31 KiB |
Before Width: | Height: | Size: 53 KiB |
|
@ -1,115 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.extension.pt.animaregia
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.multisrc.mmrcms.MMRCMS
|
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
|
||||||
import eu.kanade.tachiyomi.source.model.MangasPage
|
|
||||||
import eu.kanade.tachiyomi.source.model.SChapter
|
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
|
||||||
import eu.kanade.tachiyomi.util.asJsoup
|
|
||||||
import okhttp3.OkHttpClient
|
|
||||||
import okhttp3.Response
|
|
||||||
import org.jsoup.nodes.Element
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
|
|
||||||
class AnimaRegia : MMRCMS("AnimaRegia", "https://animaregia.net", "pt-BR") {
|
|
||||||
|
|
||||||
override val id: Long = 4378659695320121364
|
|
||||||
|
|
||||||
override val client: OkHttpClient = super.client.newBuilder()
|
|
||||||
.rateLimit(1, 2, TimeUnit.SECONDS)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
// Remove the language tag from the title name.
|
|
||||||
override fun internalMangaParse(response: Response): MangasPage {
|
|
||||||
return super.internalMangaParse(response).let {
|
|
||||||
it.copy(
|
|
||||||
mangas = it.mangas.map { manga ->
|
|
||||||
manga.apply { title = title.removeSuffix(LANGUAGE_SUFFIX) }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun latestUpdatesFromElement(element: Element, urlSelector: String): SManga? {
|
|
||||||
return super.latestUpdatesFromElement(element, urlSelector)
|
|
||||||
?.apply { title = title.removeSuffix(LANGUAGE_SUFFIX) }
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun gridLatestUpdatesFromElement(element: Element): SManga {
|
|
||||||
return super.gridLatestUpdatesFromElement(element)
|
|
||||||
.apply { title = title.removeSuffix(LANGUAGE_SUFFIX) }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Override searchMangaParse with same body from internalMangaParse since
|
|
||||||
// it can use the other endpoint instead.
|
|
||||||
override fun searchMangaParse(response: Response): MangasPage {
|
|
||||||
return super.searchMangaParse(response).let {
|
|
||||||
it.copy(
|
|
||||||
mangas = it.mangas.map { manga ->
|
|
||||||
manga.apply { title = title.removeSuffix(LANGUAGE_SUFFIX) }
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The website modified the information panel.
|
|
||||||
override fun mangaDetailsParse(response: Response): SManga = SManga.create().apply {
|
|
||||||
val document = response.asJsoup()
|
|
||||||
|
|
||||||
title = document.selectFirst("h1.widget-title")!!.text()
|
|
||||||
thumbnail_url = coverGuess(
|
|
||||||
document.select("div.col-sm-5 img.img-thumbnail").firstOrNull()?.attr("abs:src"),
|
|
||||||
document.location(),
|
|
||||||
)
|
|
||||||
description = document.select("div.row div.well p")!!.text().trim()
|
|
||||||
|
|
||||||
for (element in document.select("div.col-sm-5 ul.list-group li.list-group-item")) {
|
|
||||||
when (element.text().trim().lowercase(BRAZILIAN_LOCALE).substringBefore(":")) {
|
|
||||||
"autor(es)" -> author = element.select("a")
|
|
||||||
.joinToString(", ") { it.text().trim() }
|
|
||||||
"artist(s)" -> artist = element.select("a")
|
|
||||||
.joinToString(", ") { it.text().trim() }
|
|
||||||
"categorias" -> genre = element.select("a")
|
|
||||||
.joinToString(", ") { it.text().trim() }
|
|
||||||
"status" -> status = when (element.select("span.label").text()) {
|
|
||||||
"Completo", "Concluído" -> SManga.COMPLETED
|
|
||||||
"Ativo" -> SManga.ONGOING
|
|
||||||
else -> SManga.UNKNOWN
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun chapterListSelector(): String = "div.row ul.chapters > li"
|
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
|
||||||
return response.asJsoup()
|
|
||||||
.select(chapterListSelector())
|
|
||||||
.map { el ->
|
|
||||||
SChapter.create().apply {
|
|
||||||
name = el.select("h5.chapter-title-rtl").text()
|
|
||||||
scanlator = el.select("div.col-md-3 ul li")
|
|
||||||
.joinToString(" & ") { it.text().trim() }
|
|
||||||
date_upload = el.select("div.col-md-4").firstOrNull()
|
|
||||||
?.text()?.removeSuffix("Download")?.toDate() ?: 0L
|
|
||||||
setUrlWithoutDomain(el.select("h5.chapter-title-rtl a").first()!!.attr("href"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.toDate(): Long {
|
|
||||||
return runCatching { DATE_FORMAT.parse(trim())?.time }
|
|
||||||
.getOrNull() ?: 0L
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val LANGUAGE_SUFFIX = " (pt-br)"
|
|
||||||
private val BRAZILIAN_LOCALE = Locale("pt", "BR")
|
|
||||||
|
|
||||||
private val DATE_FORMAT by lazy {
|
|
||||||
SimpleDateFormat("dd MMM. yyyy", Locale.ENGLISH)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,9 +3,18 @@ package eu.kanade.tachiyomi.extension.fr.mangascan
|
||||||
import eu.kanade.tachiyomi.multisrc.mmrcms.MMRCMS
|
import eu.kanade.tachiyomi.multisrc.mmrcms.MMRCMS
|
||||||
import eu.kanade.tachiyomi.network.GET
|
import eu.kanade.tachiyomi.network.GET
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
|
import okhttp3.Response
|
||||||
|
|
||||||
|
class MangaScan : MMRCMS("Manga-Scan", "https://mangascan-fr.com", "fr") {
|
||||||
|
|
||||||
|
override fun mangaDetailsParse(response: Response): SManga {
|
||||||
|
return super.mangaDetailsParse(response).apply {
|
||||||
|
title = title.substringBefore("Chapitres en ligne").substringAfter("Scan").trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MangaScan : MMRCMS("Manga-Scan", "https://mangascan.cc", "fr") {
|
|
||||||
override fun imageRequest(page: Page): Request {
|
override fun imageRequest(page: Page): Request {
|
||||||
val newHeaders = headersBuilder()
|
val newHeaders = headersBuilder()
|
||||||
.set("Referer", baseUrl)
|
.set("Referer", baseUrl)
|
||||||
|
|
After Width: | Height: | Size: 5.9 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 18 KiB |
After Width: | Height: | Size: 27 KiB |
|
@ -1,7 +1,6 @@
|
||||||
package eu.kanade.tachiyomi.multisrc.flixscans
|
package eu.kanade.tachiyomi.multisrc.flixscans
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import eu.kanade.tachiyomi.network.GET
|
|
||||||
import eu.kanade.tachiyomi.network.POST
|
import eu.kanade.tachiyomi.network.POST
|
||||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import eu.kanade.tachiyomi.source.model.Filter
|
import eu.kanade.tachiyomi.source.model.Filter
|
||||||
|
@ -16,7 +15,6 @@ import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
|
||||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||||
import okhttp3.Request
|
import okhttp3.Request
|
||||||
import okhttp3.RequestBody.Companion.toRequestBody
|
import okhttp3.RequestBody.Companion.toRequestBody
|
||||||
|
@ -28,8 +26,8 @@ abstract class FlixScans(
|
||||||
override val name: String,
|
override val name: String,
|
||||||
override val baseUrl: String,
|
override val baseUrl: String,
|
||||||
override val lang: String,
|
override val lang: String,
|
||||||
protected val apiUrl: String = baseUrl.replace("://", "://api.").plus("/api/v1"),
|
protected val apiUrl: String = "$baseUrl/api/__api_party/noxApi",
|
||||||
protected val cdnUrl: String = baseUrl.replace("://", "://api.").plus("/storage/"),
|
protected val cdnUrl: String = baseUrl.replace("://", "://media.").plus("/"),
|
||||||
) : HttpSource() {
|
) : HttpSource() {
|
||||||
|
|
||||||
override val supportsLatest = true
|
override val supportsLatest = true
|
||||||
|
@ -43,6 +41,12 @@ abstract class FlixScans(
|
||||||
override fun headersBuilder() = super.headersBuilder()
|
override fun headersBuilder() = super.headersBuilder()
|
||||||
.add("Referer", baseUrl)
|
.add("Referer", baseUrl)
|
||||||
|
|
||||||
|
protected open fun postPath(path: String): Request {
|
||||||
|
val payload = """{"path":"$path","headers":{}}""".toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
|
return POST(apiUrl, headers, payload)
|
||||||
|
}
|
||||||
|
|
||||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||||
runCatching { fetchGenre() }
|
runCatching { fetchGenre() }
|
||||||
|
|
||||||
|
@ -50,7 +54,7 @@ abstract class FlixScans(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaRequest(page: Int): Request {
|
override fun popularMangaRequest(page: Int): Request {
|
||||||
return GET("$apiUrl/webtoon/homepage/home", headers)
|
return postPath("webtoon/pages/home/romance")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun popularMangaParse(response: Response): MangasPage {
|
override fun popularMangaParse(response: Response): MangasPage {
|
||||||
|
@ -70,16 +74,14 @@ abstract class FlixScans(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesRequest(page: Int): Request {
|
override fun latestUpdatesRequest(page: Int): Request {
|
||||||
return GET("$apiUrl/search/advance?page=$page&serie_type=webtoon", headers)
|
return postPath("search/advance?page=$page&serie_type=webtoon")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||||
val result = response.parseAs<ApiResponse<BrowseSeries>>()
|
val result = response.parseAs<ApiResponse<BrowseSeries>>()
|
||||||
val currentPage = response.request.url.queryParameter("page")
|
|
||||||
?.toIntOrNull() ?: 1
|
|
||||||
|
|
||||||
val entries = result.data.map { it.toSManga(cdnUrl) }
|
val entries = result.data.map { it.toSManga(cdnUrl) }
|
||||||
val hasNextPage = result.meta.lastPage > currentPage
|
val hasNextPage = result.meta.lastPage > result.meta.currentPage
|
||||||
|
|
||||||
return MangasPage(entries, hasNextPage)
|
return MangasPage(entries, hasNextPage)
|
||||||
}
|
}
|
||||||
|
@ -131,7 +133,7 @@ abstract class FlixScans(
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchGenreRequest(): Request {
|
private fun fetchGenreRequest(): Request {
|
||||||
return GET("$apiUrl/search/genres", headers)
|
return postPath("search/genres")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun fetchGenreParse(response: Response): List<GenreHolder> {
|
private fun fetchGenreParse(response: Response): List<GenreHolder> {
|
||||||
|
@ -168,53 +170,55 @@ abstract class FlixScans(
|
||||||
|
|
||||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||||
if (query.isNotEmpty()) {
|
if (query.isNotEmpty()) {
|
||||||
val requestBody = SearchInput(query.trim())
|
val searchBody = SearchInput(query.trim())
|
||||||
.let(json::encodeToString)
|
.let(json::encodeToString)
|
||||||
.toRequestBody(JSON_MEDIA_TYPE)
|
.replace("\"", "\\\"")
|
||||||
|
|
||||||
val newHeaders = headersBuilder()
|
val requestBody = """{
|
||||||
.add("Content-Length", requestBody.contentLength().toString())
|
|"path":"search/serie?page=$page",
|
||||||
.add("Content-Type", requestBody.contentType().toString())
|
|"headers":{"Content-type":"application/json"},
|
||||||
.build()
|
|"method":"POST","body":"$searchBody"
|
||||||
|
|}
|
||||||
|
""".trimMargin().toRequestBody(JSON_MEDIA_TYPE)
|
||||||
|
|
||||||
return POST("$apiUrl/search/serie?page=$page", newHeaders, requestBody)
|
return POST(apiUrl, headers, requestBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
val advSearchUrl = apiUrl.toHttpUrl().newBuilder().apply {
|
val advSearchBody = buildString {
|
||||||
addPathSegments("search/advance")
|
append("search/advance")
|
||||||
addQueryParameter("page", page.toString())
|
append("?page=", page)
|
||||||
addQueryParameter("serie_type", "webtoon")
|
append("&serie_type=webtoon")
|
||||||
|
|
||||||
filters.forEach { filter ->
|
filters.forEach { filter ->
|
||||||
when (filter) {
|
when (filter) {
|
||||||
is GenreFilter -> {
|
is GenreFilter -> {
|
||||||
filter.checked.let {
|
filter.checked.let {
|
||||||
if (it.isNotEmpty()) {
|
if (it.isNotEmpty()) {
|
||||||
addQueryParameter("genres", it.joinToString(","))
|
append("&genres=", it.joinToString(","))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is MainGenreFilter -> {
|
is MainGenreFilter -> {
|
||||||
if (filter.state > 0) {
|
if (filter.state > 0) {
|
||||||
addQueryParameter("main_genres", filter.selected)
|
append("&main_genres=", filter.selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is TypeFilter -> {
|
is TypeFilter -> {
|
||||||
if (filter.state > 0) {
|
if (filter.state > 0) {
|
||||||
addQueryParameter("type", filter.selected)
|
append("&type=", filter.selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is StatusFilter -> {
|
is StatusFilter -> {
|
||||||
if (filter.state > 0) {
|
if (filter.state > 0) {
|
||||||
addQueryParameter("status", filter.selected)
|
append("&status=", filter.selected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else -> {}
|
else -> {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.build()
|
}
|
||||||
|
|
||||||
return GET(advSearchUrl, headers)
|
return postPath(advSearchBody)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
|
override fun searchMangaParse(response: Response) = latestUpdatesParse(response)
|
||||||
|
@ -222,7 +226,7 @@ abstract class FlixScans(
|
||||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||||
val id = manga.url.split("-")[1]
|
val id = manga.url.split("-")[1]
|
||||||
|
|
||||||
return GET("$apiUrl/webtoon/series/$id", headers)
|
return postPath("webtoon/series/$id")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
override fun getMangaUrl(manga: SManga) = baseUrl + manga.url
|
||||||
|
@ -236,7 +240,7 @@ abstract class FlixScans(
|
||||||
override fun chapterListRequest(manga: SManga): Request {
|
override fun chapterListRequest(manga: SManga): Request {
|
||||||
val id = manga.url.split("-")[1]
|
val id = manga.url.split("-")[1]
|
||||||
|
|
||||||
return GET("$apiUrl/webtoon/chapters/$id-desc", headers)
|
return postPath("webtoon/chapters/$id-desc")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun chapterListParse(response: Response): List<SChapter> {
|
override fun chapterListParse(response: Response): List<SChapter> {
|
||||||
|
@ -250,7 +254,7 @@ abstract class FlixScans(
|
||||||
.substringAfterLast("/")
|
.substringAfterLast("/")
|
||||||
.substringBefore("-")
|
.substringBefore("-")
|
||||||
|
|
||||||
return GET("$apiUrl/webtoon/chapters/chapter/$id", headers)
|
return postPath("webtoon/chapters/chapter/$id")
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url
|
override fun getChapterUrl(chapter: SChapter) = baseUrl + chapter.url
|
||||||
|
@ -269,6 +273,6 @@ abstract class FlixScans(
|
||||||
use { body.string() }.let(json::decodeFromString)
|
use { body.string() }.let(json::decodeFromString)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val JSON_MEDIA_TYPE = "application/json; charset=utf-8".toMediaTypeOrNull()
|
private val JSON_MEDIA_TYPE = "application/json".toMediaTypeOrNull()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@ data class ApiResponse<T>(
|
||||||
@Serializable
|
@Serializable
|
||||||
data class PageInfo(
|
data class PageInfo(
|
||||||
@SerialName("last_page") val lastPage: Int,
|
@SerialName("last_page") val lastPage: Int,
|
||||||
|
@SerialName("current_page") val currentPage: Int,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
|
@ -9,7 +9,7 @@ class FlixScansGenerator : ThemeSourceGenerator {
|
||||||
|
|
||||||
override val themeClass = "FlixScans"
|
override val themeClass = "FlixScans"
|
||||||
|
|
||||||
override val baseVersionCode: Int = 3
|
override val baseVersionCode: Int = 4
|
||||||
|
|
||||||
override val sources = listOf(
|
override val sources = listOf(
|
||||||
SingleLang("Flix Scans", "https://flixscans.org", "en", className = "FlixScansNet", pkgName = "flixscans"),
|
SingleLang("Flix Scans", "https://flixscans.org", "en", className = "FlixScansNet", pkgName = "flixscans"),
|
||||||
|
|
|
@ -15,7 +15,6 @@ class FMReaderGenerator : ThemeSourceGenerator {
|
||||||
SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"),
|
SingleLang("Epik Manga", "https://www.epikmanga.com", "tr"),
|
||||||
SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4),
|
SingleLang("KissLove", "https://klz9.com", "ja", isNsfw = true, overrideVersionCode = 4),
|
||||||
SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 3),
|
SingleLang("Manga-TR", "https://manga-tr.com", "tr", className = "MangaTR", overrideVersionCode = 3),
|
||||||
SingleLang("ManhuaRock", "https://manhuarock.net", "vi", overrideVersionCode = 1),
|
|
||||||
SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3),
|
SingleLang("Say Truyen", "https://saytruyenvip.com", "vi", overrideVersionCode = 3),
|
||||||
SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5),
|
SingleLang("WeLoveManga", "https://weloma.art", "ja", pkgName = "rawlh", isNsfw = true, overrideVersionCode = 5),
|
||||||
SingleLang("Manga1000", "https://manga1000.top", "ja"),
|
SingleLang("Manga1000", "https://manga1000.top", "ja"),
|
||||||
|
|