Remove domains with no A record (#7481)
|
@ -1,10 +0,0 @@
|
|||
ext {
|
||||
extName = 'Eromanhwa'
|
||||
extClass = '.Eromanhwa'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://eromanhwa.org'
|
||||
overrideVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 16 KiB |
|
@ -1,12 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.all.eromanhwa
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class Eromanhwa : Madara(
|
||||
"Eromanhwa",
|
||||
"https://eromanhwa.org",
|
||||
"all",
|
||||
) {
|
||||
override val id = 3597355706480775153 // accidently set lang to en...
|
||||
override val useNewChapterEndpoint = true
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
ext {
|
||||
extName = 'Frelein Books'
|
||||
extClass = '.FreleinBooks'
|
||||
extVersionCode = 1
|
||||
isNsfw = false
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 8.2 KiB |
|
@ -1,271 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.all.freleinbooks
|
||||
|
||||
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 okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Calendar
|
||||
import java.util.Locale
|
||||
|
||||
class FreleinBooks() : ParsedHttpSource() {
|
||||
override val baseUrl = "https://books.frelein.my.id"
|
||||
override val lang = "all"
|
||||
override val name = "Frelein Books"
|
||||
override val supportsLatest = true
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
|
||||
private val Element.imgSrc: String
|
||||
get() = attr("data-lazy-src")
|
||||
.ifEmpty { attr("data-src") }
|
||||
.ifEmpty { attr("src") }
|
||||
|
||||
// Latest
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
manga.thumbnail_url = element.selectFirst("img")!!.imgSrc
|
||||
manga.title = element.select(".postTitle").text()
|
||||
manga.setUrlWithoutDomain(element.select(".postTitle > a").attr("abs:href"))
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = ".olderLink"
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return if (page == 1) {
|
||||
GET(baseUrl)
|
||||
} else {
|
||||
val dateParam = page * 7 * 2
|
||||
// Calendar set to the current date
|
||||
val calendar: Calendar = Calendar.getInstance()
|
||||
// rollback 14 days
|
||||
calendar.add(Calendar.DAY_OF_YEAR, -dateParam)
|
||||
val formatter = SimpleDateFormat("yyyy-MM-dd", Locale.US)
|
||||
// now the date is 14 days back
|
||||
GET("$baseUrl/search?updated-max=${formatter.format(calendar.time)}T12:38:00%2B07:00&max-results=12&start=12&by-date=false")
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = ".blogPosts > article"
|
||||
|
||||
// Popular
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
val manga = SManga.create()
|
||||
manga.thumbnail_url = element.selectFirst("img")!!.imgSrc
|
||||
manga.title = element.select("h3").text()
|
||||
manga.setUrlWithoutDomain(element.select("h3 > a").attr("abs:href"))
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector(): String? = null
|
||||
override fun popularMangaRequest(page: Int) = latestUpdatesRequest(page)
|
||||
override fun popularMangaSelector() = ".itemPopulars article"
|
||||
|
||||
// Search
|
||||
override fun searchMangaFromElement(element: Element) = latestUpdatesFromElement(element)
|
||||
override fun searchMangaNextPageSelector() = latestUpdatesNextPageSelector()
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val filterList = if (filters.isEmpty()) getFilterList() else filters
|
||||
val tagFilter = filterList.findInstance<TagFilter>()!!
|
||||
val groupFilter = filterList.findInstance<GroupFilter>()!!
|
||||
val magazineFilter = filterList.findInstance<MagazineFilter>()!!
|
||||
val fashionMagazineFilter = filterList.findInstance<FashionMagazineFilter>()!!
|
||||
return when {
|
||||
query.isEmpty() && groupFilter.state != 0 -> GET("$baseUrl/search/label/${groupFilter.toUriPart()}")
|
||||
query.isEmpty() && magazineFilter.state != 0 -> GET("$baseUrl/search/label/${magazineFilter.toUriPart()}")
|
||||
query.isEmpty() && fashionMagazineFilter.state != 0 -> GET("$baseUrl/search/label/${fashionMagazineFilter.toUriPart()}")
|
||||
query.isEmpty() && tagFilter.state.isNotEmpty() -> GET("$baseUrl/search/label/${tagFilter.state}")
|
||||
query.isNotEmpty() -> GET("$baseUrl/search?q=$query")
|
||||
else -> latestUpdatesRequest(page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = latestUpdatesSelector()
|
||||
|
||||
// Details
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
val manga = SManga.create()
|
||||
manga.title = document.select(".postTitle").text()
|
||||
manga.description = "Read ${document.select(".postTitle").text()} \n \nNote: If you encounters error when opening the magazine, please press the WebView button then leave a comment on our web so we can update it soon."
|
||||
manga.genre = document.select(".labelLink > a")
|
||||
.joinToString(", ") { it.text() }
|
||||
manga.status = SManga.COMPLETED
|
||||
return manga
|
||||
}
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
val chapter = SChapter.create()
|
||||
chapter.setUrlWithoutDomain(element.select("link[rel=\"canonical\"]").attr("href"))
|
||||
chapter.name = "Gallery"
|
||||
chapter.date_upload = getDate(element.select("link[rel=\"canonical\"]").attr("href"))
|
||||
return chapter
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "html"
|
||||
|
||||
// Pages
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
val pages = mutableListOf<Page>()
|
||||
document.select("noscript").remove()
|
||||
document.select(".gallerybox a > img").forEachIndexed { i, it ->
|
||||
// format new img/b/
|
||||
if (it.imgSrc.contains("img/b/")) {
|
||||
if (it.imgSrc.contains("/w768-rw/")) {
|
||||
val itUrl = it.imgSrc.replace("/w768-rw/", "/s0/")
|
||||
pages.add(Page(i, itUrl, itUrl))
|
||||
}
|
||||
if (it.imgSrc.contains("/w480-rw/")) {
|
||||
val itUrl = it.imgSrc.replace("/w480-rw/", "/s0/")
|
||||
pages.add(Page(i, itUrl, itUrl))
|
||||
}
|
||||
}
|
||||
// format new img/b/
|
||||
else {
|
||||
if (it.imgSrc.contains("=w768-rw")) {
|
||||
val itUrl = it.imgSrc.replace("=w768-rw", "")
|
||||
pages.add(Page(i, itUrl, itUrl))
|
||||
} else if (it.imgSrc.contains("=w480-rw")) {
|
||||
val itUrl = it.imgSrc.replace("=w480-rw", "")
|
||||
pages.add(Page(i, itUrl, itUrl))
|
||||
} else {
|
||||
val itUrl = it.imgSrc
|
||||
pages.add(Page(i, itUrl, itUrl))
|
||||
}
|
||||
}
|
||||
}
|
||||
return pages
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String =
|
||||
throw UnsupportedOperationException()
|
||||
|
||||
// Filters
|
||||
|
||||
override fun getFilterList(): FilterList = FilterList(
|
||||
Filter.Header("NOTE: Only one filter will be applied!"),
|
||||
Filter.Separator(),
|
||||
GroupFilter(),
|
||||
MagazineFilter(),
|
||||
FashionMagazineFilter(),
|
||||
TagFilter(),
|
||||
)
|
||||
|
||||
open class UriPartFilter(
|
||||
displayName: String,
|
||||
private val valuePair: Array<Pair<String, String>>,
|
||||
) : Filter.Select<String>(displayName, valuePair.map { it.first }.toTypedArray()) {
|
||||
fun toUriPart() = valuePair[state].second
|
||||
}
|
||||
|
||||
class MagazineFilter : UriPartFilter(
|
||||
"Magazine",
|
||||
arrayOf(
|
||||
Pair("Any", ""),
|
||||
Pair("B.L.T.", "B.L.T."),
|
||||
Pair("BIG ONE GIRLS", "BIG ONE GIRLS"),
|
||||
Pair("BOMB!", "BOMB!"),
|
||||
Pair("BRODY", "BRODY"),
|
||||
Pair("BUBKA", "BUBKA"),
|
||||
Pair("ENTAME", "ENTAME"),
|
||||
Pair("EX Taishu", "EX Taishu"),
|
||||
Pair("FINEBOYS", "FINEBOYS"),
|
||||
Pair("FLASH", "FLASH"),
|
||||
Pair("Fine", "Fine"),
|
||||
Pair("Friday", "Friday"),
|
||||
Pair("HINA_SATSU", "HINA_SATSU"),
|
||||
Pair("IDOL AND READ", "IDOL AND READ"),
|
||||
Pair("Kadokawa Scene 07", "Kadokawa Scene 07"),
|
||||
Pair("Monthly Basketball", "Monthly Basketball"),
|
||||
Pair("Monthly Young Magazine", "Monthly Young Magazine"),
|
||||
Pair("NOGI_SATSU", "NOGI_SATSU"),
|
||||
Pair("Nylon Japan", "Nylon Japan"),
|
||||
Pair("Platinum FLASH", "Platinum FLASH"),
|
||||
Pair("Shonen Magazine", "Shonen Magazine"),
|
||||
Pair("Shukan Post", "Shukan Post"),
|
||||
Pair("TOKYO NEWS MOOK", "TOKYO NEWS MOOK"),
|
||||
Pair("TV LIFE,Tarzan", "TV LIFE,Tarzan"),
|
||||
Pair("Tokyo Calendar", "Tokyo Calendar"),
|
||||
Pair("Top Yell NEO", "Top Yell NEO"),
|
||||
Pair("UTB", "UTB"),
|
||||
Pair("Weekly Playboy", "Weekly Playboy"),
|
||||
Pair("Weekly SPA", "Weekly SPA"),
|
||||
Pair("Weekly SPA!", "Weekly SPA!"),
|
||||
Pair("Weekly Shonen Champion", "Weekly Shonen Champion"),
|
||||
Pair("Weekly Shonen Magazine", "Weekly Shonen Magazine"),
|
||||
Pair("Weekly Shonen Sunday", "Weekly Shonen Sunday"),
|
||||
Pair("Weekly Shounen Magazine", "Weekly Shounen Magazine"),
|
||||
Pair("Weekly The Television Plus", "Weekly The Television Plus"),
|
||||
Pair("Weekly Zero Jump", "Weekly Zero Jump"),
|
||||
Pair("Yanmaga Web", "Yanmaga Web"),
|
||||
Pair("Young Animal", "Young Animal"),
|
||||
Pair("Young Champion", "Young Champion"),
|
||||
Pair("Young Gangan", "Young Gangan"),
|
||||
Pair("Young Jump", "Young Jump"),
|
||||
Pair("Young Magazine", "Young Magazine"),
|
||||
Pair("blt graph.", "blt graph."),
|
||||
Pair("mini", "mini"),
|
||||
),
|
||||
)
|
||||
|
||||
class FashionMagazineFilter : UriPartFilter(
|
||||
"Fashion Magazine",
|
||||
arrayOf(
|
||||
Pair("Any", ""),
|
||||
Pair("BAILA", "BAILA"),
|
||||
Pair("Biteki", "Biteki"),
|
||||
Pair("CLASSY", "CLASSY"),
|
||||
Pair("CanCam", "CanCam"),
|
||||
Pair("JJ", "JJ"),
|
||||
Pair("LARME", "LARME"),
|
||||
Pair("MARQUEE", "MARQUEE"),
|
||||
Pair("Maquia", "Maquia"),
|
||||
Pair("Men's non-no", "Men's non-no"),
|
||||
Pair("More", "More"),
|
||||
Pair("Oggi", "Oggi"),
|
||||
Pair("Ray", "Ray"),
|
||||
Pair("Seventeen", "Seventeen"),
|
||||
Pair("Sweet", "Sweet"),
|
||||
Pair("VOCE", "VOCE"),
|
||||
Pair("ViVi", "ViVi"),
|
||||
Pair("With", "With"),
|
||||
Pair("aR", "aR"),
|
||||
Pair("anan", "anan"),
|
||||
Pair("bis", "bis"),
|
||||
Pair("non-no", "non-no"),
|
||||
),
|
||||
)
|
||||
|
||||
class GroupFilter : UriPartFilter(
|
||||
"Group",
|
||||
arrayOf(
|
||||
Pair("Any", ""),
|
||||
Pair("Hinatazaka46", "Hinatazaka46"),
|
||||
Pair("Nogizaka46", "Nogizaka46"),
|
||||
Pair("Sakurazaka46", "Sakurazaka46"),
|
||||
Pair("Keyakizaka46", "Keyakizaka46"),
|
||||
),
|
||||
)
|
||||
|
||||
class TagFilter : Filter.Text("Tag")
|
||||
|
||||
private inline fun <reified T> Iterable<*>.findInstance() = find { it is T } as? T
|
||||
|
||||
private fun getDate(str: String): Long {
|
||||
val regex = "[0-9]{4}\\/[0-9]{2}\\/[0-9]{2}".toRegex()
|
||||
val match = regex.find(str)
|
||||
return runCatching { DATE_FORMAT.parse(match!!.value)?.time }.getOrNull() ?: 0L
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val DATE_FORMAT by lazy {
|
||||
SimpleDateFormat("yyyy/MM/dd", Locale.US)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
ext {
|
||||
extName = 'Comic Arab'
|
||||
extClass = '.ComicArab'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://comicarab.com'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 8.0 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 24 KiB |
Before Width: | Height: | Size: 37 KiB |
|
@ -1,12 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.comicarab
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
class ComicArab : Madara(
|
||||
"كوميك العرب",
|
||||
"https://comicarab.com",
|
||||
"ar",
|
||||
dateFormat = SimpleDateFormat("dd MMMM، yyyy", Locale("ar")),
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
ext {
|
||||
extName = 'Manga.ae'
|
||||
extClass = '.MangaAe'
|
||||
extVersionCode = 13
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 6.5 KiB |
Before Width: | Height: | Size: 9.0 KiB |
|
@ -1,193 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.ar.mangaae
|
||||
|
||||
import android.app.Application
|
||||
import android.widget.Toast
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
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 okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import okhttp3.Request
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class MangaAe : ParsedHttpSource(), ConfigurableSource {
|
||||
|
||||
override val name = "مانجا العرب"
|
||||
|
||||
override val baseUrl by lazy {
|
||||
when {
|
||||
System.getenv("CI") == "true" -> MIRROR_PREF_ENTRY_VALUES.joinToString("#, ")
|
||||
else -> preferences.getString(MIRROR_PREF_KEY, MIRROR_PREF_DEFAULT_VALUE)!!
|
||||
}
|
||||
}
|
||||
|
||||
private val preferences by lazy {
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
}
|
||||
|
||||
override val lang = "ar"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.set("Referer", "$baseUrl/")
|
||||
.set("Origin", baseUrl)
|
||||
|
||||
// ============================== Popular ===============================
|
||||
override fun popularMangaRequest(page: Int) = GET("$baseUrl/manga/page:$page", headers)
|
||||
|
||||
override fun popularMangaSelector() = "div.mangacontainer"
|
||||
|
||||
override fun popularMangaFromElement(element: Element) = SManga.create().apply {
|
||||
thumbnail_url = element.selectFirst("img")?.run {
|
||||
attr("data-pagespeed-lazy-src").ifEmpty { attr("src") }
|
||||
}
|
||||
element.selectFirst("div.mangacontainer a.manga")!!.run {
|
||||
title = text()
|
||||
setUrlWithoutDomain(absUrl("href"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "div.pagination a:last-child:not(.active)"
|
||||
|
||||
// =============================== Latest ===============================
|
||||
override fun latestUpdatesRequest(page: Int) = GET(baseUrl, headers)
|
||||
|
||||
override fun latestUpdatesSelector() = "div.popular-manga-container"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element) = SManga.create().apply {
|
||||
thumbnail_url = element.selectFirst("img")?.run {
|
||||
attr("data-pagespeed-lazy-src").ifEmpty { attr("src") }
|
||||
}
|
||||
setUrlWithoutDomain(element.selectFirst("a:has(img)")!!.attr("href"))
|
||||
title = element.selectFirst("a:last-child")!!.text()
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = null
|
||||
|
||||
// =============================== Search ===============================
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val url = buildString {
|
||||
append("$baseUrl/manga/search:$query|page:$page")
|
||||
filters.firstOrNull { it is OrderByFilter }
|
||||
?.takeUnless { it.state == 0 }
|
||||
?.also {
|
||||
val filter = it as OrderByFilter
|
||||
append("|order:${filter.toUriPart()}")
|
||||
}
|
||||
append("|arrange:minus")
|
||||
}
|
||||
return GET(url.toHttpUrl(), headers)
|
||||
}
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
// =========================== Manga Details ============================
|
||||
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
|
||||
val infoElement = document.selectFirst("div.indexcontainer")!!
|
||||
// Essential info, a NPE may be understandable
|
||||
with(infoElement) {
|
||||
title = selectFirst("h1.EnglishName")!!.text().removeSurrounding("(", ")")
|
||||
author = selectFirst("div.manga-details-author h4")?.text()
|
||||
artist = author
|
||||
thumbnail_url = selectFirst("img.manga-cover")?.attr("src")
|
||||
}
|
||||
|
||||
// Additional info
|
||||
infoElement.selectFirst("div.manga-details-extended")?.run {
|
||||
status = parseStatus(selectFirst("td h4")?.text().orEmpty())
|
||||
genre = select("a[href*=tag]").eachText().joinToString()
|
||||
description = selectFirst("h4[style*=overflow-y]")?.text()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseStatus(status: String) = when {
|
||||
status.contains("مستمرة") -> SManga.ONGOING
|
||||
status.contains("مكتملة") -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
override fun chapterListSelector() = "ul.new-manga-chapters > li a"
|
||||
|
||||
override fun chapterFromElement(element: Element) = SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href").removeSuffix("/1/") + "/0/allpages")
|
||||
name = "\u061C" + element.text() // Add unicode ARABIC LETTER MARK to ensure all titles are right to left
|
||||
}
|
||||
|
||||
// =============================== Pages ================================
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select("div#showchaptercontainer img").mapIndexed { index, item ->
|
||||
Page(index, "", item.attr("src"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException()
|
||||
|
||||
// ============================== Filters ===============================
|
||||
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 OrderByFilter : UriPartFilter(
|
||||
"الترتيب حسب",
|
||||
arrayOf(
|
||||
Pair("اختيار", ""),
|
||||
Pair("اسم المانجا", "english_name"),
|
||||
Pair("تاريخ النشر", "release_date"),
|
||||
Pair("عدد الفصول", "chapter_count"),
|
||||
Pair("الحالة", "status"),
|
||||
),
|
||||
)
|
||||
|
||||
override fun getFilterList() = FilterList(OrderByFilter())
|
||||
|
||||
// ============================== Settings ==============================
|
||||
companion object {
|
||||
private const val RESTART_TACHIYOMI = "أعد تشغيل التطبيق لتمكين الإعدادات الجديدة."
|
||||
private const val MIRROR_PREF_KEY = "MIRROR"
|
||||
private const val MIRROR_PREF_TITLE = "تعديل الرابط"
|
||||
internal val MIRROR_PREF_ENTRY_VALUES = arrayOf(
|
||||
"https://mangaae.com",
|
||||
"https://mangaat.com",
|
||||
"https://mngaar.com",
|
||||
)
|
||||
private val MIRROR_PREF_DEFAULT_VALUE = MIRROR_PREF_ENTRY_VALUES[0]
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
val mirrorPref = ListPreference(screen.context).apply {
|
||||
key = MIRROR_PREF_KEY
|
||||
title = MIRROR_PREF_TITLE
|
||||
entries = MIRROR_PREF_ENTRY_VALUES
|
||||
entryValues = MIRROR_PREF_ENTRY_VALUES
|
||||
setDefaultValue(MIRROR_PREF_DEFAULT_VALUE)
|
||||
summary = "%s"
|
||||
|
||||
setOnPreferenceChangeListener { _, _ ->
|
||||
Toast.makeText(screen.context, RESTART_TACHIYOMI, Toast.LENGTH_LONG).show()
|
||||
true
|
||||
}
|
||||
}
|
||||
screen.addPreference(mirrorPref)
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Kuma Scans (Kuma Translation)'
|
||||
extClass = '.KumaScans'
|
||||
themePkg = 'mangathemesia'
|
||||
baseUrl = 'https://kumascans.com'
|
||||
overrideVersionCode = 1
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 9.1 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 30 KiB |
Before Width: | Height: | Size: 49 KiB |
|
@ -1,14 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.kumascans
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.mangathemesia.MangaThemesia
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import okhttp3.OkHttpClient
|
||||
|
||||
class KumaScans : MangaThemesia("Kuma Scans (Kuma Translation)", "https://kumascans.com", "en") {
|
||||
|
||||
override val client: OkHttpClient = super.client.newBuilder()
|
||||
.rateLimit(4)
|
||||
.build()
|
||||
|
||||
override val hasProjectPage = true
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
ext {
|
||||
extName = 'LuxManga'
|
||||
extClass = '.LuxManga'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://luxmanga.net'
|
||||
overrideVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
|
@ -1,7 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.luxmanga
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class LuxManga : Madara("LuxManga", "https://luxmanga.net", "en") {
|
||||
override val useNewChapterEndpoint = false
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
ext {
|
||||
extName = 'Manga1k'
|
||||
extClass = '.Manga1k'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://manga1k.com'
|
||||
overrideVersionCode = 0
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 39 KiB |
|
@ -1,12 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.manga1k
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class Manga1k : Madara(
|
||||
"Manga1k",
|
||||
"https://manga1k.com",
|
||||
"en",
|
||||
) {
|
||||
override val useLoadMoreRequest = LoadMoreStrategy.Never
|
||||
override val useNewChapterEndpoint = false
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<application>
|
||||
<activity android:name=".en.mangafun.MangaFunUrlActivity"
|
||||
android:excludeFromRecents="true"
|
||||
android:exported="true"
|
||||
android:theme="@android:style/Theme.NoDisplay">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="mangafun.me"
|
||||
android:pathPattern="/title/..*" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
|
@ -1,13 +0,0 @@
|
|||
ext {
|
||||
extName = "Manga Fun"
|
||||
extClass = ".MangaFun"
|
||||
extVersionCode = 1
|
||||
isNsfw = true
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation("net.pearx.kasechange:kasechange:1.4.1")
|
||||
implementation(project(':lib:lzstring'))
|
||||
}
|
Before Width: | Height: | Size: 3.4 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.7 KiB |
Before Width: | Height: | Size: 9.3 KiB |
Before Width: | Height: | Size: 13 KiB |
|
@ -1,134 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import kotlinx.serialization.json.JsonArray
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
import kotlinx.serialization.json.JsonObject
|
||||
import kotlinx.serialization.json.JsonPrimitive
|
||||
import kotlinx.serialization.json.buildJsonArray
|
||||
import kotlinx.serialization.json.buildJsonObject
|
||||
import kotlinx.serialization.json.intOrNull
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonNull
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
|
||||
/**
|
||||
* A somewhat direct port of the decoding parts of
|
||||
* [compress-json](https://github.com/beenotung/compress-json).
|
||||
*/
|
||||
object DecompressJson {
|
||||
fun decompress(c: JsonArray): JsonElement {
|
||||
val values = c[0].jsonArray
|
||||
val key = c[1].jsonPrimitive.content
|
||||
|
||||
return decode(values, key)
|
||||
}
|
||||
|
||||
private fun decode(values: JsonArray, key: String): JsonElement {
|
||||
if (key.isEmpty() || key == "_") {
|
||||
return JsonPrimitive(null)
|
||||
}
|
||||
|
||||
val id = sToInt(key)
|
||||
val v = values[id]
|
||||
|
||||
try {
|
||||
v.jsonNull
|
||||
return v
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// v is not null, we continue on.
|
||||
}
|
||||
|
||||
val vNum = v.jsonPrimitive.intOrNull
|
||||
|
||||
if (vNum != null) {
|
||||
return v
|
||||
}
|
||||
|
||||
if (v.jsonPrimitive.isString) {
|
||||
val content = v.jsonPrimitive.content
|
||||
|
||||
if (content.length < 2) {
|
||||
return v
|
||||
}
|
||||
|
||||
return when (content.substring(0..1)) {
|
||||
"b|" -> decodeBool(content)
|
||||
"n|" -> decodeNum(content)
|
||||
"o|" -> decodeObject(values, content)
|
||||
"a|" -> decodeArray(values, content)
|
||||
else -> v
|
||||
}
|
||||
}
|
||||
|
||||
throw IllegalArgumentException("Unknown data type")
|
||||
}
|
||||
|
||||
private fun decodeObject(values: JsonArray, s: String): JsonObject {
|
||||
if (s == "o|") {
|
||||
return JsonObject(emptyMap())
|
||||
}
|
||||
|
||||
val vs = s.split("|")
|
||||
val keyId = vs[1]
|
||||
val keys = decode(values, keyId)
|
||||
val n = vs.size
|
||||
|
||||
val keyArray = try {
|
||||
keys.jsonArray.map { it.jsonPrimitive.content }
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// single-key object using existing value as key
|
||||
listOf(keys.jsonPrimitive.content)
|
||||
}
|
||||
|
||||
return buildJsonObject {
|
||||
for (i in 2 until n) {
|
||||
val k = keyArray[i - 2]
|
||||
val v = decode(values, vs[i])
|
||||
put(k, v)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeArray(values: JsonArray, s: String): JsonArray {
|
||||
if (s == "a|") {
|
||||
return JsonArray(emptyList())
|
||||
}
|
||||
|
||||
val vs = s.split("|")
|
||||
val n = vs.size - 1
|
||||
return buildJsonArray {
|
||||
for (i in 0 until n) {
|
||||
add(decode(values, vs[i + 1]))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeBool(s: String): JsonPrimitive {
|
||||
return when (s) {
|
||||
"b|T" -> JsonPrimitive(true)
|
||||
"b|F" -> JsonPrimitive(false)
|
||||
else -> JsonPrimitive(s.isNotEmpty())
|
||||
}
|
||||
}
|
||||
|
||||
private fun decodeNum(s: String): JsonPrimitive =
|
||||
JsonPrimitive(sToInt(s.substringAfter("n|")))
|
||||
|
||||
private fun sToInt(s: String): Int {
|
||||
var acc = 0
|
||||
var pow = 1
|
||||
|
||||
s.reversed().forEach {
|
||||
acc += stoi[it]!! * pow
|
||||
pow *= 62
|
||||
}
|
||||
|
||||
return acc
|
||||
}
|
||||
|
||||
private val itos = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
|
||||
|
||||
private val stoi = itos.associate {
|
||||
it to itos.indexOf(it)
|
||||
}
|
||||
}
|
|
@ -1,296 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import eu.kanade.tachiyomi.extension.en.mangafun.MangaFunUtils.toSChapter
|
||||
import eu.kanade.tachiyomi.extension.en.mangafun.MangaFunUtils.toSManga
|
||||
import eu.kanade.tachiyomi.lib.lzstring.LZString
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.asObservableSuccess
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
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 kotlinx.serialization.decodeFromString
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.decodeFromJsonElement
|
||||
import kotlinx.serialization.json.jsonArray
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import rx.Observable
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.math.min
|
||||
|
||||
class MangaFun : HttpSource() {
|
||||
|
||||
override val name = "Manga Fun"
|
||||
|
||||
override val baseUrl = "https://mangafun.me"
|
||||
|
||||
private val apiUrl = "https://a.mangafun.me/v0"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client = network.cloudflareClient
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", "$baseUrl/")
|
||||
.add("Origin", baseUrl)
|
||||
|
||||
private val json: Json by injectLazy()
|
||||
|
||||
private val nextBuildId by lazy {
|
||||
val document = client.newCall(GET(baseUrl, headers)).execute().asJsoup()
|
||||
|
||||
json.parseToJsonElement(
|
||||
document.selectFirst("#__NEXT_DATA__")!!.data(),
|
||||
)
|
||||
.jsonObject["buildId"]!!
|
||||
.jsonPrimitive
|
||||
.content
|
||||
}
|
||||
|
||||
private lateinit var directory: List<MinifiedMangaDto>
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
return if (page == 1) {
|
||||
client.newCall(popularMangaRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { popularMangaParse(it) }
|
||||
} else {
|
||||
Observable.just(parseDirectory(page))
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int) = GET("$apiUrl/title/all", headers)
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage {
|
||||
directory = response.parseAs<List<MinifiedMangaDto>>()
|
||||
.sortedBy { it.rank }
|
||||
return parseDirectory(1)
|
||||
}
|
||||
|
||||
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
|
||||
return if (page == 1) {
|
||||
client.newCall(latestUpdatesRequest(page))
|
||||
.asObservableSuccess()
|
||||
.map { latestUpdatesParse(it) }
|
||||
} else {
|
||||
Observable.just(parseDirectory(page))
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesRequest(page: Int) = popularMangaRequest(page)
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
directory = response.parseAs<List<MinifiedMangaDto>>()
|
||||
.sortedByDescending { MangaFunUtils.convertShortTime(it.updatedAt) }
|
||||
return parseDirectory(1)
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(
|
||||
page: Int,
|
||||
query: String,
|
||||
filters: FilterList,
|
||||
): Observable<MangasPage> {
|
||||
return if (query.startsWith(PREFIX_ID_SEARCH)) {
|
||||
val slug = query.removePrefix(PREFIX_ID_SEARCH)
|
||||
return fetchMangaDetails(SManga.create().apply { url = "/title/$slug" })
|
||||
.map { MangasPage(listOf(it), false) }
|
||||
} else if (page == 1) {
|
||||
client.newCall(searchMangaRequest(page, query, filters))
|
||||
.asObservableSuccess()
|
||||
.map { searchMangaParse(it, query, filters) }
|
||||
} else {
|
||||
Observable.just(parseDirectory(page))
|
||||
}
|
||||
}
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
|
||||
popularMangaRequest(page)
|
||||
|
||||
override fun searchMangaParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
private fun searchMangaParse(response: Response, query: String, filters: FilterList): MangasPage {
|
||||
directory = response.parseAs<List<MinifiedMangaDto>>()
|
||||
.filter {
|
||||
it.name.contains(query, false) ||
|
||||
it.alias.any { a -> a.contains(query, false) }
|
||||
}
|
||||
|
||||
filters.ifEmpty { getFilterList() }.forEach { filter ->
|
||||
when (filter) {
|
||||
is GenreFilter -> {
|
||||
val included = mutableListOf<Int>()
|
||||
val excluded = mutableListOf<Int>()
|
||||
|
||||
filter.state.forEach { g ->
|
||||
when (g.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> included.add(g.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> excluded.add(g.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (included.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filter { it.genres.any { g -> included.contains(g) } }
|
||||
}
|
||||
|
||||
if (excluded.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filterNot { it.genres.any { g -> excluded.contains(g) } }
|
||||
}
|
||||
}
|
||||
is TypeFilter -> {
|
||||
val included = mutableListOf<Int>()
|
||||
val excluded = mutableListOf<Int>()
|
||||
|
||||
filter.state.forEach { g ->
|
||||
when (g.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> included.add(g.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> excluded.add(g.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (included.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filter { included.any { t -> it.titleType == t } }
|
||||
}
|
||||
|
||||
if (excluded.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filterNot { excluded.any { t -> it.titleType == t } }
|
||||
}
|
||||
}
|
||||
is StatusFilter -> {
|
||||
val included = mutableListOf<Int>()
|
||||
val excluded = mutableListOf<Int>()
|
||||
|
||||
filter.state.forEach { g ->
|
||||
when (g.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> included.add(g.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> excluded.add(g.id)
|
||||
}
|
||||
}
|
||||
|
||||
if (included.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filter { included.any { t -> it.publishedStatus == t } }
|
||||
}
|
||||
|
||||
if (excluded.isNotEmpty()) {
|
||||
directory = directory
|
||||
.filterNot { excluded.any { t -> it.publishedStatus == t } }
|
||||
}
|
||||
}
|
||||
is SortFilter -> {
|
||||
directory = when (filter.state?.index) {
|
||||
0 -> directory.sortedBy { it.name }
|
||||
1 -> directory.sortedBy { it.rank }
|
||||
2 -> directory.sortedBy { MangaFunUtils.convertShortTime(it.createdAt) }
|
||||
3 -> directory.sortedBy { MangaFunUtils.convertShortTime(it.updatedAt) }
|
||||
else -> throw IllegalStateException("Unhandled sort option")
|
||||
}
|
||||
|
||||
if (filter.state?.ascending != true) {
|
||||
directory = directory.reversed()
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
return parseDirectory(1)
|
||||
}
|
||||
|
||||
override fun getMangaUrl(manga: SManga) = "$baseUrl${manga.url}"
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
val slug = manga.url.substringAfterLast("/")
|
||||
val nextDataUrl = "$baseUrl/_next/data/$nextBuildId/title/$slug.json"
|
||||
|
||||
return GET(nextDataUrl, headers)
|
||||
}
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga {
|
||||
val data = response.parseAs<NextPagePropsWrapperDto>()
|
||||
.pageProps
|
||||
.dehydratedState
|
||||
.queries
|
||||
.first()
|
||||
.state
|
||||
.data
|
||||
|
||||
return json.decodeFromJsonElement<MangaDto>(data).toSManga()
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga) = mangaDetailsRequest(manga)
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
val data = response.parseAs<NextPagePropsWrapperDto>()
|
||||
.pageProps
|
||||
.dehydratedState
|
||||
.queries
|
||||
.first()
|
||||
.state
|
||||
.data
|
||||
|
||||
val mangaData = json.decodeFromJsonElement<MangaDto>(data)
|
||||
return mangaData.chapters.map { it.toSChapter(mangaData.id, mangaData.name) }.reversed()
|
||||
}
|
||||
|
||||
override fun getChapterUrl(chapter: SChapter) = "$baseUrl${chapter.url}"
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
val chapterId = chapter.url.substringAfterLast("/").substringBefore("-")
|
||||
|
||||
return GET("$apiUrl/chapter/$chapterId", headers)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val encoded = Base64.encode(response.body.bytes(), Base64.DEFAULT or Base64.NO_WRAP).toString(Charsets.UTF_8)
|
||||
val decoded = LZString.decompressFromBase64(encoded)
|
||||
val compressedJson = json.parseToJsonElement(decoded).jsonArray
|
||||
val decompressedJson = DecompressJson.decompress(compressedJson).jsonObject
|
||||
|
||||
Log.d("MangaFun", Json.encodeToString(decompressedJson))
|
||||
|
||||
return decompressedJson.jsonObject["p"]!!.jsonArray.mapIndexed { i, it ->
|
||||
Page(i, imageUrl = MangaFunUtils.getImageUrlFromHash(it.jsonArray[0].jsonPrimitive.content))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException()
|
||||
|
||||
override fun getFilterList() = FilterList(
|
||||
GenreFilter(),
|
||||
TypeFilter(),
|
||||
StatusFilter(),
|
||||
SortFilter(),
|
||||
)
|
||||
|
||||
private fun parseDirectory(page: Int): MangasPage {
|
||||
val endRange = min((page * 24), directory.size)
|
||||
val manga = directory.subList(((page - 1) * 24), endRange).map { it.toSManga() }
|
||||
val hasNextPage = endRange < directory.lastIndex
|
||||
|
||||
return MangasPage(manga, hasNextPage)
|
||||
}
|
||||
|
||||
private inline fun <reified T> Response.parseAs(): T =
|
||||
json.decodeFromString(body.string())
|
||||
|
||||
companion object {
|
||||
internal const val PREFIX_ID_SEARCH = "id:"
|
||||
internal const val MANGAFUN_EPOCH = 1693473000
|
||||
}
|
||||
}
|
|
@ -1,70 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.json.JsonElement
|
||||
|
||||
@Serializable
|
||||
data class MinifiedMangaDto(
|
||||
@SerialName("i") val id: Int,
|
||||
@SerialName("n") val name: String,
|
||||
@SerialName("t") val thumbnailUrl: String? = null,
|
||||
@SerialName("s") val publishedStatus: Int = 0,
|
||||
@SerialName("tt") val titleType: Int = 0,
|
||||
@SerialName("a") val alias: List<String> = emptyList(),
|
||||
@SerialName("g") val genres: List<Int> = emptyList(),
|
||||
@SerialName("au") val author: List<String> = emptyList(),
|
||||
@SerialName("r") val rank: Int = 999999999,
|
||||
@SerialName("ca") val createdAt: Int = 0,
|
||||
@SerialName("ua") val updatedAt: Int = 0,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class MangaDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val thumbnailURL: String? = null,
|
||||
val publishedStatus: Int = 0,
|
||||
val titleType: Int = 0,
|
||||
val alias: List<String>,
|
||||
val description: String,
|
||||
val genres: List<GenreDto>,
|
||||
val artist: List<String?>,
|
||||
val author: List<String?>,
|
||||
val chapters: List<ChapterDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class ChapterDto(
|
||||
val id: Int,
|
||||
val name: String,
|
||||
val publishedAt: String,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class GenreDto(val id: Int, val name: String)
|
||||
|
||||
@Serializable
|
||||
data class NextPagePropsWrapperDto(
|
||||
val pageProps: NextPagePropsDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class NextPagePropsDto(
|
||||
val dehydratedState: DehydratedStateDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class DehydratedStateDto(
|
||||
val queries: List<QueriesDto>,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class QueriesDto(
|
||||
val state: StateDto,
|
||||
)
|
||||
|
||||
@Serializable
|
||||
data class StateDto(
|
||||
val data: JsonElement,
|
||||
)
|
|
@ -1,149 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
|
||||
class GenreFilter : Filter.Group<Genre>("Genre", genreList)
|
||||
|
||||
class TypeFilter : Filter.Group<Genre>("Type", titleTypeList)
|
||||
|
||||
class StatusFilter : Filter.Group<Genre>(
|
||||
"Status",
|
||||
listOf("Ongoing", "Completed", "Hiatus", "Cancelled").mapIndexed { i, it -> Genre(it, i) },
|
||||
)
|
||||
|
||||
class SortFilter : Filter.Sort(
|
||||
"Order by",
|
||||
arrayOf("Name", "Rank", "Newest", "Update"),
|
||||
Selection(1, false),
|
||||
)
|
||||
|
||||
class Genre(name: String, val id: Int) : Filter.TriState(name)
|
||||
|
||||
val genresMap by lazy {
|
||||
genreList.associate { it.id to it.name }
|
||||
}
|
||||
|
||||
val titleTypeMap by lazy {
|
||||
titleTypeList.associate { it.id to it.name }
|
||||
}
|
||||
|
||||
val titleTypeList by lazy {
|
||||
listOf(
|
||||
Genre("Manga", 0),
|
||||
Genre("Manhwa", 1),
|
||||
Genre("Manhua", 2),
|
||||
Genre("Comic", 3),
|
||||
Genre("Webtoon", 4),
|
||||
Genre("One Shot", 6),
|
||||
Genre("Doujinshi", 7),
|
||||
Genre("Other", 8),
|
||||
)
|
||||
}
|
||||
|
||||
val genreList by lazy {
|
||||
listOf(
|
||||
Genre("Supernatural", 1),
|
||||
Genre("Action", 2),
|
||||
Genre("Comedy", 3),
|
||||
Genre("Josei", 4),
|
||||
Genre("Martial Arts", 5),
|
||||
Genre("Romance", 6),
|
||||
Genre("Ecchi", 7),
|
||||
Genre("Harem", 8),
|
||||
Genre("School Life", 9),
|
||||
Genre("Seinen", 10),
|
||||
Genre("Adventure", 11),
|
||||
Genre("Fantasy", 12),
|
||||
Genre("Demons", 13),
|
||||
Genre("Magic", 14),
|
||||
Genre("Military", 15),
|
||||
Genre("Shounen", 16),
|
||||
Genre("Shoujo", 17),
|
||||
Genre("Psychological", 18),
|
||||
Genre("Drama", 19),
|
||||
Genre("Mystery", 20),
|
||||
Genre("Sci-Fi", 21),
|
||||
Genre("Slice of Life", 22),
|
||||
Genre("Doujinshi", 23),
|
||||
Genre("Police", 24),
|
||||
Genre("Mecha", 25),
|
||||
Genre("Yaoi", 26),
|
||||
Genre("Horror", 27),
|
||||
Genre("Historical", 28),
|
||||
Genre("Thriller", 29),
|
||||
Genre("Shounen Ai", 30),
|
||||
Genre("Game", 31),
|
||||
Genre("Gender Bender", 32),
|
||||
Genre("Sports", 33),
|
||||
Genre("Yuri", 34),
|
||||
Genre("Music", 35),
|
||||
Genre("Shoujo Ai", 36),
|
||||
Genre("Vampires", 37),
|
||||
Genre("Parody", 38),
|
||||
Genre("Kids", 40),
|
||||
Genre("Super Power", 41),
|
||||
Genre("Space", 43),
|
||||
Genre("Adult", 46),
|
||||
Genre("Webtoons", 47),
|
||||
Genre("Mature", 48),
|
||||
Genre("Smut", 49),
|
||||
Genre("Tragedy", 51),
|
||||
Genre("One Shot", 53),
|
||||
Genre("4-koma", 56),
|
||||
Genre("Isekai", 58),
|
||||
Genre("Food", 60),
|
||||
Genre("Crime", 63),
|
||||
Genre("Superhero", 67),
|
||||
Genre("Animals", 69),
|
||||
Genre("Manhwa", 74),
|
||||
Genre("Manhua", 75),
|
||||
Genre("Cooking", 78),
|
||||
Genre("Medical", 79),
|
||||
Genre("Magical Girls", 88),
|
||||
Genre("Monsters", 89),
|
||||
Genre("Shotacon", 90),
|
||||
Genre("Philosophical", 91),
|
||||
Genre("Wuxia", 92),
|
||||
Genre("Adaptation", 95),
|
||||
Genre("Full Color", 96),
|
||||
Genre("Korean", 97),
|
||||
Genre("Chinese", 98),
|
||||
Genre("Reincarnation", 100),
|
||||
Genre("Manga", 102),
|
||||
Genre("Comic", 104),
|
||||
Genre("Japanese", 105),
|
||||
Genre("Time Travel", 108),
|
||||
Genre("Erotica", 111),
|
||||
Genre("Survival", 114),
|
||||
Genre("Gore", 118),
|
||||
Genre("Monster Girls", 120),
|
||||
Genre("Dungeons", 123),
|
||||
Genre("System", 124),
|
||||
Genre("Cultivation", 125),
|
||||
Genre("Murim", 128),
|
||||
Genre("Suggestive", 131),
|
||||
Genre("Fighting", 134),
|
||||
Genre("Blood", 140),
|
||||
Genre("Op-Mc", 142),
|
||||
Genre("Revenge", 144),
|
||||
Genre("Overpowered", 146),
|
||||
Genre("Returner", 150),
|
||||
Genre("Office", 152),
|
||||
Genre("Loli", 163),
|
||||
Genre("Video Games", 173),
|
||||
Genre("Monster", 199),
|
||||
Genre("Mafia", 203),
|
||||
Genre("Anthology", 206),
|
||||
Genre("Villainess", 207),
|
||||
Genre("Aliens", 213),
|
||||
Genre("Zombies", 216),
|
||||
Genre("Violence", 217),
|
||||
Genre("Delinquents", 219),
|
||||
Genre("Post apocalyptic", 255),
|
||||
Genre("Ghost", 260),
|
||||
Genre("Virtual Reality", 263),
|
||||
Genre("Cheat", 324),
|
||||
Genre("Girls", 374),
|
||||
Genre("Gender Swap", 384),
|
||||
)
|
||||
}
|
|
@ -1,33 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
class MangaFunUrlActivity : Activity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val pathSegments = intent?.data?.pathSegments
|
||||
if (pathSegments != null && pathSegments.size > 1) {
|
||||
try {
|
||||
startActivity(
|
||||
Intent().apply {
|
||||
action = "eu.kanade.tachiyomi.SEARCH"
|
||||
putExtra("query", "${MangaFun.PREFIX_ID_SEARCH}${pathSegments[1]}")
|
||||
putExtra("filter", packageName)
|
||||
},
|
||||
)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Log.e("MangaFunUrlActivity", "Could not start activity", e)
|
||||
}
|
||||
} else {
|
||||
Log.e("MangaFunUrlActivity", "Could not parse URI from intent $intent")
|
||||
}
|
||||
|
||||
finish()
|
||||
exitProcess(0)
|
||||
}
|
||||
}
|
|
@ -1,77 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangafun
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import net.pearx.kasechange.toKebabCase
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
|
||||
object MangaFunUtils {
|
||||
private const val cdnUrl = "https://mimg.bid"
|
||||
|
||||
private val notAlnumRegex = Regex("""[^0-9A-Za-z\s]""")
|
||||
|
||||
private val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.ROOT)
|
||||
|
||||
private fun String.slugify(): String =
|
||||
this.replace(notAlnumRegex, "").toKebabCase()
|
||||
|
||||
private fun publishedStatusToStatus(ps: Int) = when (ps) {
|
||||
0 -> SManga.ONGOING
|
||||
1 -> SManga.COMPLETED
|
||||
2 -> SManga.ON_HIATUS
|
||||
3 -> SManga.CANCELLED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
|
||||
fun convertShortTime(value: Int): Int {
|
||||
return if (value < MangaFun.MANGAFUN_EPOCH) {
|
||||
value + MangaFun.MANGAFUN_EPOCH
|
||||
} else {
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
fun getImageUrlFromHash(hash: String?): String? {
|
||||
if (hash == null) {
|
||||
return null
|
||||
}
|
||||
|
||||
return "$cdnUrl/${hash.substring(0, 2)}/${hash.substring(2, 5)}/${hash.substring(5)}.webp"
|
||||
}
|
||||
|
||||
fun MinifiedMangaDto.toSManga() = SManga.create().apply {
|
||||
url = "/title/$id-${name.slugify()}"
|
||||
title = name
|
||||
author = this@toSManga.author.joinToString()
|
||||
thumbnail_url = getImageUrlFromHash(thumbnailUrl)
|
||||
status = publishedStatusToStatus(publishedStatus)
|
||||
genre = buildList {
|
||||
titleTypeMap[titleType]?.let { add(it) }
|
||||
addAll(genres.mapNotNull { genresMap[it] })
|
||||
}.joinToString()
|
||||
}
|
||||
|
||||
fun MangaDto.toSManga() = SManga.create().apply {
|
||||
url = "/title/$id-${name.slugify()}"
|
||||
title = name
|
||||
author = this@toSManga.author.filterNotNull().joinToString()
|
||||
artist = this@toSManga.artist.filterNotNull().joinToString()
|
||||
description = this@toSManga.description
|
||||
genre = genres.mapNotNull { genresMap[it.id] }.joinToString()
|
||||
status = publishedStatusToStatus(publishedStatus)
|
||||
thumbnail_url = thumbnailURL
|
||||
genre = buildList {
|
||||
titleTypeMap[titleType]?.let { add(it) }
|
||||
addAll(genres.mapNotNull { genresMap[it.id] })
|
||||
}.joinToString()
|
||||
}
|
||||
|
||||
fun ChapterDto.toSChapter(mangaId: Int, mangaName: String) = SChapter.create().apply {
|
||||
url = "/title/$mangaId-${mangaName.slugify()}/$id-${this@toSChapter.name.slugify()}"
|
||||
name = this@toSChapter.name
|
||||
date_upload = runCatching {
|
||||
dateFormat.parse(publishedAt)!!.time
|
||||
}.getOrDefault(0L)
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Manga Tx.gg (unoriginal)'
|
||||
extClass = '.MangaTxGg'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://mangatx.gg'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
|
@ -1,7 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.mangatxgg
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class MangaTxGg : Madara("Manga Tx.gg (unoriginal)", "https://mangatx.gg", "en") {
|
||||
override val useNewChapterEndpoint = false
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Meow Meow Comics'
|
||||
extClass = '.MeowMeowComics'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://meowmeowcomics.com'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 9.0 KiB |
Before Width: | Height: | Size: 13 KiB |
|
@ -1,34 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.meowmeowcomics
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.util.asJsoup
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
|
||||
class MeowMeowComics : Madara(
|
||||
"Meow Meow Comics",
|
||||
"https://meowmeowcomics.com",
|
||||
"en",
|
||||
) {
|
||||
override val client = super.client.newBuilder()
|
||||
.rateLimit(2)
|
||||
.build()
|
||||
|
||||
// ============================== Chapters ==============================
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
return xhrChaptersRequest(baseUrl + manga.url.removeSuffix("/"))
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return response.asJsoup()
|
||||
.select("ul.main > li.parent,ul.main:not(:has(>li.parent))")
|
||||
.sortedByDescending { it.selectFirst("a.has-child")?.text()?.toIntOrNull() ?: 0 }
|
||||
.flatMap { season ->
|
||||
season.select(chapterListSelector()).map(::chapterFromElement)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Night Comic'
|
||||
extClass = '.NightComic'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://nightcomic.com'
|
||||
overrideVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.8 KiB |
Before Width: | Height: | Size: 8.3 KiB |
Before Width: | Height: | Size: 14 KiB |
|
@ -1,8 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.nightcomic
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.madara.Madara
|
||||
|
||||
class NightComic : Madara("Night Comic", "https://nightcomic.com", "en") {
|
||||
override val useNewChapterEndpoint = true
|
||||
override val mangaDetailsSelectorAuthor = "div.manga-authors > a"
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
ext {
|
||||
extName = 'Rmanga.app'
|
||||
extClass = '.Rmanga'
|
||||
extVersionCode = 2
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 17 KiB |
|
@ -1,186 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.rmanga
|
||||
|
||||
import android.app.Application
|
||||
import android.content.SharedPreferences
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.PreferenceScreen
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.interceptor.rateLimit
|
||||
import eu.kanade.tachiyomi.source.ConfigurableSource
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
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 okhttp3.FormBody
|
||||
import okhttp3.OkHttpClient
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Document
|
||||
import org.jsoup.nodes.Element
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
class Rmanga : ConfigurableSource, ParsedHttpSource() {
|
||||
|
||||
override val name = "Rmanga.app"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = true
|
||||
|
||||
override val client: OkHttpClient = network.cloudflareClient.newBuilder()
|
||||
.rateLimit(4)
|
||||
.build()
|
||||
|
||||
private val preferences: SharedPreferences =
|
||||
Injekt.get<Application>().getSharedPreferences("source_$id", 0x0000)
|
||||
|
||||
override val baseUrl = preferences.getString(DOMAIN_PREF, "https://rmanga.app")!!
|
||||
|
||||
override fun headersBuilder() = super.headersBuilder()
|
||||
.add("Referer", baseUrl)
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request {
|
||||
return GET("$baseUrl/ranking/most-viewed/$page", headers)
|
||||
}
|
||||
|
||||
override fun popularMangaSelector() = "div.category-items > ul > li"
|
||||
|
||||
override fun popularMangaFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("div.category-name a").let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
override fun popularMangaNextPageSelector() = "a.pagination__item:contains(»)"
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request {
|
||||
return GET("$baseUrl/latest-updates/$page", headers)
|
||||
}
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage {
|
||||
return super.latestUpdatesParse(response).apply {
|
||||
this.mangas.distinctBy { it.url }
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesSelector() = "div.latest-updates > ul > li"
|
||||
|
||||
override fun latestUpdatesFromElement(element: Element): SManga {
|
||||
return SManga.create().apply {
|
||||
element.select("div.latest-updates-name a").let {
|
||||
setUrlWithoutDomain(it.attr("href"))
|
||||
title = it.text()
|
||||
}
|
||||
thumbnail_url = element.select("img").attr("abs:src")
|
||||
}
|
||||
}
|
||||
|
||||
override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
|
||||
val payload = FormBody.Builder().apply {
|
||||
add("manga-name", query.trim())
|
||||
filters.forEach { filter ->
|
||||
when (filter) {
|
||||
is TypeFilter -> {
|
||||
add("type", filter.getValue())
|
||||
}
|
||||
is AuthorFilter -> {
|
||||
add("author-name", filter.state.trim())
|
||||
}
|
||||
is ArtistFilter -> {
|
||||
add("artist-name", filter.state.trim())
|
||||
}
|
||||
is StatusFilter -> {
|
||||
add("status", filter.getValue())
|
||||
}
|
||||
is GenreFilter -> {
|
||||
filter.state.forEach { genreState ->
|
||||
when (genreState.state) {
|
||||
Filter.TriState.STATE_INCLUDE -> add("include[]", genreState.id)
|
||||
Filter.TriState.STATE_EXCLUDE -> add("exclude[]", genreState.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
}.build()
|
||||
|
||||
return POST("$baseUrl/detailed-search", headers, payload)
|
||||
}
|
||||
|
||||
override fun getFilterList() = getFilters()
|
||||
|
||||
override fun searchMangaFromElement(element: Element) = popularMangaFromElement(element)
|
||||
|
||||
override fun searchMangaSelector() = popularMangaSelector()
|
||||
|
||||
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
|
||||
|
||||
override fun mangaDetailsParse(document: Document): SManga {
|
||||
return SManga.create().apply {
|
||||
title = document.select("div.section-header-title").first()!!.text()
|
||||
description = document.select("div.empty-box").eachText().joinToString("\n\n", postfix = "\n\n")
|
||||
thumbnail_url = document.select("div.novels-detail-left img").attr("abs:src")
|
||||
document.select("div.novels-detail-right > ul").let { element ->
|
||||
author = element.select("li:contains(author)").text().substringAfter(":").trim().takeUnless { it == "N/A" }
|
||||
artist = element.select("li:contains(artist)").text().substringAfter(":").trim().takeUnless { it == "N/A" }
|
||||
genre = element.select("li:contains(genres) a").joinToString { it.text() }
|
||||
status = element.select("li:contains(status)").text().parseStatus()
|
||||
description += element.select("li:contains(alternative)").text()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.parseStatus(): Int {
|
||||
return when {
|
||||
this.contains("ongoing", true) -> SManga.ONGOING
|
||||
this.contains("completed", true) -> SManga.COMPLETED
|
||||
else -> SManga.UNKNOWN
|
||||
}
|
||||
}
|
||||
|
||||
override fun chapterListSelector() = "div.novels-detail-chapters a"
|
||||
|
||||
override fun chapterFromElement(element: Element): SChapter {
|
||||
return SChapter.create().apply {
|
||||
setUrlWithoutDomain(element.attr("href"))
|
||||
name = element.ownText()
|
||||
}
|
||||
}
|
||||
|
||||
override fun pageListParse(document: Document): List<Page> {
|
||||
return document.select("div.chapter-detail-novel-big-image img").mapIndexed { index, img ->
|
||||
Page(index = index, imageUrl = img.attr("abs:src"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun imageUrlParse(document: Document): String {
|
||||
throw UnsupportedOperationException()
|
||||
}
|
||||
|
||||
override fun setupPreferenceScreen(screen: PreferenceScreen) {
|
||||
ListPreference(screen.context).apply {
|
||||
key = DOMAIN_PREF
|
||||
title = "Preferred domain"
|
||||
entries = arrayOf("rmanga.app", "readmanga.app")
|
||||
entryValues = arrayOf("https://rmanga.app", "https://readmanga.app")
|
||||
setDefaultValue("https://rmanga.app")
|
||||
summary = "Requires App Restart"
|
||||
}.let { screen.addPreference(it) }
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DOMAIN_PREF = "pref_domain"
|
||||
}
|
||||
}
|
|
@ -1,95 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.rmanga
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.Filter
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
|
||||
class Genre(
|
||||
name: String,
|
||||
val id: String,
|
||||
) : Filter.TriState(name)
|
||||
|
||||
internal class GenreFilter(name: String, genres: List<Genre>) :
|
||||
Filter.Group<Genre>(name, genres)
|
||||
|
||||
private val genreList = listOf(
|
||||
Genre("Action", "1"),
|
||||
Genre("Adventure", "23"),
|
||||
Genre("All Categories", "42"),
|
||||
Genre("Comedy", "12"),
|
||||
Genre("Cooking", "51"),
|
||||
Genre("Doujinshi", "26"),
|
||||
Genre("Drama", "9"),
|
||||
Genre("Ecchi", "2"),
|
||||
Genre("Fantasy", "3"),
|
||||
Genre("Gender Bender", "30"),
|
||||
Genre("Harem", "4"),
|
||||
Genre("Historical", "36"),
|
||||
Genre("Horror", "34"),
|
||||
Genre("Isekai", "44"),
|
||||
Genre("Josei", "17"),
|
||||
Genre("Lolicon", "39"),
|
||||
Genre("Magic", "48"),
|
||||
Genre("Manga", "5"),
|
||||
Genre("Manhua", "31"),
|
||||
Genre("Manhwa", "32"),
|
||||
Genre("Martial Arts", "22"),
|
||||
Genre("Mature", "50"),
|
||||
Genre("Mecha", "33"),
|
||||
Genre("Mind Game", "52"),
|
||||
Genre("Mystery", "13"),
|
||||
Genre("None", "41"),
|
||||
Genre("One shot", "16"),
|
||||
Genre("Psychological", "14"),
|
||||
Genre("Recarnation", "49"),
|
||||
Genre("Romance", "6"),
|
||||
Genre("School Life", "10"),
|
||||
Genre("Sci fi", "19"),
|
||||
Genre("Seinen", "24"),
|
||||
Genre("Shotacon", "38"),
|
||||
Genre("Shoujo", "8"),
|
||||
Genre("Shoujo Ai", "37"),
|
||||
Genre("Shounen", "7"),
|
||||
Genre("Shounen Ai", "35"),
|
||||
Genre("Slice of Life", "21"),
|
||||
Genre("Sports", "29"),
|
||||
Genre("Supernatural", "11"),
|
||||
Genre("Time Travel", "45"),
|
||||
Genre("Tragedy", "15"),
|
||||
Genre("Uncategorized", "43"),
|
||||
Genre("Yaoi", "28"),
|
||||
Genre("Yuri", "20"),
|
||||
)
|
||||
|
||||
internal class TypeFilter(name: String, private val types: Array<String>) :
|
||||
Filter.Select<String>(name, types) {
|
||||
fun getValue() = types[state]
|
||||
}
|
||||
|
||||
private val typeFilter: Array<String> = arrayOf(
|
||||
"All",
|
||||
"Japanese",
|
||||
"Korean",
|
||||
"Chinese",
|
||||
)
|
||||
|
||||
internal class AuthorFilter(title: String) : Filter.Text(title)
|
||||
internal class ArtistFilter(title: String) : Filter.Text(title)
|
||||
|
||||
internal class StatusFilter(name: String, private val status: Array<String>) :
|
||||
Filter.Select<String>(name, status) {
|
||||
fun getValue() = status[state]
|
||||
}
|
||||
|
||||
private val statusFilter: Array<String> = arrayOf(
|
||||
"Both",
|
||||
"Ongoing",
|
||||
"Completed",
|
||||
)
|
||||
|
||||
fun getFilters() = FilterList(
|
||||
TypeFilter("Type", typeFilter),
|
||||
AuthorFilter("Author"),
|
||||
ArtistFilter("Artist"),
|
||||
StatusFilter("Status", statusFilter),
|
||||
GenreFilter("Genres", genreList),
|
||||
)
|
|
@ -1,7 +0,0 @@
|
|||
ext {
|
||||
extName = 'Silent Manga Audition'
|
||||
extClass = '.SilentMangaAudition'
|
||||
extVersionCode = 5
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 3.1 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 13 KiB |
|
@ -1,156 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.silentmangaaudition
|
||||
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
|
||||
data class SmaEntry(
|
||||
val name: String,
|
||||
val url: String,
|
||||
val chapterListUrl: String,
|
||||
val thumbnailUrl: String,
|
||||
) {
|
||||
|
||||
fun toSManga(index: Int): SManga = SManga.create().apply {
|
||||
title = name
|
||||
author = "Various artists"
|
||||
status = SManga.COMPLETED
|
||||
description = "The theme is… " + name.substringAfter(" ") + "."
|
||||
thumbnail_url = thumbnailUrl
|
||||
url = "${this@SmaEntry.url},$chapterListUrl,$index"
|
||||
initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
val SMA_ENTRIES = listOf(
|
||||
SmaEntry(
|
||||
"SMA-17 “MOMENTS of HASTE, RAGE or SMILES”",
|
||||
"/sma17-silent-manga-audition-2022-results-announcement",
|
||||
"/v/sma17/bones-by-rimui/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma17/header_sp.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-16 “MOMENTS of FEAR, JOY, or LOVE”",
|
||||
"/sma16-silent-manga-audition-2021-results-announcement",
|
||||
"/v/sma16/lacrimosa-by-laica-chrose/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/uploads/2021/11/hero_sp_new.jpg",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-15 “Moments of CRYING, SMILING or LOVE”",
|
||||
"/sma15-silent-manga-audition-2021-award-winners",
|
||||
"/v/sma15/blossom-by-enewald/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma15/header_sp.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-14 “Creature, Spirits & Monsters”",
|
||||
"/sma14-silent-manga-audition-2020-award-winners",
|
||||
"/v/sma14/blooming-flower-by-blackwink/",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma14/header_sp.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-13 “Together for peace”",
|
||||
"/sma13-silent-manga-audition-2020-award-winners/",
|
||||
"/v/sma13/homeless-by-simone-sanseverino/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma13/header_sp.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-12 “New beginning”",
|
||||
"/sma12-silent-manga-audition-2019-award-winners/",
|
||||
"/v/sma12/never-late-by-lucas-marques-and-priscilla-miranda/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma12/header_sp.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-EX5 “Kumamoto + Do Your Best!”",
|
||||
"/smaex5-silent-manga-audition-2019-award-winners/",
|
||||
"/v/smaex5/fish-by-youngman/",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/smaex5/top_banner_sp.jpg",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA11 “Promise”",
|
||||
"/sma11-silent-manga-audition-2019-award-winners/",
|
||||
"/v/sma11/reborn-by-riza-al-assami/?lang=en",
|
||||
"https://www.manga-audition.com/wp/wp-content/themes/gridlove-child/assets/img/award-result/sma11/top_banner_sp.jpg",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-EX4 “Kit Kat ROUND”",
|
||||
"/smaex4-2018award/",
|
||||
"/v/smaex4/lucky-charm-by-harihtaroon/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex4/smaex4_main01.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA10 “Effort / Friendship / Victory”",
|
||||
"/sma10-2018award-2/",
|
||||
"/v/sma10/run-by-riza-al-assami/?lang=en",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/sma10/sma10main_3.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-EX3 “Kumamoto + Wasamon”",
|
||||
"/smaex3-2018award/",
|
||||
"/v/smaex3/to-the-sky-by-zevania-and-nattorin/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex3/smaex3_main01.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA9 “Fairness / Respect / Teamwork”",
|
||||
"/sma9-2018award/",
|
||||
"/v/sma9/fisherman-tales-by-joao-eddie/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma9/sma9_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA8 “Fair Play”",
|
||||
"/sma8-2017award/",
|
||||
"/v/sma8/checkmate-by-sideburn004/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma8/sma8_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA7 “Unforgettable Taste”",
|
||||
"/sma7-2017award/",
|
||||
"/v/sma7/our-promised-land-by-nattorin-and-zevania/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma7/sma7_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-EX2 “Kumamoto + Smile”",
|
||||
"http://data.smacmag.net/sma/smaex2-2017award/",
|
||||
"https://smacmag.net/v/sma2/drawing-a-smile-out-by-dee-juusan/",
|
||||
"http://data.smacmag.net/sma/smaex2-2017award/images/smaex2_title.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA6 “Childhood”",
|
||||
"/sma6-2016award/",
|
||||
"/v/sma6/forbidden-by-yos/13828",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma6/SMA06_themes.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA5 “Friend-ship + Communication Tool”",
|
||||
"/sma05-2016award/",
|
||||
"/v/sma5/im-happy-by-ds-studio/11915",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma5/sma5_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA-EX1 “Fukushima Sakuramori”",
|
||||
"/smaex1-2016award/",
|
||||
"/v/smaex1/seeds-by-jim/9574",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/smaex1/smaex1_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA4 “A Charming Gift”",
|
||||
"/sma04-2015award/",
|
||||
"/v/sma4/birdy-by-kalongzz/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma4/sma4_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA3 “Mother”",
|
||||
"/sma03-2015award/",
|
||||
"/v/sma3/homesick-alien-by-ichirou/4390",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma3/sma3_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA2 “The Finest Smile”",
|
||||
"/sma02-2014award/",
|
||||
"/v/sma2/fathers-gift-by-ichirou/1775",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma2/sma2_theme.png",
|
||||
),
|
||||
SmaEntry(
|
||||
"SMA1 “Love Letter”",
|
||||
"/sma01-2013award/",
|
||||
"/v/sma1/excuse-me-by-alex-irzaqi/",
|
||||
"https://s3-ap-northeast-1.amazonaws.com/data.smacmag.net/_images/sma_page/pc/sma1/sma1_theme.png",
|
||||
),
|
||||
)
|
|
@ -1,125 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.silentmangaaudition
|
||||
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
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 okhttp3.Headers
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import org.jsoup.nodes.Element
|
||||
import rx.Observable
|
||||
|
||||
class SilentMangaAudition : HttpSource() {
|
||||
|
||||
override val name = "Silent Manga Audition"
|
||||
|
||||
override val baseUrl = "https://manga-audition.com"
|
||||
|
||||
override val lang = "en"
|
||||
|
||||
override val supportsLatest = false
|
||||
|
||||
override fun headersBuilder(): Headers.Builder = Headers.Builder()
|
||||
.add("User-Agent", USER_AGENT)
|
||||
.add("Referer", baseUrl)
|
||||
|
||||
override fun fetchPopularManga(page: Int): Observable<MangasPage> {
|
||||
val entries = SMA_ENTRIES.mapIndexed { i, entry -> entry.toSManga(i) }
|
||||
return Observable.just(MangasPage(entries, false))
|
||||
}
|
||||
|
||||
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
|
||||
val filteredEntries = SMA_ENTRIES
|
||||
.mapIndexed { i, entry -> entry.toSManga(i) }
|
||||
.filter {
|
||||
it.title.contains(query, true) ||
|
||||
it.description!!.contains(query, true)
|
||||
}
|
||||
|
||||
return Observable.just(MangasPage(filteredEntries, false))
|
||||
}
|
||||
|
||||
override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
|
||||
val index = manga.url.substringAfterLast(",").toInt()
|
||||
val entry = SMA_ENTRIES[index]
|
||||
|
||||
return Observable.just(entry.toSManga(index))
|
||||
}
|
||||
|
||||
override fun mangaDetailsRequest(manga: SManga): Request {
|
||||
val url = manga.url.substringBefore(",")
|
||||
return GET(if (url.startsWith("/")) baseUrl + url else url, headers)
|
||||
}
|
||||
|
||||
override fun chapterListRequest(manga: SManga): Request {
|
||||
val url = manga.url
|
||||
.substringAfter(",")
|
||||
.substringBefore(",")
|
||||
|
||||
return GET(SMACMAG_URL + url, headers)
|
||||
}
|
||||
|
||||
override fun chapterListParse(response: Response): List<SChapter> {
|
||||
return response.asJsoup()
|
||||
.select(chapterListSelector())
|
||||
.map { chapterFromElement(it) }
|
||||
}
|
||||
|
||||
private fun chapterListSelector(): String = "ol.playlist li a"
|
||||
|
||||
private fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
|
||||
name = element.select("span.ttl").text()
|
||||
scanlator = element.select("span.name").text()
|
||||
url = element.attr("abs:href")
|
||||
}
|
||||
|
||||
override fun pageListRequest(chapter: SChapter): Request {
|
||||
return GET(chapter.url, headers)
|
||||
}
|
||||
|
||||
override fun pageListParse(response: Response): List<Page> {
|
||||
val chapterUrl = response.request.url.toString()
|
||||
|
||||
return response.asJsoup()
|
||||
.select("div.swiper-wrapper div.swiper-slide img.swiper-lazy")
|
||||
.mapIndexed { i, element -> Page(i, chapterUrl, element.attr("data-src")) }
|
||||
}
|
||||
|
||||
override fun fetchImageUrl(page: Page): Observable<String> = Observable.just(page.imageUrl!!)
|
||||
|
||||
override fun imageUrlParse(response: Response): String = ""
|
||||
|
||||
override fun imageRequest(page: Page): Request {
|
||||
val newHeaders = headersBuilder()
|
||||
.set("Referer", page.url)
|
||||
.build()
|
||||
|
||||
return GET(page.imageUrl!!, newHeaders)
|
||||
}
|
||||
|
||||
override fun popularMangaRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||
|
||||
override fun popularMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||
|
||||
override fun latestUpdatesRequest(page: Int): Request = throw UnsupportedOperationException()
|
||||
|
||||
override fun latestUpdatesParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = throw UnsupportedOperationException()
|
||||
|
||||
override fun searchMangaParse(response: Response): MangasPage = throw UnsupportedOperationException()
|
||||
|
||||
override fun mangaDetailsParse(response: Response): SManga = throw UnsupportedOperationException()
|
||||
|
||||
companion object {
|
||||
private const val SMACMAG_URL = "https://smacmag.net"
|
||||
|
||||
private const val USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " +
|
||||
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36"
|
||||
}
|
||||
}
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Silent Sky'
|
||||
extClass = '.SilentSky'
|
||||
themePkg = 'foolslide'
|
||||
baseUrl = 'https://reader.silentsky-scans.net'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 8.6 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 29 KiB |
Before Width: | Height: | Size: 50 KiB |
|
@ -1,5 +0,0 @@
|
|||
package eu.kanade.tachiyomi.extension.en.silentsky
|
||||
|
||||
import eu.kanade.tachiyomi.multisrc.foolslide.FoolSlide
|
||||
|
||||
class SilentSky : FoolSlide("Silent Sky", "https://reader.silentsky-scans.net", "en")
|
|
@ -1,9 +0,0 @@
|
|||
ext {
|
||||
extName = 'Solo Leveling'
|
||||
extClass = '.SoloLeveling'
|
||||
themePkg = 'madara'
|
||||
baseUrl = 'https://readsololeveling.online'
|
||||
overrideVersionCode = 0
|
||||
}
|
||||
|
||||
apply from: "$rootDir/common.gradle"
|
Before Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 17 KiB |