Remove some dead sources (#11133)

* Remove Manhwafull

* Remove Mangahasu

* Remove MangaScans

* Remove MyShojo

* Remove Iris Scans

* Remove Constellar Scans

* Remove King of Scans

* Remove 1st-Kiss Manga.net

* Remove Banana Manga

* Remove Todaymic

* Remove Comicsekai

* Remove KataKomik

* Remove Komik Lovers

* Remove Mangakyo

* Remove MangaYu

* Remove Tenshi.id

* Remove TukangKomik

* Remove YuraManga

* Remove Pojok Manga

* Remove Scan-Manga

* Remove Valkyrie Scan

* Remove Starbound Scans

* Remove Manga Rock Team

* Remove Tres Daos Net
This commit is contained in:
Vetle Ledaal 2025-10-19 11:16:53 +02:00 committed by Draff
parent e92d5d1819
commit d2878c5670
Signed by: Draff
GPG Key ID: E8A89F3211677653
156 changed files with 0 additions and 1946 deletions

View File

@ -1,10 +0,0 @@
ext {
extName = 'Banana Manga'
extClass = '.BananaManga'
themePkg = 'madara'
baseUrl = 'https://bananamanga.net'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.en.bananamanga
import eu.kanade.tachiyomi.multisrc.madara.Madara
class BananaManga : Madara("Banana Manga", "https://bananamanga.net", "en") {
override val useNewChapterEndpoint = true
}

View File

@ -1,14 +0,0 @@
ext {
extName = 'Constellar Scans'
extClass = '.ConstellarScans'
themePkg = 'mangathemesia'
baseUrl = 'https://constellarcomic.com'
overrideVersionCode = 17
isNsfw = true
}
apply from: "$rootDir/common.gradle"
dependencies {
implementation(project(":lib:randomua"))
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

View File

@ -1,102 +0,0 @@
package eu.kanade.tachiyomi.extension.en.constellarscans
import android.content.SharedPreferences
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.lib.randomua.addRandomUAPreferenceToScreen
import eu.kanade.tachiyomi.lib.randomua.getPrefCustomUA
import eu.kanade.tachiyomi.lib.randomua.getPrefUAType
import eu.kanade.tachiyomi.lib.randomua.setRandomUserAgent
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.ConfigurableSource
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import keiyoushi.utils.getPreferencesLazy
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.CacheControl
import okhttp3.Headers
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
class ConstellarScans :
MangaThemesia(
"Constellar Scans",
"https://constellarcomic.com",
"en",
),
ConfigurableSource {
private val preferences: SharedPreferences by getPreferencesLazy()
override fun setupPreferenceScreen(screen: PreferenceScreen) {
addRandomUAPreferenceToScreen(screen)
}
override val client: OkHttpClient by lazy {
network.cloudflareClient.newBuilder()
.setRandomUserAgent(
preferences.getPrefUAType(),
preferences.getPrefCustomUA(),
)
.rateLimit(1, 1)
.build()
}
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", "$baseUrl/")
.add("Accept-Language", "en-US,en;q=0.9")
.add("DNT", "1")
.add("Upgrade-Insecure-Requests", "1")
override val seriesStatusSelector = ".status"
override fun pageListRequest(chapter: SChapter): Request =
super.pageListRequest(chapter).newBuilder()
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
)
.header("Sec-Fetch-Site", "same-origin")
.header("Sec-Fetch-Mode", "navigate")
.header("Sec-Fetch-Dest", "document")
.header("Sec-Fetch-User", "?1")
.cacheControl(CacheControl.FORCE_NETWORK)
.build()
override fun pageListParse(document: Document): List<Page> {
countViews(document)
val html = document.toString()
if (!html.contains("ts_rea_der_._run(\"")) {
return super.pageListParse(document)
}
val tsReaderRawData = html
.substringAfter("ts_rea_der_._run(\"")
.substringBefore("\")")
.replace(Regex("""\D"""), "")
.chunked(4)
.map {
val tenthsAndOnes = it.chunked(2).map {
val num = it.toInt()
num / 10 + num % 10
}
(tenthsAndOnes[0] * 10 + tenthsAndOnes[1] + 32).toChar()
}
.joinToString("")
return json.parseToJsonElement(tsReaderRawData).jsonObject["sources"]!!.jsonArray[0].jsonObject["images"]!!.jsonArray.mapIndexed { idx, it ->
Page(idx, imageUrl = it.jsonPrimitive.content)
}
}
override fun imageRequest(page: Page): Request = super.imageRequest(page).newBuilder()
.header("Accept", "image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8")
.header("Sec-Fetch-Dest", "image")
.header("Sec-Fetch-Mode", "no-cors")
.header("Sec-Fetch-Site", "same-origin")
.build()
}

View File

@ -1,10 +0,0 @@
ext {
extName = '1st-Kiss Manga.net'
extClass = '.FirstKissMangaNet'
themePkg = 'madara'
baseUrl = 'https://1stkissmanga.org'
overrideVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

View File

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.extension.en.firstkissmanganet
import eu.kanade.tachiyomi.multisrc.madara.Madara
class FirstKissMangaNet : Madara(
"1st-Kiss Manga.net",
"https://1stkissmanga.org",
"en",
) {
override val useLoadMoreRequest = LoadMoreStrategy.Never
override val useNewChapterEndpoint = false
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Iris Scans'
extClass = '.IrisScans'
themePkg = 'mangathemesia'
baseUrl = 'https://irisscans.xyz'
overrideVersionCode = 0
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,15 +0,0 @@
package eu.kanade.tachiyomi.extension.en.irisscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import okhttp3.OkHttpClient
class IrisScans : MangaThemesia(
"Iris Scans",
"https://irisscans.xyz",
"en",
) {
override val client: OkHttpClient = super.client.newBuilder()
.rateLimit(4)
.build()
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'King of Scans'
extClass = '.KingOfScans'
themePkg = 'mangathemesia'
baseUrl = 'https://kingofscans.com'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

View File

@ -1,24 +0,0 @@
package eu.kanade.tachiyomi.extension.en.kingofscans
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
import eu.kanade.tachiyomi.source.model.Page
import org.jsoup.nodes.Document
class KingOfScans : MangaThemesia(
"King of Scans",
"https://kingofscans.com",
"en",
mangaUrlDirectory = "/comics",
) {
// Strip out position- and name-dependant ads. "Read first at ..."
override fun pageListParse(document: Document): List<Page> {
val pages = super.pageListParse(document)
return pages.filterIndexed { index, page ->
when (index) {
0 -> !page.imageUrl!!.endsWith("/START-KS.jpg")
pages.lastIndex -> !page.imageUrl!!.endsWith("/END-KS.jpg")
else -> true
}
}
}
}

View File

@ -1,8 +0,0 @@
ext {
extName = 'Mangahasu'
extClass = '.Mangahasu'
extVersionCode = 17
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -1,369 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangahasu
import android.util.Base64
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.int
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.text.SimpleDateFormat
import java.util.Locale
class Mangahasu : ParsedHttpSource() {
override val name = "Mangahasu"
override val baseUrl = "https://mangahasu.me"
override val lang = "en"
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient
private val json: Json by injectLazy()
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Referer", baseUrl)
override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/most-popular.html?page=$page", headers)
override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/latest-releases.html?page=$page", headers)
// Only selects popular of all time
override fun popularMangaSelector() = "div.right div.div_item"
override fun latestUpdatesSelector() = "div.div_item"
override fun popularMangaFromElement(element: Element): SManga {
val manga = SManga.create()
manga.thumbnail_url = element.select("img").first()!!.attr("src")
element.select("a:has(h3.name-manga), a.name-manga").first()!!.let {
manga.setUrlWithoutDomain(it.attr("href"))
manga.title = it.text()
}
return manga
}
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun popularMangaNextPageSelector() = "a[title = Tiếp]"
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/advanced-search.html".toHttpUrl().newBuilder()
url.addQueryParameter("keyword", query)
url.addQueryParameter("page", page.toString())
filters.forEach { filter ->
when (filter) {
is AuthorFilter -> url.addQueryParameter("author", filter.state)
is ArtistFilter -> url.addQueryParameter("artist", filter.state)
is StatusFilter -> url.addQueryParameter("status", filter.toUriPart())
is TypeFilter -> url.addQueryParameter("typeid", filter.toUriPart())
is GenreFilter -> {
filter.state.forEach {
when (it.state) {
Filter.TriState.STATE_INCLUDE -> url.addQueryParameter("g_i[]", it.id)
Filter.TriState.STATE_EXCLUDE -> url.addQueryParameter("g_e[]", it.id)
}
}
}
is OrderByFilter -> {
filter.state?.let {
var sortId = it.index
// Increment for consistency
when (sortId) {
1 -> sortId += 1
2 -> sortId += 2
}
val value = if (it.ascending) "${(sortId + 1)}" else "$sortId"
url.addQueryParameter("orderby", value)
}
}
else -> {
// ignore
}
}
}
return GET(url.build(), headers)
}
override fun searchMangaSelector() = latestUpdatesSelector()
override fun searchMangaFromElement(element: Element): SManga =
popularMangaFromElement(element)
// max 200 results
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
override fun mangaDetailsParse(document: Document): SManga {
val infoElement = document.select(".info-c").first()!!
val manga = SManga.create()
manga.author = isUpdating(infoElement.select(".info")[0].text())
manga.artist = isUpdating(infoElement.select(".info")[1].text())
manga.genre = isUpdating(infoElement.select(".info")[3].text())
manga.status = parseStatus(infoElement.select(".info")[4].text())
manga.description =
document.select("div.content-info:has(h3:contains(summary)) div").first()?.text()
manga.thumbnail_url = document.select("div.info-img img").attr("src")
return manga
}
private fun parseStatus(element: String): Int = when {
element.contains("Ongoing") -> SManga.ONGOING
element.contains("Completed") -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
private fun isUpdating(string: String): String {
return if (string == "Updating...") "" else string
}
override fun chapterListSelector() = "tbody tr"
override fun chapterFromElement(element: Element): SChapter {
val urlElement = element.select("a").first()!!
val chapter = SChapter.create()
chapter.setUrlWithoutDomain(urlElement.attr("href"))
urlElement.select("span.name-manga").remove()
chapter.name = urlElement.text()
chapter.date_upload = element.select(".date-updated").last()?.text()?.let {
SimpleDateFormat("MMM dd, yyyy", Locale.US).parse(it)?.time ?: 0
} ?: 0
return chapter
}
override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
val basic = Regex("""Chapter\s([0-9]+)""")
when {
basic.containsMatchIn(chapter.name) -> {
basic.find(chapter.name)?.let {
chapter.chapter_number = it.groups[1]?.value!!.toFloat()
}
}
}
}
override fun pageListParse(document: Document): List<Page> {
// Grab All Pages from site
// Some images are place holders on new chapters.
val pageList = document.select("div.img img")
.mapIndexed { _, el ->
val pageNumber = el.attr("class").substringAfter("page").toInt()
Page(pageNumber, "", el.attr("src"))
}
.toMutableList()
// Some images are not yet loaded onto Mangahasu's image server.
// Decode temporary URLs and replace placeholder images.
val lstDUrls = document.select("script:containsData(lstDUrls)")
.html()
.substringAfter("lstDUrls")
.substringAfter("\"")
.substringBefore("\"")
// Base64 = [] or empty file
// Ensures lstDUrls exists, otherwise errors will occur
if (lstDUrls.isNotEmpty() && lstDUrls != "W10=") {
val jsonRaw = String(Base64.decode(lstDUrls, Base64.DEFAULT))
val jsonArr = json.parseToJsonElement(jsonRaw).jsonArray
jsonArr.forEach { jsonEl ->
val jsonObj = jsonEl.jsonObject
val pageNumber = jsonObj["page"]!!.jsonPrimitive.int
pageList.removeAll { page -> page.index == pageNumber }
pageList.add(Page(pageNumber, "", jsonObj["url"]!!.jsonPrimitive.content))
}
}
return pageList.sortedBy { page -> page.index }
}
override fun imageUrlParse(document: Document) = ""
// Filters
override fun getFilterList() = FilterList(
AuthorFilter(),
ArtistFilter(),
OrderByFilter(),
StatusFilter(),
TypeFilter(),
GenreFilter(getGenreList()),
)
private class AuthorFilter : Filter.Text("Author")
private class ArtistFilter : Filter.Text("Artist")
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
fun toUriPart() = vals[state].second
}
private class TypeFilter : UriPartFilter(
"Type",
arrayOf(
Pair("Any", ""),
Pair("Manga", "10"),
Pair("Manhwa", "12"),
Pair("Manhua", "19"),
),
)
private class OrderByFilter : Filter.Sort(
"Order By",
arrayOf(
"Updated",
"Views",
"Subscribers",
),
Selection(0, false),
)
private class StatusFilter : UriPartFilter(
"Status",
arrayOf(
Pair("Any", ""),
Pair("Completed", "1"),
Pair("Ongoing", "2"),
),
)
private class Genre(name: String, val id: String) : Filter.TriState(name)
private class GenreFilter(genres: List<Genre>) : Filter.Group<Genre>("Genres", genres)
private fun getGenreList() = listOf(
Genre("4-koma", "46"),
Genre("Action", "1"),
Genre("Adaptation", "101"),
Genre("Adult", "2"),
Genre("Adventure", "3"),
Genre("Aliens", "103"),
Genre("Animals", "73"),
Genre("Anime", "57"),
Genre("Anthology", "99"),
Genre("Award Winning", "48"),
Genre("Bara", "60"),
Genre("Comedy", "4"),
Genre("Comic", "5"),
Genre("Cooking", "6"),
Genre("Crime", "92"),
Genre("Crossdressing", "86"),
Genre("Delinquents", "83"),
Genre("Demons", "51"),
Genre("Doujinshi", "7"),
Genre("Drama", "8"),
Genre("Ecchi", "9"),
Genre("Fan Colored", "107"),
Genre("Fantasy", "10"),
Genre("Full Color", "95"),
Genre("Game", "68"),
Genre("Gender Bender", "11"),
Genre("Genderswap", "81"),
Genre("Ghosts", "90"),
Genre("Gore", "100"),
Genre("Gyaru", "97"),
Genre("Harem", "12"),
Genre("Historical", "13"),
Genre("Horror", "14"),
Genre("Incest", "84"),
Genre("Isekai", "67"),
Genre("Josei", "15"),
Genre("Live Action", "59"),
Genre("Loli", "91"),
Genre("Lolicon", "16"),
Genre("Long Strip", "93"),
Genre("Mafia", "113"),
Genre("Magic", "55"),
Genre("Magical Girls", "89"),
Genre("Manga Reviews", "64"),
Genre("Martial Arts", "20"),
Genre("Mature", "21"),
Genre("Mecha", "22"),
Genre("Medical", "23"),
Genre("Military", "62"),
Genre("Monster Girls", "87"),
Genre("Monsters", "72"),
Genre("Music", "24"),
Genre("Mystery", "25"),
Genre("Ninja", "112"),
Genre("Office Workers", "80"),
Genre("Official Colored", "96"),
Genre("One shot", "26"),
Genre("Others", "114"),
Genre("Philosophical", "110"),
Genre("Police", "105"),
Genre("Post-Apocalyptic", "76"),
Genre("Psychological", "27"),
Genre("Reincarnation", "74"),
Genre("Reverse harem", "69"),
Genre("Romance", "28"),
Genre("Samurai", "108"),
Genre("School Life", "29"),
Genre("Sci-fi", "30"),
Genre("Seinen", "31"),
Genre("Seinen Supernatural", "66"),
Genre("Sexual Violence", "98"),
Genre("Shota", "104"),
Genre("Shotacon", "32"),
Genre("Shoujo", "33"),
Genre("Shoujo Ai", "34"),
Genre("Shoujoai", "63"),
Genre("Shounen", "35"),
Genre("Shounen Ai", "36"),
Genre("Shounenai", "61"),
Genre("Slice of Life", "37"),
Genre("Smut", "38"),
Genre("Sports", "39"),
Genre("Super power", "70"),
Genre("Superhero", "88"),
Genre("Supernatural", "40"),
Genre("Survival", "77"),
Genre("Thriller", "75"),
Genre("Time Travel", "78"),
Genre("Traditional Games", "111"),
Genre("Tragedy", "41"),
Genre("Uncategorized", "65"),
Genre("User Created", "102"),
Genre("Vampire", "58"),
Genre("Vampires", "82"),
Genre("Video Games", "85"),
Genre("Virtual Reality", "109"),
Genre("Web Comic", "94"),
Genre("Webtoon", "42"),
Genre("Webtoons", "56"),
Genre("Wuxia", "71"),
Genre("Yaoi", "43"),
Genre("Youkai", "106"),
Genre("Yuri", "44"),
Genre("Zombies", "79"),
)
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Manga Rock Team'
extClass = '.MangaRockTeam'
themePkg = 'madara'
baseUrl = 'https://mangarockteam.com'
overrideVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangarockteam
import eu.kanade.tachiyomi.multisrc.madara.Madara
class MangaRockTeam : Madara("Manga Rock Team", "https://mangarockteam.com", "en")

View File

@ -1,8 +0,0 @@
ext {
extName = 'MangaScans'
extClass = '.MangaScans'
extVersionCode = 2
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

View File

@ -1,131 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangatop
import eu.kanade.tachiyomi.source.model.Filter
import okhttp3.HttpUrl
interface UriFilter {
fun addToUri(builder: HttpUrl.Builder)
}
open class UriMultiSelectOption(name: String, val value: String) : Filter.CheckBox(name)
open class UriMultiSelectFilter(
name: String,
private val param: String,
private val vals: Array<Pair<String, String>>,
) : Filter.Group<UriMultiSelectOption>(name, vals.map { UriMultiSelectOption(it.first, it.second) }), UriFilter {
override fun addToUri(builder: HttpUrl.Builder) {
state.filter { it.state }.forEach {
builder.addQueryParameter(param, it.value)
}
}
}
class TypeFilter : UriMultiSelectFilter(
"Type",
"types[]",
arrayOf(
Pair("Manga", "1"),
Pair("Novel", "2"),
Pair("One Shot", "3"),
Pair("Doujinshi", "4"),
Pair("Manhwa", "5"),
Pair("Manhua", "6"),
Pair("OEL", "7"),
Pair("Light Novel", "8"),
),
)
class GenreFilter : UriMultiSelectFilter(
"Genre",
"genres[]",
arrayOf(
Pair("Action", "1"),
Pair("Adventure", "2"),
Pair("Avant Garde", "5"),
Pair("Award Winning", "46"),
Pair("Boys Love", "28"),
Pair("Comedy", "4"),
Pair("Drama", "8"),
Pair("Fantasy", "10"),
Pair("Girls Love", "26"),
Pair("Gourmet", "47"),
Pair("Horror", "14"),
Pair("Mystery", "7"),
Pair("Romance", "22"),
Pair("Sci-Fi", "24"),
Pair("Slice of Life", "36"),
Pair("Sports", "30"),
Pair("Supernatural", "37"),
Pair("Suspense", "45"),
Pair("Ecchi", "9"),
Pair("Erotica", "49"),
Pair("Hentai", "12"),
Pair("Adult Cast", "50"),
Pair("Anthropomorphic", "51"),
Pair("CGDCT", "52"),
Pair("Childcare", "53"),
Pair("Combat Sports", "54"),
Pair("Crossdressing", "44"),
Pair("Delinquents", "55"),
Pair("Detective", "39"),
Pair("Educational", "56"),
Pair("Gag Humor", "57"),
Pair("Gore", "58"),
Pair("Harem", "35"),
Pair("High Stakes Game", "59"),
Pair("Historical", "13"),
Pair("Idols (Female)", "60"),
Pair("Idols (Male)", "61"),
Pair("Isekai", "62"),
Pair("Iyashikei", "63"),
Pair("Love Polygon", "64"),
Pair("Magical Sex Shift", "65"),
Pair("Mahou Shoujo", "66"),
Pair("Martial Arts", "17"),
Pair("Mecha", "18"),
Pair("Medical", "67"),
Pair("Memoir", "68"),
Pair("Military", "38"),
Pair("Music", "19"),
Pair("Mythology", "6"),
Pair("Organized Crime", "69"),
Pair("Otaku Culture", "70"),
Pair("Parody", "20"),
Pair("Performing Arts", "71"),
Pair("Pets", "72"),
Pair("Psychological", "40"),
Pair("Racing", "3"),
Pair("Reincarnation", "73"),
Pair("Reverse Harem", "74"),
Pair("Romantic Subtext", "75"),
Pair("Samurai", "21"),
Pair("School", "23"),
Pair("Showbiz", "76"),
Pair("Space", "29"),
Pair("Strategy Game", "11"),
Pair("Super Power", "31"),
Pair("Survival", "77"),
Pair("Team Sports", "78"),
Pair("Time Travel", "79"),
Pair("Vampire", "32"),
Pair("Video Game", "80"),
Pair("Villainess", "81"),
Pair("Visual Arts", "82"),
Pair("Workplace", "48"),
Pair("Josei", "42"),
Pair("Kids", "15"),
Pair("Seinen", "41"),
Pair("Shoujo", "25"),
Pair("Shounen", "27"),
),
)
class StatusFilter : UriMultiSelectFilter(
"Status",
"status[]",
arrayOf(
Pair("Ongoing", "0"),
Pair("Completed", "1"),
),
)

View File

@ -1,358 +0,0 @@
package eu.kanade.tachiyomi.extension.en.mangatop
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.interceptor.rateLimit
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.decodeFromStream
import okhttp3.FormBody
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.Interceptor
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import uy.kohesive.injekt.injectLazy
import java.io.IOException
import java.lang.Exception
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
class MangaScans : ParsedHttpSource() {
override val name = "MangaScans"
override val baseUrl = "https://mangascans.to"
override val lang = "en"
override val supportsLatest = true
override val id = 85127596998931837
override val client = network.cloudflareClient.newBuilder()
.addInterceptor(::tokenInterceptor)
.rateLimit(2)
.build()
override fun headersBuilder() = super.headersBuilder()
.add("Referer", "$baseUrl/")
private val json: Json by injectLazy()
private val dateFormat = SimpleDateFormat("MMM d, yyyy", Locale.ENGLISH)
private var storedToken: String? = null
// From Akuma
private fun tokenInterceptor(chain: Interceptor.Chain): Response {
val request = chain.request()
if (request.method == "POST" && request.header("X-CSRF-TOKEN") == null) {
val newRequest = request.newBuilder()
val token = getToken()
val response = chain.proceed(
newRequest
.addHeader("X-CSRF-TOKEN", token)
.build(),
)
if (response.code == 419) {
response.close()
storedToken = null // reset the token
val newToken = getToken()
return chain.proceed(
newRequest
.addHeader("X-CSRF-TOKEN", newToken)
.build(),
)
}
return response
}
return chain.proceed(request)
}
private fun getToken(): String {
if (storedToken.isNullOrEmpty()) {
val request = GET(baseUrl, headers)
val response = client.newCall(request).execute()
val document = response.asJsoup()
document.updateToken()
}
return storedToken!!
}
// ============================== Popular ===============================
override fun popularMangaRequest(page: Int): Request = GET(baseUrl, headers)
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
document.updateToken()
val mangaList = document.select(popularMangaSelector())
.map(::popularMangaFromElement)
return MangasPage(mangaList, false)
}
override fun popularMangaSelector(): String = "aside div > article"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
thumbnail_url = element.selectFirst("img")!!.imgAttr()
with(element.selectFirst("a:has(h3)")!!) {
setUrlWithoutDomain(attr("abs:href"))
title = text()
}
}
override fun popularMangaNextPageSelector(): String? = null
// =============================== Latest ===============================
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/latest?page=$page", headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
document.updateToken()
val mangaList = document.select(latestUpdatesSelector())
.map(::latestUpdatesFromElement)
val hasNextPage = document.selectFirst(latestUpdatesNextPageSelector()) != null
return MangasPage(mangaList, hasNextPage)
}
override fun latestUpdatesSelector(): String = "div > article.manga-item"
override fun latestUpdatesFromElement(element: Element): SManga =
popularMangaFromElement(element)
override fun latestUpdatesNextPageSelector(): String = "ul.pagination > li.active + li:has(a)"
// =============================== Search ===============================
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val filterList = filters.ifEmpty { getFilterList() }
val url = "$baseUrl/search".toHttpUrl().newBuilder().apply {
addQueryParameter("q", query)
filterList.filterIsInstance<UriFilter>().forEach {
it.addToUri(this)
}
addQueryParameter("page", page.toString())
}.build()
return GET(url, headers)
}
override fun searchMangaParse(response: Response): MangasPage =
latestUpdatesParse(response)
override fun searchMangaSelector(): String =
throw UnsupportedOperationException()
override fun searchMangaFromElement(element: Element): SManga =
throw UnsupportedOperationException()
override fun searchMangaNextPageSelector(): String =
throw UnsupportedOperationException()
// =============================== Filters ==============================
override fun getFilterList(): FilterList = FilterList(
TypeFilter(),
GenreFilter(),
StatusFilter(),
)
// =========================== Manga Details ============================
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
thumbnail_url = document.selectFirst("picture img")!!.imgAttr()
with(document.selectFirst(".manga-info")!!) {
title = selectFirst("h1.page-heading")!!.text()
author = selectFirst("ul > li:has(span:contains(Authors))")?.ownText()
genre = select("ul > li:has(span:contains(Genres)) a").joinToString { it.text() }
status = selectFirst(".text-info").parseStatus()
description = selectFirst("#manga-description")?.text()
?.split(".")
?.filterNot { it.contains("MangaTop") }
?.joinToString(".")
?.trim()
}
}
private fun Element?.parseStatus(): Int = when (this?.text()?.lowercase()) {
"ongoing" -> SManga.ONGOING
"completed" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
// ============================== Chapters ==============================
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
document.updateToken()
val mangaName = document.selectFirst("script:containsData(mangaName)")
?.data()
?.substringAfter("mangaName")
?.substringAfter("'")
?.substringBefore("'")
?: throw Exception("Failed to get form data")
val postHeaders = apiHeadersBuilder().apply {
set("Referer", response.request.url.toString())
}.build()
val postBody = FormBody.Builder().apply {
add("mangaIdx", response.request.url.toString().substringAfterLast("-"))
add("mangaName", mangaName)
}.build()
val postResponse = client.newCall(
POST("$baseUrl/chapter-list", postHeaders, postBody),
).execute()
return super.chapterListParse(postResponse)
}
override fun chapterListSelector() = "li"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
element.selectFirst(".text-muted")?.also {
date_upload = it.text().parseDate()
}
name = element.selectFirst("span:not(.text-muted)")!!.text()
setUrlWithoutDomain(element.selectFirst("a")!!.attr("abs:href"))
}
private fun String.parseDate(): Long {
return if (this.contains("ago")) {
this.parseRelativeDate()
} else {
try {
dateFormat.parse(this)!!.time
} catch (_: ParseException) {
0L
}
}
}
private fun String.parseRelativeDate(): Long {
val now = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val relativeDate = this.split(" ").firstOrNull()
?.toIntOrNull()
?: return 0L
when {
"second" in this -> now.add(Calendar.SECOND, -relativeDate) // parse: 30 seconds ago
"minute" in this -> now.add(Calendar.MINUTE, -relativeDate) // parses: "42 minutes ago"
"hour" in this -> now.add(Calendar.HOUR, -relativeDate) // parses: "1 hour ago" and "2 hours ago"
"day" in this -> now.add(Calendar.DAY_OF_YEAR, -relativeDate) // parses: "2 days ago"
"week" in this -> now.add(Calendar.WEEK_OF_YEAR, -relativeDate) // parses: "2 weeks ago"
"month" in this -> now.add(Calendar.MONTH, -relativeDate) // parses: "2 months ago"
"year" in this -> now.add(Calendar.YEAR, -relativeDate) // parse: "2 years ago"
}
return now.timeInMillis
}
// =============================== Pages ================================
override fun pageListRequest(chapter: SChapter): Request {
val chapterId = chapter.url.substringBeforeLast(".html")
.substringAfterLast("-")
val postHeaders = apiHeadersBuilder().apply {
set("Referer", baseUrl + chapter.url)
}.build()
val postBody = FormBody.Builder().apply {
add("chapterIdx", chapterId)
}.build()
return POST("$baseUrl/chapter-resources", postHeaders, postBody)
}
@Serializable
class PageListResponse(
val data: PageListDataDto,
) {
@Serializable
class PageListDataDto(
val resources: List<PageDto>,
) {
@Serializable
class PageDto(
val name: Int,
val thumb: String,
)
}
}
override fun pageListParse(response: Response): List<Page> {
return response.parseAs<PageListResponse>().data.resources.map {
Page(it.name, imageUrl = it.thumb)
}
}
override fun pageListParse(document: Document): List<Page> =
throw UnsupportedOperationException()
override fun imageUrlParse(document: Document) = ""
override fun imageRequest(page: Page): Request {
val imgHeaders = headersBuilder().apply {
add("Accept", "image/avif,image/webp,*/*")
add("Host", page.imageUrl!!.toHttpUrl().host)
}.build()
return GET(page.imageUrl!!, imgHeaders)
}
// ============================= Utilities ==============================
private fun Document.updateToken() {
storedToken = this.selectFirst("head meta[name*=csrf-token]")
?.attr("content")
?: throw IOException("Failed to update token")
}
private inline fun <reified T> Response.parseAs(): T = use {
json.decodeFromStream(it.body.byteStream())
}
private fun apiHeadersBuilder() = headersBuilder().apply {
add("Accept", "*/*")
add("Host", baseUrl.toHttpUrl().host)
add("Origin", baseUrl)
add("X-Requested-With", "XMLHttpRequest")
}
private fun Element.imgAttr(): String = when {
hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
hasAttr("data-src") -> attr("abs:data-src")
else -> attr("abs:src")
}
}

View File

@ -1,9 +0,0 @@
ext {
extName = 'Manhwafull'
extClass = '.Manhwafull'
themePkg = 'madara'
baseUrl = 'https://manhwafull.com'
overrideVersionCode = 1
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

View File

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.extension.en.manhwafull
import eu.kanade.tachiyomi.multisrc.madara.Madara
import java.text.SimpleDateFormat
import java.util.Locale
class Manhwafull : Madara("Manhwafull", "https://manhwafull.com", "en", SimpleDateFormat("MM/dd/yyyy", Locale.ENGLISH))

View File

@ -1,10 +0,0 @@
ext {
extName = 'MyShojo'
extClass = '.MyShojo'
themePkg = 'mangathemesia'
baseUrl = 'https://myshojo.com'
overrideVersionCode = 0
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,5 +0,0 @@
package eu.kanade.tachiyomi.extension.en.myshojo
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
class MyShojo : MangaThemesia("MyShojo", "https://myshojo.com", "en")

View File

@ -1,10 +0,0 @@
ext {
extName = 'Todaymic'
extClass = '.Todaymic'
themePkg = 'madara'
baseUrl = 'https://todaymic.com'
overrideVersionCode = 2
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -1,10 +0,0 @@
package eu.kanade.tachiyomi.extension.en.todaymic
import eu.kanade.tachiyomi.multisrc.madara.Madara
class Todaymic : Madara("Todaymic", "https://todaymic.com", "en") {
override val filterNonMangaItems = false
override val useNewChapterEndpoint = true
override val useLoadMoreRequest = LoadMoreStrategy.Always
override val mangaDetailsSelectorDescription = ".manga-about > p:nth-child(2)"
}

View File

@ -1,10 +0,0 @@
ext {
extName = 'Tres Daos Net'
extClass = '.TresDaosNet'
themePkg = 'madara'
baseUrl = 'https://tresdaos.net'
overrideVersionCode = 0
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,11 +0,0 @@
package eu.kanade.tachiyomi.extension.es.tresdaosnet
import eu.kanade.tachiyomi.multisrc.madara.Madara
class TresDaosNet : Madara(
"Tres Daos Net",
"https://tresdaos.net",
"es",
) {
override val useLoadMoreRequest = LoadMoreStrategy.Always
}

View File

@ -1 +0,0 @@
local.properties

View File

@ -1,8 +0,0 @@
ext {
extName = 'Scan-Manga'
extClass = '.ScanManga'
extVersionCode = 12
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,248 +0,0 @@
package eu.kanade.tachiyomi.extension.fr.scanmanga
import android.util.Base64
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.FilterList
import eu.kanade.tachiyomi.source.model.MangasPage
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.asJsoup
import keiyoushi.utils.parseAs
import okhttp3.CookieJar
import okhttp3.Headers
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.Request
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.Response
import java.util.zip.Inflater
class ScanManga : HttpSource() {
override val name = "Scan-Manga"
override val baseUrl = "https://m.scan-manga.com"
private val baseImageUrl = "https://static.scan-manga.com/img/manga"
override val lang = "fr"
override val supportsLatest = true
override fun headersBuilder(): Headers.Builder = super.headersBuilder()
.add("Accept-Language", "fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7")
.set("User-Agent", "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Mobile Safari/537.36")
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/TOP-Manga-Webtoon-36.html", headers)
}
override fun popularMangaParse(response: Response): MangasPage {
val mangas = response.asJsoup().select("#carouselTOPContainer > div.top").map { element ->
SManga.create().apply {
val titleElement = element.selectFirst("a.atop")!!
title = titleElement.text()
setUrlWithoutDomain(titleElement.attr("href"))
thumbnail_url = element.selectFirst("img")?.attr("data-original")
}
}
return MangasPage(mangas, false)
}
// Latest
override fun latestUpdatesRequest(page: Int): Request = GET(baseUrl, headers)
override fun latestUpdatesParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select("#content_news .publi").map { element ->
SManga.create().apply {
val mangaElement = element.selectFirst("a.l_manga")!!
title = mangaElement.text()
setUrlWithoutDomain(mangaElement.attr("href"))
thumbnail_url = element.selectFirst("img")?.attr("src")
}
}
return MangasPage(mangas, false)
}
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val url = "$baseUrl/api/search/quick.json"
.toHttpUrl().newBuilder()
.addQueryParameter("term", query)
.build()
.toString()
val newHeaders = headers.newBuilder()
.add("Content-type", "application/json; charset=UTF-8")
.build()
return GET(url, newHeaders)
}
override fun searchMangaParse(response: Response): MangasPage {
val json = response.body.string()
if (json == "[]") { return MangasPage(emptyList(), false) }
return MangasPage(
json.parseAs<MangaSearchDto>().title?.map {
SManga.create().apply {
title = it.nom_match
setUrlWithoutDomain(it.url)
thumbnail_url = "$baseImageUrl/${it.image}"
}
} ?: emptyList(),
false,
)
}
// Details
override fun mangaDetailsParse(response: Response): SManga {
val document = response.asJsoup()
return SManga.create().apply {
title = document.select("h1.main_title[itemprop=name]").text()
author = document.select("div[itemprop=author]").text()
description = document.selectFirst("div.titres_desc[itemprop=description]")?.text()
genre = document.selectFirst("div.titres_souspart span[itemprop=genre]")?.text()
val statutText = document.selectFirst("div.titres_souspart")?.ownText()
status = when {
statutText?.contains("En cours", ignoreCase = true) == true -> SManga.ONGOING
statutText?.contains("Terminé", ignoreCase = true) == true -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.select("div.full_img_serie img[itemprop=image]").attr("src")
}
}
// Chapters
override fun chapterListParse(response: Response): List<SChapter> {
val document = response.asJsoup()
return document.select("div.chapt_m").map { element ->
val linkEl = element.selectFirst("td.publimg span.i a")!!
val titleEl = element.selectFirst("td.publititle")
val chapterName = linkEl.text()
val extraTitle = titleEl?.text()
SChapter.create().apply {
name = if (!extraTitle.isNullOrEmpty()) "$chapterName - $extraTitle" else chapterName
setUrlWithoutDomain(linkEl.absUrl("href"))
}
}
}
// Pages
private fun decodeHunter(obfuscatedJs: String): String {
val regex = Regex("""eval\(function\(h,u,n,t,e,r\)\{.*?\}\("([^"]+)",\d+,"([^"]+)",(\d+),(\d+),\d+\)\)""")
val (encoded, mask, intervalStr, optionStr) = regex.find(obfuscatedJs)?.destructured
?: error("Failed to match obfuscation pattern: $obfuscatedJs")
val interval = intervalStr.toInt()
val option = optionStr.toInt()
val delimiter = mask[option]
val tokens = encoded.split(delimiter).filter { it.isNotEmpty() }
val reversedMap = mask.withIndex().associate { it.value to it.index }
return buildString {
for (token in tokens) {
// Reverse the hashIt() operation: convert masked characters back to digits
val digitString = token.map { c ->
reversedMap[c]?.toString() ?: error("Invalid masked character: $c")
}.joinToString("")
// Convert from base `option` to decimal
val number = digitString.toIntOrNull(option)
?: error("Failed to parse token: $digitString as base $option")
// Reverse the shift done during encodeIt()
val originalCharCode = number - interval
append(originalCharCode.toChar())
}
}
}
private fun dataAPI(data: String, idc: Int): UrlPayload {
// Step 1: Base64 decode the input
val compressedBytes = Base64.decode(data, Base64.NO_WRAP or Base64.NO_PADDING)
// Step 2: Inflate (zlib decompress)
val inflater = Inflater()
inflater.setInput(compressedBytes)
val outputBuffer = ByteArray(512 * 1024) // 512 KB buffer, should be more than enough
val decompressedLength = inflater.inflate(outputBuffer)
inflater.end()
val inflated = String(outputBuffer, 0, decompressedLength)
// Step 3: Remove trailing hex string and reverse
val hexIdc = idc.toString(16)
val cleaned = inflated.removeSuffix(hexIdc)
val reversed = cleaned.reversed()
// Step 4: Base64 decode and parse JSON
val finalJsonStr = String(Base64.decode(reversed, Base64.DEFAULT))
return finalJsonStr.parseAs<UrlPayload>()
}
override fun pageListParse(response: Response): List<Page> {
val document = response.asJsoup()
val packedScript = document.selectFirst("script:containsData(h,u,n,t,e,r)")!!.data()
val unpackedScript = decodeHunter(packedScript)
val parametersRegex = Regex("""sml = '([^']+)';\n?.*var sme = '([^']+)'""")
val (sml, sme) = parametersRegex.find(unpackedScript)?.destructured
?: error("Failed to extract parameters from script.")
val chapterInfoRegex = Regex("""const idc = (\d+)""")
val (chapterId) = chapterInfoRegex.find(packedScript)?.destructured
?: error("Failed to extract chapter ID.")
val mediaType = "application/json; charset=UTF-8".toMediaType()
val requestBody = """{"a":"$sme","b":"$sml"}"""
val documentUrl = document.baseUri().toHttpUrl()
val pageListRequest = POST(
"$baseUrl/api/lel/$chapterId.json",
headers.newBuilder()
.add("Origin", "${documentUrl.scheme}://${documentUrl.host}")
.add("Referer", documentUrl.toString())
.add("Token", "yf")
.build(),
requestBody.toRequestBody(mediaType),
)
val lelResponse = client.newBuilder().cookieJar(CookieJar.NO_COOKIES).build()
.newCall(pageListRequest).execute().use { response ->
if (!response.isSuccessful) { error("Unexpected error while fetching lel.") }
dataAPI(response.body.string(), chapterId.toInt())
}
return lelResponse.generateImageUrls().map { Page(it.first, imageUrl = it.second) }
}
// Page
override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException()
override fun imageRequest(page: Page): Request {
val imgHeaders = headers.newBuilder()
.add("Origin", baseUrl)
.build()
return GET(page.imageUrl!!, imgHeaders)
}
}

View File

@ -1,41 +0,0 @@
package eu.kanade.tachiyomi.extension.fr.scanmanga
import kotlinx.serialization.Serializable
@Serializable
class Page(
val f: String, // filename
val e: String, // extension
)
@Serializable
class UrlPayload(
private val dN: String,
private val s: String,
private val v: String,
private val c: String,
private val p: Map<String, Page>,
) {
fun generateImageUrls(): List<Pair<Int, String>> {
val baseUrl = "https://$dN/$s/$v/$c"
return p.entries
.mapNotNull { (key, page) ->
key.toIntOrNull()?.let { pageIndex ->
pageIndex to "$baseUrl/${page.f}.${page.e}"
}
}
.sortedBy { it.first } // sort by page index
}
}
@Serializable
class MangaSearchDto(
val title: List<MangaItemDto>?,
)
@Serializable
class MangaItemDto(
val nom_match: String,
val url: String,
val image: String,
)

View File

@ -1,10 +0,0 @@
ext {
extName = 'Starbound Scans'
extClass = '.StarboundScans'
themePkg = 'madara'
baseUrl = 'https://starboundscans.com'
overrideVersionCode = 2
isNsfw = false
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

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