Compare commits
No commits in common. "eb4c7f00be38a555e984f4d8e5ccb092adf5c868" and "d61cbfc0c18a50f7b9805c3dfc01d102f69e353d" have entirely different histories.
eb4c7f00be
...
d61cbfc0c1
@ -769,11 +769,9 @@ Please **do test your changes by compiling it through Android Studio** before su
|
|||||||
|
|
||||||
### Pull Request checklist
|
### Pull Request checklist
|
||||||
|
|
||||||
- Updated `extVersionCode` value in `build.gradle` for individual extensions
|
- Update `extVersionCode` value in `build.gradle` for individual extensions
|
||||||
- Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
|
- Update `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
|
||||||
- Referenced all related issues in the PR body (e.g. "Closes #xyz")
|
- Reference all related issues in the PR body (e.g. "Closes #xyz")
|
||||||
- Added the `isNsfw = true` flag in `build.gradle` when appropriate
|
- Add the `isNsfw = true` flag in `build.gradle` when appropriate
|
||||||
- Have not changed source names
|
- Explicitly kept the `id` if a source's name or language were changed
|
||||||
- Have explicitly kept the `id` if a source's name or language were changed
|
- Test the modifications by compiling and running the extension through Android Studio
|
||||||
- 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://keiyoushi.github.io/user-agents/user-agents.json"
|
private const val UA_DB_URL = "https://tachiyomiorg.github.io/user-agents/user-agents.json"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 1.8 KiB |
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 8.0 KiB |
|
Before Width: | Height: | Size: 17 KiB After Width: | Height: | Size: 12 KiB |
@ -0,0 +1,5 @@
|
|||||||
|
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,7 +1,13 @@
|
|||||||
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 4.4 KiB |
|
Before Width: | Height: | Size: 8.4 KiB |
|
Before 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.net",
|
"https://cerisescan.com",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
SimpleDateFormat("dd 'de' MMMMM 'de' yyyy", Locale("pt", "BR")),
|
||||||
) {
|
) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.0 KiB |
|
Before Width: | Height: | Size: 8.7 KiB |
|
Before Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 25 KiB |
@ -1,9 +0,0 @@
|
|||||||
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/"
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 3.7 KiB |
|
Before Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@ -1,19 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 6.7 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 23 KiB |
|
Before Width: | Height: | Size: 38 KiB |
@ -1,112 +0,0 @@
|
|||||||
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: 3.4 KiB After Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 7.1 KiB |
|
Before Width: | Height: | Size: 8.5 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 20 KiB |
@ -6,14 +6,8 @@ import org.jsoup.nodes.Document
|
|||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
class HentaiCB : Madara("CBHentai", "https://cbhentai.net", "vi", SimpleDateFormat("dd/MM/yyyy", Locale("vi"))) {
|
class HentaiCB : Madara("Hentai CB", "https://hentaicube.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://hiper.cool", "pt-BR") {
|
class Hipercool : Madara("HipercooL", "https://hipercool.xyz", "pt-BR") {
|
||||||
|
|
||||||
// Migrated from a custom CMS to Madara.
|
// Migrated from a custom CMS to Madara.
|
||||||
override val versionId = 2
|
override val versionId = 2
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 8.5 KiB |
|
Before Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@ -1,59 +0,0 @@
|
|||||||
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,6 +6,7 @@ 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
|
||||||
@ -61,4 +62,14 @@ 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,6 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -9,4 +10,14 @@ 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,16 +2,11 @@ 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
|
||||||
@ -77,14 +72,13 @@ class MangasNoSekai : Madara(
|
|||||||
|
|
||||||
override fun searchMangaNextPageSelector() = "nav.navigation a.next"
|
override fun searchMangaNextPageSelector() = "nav.navigation a.next"
|
||||||
|
|
||||||
override val mangaDetailsSelectorTitle = "div.thumble-container p.titleMangaSingle"
|
override val mangaDetailsSelectorTitle = "div.summary-content h1.titleManga"
|
||||||
override val mangaDetailsSelectorThumbnail = "div.thumble-container img.img-responsive"
|
override val mangaDetailsSelectorThumbnail = "div.tab-summary img.img-responsive"
|
||||||
override val mangaDetailsSelectorDescription = "section#section-sinopsis > p"
|
override val mangaDetailsSelectorDescription = "div.summary-content div.artist-content"
|
||||||
override val mangaDetailsSelectorStatus = "section#section-sinopsis div.d-flex:has(div:contains(Estado)) p"
|
override val mangaDetailsSelectorStatus = "div.summary-content ul.general-List li:has(span:contains(Estado))"
|
||||||
override val mangaDetailsSelectorAuthor = "section#section-sinopsis div.d-flex:has(div:contains(Autor)) p"
|
override val mangaDetailsSelectorAuthor = "div.summary-content ul.general-List li:has(span:contains(Autor))"
|
||||||
override val mangaDetailsSelectorGenre = "section#section-sinopsis div.d-flex:has(div:contains(Generos)) p a"
|
override val mangaDetailsSelectorArtist = "div.summary-content ul.general-List li:has(span:contains(Dibujante))"
|
||||||
override val altNameSelector = "section#section-sinopsis div.d-flex:has(div:contains(Otros nombres)) p"
|
override val seriesTypeSelector = "div.summary-content ul.general-List li:has(span:contains(Tipo))"
|
||||||
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()
|
||||||
@ -95,6 +89,9 @@ 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()
|
||||||
}
|
}
|
||||||
@ -114,6 +111,13 @@ 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()) {
|
||||||
@ -126,6 +130,7 @@ 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 {
|
||||||
@ -148,64 +153,4 @@ 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,148 +1,46 @@
|
|||||||
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.GET
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
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)) {
|
||||||
|
|
||||||
private val preferences: SharedPreferences by lazy {
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
.rateLimit(20, 5, TimeUnit.SECONDS)
|
||||||
}
|
|
||||||
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()
|
|
||||||
|
|
||||||
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()
|
|
||||||
.addInterceptor(uaIntercept)
|
|
||||||
.connectTimeout(10, TimeUnit.SECONDS)
|
|
||||||
.readTimeout(30, TimeUnit.SECONDS)
|
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
override fun headersBuilder(): Headers.Builder {
|
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
|
||||||
val builder = super.headersBuilder()
|
.add("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8")
|
||||||
.add("Sec-Fetch-Dest", "document")
|
.add("Accept-Language", "en-US,en;q=0.9,id;q=0.8")
|
||||||
.add("Sec-Fetch-Mode", "navigate")
|
.add("Sec-Fetch-Dest", "document")
|
||||||
.add("Sec-Fetch-Site", "same-origin")
|
.add("Sec-Fetch-Mode", "navigate")
|
||||||
.add("Upgrade-Insecure-Requests", "1")
|
.add("Sec-Fetch-Site", "same-origin")
|
||||||
.add("X-Requested-With", "") // added for webview, and removed in interceptor for normal use
|
.add("Sec-Fetch-User", "?1")
|
||||||
|
.add("Upgrade-Insecure-Requests", "1")
|
||||||
|
.add("X-Requested-With", randomString)
|
||||||
|
|
||||||
// used to flush tachi custom ua in webview and use system ua instead
|
private fun generateRandomString(length: Int): String {
|
||||||
if (userAgent.isNullOrBlank()) builder.removeAll("User-Agent")
|
val charset = "HALOGaES.BCDFHIJKMNPQRTUVWXYZ.bcdefghijklmnopqrstuvwxyz0123456789"
|
||||||
|
return (1..length)
|
||||||
return builder
|
.map { charset.random() }
|
||||||
|
.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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 6.5 KiB |
|
Before Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 9.9 KiB |
|
Before Width: | Height: | Size: 19 KiB |
|
Before Width: | Height: | Size: 29 KiB |
@ -1,60 +0,0 @@
|
|||||||
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,17 +1,28 @@
|
|||||||
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.interceptor.rateLimit
|
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.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") {
|
||||||
@ -22,37 +33,97 @@ 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/"
|
||||||
|
|
||||||
override fun headersBuilder() = super.headersBuilder().apply {
|
private val preferences: SharedPreferences by lazy {
|
||||||
add("Sec-Fetch-Dest", "document")
|
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
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="
|
||||||
.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())
|
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()
|
||||||
|
.addInterceptor(uaIntercept)
|
||||||
.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)!!
|
||||||
|
|
||||||
@ -77,15 +148,13 @@ 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 keyMatch = KEY_REGEX.find(deobfuscated)?.groupValues
|
val postId = script.substringAfter("var post_id = '").substringBefore("'")
|
||||||
?: throw Exception("Unable to find key")
|
|
||||||
|
|
||||||
val chapterData = json.decodeFromString<CDT>(
|
val chapterData = json.decodeFromString<CDT>(
|
||||||
CHAPTER_DATA_REGEX.find(script)?.groupValues?.get(1) ?: throw Exception("Unable to get chapter data"),
|
script.substringAfter("var chapter_data = '").substringBefore("'"),
|
||||||
)
|
)
|
||||||
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 keyMatch = KEY_REGEX.find(deobfuscated)!!.groupValues
|
||||||
val key = otherId + keyMatch[1] + postId + keyMatch[2] + postId
|
val key = postId + 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)
|
||||||
@ -107,10 +176,36 @@ 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 {
|
||||||
private val KEY_REGEX by lazy { Regex("""_id\s+\+\s+'(.*?)'\s+\+\s+post_id\s+\+\s+'(.*?)'\s+\+\s+post_id""") }
|
const val TITLE_CUSTOM_UA = "Custom User-Agent"
|
||||||
private val CHAPTER_DATA_REGEX by lazy { Regex("""var chapter_data\s*=\s*'(.*?)'""") }
|
const val PREF_KEY_CUSTOM_UA = "pref_key_custom_ua"
|
||||||
private val POST_ID_REGEX by lazy { Regex("""var post_id\s*=\s*'(.*?)'""") }
|
const val SUMMARY_STRING_CUSTOM_UA = "\n\nBiarkan kosong untuk menggunakan User-Agent secara random"
|
||||||
private val OTHER_ID_REGEX by lazy { Regex("""var (\w+)_id\s*=\s*'(.*?)'""") }
|
const val SUMMARY_STRING2_CUSTOM_UA = "\n\nKosongkan untuk menggunakan User-Agent secara random"
|
||||||
|
|
||||||
|
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""") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 6.9 KiB |
|
Before Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 18 KiB |
@ -1,9 +0,0 @@
|
|||||||
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/"
|
|
||||||
}
|
|
||||||
@ -0,0 +1,22 @@
|
|||||||
|
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.en.asurascans
|
package eu.kanade.tachiyomi.extension.all.asurascans
|
||||||
|
|
||||||
import android.app.Application
|
import android.app.Application
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@ -18,6 +18,7 @@ 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
|
||||||
@ -26,7 +27,7 @@ import java.text.SimpleDateFormat
|
|||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class AsuraScans : MangaThemesia(
|
class AsuraScansEn : MangaThemesia(
|
||||||
"Asura Scans",
|
"Asura Scans",
|
||||||
"https://asuratoon.com",
|
"https://asuratoon.com",
|
||||||
"en",
|
"en",
|
||||||
@ -114,6 +115,13 @@ class AsuraScans : 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(
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package eu.kanade.tachiyomi.extension.all.asurascans
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.source.SourceFactory
|
||||||
|
|
||||||
|
class AsuraScansFactory : SourceFactory {
|
||||||
|
override fun createSources() = listOf(
|
||||||
|
AsuraScansEn(),
|
||||||
|
AsuraScansTr(),
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,66 @@
|
|||||||
|
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,6 +1,7 @@
|
|||||||
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
|
||||||
|
|
||||||
@ -14,6 +15,13 @@ 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))"
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 6.0 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@ -1,22 +0,0 @@
|
|||||||
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,47 +1,11 @@
|
|||||||
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.util.asJsoup
|
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||||
import okhttp3.Cookie
|
import okhttp3.OkHttpClient
|
||||||
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 = super.client.newBuilder().addInterceptor(::sucuriInterceptor).build()
|
override val client: OkHttpClient = super.client.newBuilder()
|
||||||
|
.rateLimit(4)
|
||||||
// Taken from es/ManhwasNet
|
.build()
|
||||||
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: 2.4 KiB After Width: | Height: | Size: 2.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 5.4 KiB |
|
Before Width: | Height: | Size: 7.3 KiB After Width: | Height: | Size: 7.3 KiB |
BIN
multisrc/overrides/mangathemesia/opscans/res/web_hi_res_512.png
Normal file
|
After Width: | Height: | Size: 25 KiB |
@ -0,0 +1,36 @@
|
|||||||
|
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.br",
|
"https://sssscanlator.com",
|
||||||
"pt-BR",
|
"pt-BR",
|
||||||
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
dateFormat = SimpleDateFormat("MMMMM dd, yyyy", Locale("pt", "BR")),
|
||||||
) {
|
) {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 4.2 KiB |
|
Before Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 16 KiB |
@ -1,27 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 4.3 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 53 KiB |
115
multisrc/overrides/mmrcms/animaregia/src/AnimaRegia.kt
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
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,18 +3,9 @@ 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)
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.9 KiB |
|
Before Width: | Height: | Size: 3.1 KiB |
|
Before Width: | Height: | Size: 9.0 KiB |
|
Before Width: | Height: | Size: 18 KiB |