Compare commits

..

No commits in common. "eb4c7f00be38a555e984f4d8e5ccb092adf5c868" and "d61cbfc0c18a50f7b9805c3dfc01d102f69e353d" have entirely different histories.

244 changed files with 1403 additions and 4724 deletions

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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/")

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -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")),
) { ) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View File

@ -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/"
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -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()
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

View File

@ -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")
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 20 KiB

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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")
}
}

View File

@ -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")
}
}
} }

View File

@ -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")
}
}
}

View File

@ -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()
}
} }

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

View File

@ -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")
}
}

View File

@ -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""") }
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

View File

@ -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/"
}

View File

@ -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: "
}

View File

@ -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(

View File

@ -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(),
)
}

View File

@ -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>,
)
}

View File

@ -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))"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

View File

Before

Width:  |  Height:  |  Size: 7.3 KiB

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View File

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

View File

@ -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")),
) { ) {

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

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

View File

@ -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)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More