Misc code cleanup

This commit is contained in:
Eugene 2019-10-27 15:41:40 -04:00
parent 170c382b15
commit 40de8b9723
No known key found for this signature in database
GPG Key ID: E1FD745328866B0A
32 changed files with 777 additions and 691 deletions

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: ComiCake' appName = 'Tachiyomi: ComiCake'
pkgNameSuffix = 'all.comicake' pkgNameSuffix = 'all.comicake'
extClass = '.ComiCakeFactory' extClass = '.ComiCakeFactory'
extVersionCode = 6 extVersionCode = 7
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,9 +1,13 @@
package eu.kanade.tachiyomi.extension.all.comicake package eu.kanade.tachiyomi.extension.all.comicake
import android.os.Build import android.os.Build
import eu.kanade.tachiyomi.extension.BuildConfig import eu.kanade.tachiyomi.extensions.BuildConfig
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.* 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.source.online.HttpSource
import okhttp3.Headers import okhttp3.Headers
import okhttp3.Request import okhttp3.Request
@ -12,23 +16,22 @@ import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
const val COMICAKE_DEFAULT_API_ENDPOINT = "/api" // Highly unlikely to change abstract class ComiCake(
const val COMICAKE_DEFAULT_READER_ENDPOINT = "/r" // Can change based on CC config override val name: String,
final override val baseUrl: String,
override val lang: String,
readerEndpoint: String = COMICAKE_DEFAULT_READER_ENDPOINT,
apiEndpoint: String = COMICAKE_DEFAULT_API_ENDPOINT
) : HttpSource() {
open class ComiCake(override val name: String,
final override val baseUrl: String,
override val lang: String,
readerEndpoint: String = COMICAKE_DEFAULT_READER_ENDPOINT,
apiEndpoint: String = COMICAKE_DEFAULT_API_ENDPOINT) : HttpSource() {
override val versionId = 1 override val versionId = 1
override val supportsLatest = true override val supportsLatest = true
private val readerBase = baseUrl + readerEndpoint private val readerBase = baseUrl + readerEndpoint
private var apiBase = baseUrl + apiEndpoint private var apiBase = baseUrl + apiEndpoint
private val userAgent = "Mozilla/5.0 (" + private val userAgent = "Mozilla/5.0 (" +
"Android ${Build.VERSION.RELEASE}; Mobile) " + "Android ${Build.VERSION.RELEASE}; Mobile) " +
"Tachiyomi/${BuildConfig.VERSION_NAME}" "Tachiyomi/${BuildConfig.VERSION_NAME}"
override fun headersBuilder() = Headers.Builder().apply { override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", userAgent) add("User-Agent", userAgent)
@ -69,6 +72,7 @@ open class ComiCake(override val name: String,
mangas.add(parseComicJson(obj)) mangas.add(parseComicJson(obj))
} }
} }
return MangasPage(mangas, !(response.getString("next").isNullOrEmpty() || response.getString("next") == "null")) return MangasPage(mangas, !(response.getString("next").isNullOrEmpty() || response.getString("next") == "null"))
} }
@ -156,4 +160,9 @@ open class ComiCake(override val name: String,
} }
override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!") override fun imageUrlParse(response: Response) = throw UnsupportedOperationException("This method should not be called!")
companion object {
private const val COMICAKE_DEFAULT_API_ENDPOINT = "/api" // Highly unlikely to change
private const val COMICAKE_DEFAULT_READER_ENDPOINT = "/r" // Can change based on CC config
}
} }

View File

@ -4,19 +4,13 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
class ComiCakeFactory : SourceFactory { class ComiCakeFactory : SourceFactory {
override fun createSources(): List<Source> = getAllComiCake() override fun createSources(): List<Source> = listOf(
} LetItGoScans(),
fun getAllComiCake(): List<Source> {
return listOf(
WhimSubs(),
PTScans(), PTScans(),
LetItGoScans() WhimSubs()
) )
} }
class WhimSubs : ComiCake("WhimSubs", "https://whimsubs.xyz", "en")
class PTScans : ComiCake("ProjectTime Scans", "https://read.ptscans.com", "en", "/")
class LetItGoScans : ComiCake("LetItGo Scans", "https://reader.letitgo.scans.today", "en", "/") class LetItGoScans : ComiCake("LetItGo Scans", "https://reader.letitgo.scans.today", "en", "/")
class PTScans : ComiCake("ProjectTime Scans", "https://read.ptscans.com", "en", "/")
class WhimSubs : ComiCake("WhimSubs", "https://whimsubs.xyz", "en")

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: FMReader (multiple aggregators)' appName = 'Tachiyomi: FMReader (multiple aggregators)'
pkgNameSuffix = 'all.fmreader' pkgNameSuffix = 'all.fmreader'
extClass = '.FMReaderFactory' extClass = '.FMReaderFactory'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,17 +1,27 @@
package eu.kanade.tachiyomi.extension.all.fmreader package eu.kanade.tachiyomi.extension.all.fmreader
// For sites based on the Flat-Manga CMS
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.* 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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import eu.kanade.tachiyomi.util.asJsoup import java.util.Calendar
import okhttp3.*
import java.util.*
abstract class FMReader ( /**
* For sites based on the Flat-Manga CMS
*/
abstract class FMReader(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String override val lang: String
@ -43,10 +53,8 @@ abstract class FMReader (
} }
is TextField -> url.addQueryParameter(filter.key, filter.state) is TextField -> url.addQueryParameter(filter.key, filter.state)
is GenreList -> { is GenreList -> {
var genre = String() var genre = String()
var ungenre = String() var ungenre = String()
filter.state.forEach { filter.state.forEach {
if (it.isIncluded()) genre += ",${it.name}" if (it.isIncluded()) genre += ",${it.name}"
if (it.isExcluded()) ungenre += ",${it.name}" if (it.isExcluded()) ungenre += ",${it.name}"
@ -79,7 +87,7 @@ abstract class FMReader (
val mangas = mutableListOf<SManga>() val mangas = mutableListOf<SManga>()
var hasNextPage = true var hasNextPage = true
document.select(popularMangaSelector()).map{ mangas.add(popularMangaFromElement(it)) } document.select(popularMangaSelector()).map { mangas.add(popularMangaFromElement(it)) }
// check if there's a next page // check if there's a next page
document.select(popularMangaNextPageSelector()).first().text().split(" ").let { document.select(popularMangaNextPageSelector()).first().text().split(" ").let {
@ -108,7 +116,7 @@ abstract class FMReader (
manga.setUrlWithoutDomain(it.attr("abs:href")) manga.setUrlWithoutDomain(it.attr("abs:href"))
manga.title = it.text() manga.title = it.text()
} }
manga.thumbnail_url = element.select("img").let{ manga.thumbnail_url = element.select("img").let {
if (it.hasAttr("src")) { if (it.hasAttr("src")) {
it.attr("abs:src") it.attr("abs:src")
} else { } else {
@ -160,11 +168,11 @@ abstract class FMReader (
override fun chapterFromElement(element: Element): SChapter { override fun chapterFromElement(element: Element): SChapter {
val chapter = SChapter.create() val chapter = SChapter.create()
element.select(chapterUrlSelector).first().let{ element.select(chapterUrlSelector).first().let {
chapter.setUrlWithoutDomain(it.attr("abs:href")) chapter.setUrlWithoutDomain(it.attr("abs:href"))
chapter.name = it.text() chapter.name = it.text()
} }
chapter.date_upload = element.select(chapterTimeSelector).let{ if(it.hasText()) parseChapterDate(it.text()) else 0 } chapter.date_upload = element.select(chapterTimeSelector).let { if (it.hasText()) parseChapterDate(it.text()) else 0 }
return chapter return chapter
} }
@ -177,7 +185,7 @@ abstract class FMReader (
private fun parseChapterDate(date: String): Long { private fun parseChapterDate(date: String): Long {
val value = date.split(' ')[dateValueIndex].toInt() val value = date.split(' ')[dateValueIndex].toInt()
val dateWord = date.split(' ')[dateWordIndex].let{ val dateWord = date.split(' ')[dateWordIndex].let {
if (it.contains("(")) { if (it.contains("(")) {
it.substringBefore("(") it.substringBefore("(")
} else { } else {
@ -227,7 +235,7 @@ abstract class FMReader (
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("img.chapter-img").forEachIndexed { i, img -> document.select("img.chapter-img").forEachIndexed { i, img ->
pages.add(Page(i, "", img.attr("abs:data-src").let{ if (it.isNotEmpty()) it else img.attr("abs:src") })) pages.add(Page(i, "", img.attr("abs:data-src").let { if (it.isNotEmpty()) it else img.attr("abs:src") }))
} }
return pages return pages
} }

View File

@ -5,9 +5,17 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservableSuccess import eu.kanade.tachiyomi.network.asObservableSuccess
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
import eu.kanade.tachiyomi.source.model.* 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.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.* import okhttp3.FormBody
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
@ -40,6 +48,7 @@ class FMReaderFactory : SourceFactory {
* most likely the fix is to override popularMangaNextPageSelector() */ * most likely the fix is to override popularMangaNextPageSelector() */
class LHTranslation : FMReader("LHTranslation", "https://lhtranslation.net", "en") class LHTranslation : FMReader("LHTranslation", "https://lhtranslation.net", "en")
class MangaHato : FMReader("MangaHato", "https://mangahato.com", "ja") class MangaHato : FMReader("MangaHato", "https://mangahato.com", "ja")
class ManhwaScan : FMReader("ManhwaScan", "https://manhwascan.com", "en") class ManhwaScan : FMReader("ManhwaScan", "https://manhwascan.com", "en")
class MangaTiki : FMReader("MangaTiki", "https://mangatiki.com", "ja") class MangaTiki : FMReader("MangaTiki", "https://mangatiki.com", "ja")
@ -47,16 +56,20 @@ class MangaBone : FMReader("MangaBone", "https://mangabone.com", "en")
class YoloManga : FMReader("Yolo Manga", "https://yolomanga.ca", "es") { class YoloManga : FMReader("Yolo Manga", "https://yolomanga.ca", "es") {
override fun chapterListSelector() = "div#tab-chapper ~ div#tab-chapper table tr" override fun chapterListSelector() = "div#tab-chapper ~ div#tab-chapper table tr"
} }
class MangaLeer : FMReader("MangaLeer", "https://mangaleer.com", "es") { class MangaLeer : FMReader("MangaLeer", "https://mangaleer.com", "es") {
override val dateValueIndex = 1 override val dateValueIndex = 1
override val dateWordIndex = 2 override val dateWordIndex = 2
} }
class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") { class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") {
override val requestPath = "danh-sach-truyen.html" override val requestPath = "danh-sach-truyen.html"
// TODO: could add a genre search (different URL paths for genres) // TODO: could add a genre search (different URL paths for genres)
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
// I don't know why, but I have to override searchMangaRequest to make it work for this source // I don't know why, but I have to override searchMangaRequest to make it work for this source
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/$requestPath?name=$query&page=$page") override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = GET("$baseUrl/$requestPath?name=$query&page=$page")
override fun chapterListSelector() = "div#tab-chapper table tr" override fun chapterListSelector() = "div#tab-chapper table tr"
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val manga = SManga.create()
@ -72,10 +85,12 @@ class AiLoveManga : FMReader("AiLoveManga", "https://ailovemanga.com", "vi") {
return manga return manga
} }
} }
class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconline.org", "en") { class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconline.org", "en") {
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addInterceptor { requestIntercept(it) } .addInterceptor { requestIntercept(it) }
.build() .build()
private fun requestIntercept(chain: Interceptor.Chain): Response { private fun requestIntercept(chain: Interceptor.Chain): Response {
val request = chain.request() val request = chain.request()
val response = chain.proceed(request) val response = chain.proceed(request)
@ -85,46 +100,55 @@ class ReadComicOnlineOrg : FMReader("ReadComicOnline.org", "https://readcomiconl
.add("dqh_firewall", "%2F") .add("dqh_firewall", "%2F")
.build() .build()
val cookie = mutableListOf<String>() val cookie = mutableListOf<String>()
response.headers("set-cookie").map{ cookie.add(it.substringBefore(" ")) } response.headers("set-cookie").map { cookie.add(it.substringBefore(" ")) }
headers.newBuilder().add("Cookie", cookie.joinToString { " " }).build() headers.newBuilder().add("Cookie", cookie.joinToString { " " }).build()
client.newCall(POST(request.url().toString(), headers, body)).execute() client.newCall(POST(request.url().toString(), headers, body)).execute()
} else { } else {
response response
} }
} }
override val requestPath = "comic-list.html" override val requestPath = "comic-list.html"
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("div#divImage > select:first-of-type option").forEachIndexed{ i, imgPage -> document.select("div#divImage > select:first-of-type option").forEachIndexed { i, imgPage ->
pages.add(Page(i, imgPage.attr("value"), "")) pages.add(Page(i, imgPage.attr("value"), ""))
} }
return pages.dropLast(1) // last page is a comments page return pages.dropLast(1) // last page is a comments page
} }
override fun imageUrlRequest(page: Page): Request = GET(baseUrl + page.url, headers) override fun imageUrlRequest(page: Page): Request = GET(baseUrl + page.url, headers)
override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim() override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim()
override fun getGenreList() = getComicsGenreList() override fun getGenreList() = getComicsGenreList()
} }
class MangaWeek : FMReader("MangaWeek", "https://mangaweek.com", "en") class MangaWeek : FMReader("MangaWeek", "https://mangaweek.com", "en")
class HanaScan : FMReader("HanaScan (RawQQ)", "http://rawqq.com", "ja") { class HanaScan : FMReader("HanaScan (RawQQ)", "http://rawqq.com", "ja") {
override fun popularMangaNextPageSelector() = "div.col-md-8 button" override fun popularMangaNextPageSelector() = "div.col-md-8 button"
} }
class RawLH : FMReader("RawLH", "https://lhscan.net", "ja") { class RawLH : FMReader("RawLH", "https://lhscan.net", "ja") {
override fun popularMangaNextPageSelector() = "div.col-md-8 button" override fun popularMangaNextPageSelector() = "div.col-md-8 button"
} }
class Manhwa18 : FMReader("Manhwa18", "https://manhwa18.com", "en") { class Manhwa18 : FMReader("Manhwa18", "https://manhwa18.com", "en") {
override fun getGenreList() = getAdultGenreList() override fun getGenreList() = getAdultGenreList()
} }
class TruyenTranhLH : FMReader("TruyenTranhLH", "https://truyentranhlh.net", "vi") { class TruyenTranhLH : FMReader("TruyenTranhLH", "https://truyentranhlh.net", "vi") {
override val requestPath = "danh-sach-truyen.html" override val requestPath = "danh-sach-truyen.html"
} }
class EighteenLHPlus : FMReader("18LHPlus", "https://18lhplus.com", "en") { class EighteenLHPlus : FMReader("18LHPlus", "https://18lhplus.com", "en") {
override fun getGenreList() = getAdultGenreList() override fun getGenreList() = getAdultGenreList()
} }
class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") { class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
override fun popularMangaNextPageSelector() = "div.btn-group:not(div.btn-block) button.btn-info" override fun popularMangaNextPageSelector() = "div.btn-group:not(div.btn-block) button.btn-info"
// TODO: genre search possible but a bit of a pain // TODO: genre search possible but a bit of a pain
override fun getFilterList() = FilterList() override fun getFilterList() = FilterList()
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/arama.html?icerik=$query", headers) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/arama.html?icerik=$query", headers)
override fun searchMangaParse(response: Response): MangasPage { override fun searchMangaParse(response: Response): MangasPage {
val mangas = mutableListOf<SManga>() val mangas = mutableListOf<SManga>()
@ -135,6 +159,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
return MangasPage(mangas, false) return MangasPage(mangas, false)
} }
override fun searchMangaFromElement(element: Element): SManga { override fun searchMangaFromElement(element: Element): SManga {
val manga = SManga.create() val manga = SManga.create()
@ -143,6 +168,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
return manga return manga
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
val manga = SManga.create() val manga = SManga.create()
val infoElement = document.select("div#tab1").first() val infoElement = document.select("div#tab1").first()
@ -156,6 +182,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
return manga return manga
} }
override fun chapterListSelector() = "tr.table-bordered" override fun chapterListSelector() = "tr.table-bordered"
override val chapterUrlSelector = "td[align=left] > a" override val chapterUrlSelector = "td[align=left] > a"
override val chapterTimeSelector = "td[align=right]" override val chapterTimeSelector = "td[align=right]"
@ -172,6 +199,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
Observable.error(Exception("Licensed - No chapters to show")) Observable.error(Exception("Licensed - No chapters to show"))
} }
} }
private fun chapterListParse(response: Response, requestUrl: String): List<SChapter> { private fun chapterListParse(response: Response, requestUrl: String): List<SChapter> {
val chapters = mutableListOf<SChapter>() val chapters = mutableListOf<SChapter>()
var document = response.asJsoup() var document = response.asJsoup()
@ -180,7 +208,7 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
// chapters are paginated // chapters are paginated
while (moreChapters) { while (moreChapters) {
document.select(chapterListSelector()).map{ chapters.add(chapterFromElement(it)) } document.select(chapterListSelector()).map { chapters.add(chapterFromElement(it)) }
if (document.select("a[data-page=$nextPage]").isNotEmpty()) { if (document.select("a[data-page=$nextPage]").isNotEmpty()) {
val body = FormBody.Builder() val body = FormBody.Builder()
.add("page", nextPage.toString()) .add("page", nextPage.toString())
@ -193,21 +221,25 @@ class MangaTR : FMReader("Manga-TR", "https://manga-tr.com", "tr") {
} }
return chapters return chapters
} }
override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/${chapter.url.substringAfter("cek/")}", headers) override fun pageListRequest(chapter: SChapter): Request = GET("$baseUrl/${chapter.url.substringAfter("cek/")}", headers)
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("div.chapter-content select:first-of-type option").forEachIndexed{ i, imgPage -> document.select("div.chapter-content select:first-of-type option").forEachIndexed { i, imgPage ->
pages.add(Page(i, "$baseUrl/${imgPage.attr("value")}", "")) pages.add(Page(i, "$baseUrl/${imgPage.attr("value")}", ""))
} }
return pages.dropLast(1) // last page is a comments page return pages.dropLast(1) // last page is a comments page
} }
override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim() override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim()
} }
class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") { class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") {
override val requestPath = "comic-dir" override val requestPath = "comic-dir"
// this source doesn't have the "page x of y" element // this source doesn't have the "page x of y" element
override fun popularMangaNextPageSelector() = "li:contains(»)" override fun popularMangaNextPageSelector() = "li:contains(»)"
override fun popularMangaParse(response: Response) = defaultMangaParse(response) override fun popularMangaParse(response: Response) = defaultMangaParse(response)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic-dir?q=$query", headers) override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request = GET("$baseUrl/comic-dir?q=$query", headers)
override fun searchMangaParse(response: Response): MangasPage = defaultMangaParse(response) override fun searchMangaParse(response: Response): MangasPage = defaultMangaParse(response)
@ -224,39 +256,43 @@ class Comicastle : FMReader("Comicastle", "https://www.comicastle.org", "en") {
return manga return manga
} }
override fun chapterListSelector() = "div.col-md-9 table:last-of-type tr" override fun chapterListSelector() = "div.col-md-9 table:last-of-type tr"
override fun chapterListParse(response: Response): List<SChapter> = super.chapterListParse(response).reversed() override fun chapterListParse(response: Response): List<SChapter> = super.chapterListParse(response).reversed()
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
document.select("div.text-center select option").forEachIndexed{ i, imgPage -> document.select("div.text-center select option").forEachIndexed { i, imgPage ->
pages.add(Page(i, imgPage.attr("value"), "")) pages.add(Page(i, imgPage.attr("value"), ""))
} }
return pages return pages
} }
override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim() override fun imageUrlParse(document: Document): String = document.select("img.chapter-img").attr("abs:src").trim()
override fun getGenreList() = getComicsGenreList() override fun getGenreList() = getComicsGenreList()
} }
class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") { class Manhwa18Net : FMReader("Manhwa18.net", "https://manhwa18.net", "en") {
override fun popularMangaRequest(page: Int): Request = override fun popularMangaRequest(page: Int): Request =
GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC&ungenre=raw", headers) GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=views&sort_type=DESC&ungenre=raw", headers)
override fun latestUpdatesRequest(page: Int): Request = override fun latestUpdatesRequest(page: Int): Request =
GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC&ungenre=raw", headers) GET("$baseUrl/$requestPath?listType=pagination&page=$page&sort=last_update&sort_type=DESC&ungenre=raw", headers)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val noRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("ungenre", "raw").toString() val noRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("ungenre", "raw").toString()
return GET(noRawsUrl, headers) return GET(noRawsUrl, headers)
} }
override fun getGenreList() = getAdultGenreList() override fun getGenreList() = getAdultGenreList()
} }
class Manhwa18NetRaw : FMReader("Manhwa18.net Raw", "https://manhwa18.net", "ko") { class Manhwa18NetRaw : FMReader("Manhwa18.net Raw", "https://manhwa18.net", "ko") {
override val requestPath = "manga-list-genre-raw.html" override val requestPath = "manga-list-genre-raw.html"
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("genre", "raw").toString() val onlyRawsUrl = super.searchMangaRequest(page, query, filters).url().newBuilder().addQueryParameter("genre", "raw").toString()
return GET(onlyRawsUrl, headers) return GET(onlyRawsUrl, headers)
} }
override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) }) override fun getFilterList() = FilterList(super.getFilterList().filterNot { it == GenreList(getGenreList()) })
} }

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: FoolSlide (multiple sources)' appName = 'Tachiyomi: FoolSlide (multiple sources)'
pkgNameSuffix = 'all.foolslide' pkgNameSuffix = 'all.foolslide'
extClass = '.FoolSlideFactory' extClass = '.FoolSlideFactory'
extVersionCode = 28 extVersionCode = 29
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -4,7 +4,11 @@ import com.github.salomonbrys.kotson.get
import com.google.gson.JsonParser import com.google.gson.JsonParser
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.* 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.source.online.ParsedHttpSource
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.Request import okhttp3.Request
@ -13,13 +17,17 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.text.ParseException import java.text.ParseException
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Calendar
import java.util.Date
import java.util.HashSet
import java.util.Locale
abstract class FoolSlide(
open class FoolSlide(override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String, override val lang: String,
val urlModifier: String = "") : ParsedHttpSource() { val urlModifier: String = ""
) : ParsedHttpSource() {
protected open val dedupeLatestUpdates = true protected open val dedupeLatestUpdates = true
@ -81,7 +89,7 @@ open class FoolSlide(override val name: String,
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val form = FormBody.Builder() val form = FormBody.Builder()
.add("search", query) .add("search", query)
return POST("$baseUrl$urlModifier/search/", headers, form.build()) return POST("$baseUrl$urlModifier/search/", headers, form.build())
} }
@ -123,8 +131,8 @@ open class FoolSlide(override val name: String,
private fun allowAdult(url: String): Request { private fun allowAdult(url: String): Request {
return POST(url, body = FormBody.Builder() return POST(url, body = FormBody.Builder()
.add("adult", "true") .add("adult", "true")
.build()) .build())
} }
override fun chapterListRequest(manga: SManga) = allowAdult(super.chapterListRequest(manga)) override fun chapterListRequest(manga: SManga) = allowAdult(super.chapterListRequest(manga))
@ -142,7 +150,7 @@ open class FoolSlide(override val name: String,
chapter.setUrlWithoutDomain(urlElement.attr("href")) chapter.setUrlWithoutDomain(urlElement.attr("href"))
chapter.name = urlElement.text() chapter.name = urlElement.text()
chapter.date_upload = dateElement.text()?.let { parseChapterDate(it.substringAfter(", ")) } chapter.date_upload = dateElement.text()?.let { parseChapterDate(it.substringAfter(", ")) }
?: 0 ?: 0
return chapter return chapter
} }
@ -247,8 +255,8 @@ open class FoolSlide(override val name: String,
json.forEach { json.forEach {
// Create dummy element to resolve relative URL // Create dummy element to resolve relative URL
val absUrl = document.createElement("a") val absUrl = document.createElement("a")
.attr("href", it["url"].asString) .attr("href", it["url"].asString)
.absUrl("href") .absUrl("href")
pages.add(Page(pages.size, "", absUrl)) pages.add(Page(pages.size, "", absUrl))
} }

View File

@ -12,41 +12,36 @@ import okhttp3.Request
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
class FoolSlideFactory : SourceFactory { class FoolSlideFactory : SourceFactory {
override fun createSources(): List<Source> = getAllFoolSlide() override fun createSources(): List<Source> = listOf(
} JaminisBox(),
SenseScans(),
fun getAllFoolSlide(): List<Source> { KireiCake(),
return listOf( SilentSky(),
JaminisBox(), Mangatellers(),
SenseScans(), IskultripScans(),
KireiCake(), AnataNoMotokare(),
SilentSky(), DeathTollScans(),
Mangatellers(), DKThias(),
IskultripScans(), WorldThree(),
AnataNoMotokare(), DokiFansubs(),
DeathTollScans(), YuriIsm(),
DKThias(), AjiaNoScantrad(),
WorldThree(), OneTimeScans(),
DokiFansubs(), TsubasaSociety(),
YuriIsm(), MangaScouts(),
AjiaNoScantrad(), StormInHeaven(),
OneTimeScans(), Lilyreader(),
TsubasaSociety(), Russification(),
MangaScouts(), EvilFlowers(),
StormInHeaven(), AkaiYuhiMunTeam(),
Lilyreader(), LupiTeam(),
Russification(), HentaiCafe(),
EvilFlowers(), TheCatScans(),
AkaiYuhiMunTeam(), ZandynoFansub()
LupiTeam(),
HentaiCafe(),
TheCatScans(),
ZandynoFansub()
) )
} }
class JaminisBox : FoolSlide("Jaimini's Box", "https://jaiminisbox.com", "en", "/reader") { class JaminisBox : FoolSlide("Jaimini's Box", "https://jaiminisbox.com", "en", "/reader") {
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
val doc = document.toString() val doc = document.toString()
var jsonstr = doc.substringAfter("var pages = ").substringBefore(";") var jsonstr = doc.substringAfter("var pages = ").substringBefore(";")
@ -132,7 +127,6 @@ class LupiTeam : FoolSlide("LupiTeam", "https://lupiteam.net", "it", "/reader")
return manga return manga
} }
} }
class ZandynoFansub : FoolSlide("Zandy no Fansub", "http://zandynofansub.aishiteru.org", "en", "/reader") class ZandynoFansub : FoolSlide("Zandy no Fansub", "http://zandynofansub.aishiteru.org", "en", "/reader")

View File

@ -2,7 +2,11 @@ package eu.kanade.tachiyomi.extension.all.foolslide
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.source.model.* 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.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Request import okhttp3.Request
import okhttp3.Response import okhttp3.Response
@ -56,11 +60,11 @@ class HentaiCafe : FoolSlide("Hentai Cafe", "https://hentai.cafe", "en", "/manga
// we still need to parse the manga info page // we still need to parse the manga info page
// Example: https://hentai.cafe/aiya-youngest-daughters-circumstances/ // Example: https://hentai.cafe/aiya-youngest-daughters-circumstances/
override fun chapterListParse(response: Response) = listOf( override fun chapterListParse(response: Response) = listOf(
SChapter.create().apply { SChapter.create().apply {
setUrlWithoutDomain(response.asJsoup().select("[title=Read]").attr("href")) setUrlWithoutDomain(response.asJsoup().select("[title=Read]").attr("href"))
name = "Chapter" name = "Chapter"
chapter_number = 1f chapter_number = 1f
} }
) )
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
@ -74,9 +78,9 @@ class HentaiCafe : FoolSlide("Hentai Cafe", "https://hentai.cafe", "en", "/manga
if (f.state.isNotBlank()) { if (f.state.isNotBlank()) {
requireNoUrl() requireNoUrl()
url = "/hc.fyi/artist/${f.state url = "/hc.fyi/artist/${f.state
.trim() .trim()
.toLowerCase() .toLowerCase()
.replace(ARTIST_INVALID_CHAR_REGEX, "-")}/" .replace(ARTIST_INVALID_CHAR_REGEX, "-")}/"
} }
} }
filters.findInstance<BookFilter>()?.let { f -> filters.findInstance<BookFilter>()?.let { f ->
@ -113,79 +117,79 @@ class HentaiCafe : FoolSlide("Hentai Cafe", "https://hentai.cafe", "en", "/manga
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters)) return client.newCall(searchMangaRequest(page, query, filters))
.asObservable().doOnNext { response -> .asObservable().doOnNext { response ->
if (!response.isSuccessful) { if (!response.isSuccessful) {
response.close() response.close()
// Better error message for invalid artist // Better error message for invalid artist
if (response.code() == 404 if (response.code() == 404
&& !filters.findInstance<ArtistFilter>()?.state.isNullOrBlank()) && !filters.findInstance<ArtistFilter>()?.state.isNullOrBlank())
error("Invalid artist!") error("Invalid artist!")
else throw Exception("HTTP error ${response.code()}") else throw Exception("HTTP error ${response.code()}")
}
}
.map { response ->
searchMangaParse(response)
} }
}
.map { response ->
searchMangaParse(response)
}
} }
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Filter.Header("Filters cannot be used while searching."), Filter.Header("Filters cannot be used while searching."),
Filter.Header("Only one filter may be used at a time."), Filter.Header("Only one filter may be used at a time."),
Filter.Separator(), Filter.Separator(),
ArtistFilter(), ArtistFilter(),
BookFilter(), BookFilter(),
TagFilter() TagFilter()
) )
class ArtistFilter : Filter.Text("Artist (must be exact match)") class ArtistFilter : Filter.Text("Artist (must be exact match)")
class BookFilter : Filter.CheckBox("Show books only", false) class BookFilter : Filter.CheckBox("Show books only", false)
class TagFilter : Filter.Select<Tag>("Tag", arrayOf( class TagFilter : Filter.Select<Tag>("Tag", arrayOf(
Tag("", "<select>"), Tag("", "<select>"),
Tag("ahegao", "Ahegao"), Tag("ahegao", "Ahegao"),
Tag("anal", "Anal"), Tag("anal", "Anal"),
Tag("big-ass", "Big ass"), Tag("big-ass", "Big ass"),
Tag("big-breast", "Big breast"), Tag("big-breast", "Big breast"),
Tag("big-dick", "Big dick"), Tag("big-dick", "Big dick"),
Tag("bondage", "Bondage"), Tag("bondage", "Bondage"),
Tag("cheating", "Cheating"), Tag("cheating", "Cheating"),
Tag("chubby", "Chubby"), Tag("chubby", "Chubby"),
Tag("color", "Color"), Tag("color", "Color"),
Tag("condom", "Condom"), Tag("condom", "Condom"),
Tag("cosplay", "Cosplay"), Tag("cosplay", "Cosplay"),
Tag("cunnilingus", "Cunnilingus"), Tag("cunnilingus", "Cunnilingus"),
Tag("dark-skin", "Dark skin"), Tag("dark-skin", "Dark skin"),
Tag("exhibitionism", "Exhibitionism"), Tag("exhibitionism", "Exhibitionism"),
Tag("fellatio", "Fellatio"), Tag("fellatio", "Fellatio"),
Tag("femdom", "Femdom"), Tag("femdom", "Femdom"),
Tag("flat-chest", "Flat chest"), Tag("flat-chest", "Flat chest"),
Tag("full-color", "Full color"), Tag("full-color", "Full color"),
Tag("glasses", "Glasses"), Tag("glasses", "Glasses"),
Tag("group", "Group"), Tag("group", "Group"),
Tag("hairy", "Hairy"), Tag("hairy", "Hairy"),
Tag("handjob", "Handjob"), Tag("handjob", "Handjob"),
Tag("heart-pupils", "Heart pupils"), Tag("heart-pupils", "Heart pupils"),
Tag("housewife", "Housewife"), Tag("housewife", "Housewife"),
Tag("incest", "Incest"), Tag("incest", "Incest"),
Tag("lingerie", "Lingerie"), Tag("lingerie", "Lingerie"),
Tag("loli", "Loli"), Tag("loli", "Loli"),
Tag("masturbation", "Masturbation"), Tag("masturbation", "Masturbation"),
Tag("nakadashi", "Nakadashi"), Tag("nakadashi", "Nakadashi"),
Tag("osananajimi", "Osananajimi"), Tag("osananajimi", "Osananajimi"),
Tag("paizuri", "Paizuri"), Tag("paizuri", "Paizuri"),
Tag("pettanko", "Pettanko"), Tag("pettanko", "Pettanko"),
Tag("rape", "Rape"), Tag("rape", "Rape"),
Tag("schoolgirl", "Schoolgirl"), Tag("schoolgirl", "Schoolgirl"),
Tag("sex-toys", "Sex toys"), Tag("sex-toys", "Sex toys"),
Tag("shota", "Shota"), Tag("shota", "Shota"),
Tag("socks", "Socks"), Tag("socks", "Socks"),
Tag("stocking", "Stocking"), Tag("stocking", "Stocking"),
Tag("stockings", "Stockings"), Tag("stockings", "Stockings"),
Tag("swimsuit", "Swimsuit"), Tag("swimsuit", "Swimsuit"),
Tag("teacher", "Teacher"), Tag("teacher", "Teacher"),
Tag("tsundere", "Tsundere"), Tag("tsundere", "Tsundere"),
Tag("uncensored", "Uncensored"), Tag("uncensored", "Uncensored"),
Tag("vanilla", "Vanilla"), Tag("vanilla", "Vanilla"),
Tag("x-ray", "X-Ray") Tag("x-ray", "X-Ray")
)) ))
class Tag(val name: String, private val displayName: String) { class Tag(val name: String, private val displayName: String) {

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Genkan (multiple sources)' appName = 'Tachiyomi: Genkan (multiple sources)'
pkgNameSuffix = 'all.genkan' pkgNameSuffix = 'all.genkan'
extClass = '.GenkanFactory' extClass = '.GenkanFactory'
extVersionCode = 7 extVersionCode = 8
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,7 +1,11 @@
package eu.kanade.tachiyomi.extension.all.genkan package eu.kanade.tachiyomi.extension.all.genkan
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.* 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.source.online.ParsedHttpSource
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -11,12 +15,13 @@ import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import org.jsoup.select.Elements import org.jsoup.select.Elements
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Calendar
import java.util.Locale
abstract class Genkan( abstract class Genkan(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String override val lang: String
) : ParsedHttpSource() { ) : ParsedHttpSource() {
override val supportsLatest = true override val supportsLatest = true
@ -88,7 +93,7 @@ abstract class Genkan(
private fun styleToUrl(element: Element): String { private fun styleToUrl(element: Element): String {
return element.attr("style").substringAfter("(").substringBefore(")") return element.attr("style").substringAfter("(").substringBefore(")")
.let{ if (it.startsWith("http")) it else baseUrl + it } .let { if (it.startsWith("http")) it else baseUrl + it }
} }
override fun mangaDetailsParse(document: Document): SManga { override fun mangaDetailsParse(document: Document): SManga {
@ -155,11 +160,11 @@ abstract class Genkan(
val pages = mutableListOf<Page>() val pages = mutableListOf<Page>()
val allImages = document.select("div#pages-container + script").first().data() val allImages = document.select("div#pages-container + script").first().data()
.substringAfter("[").substringBefore("];") .substringAfter("[").substringBefore("];")
.replace(Regex("""["\\]"""), "") .replace(Regex("""["\\]"""), "")
.split(",") .split(",")
for (i in 0 until allImages.size) { for (i in allImages.indices) {
pages.add(Page(i, "", allImages[i])) pages.add(Page(i, "", allImages[i]))
} }
@ -178,9 +183,9 @@ abstract class Genkan(
// For sites using the older Genkan CMS that didn't have a search function // For sites using the older Genkan CMS that didn't have a search function
abstract class GenkanOriginal( abstract class GenkanOriginal(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String override val lang: String
) : Genkan(name, baseUrl, lang) { ) : Genkan(name, baseUrl, lang) {
private var searchQuery = "" private var searchQuery = ""
@ -223,7 +228,7 @@ abstract class GenkanOriginal(
// search additional pages if called // search additional pages if called
private fun searchMorePages(): MutableList<SManga> { private fun searchMorePages(): MutableList<SManga> {
searchPage++ searchPage++
val nextPage = client.newCall(popularMangaRequest(searchPage)).execute().asJsoup() val nextPage = client.newCall(popularMangaRequest(searchPage)).execute().asJsoup()
val searchMatches = mutableListOf<SManga>() val searchMatches = mutableListOf<SManga>()
searchMatches.addAll(getMatchesFrom(nextPage)) searchMatches.addAll(getMatchesFrom(nextPage))
nextPageSelectorElement = nextPage.select(searchMangaNextPageSelector()) nextPageSelectorElement = nextPage.select(searchMangaNextPageSelector())
@ -238,4 +243,3 @@ abstract class GenkanOriginal(
override fun searchMangaNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
} }

View File

@ -5,14 +5,14 @@ import eu.kanade.tachiyomi.source.SourceFactory
class GenkanFactory : SourceFactory { class GenkanFactory : SourceFactory {
override fun createSources(): List<Source> = listOf( override fun createSources(): List<Source> = listOf(
LeviatanScans(), LeviatanScans(),
LeviatanScansES(), LeviatanScansES(),
PsychoPlay(), PsychoPlay(),
OneShotScans(), OneShotScans(),
KaguyaDex(), KaguyaDex(),
KomiScans(), KomiScans(),
HunlightScans(), HunlightScans(),
WoweScans() WoweScans()
) )
} }

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Madara (multiple sources)' appName = 'Tachiyomi: Madara (multiple sources)'
pkgNameSuffix = "all.madara" pkgNameSuffix = "all.madara"
extClass = '.MadaraFactory' extClass = '.MadaraFactory'
extVersionCode = 22 extVersionCode = 23
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -3,31 +3,43 @@ package eu.kanade.tachiyomi.extension.all.madara
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.asObservable import eu.kanade.tachiyomi.network.asObservable
import eu.kanade.tachiyomi.source.model.* 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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.* import okhttp3.CacheControl
import java.text.ParseException import okhttp3.FormBody
import java.text.SimpleDateFormat import okhttp3.HttpUrl
import java.util.* import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit import okhttp3.Request
import okhttp3.Response
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import rx.Observable import rx.Observable
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Date
import java.util.Locale
import java.util.concurrent.TimeUnit
open class Madara( abstract class Madara(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String, override val lang: String,
private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US) private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMMM dd, yyyy", Locale.US)
) : ParsedHttpSource() { ) : ParsedHttpSource() {
override val supportsLatest = true
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.build() .build()
override val supportsLatest = true
// Popular Manga // Popular Manga
override fun popularMangaSelector() = "div.page-item-detail" override fun popularMangaSelector() = "div.page-item-detail"
@ -42,7 +54,7 @@ open class Madara(
} }
select("img").first()?.let { select("img").first()?.let {
manga.thumbnail_url = it.absUrl(if(it.hasAttr("data-src")) "data-src" else "src") manga.thumbnail_url = it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
} }
} }
@ -52,7 +64,7 @@ open class Madara(
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val form = FormBody.Builder().apply { val form = FormBody.Builder().apply {
add("action", "madara_load_more") add("action", "madara_load_more")
add("page", (page-1).toString()) add("page", (page - 1).toString())
add("template", "madara-core/content/content-archive") add("template", "madara-core/content/content-archive")
add("vars[orderby]", "meta_value_num") add("vars[orderby]", "meta_value_num")
add("vars[paged]", "1") add("vars[paged]", "1")
@ -81,7 +93,7 @@ open class Madara(
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
val form = FormBody.Builder().apply { val form = FormBody.Builder().apply {
add("action", "madara_load_more") add("action", "madara_load_more")
add("page", (page-1).toString()) add("page", (page - 1).toString())
add("template", "madara-core/content/content-archive") add("template", "madara-core/content/content-archive")
add("vars[orderby]", "meta_value_num") add("vars[orderby]", "meta_value_num")
add("vars[paged]", "1") add("vars[paged]", "1")
@ -107,7 +119,7 @@ open class Madara(
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return client.newCall(searchMangaRequest(page, query, filters)) return client.newCall(searchMangaRequest(page, query, filters))
.asObservable().doOnNext { response -> .asObservable().doOnNext { response ->
if(!response.isSuccessful) { if (!response.isSuccessful) {
response.close() response.close()
// Error message for exceeding last page // Error message for exceeding last page
if (response.code() == 404) if (response.code() == 404)
@ -129,17 +141,17 @@ open class Madara(
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is AuthorFilter -> { is AuthorFilter -> {
if(filter.state.isNotBlank()) { if (filter.state.isNotBlank()) {
url.addQueryParameter("author", filter.state) url.addQueryParameter("author", filter.state)
} }
} }
is ArtistFilter -> { is ArtistFilter -> {
if(filter.state.isNotBlank()) { if (filter.state.isNotBlank()) {
url.addQueryParameter("artist", filter.state) url.addQueryParameter("artist", filter.state)
} }
} }
is YearFilter -> { is YearFilter -> {
if(filter.state.isNotBlank()) { if (filter.state.isNotBlank()) {
url.addQueryParameter("release", filter.state) url.addQueryParameter("release", filter.state)
} }
} }
@ -151,7 +163,7 @@ open class Madara(
} }
} }
is OrderByFilter -> { is OrderByFilter -> {
if(filter.state != 0) { if (filter.state != 0) {
url.addQueryParameter("m_orderby", filter.toUriPart()) url.addQueryParameter("m_orderby", filter.toUriPart())
} }
} }
@ -165,28 +177,28 @@ open class Madara(
private class YearFilter : Filter.Text("Year of Released") private class YearFilter : Filter.Text("Year of Released")
private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status) private class StatusFilter(status: List<Tag>) : Filter.Group<Tag>("Status", status)
private class OrderByFilter : UriPartFilter("Order By", arrayOf( private class OrderByFilter : UriPartFilter("Order By", arrayOf(
Pair("<select>", ""), Pair("<select>", ""),
Pair("Latest", "latest"), Pair("Latest", "latest"),
Pair("A-Z", "alphabet"), Pair("A-Z", "alphabet"),
Pair("Rating", "rating"), Pair("Rating", "rating"),
Pair("Trending", "trending"), Pair("Trending", "trending"),
Pair("Most Views", "views"), Pair("Most Views", "views"),
Pair("New", "new-manga") Pair("New", "new-manga")
)) ))
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
AuthorFilter(), AuthorFilter(),
ArtistFilter(), ArtistFilter(),
YearFilter(), YearFilter(),
StatusFilter(getStatusList()), StatusFilter(getStatusList()),
OrderByFilter() OrderByFilter()
) )
private fun getStatusList() = listOf( private fun getStatusList() = listOf(
Tag("end" , "Completed"), Tag("end", "Completed"),
Tag("on-going" , "Ongoing"), Tag("on-going", "Ongoing"),
Tag("canceled" , "Canceled"), Tag("canceled", "Canceled"),
Tag("on-hold" , "On Hold") Tag("on-hold", "On Hold")
) )
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
@ -207,7 +219,7 @@ open class Madara(
manga.title = it.ownText() manga.title = it.ownText()
} }
select("img").first()?.let { select("img").first()?.let {
manga.thumbnail_url = it.absUrl(if(it.hasAttr("data-src")) "data-src" else "src") manga.thumbnail_url = it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
} }
} }
@ -241,10 +253,10 @@ open class Madara(
} }
} }
select("div.summary_image img").first()?.let { select("div.summary_image img").first()?.let {
manga.thumbnail_url = it.absUrl(if(it.hasAttr("data-src")) "data-src" else "src") manga.thumbnail_url = it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
} }
select("div.summary-content").last()?.let { select("div.summary-content").last()?.let {
manga.status = when(it.text()) { manga.status = when (it.text()) {
// I don't know what's the corresponding for COMPLETED and LICENSED // I don't know what's the corresponding for COMPLETED and LICENSED
// There's no support for "Canceled" or "On Hold" // There's no support for "Canceled" or "On Hold"
"Completed" -> SManga.COMPLETED "Completed" -> SManga.COMPLETED
@ -271,7 +283,7 @@ open class Madara(
with(element) { with(element) {
select("a").first()?.let { urlElement -> select("a").first()?.let { urlElement ->
chapter.url = urlElement.attr("abs:href").let { chapter.url = urlElement.attr("abs:href").let {
it.substringBefore("?style=paged") + if(!it.endsWith("?style=list")) "?style=list" else "" it.substringBefore("?style=paged") + if (!it.endsWith("?style=list")) "?style=list" else ""
} }
chapter.name = urlElement.text() chapter.name = urlElement.text()
} }
@ -353,8 +365,8 @@ open class Madara(
override fun pageListParse(document: Document): List<Page> { override fun pageListParse(document: Document): List<Page> {
return document.select(pageListParseSelector).mapIndexed { index, element -> return document.select(pageListParseSelector).mapIndexed { index, element ->
Page(index, "", element.select("img").first()?.let{ Page(index, "", element.select("img").first()?.let {
it.absUrl(if(it.hasAttr("data-src")) "data-src" else "src") it.absUrl(if (it.hasAttr("data-src")) "data-src" else "src")
}) })
} }
} }

View File

@ -1,6 +1,5 @@
package eu.kanade.tachiyomi.extension.all.madara package eu.kanade.tachiyomi.extension.all.madara
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
@ -11,10 +10,10 @@ import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers import okhttp3.Headers
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.Request
import okhttp3.Response
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Locale import java.util.Locale
import okhttp3.Response
import okhttp3.Request
class MadaraFactory : SourceFactory { class MadaraFactory : SourceFactory {
override fun createSources(): List<Source> = listOf( override fun createSources(): List<Source> = listOf(
@ -56,11 +55,13 @@ class MadaraFactory : SourceFactory {
class Mangasushi : Madara("Mangasushi", "https://mangasushi.net", "en") { class Mangasushi : Madara("Mangasushi", "https://mangasushi.net", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class NinjaScans : Madara("NinjaScans", "https://ninjascans.com", "en") class NinjaScans : Madara("NinjaScans", "https://ninjascans.com", "en")
class ReadManhua : Madara("ReadManhua", "https://readmanhua.net", "en", class ReadManhua : Madara("ReadManhua", "https://readmanhua.net", "en",
dateFormat = SimpleDateFormat("dd MMM yy", Locale.US)) { dateFormat = SimpleDateFormat("dd MMM yy", Locale.US)) {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ZeroScans : Madara("ZeroScans", "https://zeroscans.com", "en") class ZeroScans : Madara("ZeroScans", "https://zeroscans.com", "en")
class IsekaiScanCom : Madara("IsekaiScan.com", "https://isekaiscan.com/", "en") class IsekaiScanCom : Madara("IsekaiScan.com", "https://isekaiscan.com/", "en")
class HappyTeaScans : Madara("Happy Tea Scans", "https://happyteascans.com/", "en") class HappyTeaScans : Madara("Happy Tea Scans", "https://happyteascans.com/", "en")
@ -68,7 +69,8 @@ class JustForFun : Madara("Just For Fun", "https://just-for-fun.ru/", "ru",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) { dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class AoCTranslations : Madara("Agent of Change Translations", "https://aoc.moe/", "en"){
class AoCTranslations : Madara("Agent of Change Translations", "https://aoc.moe/", "en") {
override fun chapterListParse(response: Response): List<SChapter> { override fun chapterListParse(response: Response): List<SChapter> {
val chapters = mutableListOf<SChapter>() val chapters = mutableListOf<SChapter>()
val document = response.asJsoup() val document = response.asJsoup()
@ -80,7 +82,7 @@ class AoCTranslations : Madara("Agent of Change Translations", "https://aoc.moe/
} else { } else {
// For their "fancy" volume/chapter lists // For their "fancy" volume/chapter lists
document.select("div.wpb_wrapper:contains(volume) a") document.select("div.wpb_wrapper:contains(volume) a")
.filter { it.attr("href").contains(baseUrl) && !it.attr("href").contains("imgur")} .filter { it.attr("href").contains(baseUrl) && !it.attr("href").contains("imgur") }
.map { it -> .map { it ->
val chapter = SChapter.create() val chapter = SChapter.create()
if (it.attr("href").contains("volume")) { if (it.attr("href").contains("volume")) {
@ -99,9 +101,11 @@ class AoCTranslations : Madara("Agent of Change Translations", "https://aoc.moe/
return chapters.reversed() return chapters.reversed()
} }
} }
class KomikGo : Madara("KomikGo", "https://komikgo.com", "id") { class KomikGo : Madara("KomikGo", "https://komikgo.com", "id") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class LuxyScans : Madara("Luxy Scans", "https://luxyscans.com/", "en") class LuxyScans : Madara("Luxy Scans", "https://luxyscans.com/", "en")
class TritiniaScans : Madara("Tritinia Scans", "http://ghajik.ml/", "en", class TritiniaScans : Madara("Tritinia Scans", "http://ghajik.ml/", "en",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) { dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) {
@ -110,25 +114,32 @@ class TritiniaScans : Madara("Tritinia Scans", "http://ghajik.ml/", "en",
override fun latestUpdatesNextPageSelector(): String? = null override fun latestUpdatesNextPageSelector(): String? = null
override fun popularMangaNextPageSelector(): String? = null override fun popularMangaNextPageSelector(): String? = null
} }
class TsubakiNoScan : Madara("Tsubaki No Scan", "https://tsubakinoscan.com/", class TsubakiNoScan : Madara("Tsubaki No Scan", "https://tsubakinoscan.com/",
"fr", dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) "fr", dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US))
class YokaiJump : Madara("Yokai Jump", "https://yokaijump.fr/", "fr", class YokaiJump : Madara("Yokai Jump", "https://yokaijump.fr/", "fr",
dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) { dateFormat = SimpleDateFormat("dd/MM/yy", Locale.US)) {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ZManga : Madara("ZManga", "https://zmanga.org/", "es") { class ZManga : Madara("ZManga", "https://zmanga.org/", "es") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class MangazukiMe : Madara("Mangazuki.me", "https://mangazuki.me/", "en") class MangazukiMe : Madara("Mangazuki.me", "https://mangazuki.me/", "en")
class MangazukiOnline : Madara("Mangazuki.online", "https://www.mangazuki.online/", "en") { class MangazukiOnline : Madara("Mangazuki.online", "https://www.mangazuki.online/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class MangazukiClubJP : Madara("Mangazuki.club", "https://mangazuki.club/", "ja") { class MangazukiClubJP : Madara("Mangazuki.club", "https://mangazuki.club/", "ja") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class MangazukiClubKO : Madara("Mangazuki.club", "https://mangazuki.club/", "ko") { class MangazukiClubKO : Madara("Mangazuki.club", "https://mangazuki.club/", "ko") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class FirstKissManga : Madara("1st Kiss", "https://1stkissmanga.com/", "en") { class FirstKissManga : Madara("1st Kiss", "https://1stkissmanga.com/", "en") {
override val pageListParseSelector = "div.reading-content img" override val pageListParseSelector = "div.reading-content img"
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
@ -141,12 +152,15 @@ class FirstKissManga : Madara("1st Kiss", "https://1stkissmanga.com/", "en") {
return if (page.imageUrl!!.contains(cdnUrl)) GET(page.imageUrl!!, cdnHeaders) else GET(page.imageUrl!!, headers) return if (page.imageUrl!!.contains(cdnUrl)) GET(page.imageUrl!!, cdnHeaders) else GET(page.imageUrl!!, headers)
} }
} }
class MangaKomi : Madara("MangaKomi", "https://mangakomi.com/", "en") { class MangaKomi : Madara("MangaKomi", "https://mangakomi.com/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class MangaSY : Madara("Manga SY", "https://www.mangasy.com/", "en") { class MangaSY : Madara("Manga SY", "https://www.mangasy.com/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ManwhaClub : Madara("Manwha Club", "https://manhwa.club/", "en") class ManwhaClub : Madara("Manwha Club", "https://manhwa.club/", "en")
class WuxiaWorld : Madara("WuxiaWorld", "https://wuxiaworld.site/", "en") { class WuxiaWorld : Madara("WuxiaWorld", "https://wuxiaworld.site/", "en") {
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/tag/webcomic/page/$page/?m_orderby=views", headers) override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/tag/webcomic/page/$page/?m_orderby=views", headers)
@ -154,6 +168,7 @@ class WuxiaWorld : Madara("WuxiaWorld", "https://wuxiaworld.site/", "en") {
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = super.searchMangaRequest(page, "$query comics", filters) override fun searchMangaRequest(page: Int, query: String, filters: FilterList) = super.searchMangaRequest(page, "$query comics", filters)
override fun popularMangaNextPageSelector() = "div.nav-previous.float-left" override fun popularMangaNextPageSelector() = "div.nav-previous.float-left"
} }
class WordRain : Madara("WordRain Translation", "https://wordrain69.com", "en") { class WordRain : Madara("WordRain Translation", "https://wordrain69.com", "en") {
override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga-genre/manga/page/$page/?m_orderby=views", headers) override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga-genre/manga/page/$page/?m_orderby=views", headers)
override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga-genre/manga/page/$page/?m_orderby=latest", headers) override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/manga-genre/manga/page/$page/?m_orderby=latest", headers)
@ -167,24 +182,31 @@ class WordRain : Madara("WordRain Translation", "https://wordrain69.com", "en")
return super.searchMangaParse(res) return super.searchMangaParse(res)
} }
} }
class YoManga : Madara("Yo Manga", "https://yomanga.info/", "en") { class YoManga : Madara("Yo Manga", "https://yomanga.info/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ManyToon : Madara("ManyToon", "https://manytoon.com/", "en") { class ManyToon : Madara("ManyToon", "https://manytoon.com/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ChibiManga : Madara("Chibi Manga", "http://www.cmreader.info/", "en") { class ChibiManga : Madara("Chibi Manga", "http://www.cmreader.info/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ZinManga : Madara("Zin Translator", "https://zinmanga.com/", "en") { class ZinManga : Madara("Zin Translator", "https://zinmanga.com/", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class ManwahentaiMe : Madara("Manwahentai.me", "https://manhwahentai.me", "en") class ManwahentaiMe : Madara("Manwahentai.me", "https://manhwahentai.me", "en")
class Manga3asq: Madara("مانجا العاشق", "https://3asq.org", "ar") { class Manga3asq : Madara("مانجا العاشق", "https://3asq.org", "ar") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class NManhwa: Madara("N Manhwa", "https://nmanhwa.com", "en") {
class NManhwa : Madara("N Manhwa", "https://nmanhwa.com", "en") {
override fun searchMangaNextPageSelector() = "nav.navigation-ajax" override fun searchMangaNextPageSelector() = "nav.navigation-ajax"
} }
class Indiancomicsonline: Madara("Indian Comics Online", "http://www.indiancomicsonline.com", "hi")
class Indiancomicsonline : Madara("Indian Comics Online", "http://www.indiancomicsonline.com", "hi")

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: MangaCards (Valhalla, NANI?)' appName = 'Tachiyomi: MangaCards (Valhalla, NANI?)'
pkgNameSuffix = 'all.mangacards' pkgNameSuffix = 'all.mangacards'
extClass = '.MangaCardsFactory' extClass = '.MangaCardsFactory'
extVersionCode = 4 extVersionCode = 5
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -2,19 +2,23 @@ package eu.kanade.tachiyomi.extension.all.mangacards
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.POST import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.source.model.* 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.source.online.ParsedHttpSource
import okhttp3.OkHttpClient
import okhttp3.Request
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import eu.kanade.tachiyomi.util.asJsoup import eu.kanade.tachiyomi.util.asJsoup
import okhttp3.FormBody import okhttp3.FormBody
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.Locale
abstract class MangaCards ( abstract class MangaCards(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
override val lang: String override val lang: String

View File

@ -14,4 +14,3 @@ class MangaCardsFactory : SourceFactory {
class ValhallaScans : MangaCards("Valhalla Scans", "https://valhallascans.com", "en") class ValhallaScans : MangaCards("Valhalla Scans", "https://valhallascans.com", "en")
class NaniScans : MangaCards("NANI? Scans", "https://naniscans.xyz", "en") class NaniScans : MangaCards("NANI? Scans", "https://naniscans.xyz", "en")
class IneptBastards : MangaCards("Inept Bastards", "https://ineptbastards.xyz", "en") class IneptBastards : MangaCards("Inept Bastards", "https://ineptbastards.xyz", "en")

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: MangaDex' appName = 'Tachiyomi: MangaDex'
pkgNameSuffix = 'all.mangadex' pkgNameSuffix = 'all.mangadex'
extClass = '.MangadexFactory' extClass = '.MangadexFactory'
extVersionCode = 68 extVersionCode = 69
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -43,7 +43,11 @@ import java.util.Date
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import kotlin.collections.set import kotlin.collections.set
open class Mangadex(override val lang: String, private val internalLang: String, private val langCode: Int) : ConfigurableSource, ParsedHttpSource() { abstract class Mangadex(
override val lang: String,
private val internalLang: String,
private val langCode: Int
) : ConfigurableSource, ParsedHttpSource() {
override val name = "MangaDex" override val name = "MangaDex"
@ -60,27 +64,27 @@ open class Mangadex(override val lang: String, private val internalLang: String,
private val rateLimitInterceptor = RateLimitInterceptor(4) private val rateLimitInterceptor = RateLimitInterceptor(4)
override val client: OkHttpClient = network.cloudflareClient.newBuilder() override val client: OkHttpClient = network.cloudflareClient.newBuilder()
.addNetworkInterceptor(rateLimitInterceptor) .addNetworkInterceptor(rateLimitInterceptor)
.build() .build()
private fun clientBuilder(): OkHttpClient = clientBuilder(getShowR18()) private fun clientBuilder(): OkHttpClient = clientBuilder(getShowR18())
private fun clientBuilder(r18Toggle: Int): OkHttpClient = network.cloudflareClient.newBuilder() private fun clientBuilder(r18Toggle: Int): OkHttpClient = network.cloudflareClient.newBuilder()
.connectTimeout(10, TimeUnit.SECONDS) .connectTimeout(10, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor(rateLimitInterceptor) .addNetworkInterceptor(rateLimitInterceptor)
.addNetworkInterceptor { chain -> .addNetworkInterceptor { chain ->
val originalCookies = chain.request().header("Cookie") ?: "" val originalCookies = chain.request().header("Cookie") ?: ""
val newReq = chain val newReq = chain
.request() .request()
.newBuilder() .newBuilder()
.header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle, langCode)}") .header("Cookie", "$originalCookies; ${cookiesHeader(r18Toggle, langCode)}")
.build() .build()
chain.proceed(newReq) chain.proceed(newReq)
}.build()!! }.build()!!
override fun headersBuilder() = Headers.Builder().apply { override fun headersBuilder() = Headers.Builder().apply {
add("User-Agent", "Tachiyomi "+ System.getProperty("http.agent")) add("User-Agent", "Tachiyomi " + System.getProperty("http.agent"))
} }
private fun cookiesHeader(r18Toggle: Int, langCode: Int): String { private fun cookiesHeader(r18Toggle: Int, langCode: Int): String {
@ -150,36 +154,36 @@ open class Mangadex(override val lang: String, private val internalLang: String,
override fun fetchPopularManga(page: Int): Observable<MangasPage> { override fun fetchPopularManga(page: Int): Observable<MangasPage> {
return clientBuilder().newCall(popularMangaRequest(page)) return clientBuilder().newCall(popularMangaRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
popularMangaParse(response) popularMangaParse(response)
} }
} }
override fun fetchLatestUpdates(page: Int): Observable<MangasPage> { override fun fetchLatestUpdates(page: Int): Observable<MangasPage> {
return clientBuilder().newCall(latestUpdatesRequest(page)) return clientBuilder().newCall(latestUpdatesRequest(page))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
latestUpdatesParse(response) latestUpdatesParse(response)
} }
} }
override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> { override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable<MangasPage> {
return if (query.startsWith(PREFIX_ID_SEARCH)) { return if (query.startsWith(PREFIX_ID_SEARCH)) {
val realQuery = query.removePrefix(PREFIX_ID_SEARCH) val realQuery = query.removePrefix(PREFIX_ID_SEARCH)
client.newCall(searchMangaByIdRequest(realQuery)) client.newCall(searchMangaByIdRequest(realQuery))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
val details = mangaDetailsParse(response) val details = mangaDetailsParse(response)
details.url = "/manga/$realQuery/" details.url = "/manga/$realQuery/"
MangasPage(listOf(details), false) MangasPage(listOf(details), false)
} }
} else { } else {
getSearchClient(filters).newCall(searchMangaRequest(page, query, filters)) getSearchClient(filters).newCall(searchMangaRequest(page, query, filters))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
searchMangaParse(response) searchMangaParse(response)
} }
} }
} }
@ -205,8 +209,8 @@ open class Mangadex(override val lang: String, private val internalLang: String,
// Do traditional search // Do traditional search
val url = HttpUrl.parse("$baseUrl/?page=search")!!.newBuilder() val url = HttpUrl.parse("$baseUrl/?page=search")!!.newBuilder()
.addQueryParameter("p", page.toString()) .addQueryParameter("p", page.toString())
.addQueryParameter("title", query.replace(WHITESPACE_REGEX, " ")) .addQueryParameter("title", query.replace(WHITESPACE_REGEX, " "))
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
@ -323,10 +327,10 @@ open class Mangadex(override val lang: String, private val internalLang: String,
override fun fetchMangaDetails(manga: SManga): Observable<SManga> { override fun fetchMangaDetails(manga: SManga): Observable<SManga> {
return clientBuilder().newCall(apiRequest(manga)) return clientBuilder().newCall(apiRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
mangaDetailsParse(response).apply { initialized = true } mangaDetailsParse(response).apply { initialized = true }
} }
} }
private fun apiRequest(manga: SManga): Request { private fun apiRequest(manga: SManga): Request {
@ -362,14 +366,14 @@ open class Mangadex(override val lang: String, private val internalLang: String,
val finalChapterNumber = getFinalChapter(mangaJson) val finalChapterNumber = getFinalChapter(mangaJson)
if ((status == 2 || status == 3) && chapterJson != null && isMangaCompleted(chapterJson, finalChapterNumber)) { if ((status == 2 || status == 3) && chapterJson != null && isMangaCompleted(chapterJson, finalChapterNumber)) {
manga.status = SManga.COMPLETED manga.status = SManga.COMPLETED
} else if (status == 2 && chapterJson != null && isOneshot(chapterJson, finalChapterNumber)){ } else if (status == 2 && chapterJson != null && isOneshot(chapterJson, finalChapterNumber)) {
manga.status = SManga.COMPLETED manga.status = SManga.COMPLETED
} else { } else {
manga.status = parseStatus(status) manga.status = parseStatus(status)
} }
val genres = (if (mangaJson.get("hentai").int == 1) listOf("Hentai") else listOf()) + val genres = (if (mangaJson.get("hentai").int == 1) listOf("Hentai") else listOf()) +
mangaJson.get("genres").asJsonArray.mapNotNull { GENRES[it.toString()] } mangaJson.get("genres").asJsonArray.mapNotNull { GENRES[it.toString()] }
manga.genre = genres.joinToString(", ") manga.genre = genres.joinToString(", ")
return manga return manga
@ -395,10 +399,10 @@ open class Mangadex(override val lang: String, private val internalLang: String,
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> { override fun fetchChapterList(manga: SManga): Observable<List<SChapter>> {
return clientBuilder().newCall(apiRequest(manga)) return clientBuilder().newCall(apiRequest(manga))
.asObservableSuccess() .asObservableSuccess()
.map { response -> .map { response ->
chapterListParse(response) chapterListParse(response)
} }
} }
private fun getFinalChapter(jsonObj: JsonObject): String = jsonObj.get("last_chapter").string.trim() private fun getFinalChapter(jsonObj: JsonObject): String = jsonObj.get("last_chapter").string.trim()
@ -414,8 +418,8 @@ open class Mangadex(override val lang: String, private val internalLang: String,
private fun isMangaCompleted(chapterJson: JsonObject, finalChapterNumber: String): Boolean { private fun isMangaCompleted(chapterJson: JsonObject, finalChapterNumber: String): Boolean {
val count = chapterJson.entrySet() val count = chapterJson.entrySet()
.filter { it.value.asJsonObject.get("lang_code").string == internalLang } .filter { it.value.asJsonObject.get("lang_code").string == internalLang }
.filter { doesFinalChapterExist(finalChapterNumber, it.value) }.count() .filter { doesFinalChapterExist(finalChapterNumber, it.value) }.count()
return count != 0 return count != 0
} }
@ -460,7 +464,7 @@ open class Mangadex(override val lang: String, private val internalLang: String,
chapterName.add(chapterJson.get("title").string) chapterName.add(chapterJson.get("title").string)
} }
//if volume, chapter and title is empty its a oneshot //if volume, chapter and title is empty its a oneshot
if(chapterName.isEmpty()){ if (chapterName.isEmpty()) {
chapterName.add("Oneshot") chapterName.add("Oneshot")
} }
if ((status == 2 || status == 3) && doesFinalChapterExist(finalChapterNumber, chapterJson)) { if ((status == 2 || status == 3) && doesFinalChapterExist(finalChapterNumber, chapterJson)) {
@ -611,17 +615,17 @@ open class Mangadex(override val lang: String, private val internalLang: String,
private class R18 : Filter.Select<String>("R18+", arrayOf("Default", "Show all", "Show only", "Show none")) private class R18 : Filter.Select<String>("R18+", arrayOf("Default", "Show all", "Show only", "Show none"))
private fun getDemographic() = listOf( private fun getDemographic() = listOf(
Tag("1", "Shounen"), Tag("1", "Shounen"),
Tag("2", "Shoujo"), Tag("2", "Shoujo"),
Tag("3", "Seinen"), Tag("3", "Seinen"),
Tag("4", "Josei") Tag("4", "Josei")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private fun getPublicationStatus() = listOf( private fun getPublicationStatus() = listOf(
Tag("1", "Ongoing"), Tag("1", "Ongoing"),
Tag("2", "Completed"), Tag("2", "Completed"),
Tag("3", "Cancelled"), Tag("3", "Cancelled"),
Tag("4", "Hiatus") Tag("4", "Hiatus")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private class ThemeList(themes: List<Tag>) : Filter.Group<Tag>("Themes", themes) private class ThemeList(themes: List<Tag>) : Filter.Group<Tag>("Themes", themes)
@ -630,115 +634,115 @@ open class Mangadex(override val lang: String, private val internalLang: String,
// default selection (Rating Descending) matches popularMangaRequest url // default selection (Rating Descending) matches popularMangaRequest url
class SortFilter : Filter.Sort("Sort", class SortFilter : Filter.Sort("Sort",
sortables.map { it.first }.toTypedArray(), sortables.map { it.first }.toTypedArray(),
Selection(3, false)) Selection(3, false))
private class OriginalLanguage : Filter.Select<String>("Original Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray()) private class OriginalLanguage : Filter.Select<String>("Original Language", SOURCE_LANG_LIST.map { it.first }.toTypedArray())
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
TextField("Author", "author"), TextField("Author", "author"),
TextField("Artist", "artist"), TextField("Artist", "artist"),
R18(), R18(),
SortFilter(), SortFilter(),
Demographic(getDemographic()), Demographic(getDemographic()),
PublicationStatus(getPublicationStatus()), PublicationStatus(getPublicationStatus()),
OriginalLanguage(), OriginalLanguage(),
ContentList(getContentList()), ContentList(getContentList()),
FormatList(getFormatList()), FormatList(getFormatList()),
GenreList(getGenreList()), GenreList(getGenreList()),
ThemeList(getThemeList()), ThemeList(getThemeList()),
TagInclusionMode(), TagInclusionMode(),
TagExclusionMode() TagExclusionMode()
) )
private fun getContentList() = listOf( private fun getContentList() = listOf(
Tag("9", "Ecchi"), Tag("9", "Ecchi"),
Tag("32", "Smut"), Tag("32", "Smut"),
Tag("49", "Gore"), Tag("49", "Gore"),
Tag("50", "Sexual Violence") Tag("50", "Sexual Violence")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private fun getFormatList() = listOf( private fun getFormatList() = listOf(
Tag("1", "4-koma"), Tag("1", "4-koma"),
Tag("4", "Award Winning"), Tag("4", "Award Winning"),
Tag("7", "Doujinshi"), Tag("7", "Doujinshi"),
Tag("21", "Oneshot"), Tag("21", "Oneshot"),
Tag("36", "Long Strip"), Tag("36", "Long Strip"),
Tag("42", "Adaptation"), Tag("42", "Adaptation"),
Tag("43", "Anthology"), Tag("43", "Anthology"),
Tag("44", "Web Comic"), Tag("44", "Web Comic"),
Tag("45", "Full Color"), Tag("45", "Full Color"),
Tag("46", "User Created"), Tag("46", "User Created"),
Tag("47", "Official Colored"), Tag("47", "Official Colored"),
Tag("48", "Fan Colored") Tag("48", "Fan Colored")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Tag("2", "Action"), Tag("2", "Action"),
Tag("3", "Adventure"), Tag("3", "Adventure"),
Tag("5", "Comedy"), Tag("5", "Comedy"),
Tag("8", "Drama"), Tag("8", "Drama"),
Tag("10", "Fantasy"), Tag("10", "Fantasy"),
Tag("13", "Historical"), Tag("13", "Historical"),
Tag("14", "Horror"), Tag("14", "Horror"),
Tag("17", "Mecha"), Tag("17", "Mecha"),
Tag("18", "Medical"), Tag("18", "Medical"),
Tag("20", "Mystery"), Tag("20", "Mystery"),
Tag("22", "Psychological"), Tag("22", "Psychological"),
Tag("23", "Romance"), Tag("23", "Romance"),
Tag("25", "Sci-Fi"), Tag("25", "Sci-Fi"),
Tag("28", "Shoujo Ai"), Tag("28", "Shoujo Ai"),
Tag("30", "Shounen Ai"), Tag("30", "Shounen Ai"),
Tag("31", "Slice of Life"), Tag("31", "Slice of Life"),
Tag("33", "Sports"), Tag("33", "Sports"),
Tag("35", "Tragedy"), Tag("35", "Tragedy"),
Tag("37", "Yaoi"), Tag("37", "Yaoi"),
Tag("38", "Yuri"), Tag("38", "Yuri"),
Tag("41", "Isekai"), Tag("41", "Isekai"),
Tag("51", "Crime"), Tag("51", "Crime"),
Tag("52", "Magical Girls"), Tag("52", "Magical Girls"),
Tag("53", "Philosophical"), Tag("53", "Philosophical"),
Tag("54", "Superhero"), Tag("54", "Superhero"),
Tag("55", "Thriller"), Tag("55", "Thriller"),
Tag("56", "Wuxia") Tag("56", "Wuxia")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private fun getThemeList() = listOf( private fun getThemeList() = listOf(
Tag("6", "Cooking"), Tag("6", "Cooking"),
Tag("11", "Gyaru"), Tag("11", "Gyaru"),
Tag("12", "Harem"), Tag("12", "Harem"),
Tag("16", "Martial Arts"), Tag("16", "Martial Arts"),
Tag("19", "Music"), Tag("19", "Music"),
Tag("24", "School Life"), Tag("24", "School Life"),
Tag("34", "Supernatural"), Tag("34", "Supernatural"),
Tag("40", "Video Games"), Tag("40", "Video Games"),
Tag("57", "Aliens"), Tag("57", "Aliens"),
Tag("58", "Animals"), Tag("58", "Animals"),
Tag("59", "Crossdressing"), Tag("59", "Crossdressing"),
Tag("60", "Demons"), Tag("60", "Demons"),
Tag("61", "Delinquents"), Tag("61", "Delinquents"),
Tag("62", "Genderswap"), Tag("62", "Genderswap"),
Tag("63", "Ghosts"), Tag("63", "Ghosts"),
Tag("64", "Monster Girls"), Tag("64", "Monster Girls"),
Tag("65", "Loli"), Tag("65", "Loli"),
Tag("66", "Magic"), Tag("66", "Magic"),
Tag("67", "Military"), Tag("67", "Military"),
Tag("68", "Monsters"), Tag("68", "Monsters"),
Tag("69", "Ninja"), Tag("69", "Ninja"),
Tag("70", "Office Workers"), Tag("70", "Office Workers"),
Tag("71", "Police"), Tag("71", "Police"),
Tag("72", "Post-Apocalyptic"), Tag("72", "Post-Apocalyptic"),
Tag("73", "Reincarnation"), Tag("73", "Reincarnation"),
Tag("74", "Reverse Harem"), Tag("74", "Reverse Harem"),
Tag("75", "Samurai"), Tag("75", "Samurai"),
Tag("76", "Shota"), Tag("76", "Shota"),
Tag("77", "Survival"), Tag("77", "Survival"),
Tag("78", "Time Travel"), Tag("78", "Time Travel"),
Tag("79", "Vampires"), Tag("79", "Vampires"),
Tag("80", "Traditional Games"), Tag("80", "Traditional Games"),
Tag("81", "Virtual Reality"), Tag("81", "Virtual Reality"),
Tag("82", "Zombies"), Tag("82", "Zombies"),
Tag("83", "Incest") Tag("83", "Incest")
).sortedWith(compareBy { it.name }) ).sortedWith(compareBy { it.name })
private val GENRES = (getContentList() + getFormatList() + getGenreList() + getThemeList()).map { it.id to it.name }.toMap() private val GENRES = (getContentList() + getFormatList() + getGenreList() + getThemeList()).map { it.id to it.name }.toMap()
@ -768,26 +772,26 @@ open class Mangadex(override val lang: String, private val internalLang: String,
const val PREFIX_ID_SEARCH = "id:" const val PREFIX_ID_SEARCH = "id:"
private val sortables = listOf( private val sortables = listOf(
Triple("Update date", 0, 1), Triple("Update date", 0, 1),
Triple("Alphabetically", 2, 3), Triple("Alphabetically", 2, 3),
Triple("Number of comments", 4, 5), Triple("Number of comments", 4, 5),
Triple("Rating", 6, 7), Triple("Rating", 6, 7),
Triple("Views", 8, 9), Triple("Views", 8, 9),
Triple("Follows", 10, 11)) Triple("Follows", 10, 11))
private val SOURCE_LANG_LIST = listOf( private val SOURCE_LANG_LIST = listOf(
Pair("All", "0"), Pair("All", "0"),
Pair("Japanese", "2"), Pair("Japanese", "2"),
Pair("English", "1"), Pair("English", "1"),
Pair("Polish", "3"), Pair("Polish", "3"),
Pair("German", "8"), Pair("German", "8"),
Pair("French", "10"), Pair("French", "10"),
Pair("Vietnamese", "12"), Pair("Vietnamese", "12"),
Pair("Chinese", "21"), Pair("Chinese", "21"),
Pair("Indonesian", "27"), Pair("Indonesian", "27"),
Pair("Korean", "28"), Pair("Korean", "28"),
Pair("Spanish (LATAM)", "29"), Pair("Spanish (LATAM)", "29"),
Pair("Thai", "32"), Pair("Thai", "32"),
Pair("Filipino", "34")) Pair("Filipino", "34"))
} }
} }

View File

@ -3,7 +3,83 @@ package eu.kanade.tachiyomi.extension.all.mangadex
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
class MangadexFactory : SourceFactory { class MangadexFactory : SourceFactory {
override fun createSources(): List<Source> = getAllMangaDexLanguages() override fun createSources(): List<Source> = listOf(
MangaDexEnglish(),
MangaDexPolish(),
MangaDexItalian(),
MangaDexRussian(),
MangaDexGerman(),
MangaDexFrench(),
MangaDexVietnamese(),
MangaDexSpanishSpain(),
MangaDexSpanishLTAM(),
MangaDexCatalan(),
MangaDexPortuguesePortugal(),
MangaDexPortugueseBrazil(),
MangaDexSwedish(),
MangaDexTurkish(),
MangaDexIndonesian(),
MangaDexHungarian(),
MangaDexBulgarian(),
MangaDexFilipino(),
MangaDexDutch(),
MangaDexArabic(),
MangaDexChineseSimp(),
MangaDexChineseTrad(),
MangaDexThai(),
MangaDexBengali(),
MangaDexBurmese(),
MangaDexCzech(),
MangaDexDanish(),
MangaDexFinnish(),
MangaDexGreek(),
MangaDexJapanese(),
MangaDexKorean(),
MangaDexLithuanian(),
MangaDexMalay(),
MangaDexMongolian(),
MangaDexPersian(),
MangaDexRomanian(),
MangaDexSerboCroatian(),
MangaDexUkrainian()
)
} }
class MangaDexPolish : Mangadex("pl", "pl", 3)
class MangaDexItalian : Mangadex("it", "it", 6)
class MangaDexRussian : Mangadex("ru", "ru", 7)
class MangaDexGerman : Mangadex("de", "de", 8)
class MangaDexFrench : Mangadex("fr", "fr", 10)
class MangaDexVietnamese : Mangadex("vi", "vn", 12)
class MangaDexSpanishSpain : Mangadex("es", "es", 15)
class MangaDexSpanishLTAM : Mangadex("es-419", "mx", 29)
class MangaDexCatalan : Mangadex("ca", "ct", 33)
class MangaDexPortuguesePortugal : Mangadex("pt", "pt", 17)
class MangaDexPortugueseBrazil : Mangadex("pt-BR", "br", 16)
class MangaDexSwedish : Mangadex("sv", "se", 18)
class MangaDexTurkish : Mangadex("tr", "tr", 26)
class MangaDexIndonesian : Mangadex("id", "id", 27)
class MangaDexHungarian : Mangadex("hu", "hu", 9)
class MangaDexBulgarian : Mangadex("bg", "bg", 14)
class MangaDexFilipino : Mangadex("fil", "ph", 34)
class MangaDexDutch : Mangadex("nl", "nl", 5)
class MangaDexArabic : Mangadex("ar", "sa", 19)
class MangaDexChineseSimp : Mangadex("zh-Hans", "cn", 21)
class MangaDexChineseTrad : Mangadex("zh-Hant", "hk", 35)
class MangaDexThai : Mangadex("th", "th", 32)
class MangaDexBengali : Mangadex("bn", "bd", 22)
class MangaDexBurmese : Mangadex("my", "mm", 37)
class MangaDexCzech : Mangadex("cs", "cz", 24)
class MangaDexDanish : Mangadex("da", "dk", 20)
class MangaDexFinnish : Mangadex("fi", "fi", 11)
class MangaDexGreek : Mangadex("el", "gr", 13)
class MangaDexJapanese : Mangadex("ja", "jp", 2)
class MangaDexKorean : Mangadex("ko", "kr", 28)
class MangaDexLithuanian : Mangadex("lt", "lt", 38)
class MangaDexMalay : Mangadex("ms", "my", 31)
class MangaDexMongolian : Mangadex("mn", "mn", 25)
class MangaDexPersian : Mangadex("fa", "ir", 30)
class MangaDexRomanian : Mangadex("ro", "ro", 23)
class MangaDexSerboCroatian : Mangadex("sh", "rs", 4)
class MangaDexUkrainian : Mangadex("uk", "ua", 36)

View File

@ -1,84 +0,0 @@
package eu.kanade.tachiyomi.extension.all.mangadex
/**
* Mangadex languages
*/
class MangaDexPolish : Mangadex("pl", "pl", 3)
class MangaDexItalian : Mangadex("it", "it", 6)
class MangaDexRussian : Mangadex("ru", "ru", 7)
class MangaDexGerman : Mangadex("de", "de", 8)
class MangaDexFrench : Mangadex("fr", "fr", 10)
class MangaDexVietnamese : Mangadex("vi", "vn", 12)
class MangaDexSpanishSpain : Mangadex("es", "es", 15)
class MangaDexSpanishLTAM : Mangadex("es-419", "mx", 29)
class MangaDexCatalan : Mangadex("ca", "ct", 33)
class MangaDexPortuguesePortugal : Mangadex("pt", "pt", 17)
class MangaDexPortugueseBrazil : Mangadex("pt-BR", "br", 16)
class MangaDexSwedish : Mangadex("sv", "se", 18)
class MangaDexTurkish : Mangadex("tr", "tr", 26)
class MangaDexIndonesian : Mangadex("id", "id", 27)
class MangaDexHungarian : Mangadex("hu", "hu", 9)
class MangaDexBulgarian : Mangadex("bg", "bg", 14)
class MangaDexFilipino : Mangadex("fil", "ph", 34)
class MangaDexDutch : Mangadex("nl", "nl", 5)
class MangaDexArabic : Mangadex("ar", "sa", 19)
class MangaDexChineseSimp : Mangadex("zh-Hans", "cn", 21)
class MangaDexChineseTrad : Mangadex("zh-Hant", "hk", 35)
class MangaDexThai : Mangadex("th", "th", 32)
class MangaDexBengali : Mangadex("bn", "bd", 22)
class MangaDexBurmese : Mangadex("my", "mm", 37)
class MangaDexCzech : Mangadex("cs", "cz", 24)
class MangaDexDanish : Mangadex("da", "dk", 20)
class MangaDexFinnish : Mangadex("fi", "fi", 11)
class MangaDexGreek : Mangadex("el", "gr", 13)
class MangaDexJapanese : Mangadex("ja", "jp", 2)
class MangaDexKorean : Mangadex("ko", "kr", 28)
class MangaDexLithuanian : Mangadex("lt", "lt", 38)
class MangaDexMalay : Mangadex("ms", "my", 31)
class MangaDexMongolian : Mangadex("mn", "mn", 25)
class MangaDexPersian : Mangadex("fa", "ir", 30)
class MangaDexRomanian : Mangadex("ro", "ro", 23)
class MangaDexSerboCroatian : Mangadex("sh", "rs", 4)
class MangaDexUkrainian : Mangadex("uk", "ua", 36)
fun getAllMangaDexLanguages() = listOf(
MangaDexEnglish(),
MangaDexPolish(),
MangaDexItalian(),
MangaDexRussian(),
MangaDexGerman(),
MangaDexFrench(),
MangaDexVietnamese(),
MangaDexSpanishSpain(),
MangaDexSpanishLTAM(),
MangaDexCatalan(),
MangaDexPortuguesePortugal(),
MangaDexPortugueseBrazil(),
MangaDexSwedish(),
MangaDexTurkish(),
MangaDexIndonesian(),
MangaDexHungarian(),
MangaDexBulgarian(),
MangaDexFilipino(),
MangaDexDutch(),
MangaDexArabic(),
MangaDexChineseSimp(),
MangaDexChineseTrad(),
MangaDexThai(),
MangaDexBengali(),
MangaDexBurmese(),
MangaDexCzech(),
MangaDexDanish(),
MangaDexFinnish(),
MangaDexGreek(),
MangaDexJapanese(),
MangaDexKorean(),
MangaDexLithuanian(),
MangaDexMalay(),
MangaDexMongolian(),
MangaDexPersian(),
MangaDexRomanian(),
MangaDexSerboCroatian(),
MangaDexUkrainian()
)

View File

@ -5,6 +5,7 @@ import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import kotlin.system.exitProcess
/** /**
* Springboard that accepts https://mangadex.com/title/xxx intents and redirects them to * Springboard that accepts https://mangadex.com/title/xxx intents and redirects them to
@ -38,7 +39,7 @@ class MangadexUrlActivity : Activity() {
} }
finish() finish()
System.exit(0) exitProcess(0)
} }
} }

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: MangAdventure' appName = 'Tachiyomi: MangAdventure'
pkgNameSuffix = 'all.mangadventure' pkgNameSuffix = 'all.mangadventure'
extClass = '.MangAdventureFactory' extClass = '.MangAdventureFactory'
extVersionCode = 3 extVersionCode = 4
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -26,7 +26,7 @@ import java.util.Locale
* *
* @property categories the available manga categories of the site. * @property categories the available manga categories of the site.
*/ */
open class MangAdventure( abstract class MangAdventure(
override val name: String, override val name: String,
override val baseUrl: String, override val baseUrl: String,
val categories: Array<String> = DEFAULT_CATEGORIES val categories: Array<String> = DEFAULT_CATEGORIES

View File

@ -31,7 +31,7 @@ class MangAdventureActivity : Activity() {
} }
private fun logInvalidIntent(intent: Intent) { private fun logInvalidIntent(intent: Intent) {
val msg = "Failed to parse URI from intent" Log.e("MangAdventureActivity", "Failed to parse URI from intent: $intent")
Log.e("MangAdventureActivity", "$msg $intent")
} }
} }

View File

@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.extension.all.mangadventure
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
/** [MangAdventure] source factory. */
class MangAdventureFactory : SourceFactory { class MangAdventureFactory : SourceFactory {
override fun createSources() = listOf( override fun createSources() = listOf(
ArcRelight() ArcRelight()
@ -11,22 +10,22 @@ class MangAdventureFactory : SourceFactory {
/** Arc-Relight source. */ /** Arc-Relight source. */
class ArcRelight : MangAdventure( class ArcRelight : MangAdventure(
"Arc-Relight", "https://arc-relight.com", arrayOf( "Arc-Relight", "https://arc-relight.com", arrayOf(
"4-Koma", "4-Koma",
"Chaos;Head", "Chaos;Head",
"Collection", "Collection",
"Comedy", "Comedy",
"Drama", "Drama",
"Jubilee", "Jubilee",
"Mystery", "Mystery",
"Psychological", "Psychological",
"Robotics;Notes", "Robotics;Notes",
"Romance", "Romance",
"Sci-Fi", "Sci-Fi",
"Seinen", "Seinen",
"Shounen", "Shounen",
"Steins;Gate", "Steins;Gate",
"Supernatural", "Supernatural",
"Tragedy" "Tragedy"
) )
) )
} }

View File

@ -5,7 +5,7 @@ ext {
appName = 'Tachiyomi: Mangatensei' appName = 'Tachiyomi: Mangatensei'
pkgNameSuffix = 'all.mangatensei' pkgNameSuffix = 'all.mangatensei'
extClass = '.MangatenseiFactory' extClass = '.MangatenseiFactory'
extVersionCode = 2 extVersionCode = 3
libVersion = '1.2' libVersion = '1.2'
} }

View File

@ -1,7 +1,11 @@
package eu.kanade.tachiyomi.extension.all.mangatensei package eu.kanade.tachiyomi.extension.all.mangatensei
import eu.kanade.tachiyomi.network.GET import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.* 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 eu.kanade.tachiyomi.source.online.ParsedHttpSource
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
@ -9,7 +13,7 @@ import okhttp3.Request
import org.json.JSONObject import org.json.JSONObject
import org.jsoup.nodes.Document import org.jsoup.nodes.Document
import org.jsoup.nodes.Element import org.jsoup.nodes.Element
import java.util.* import java.util.Calendar
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
open class Mangatensei(override val lang: String, private val Mtlang: String) : ParsedHttpSource() { open class Mangatensei(override val lang: String, private val Mtlang: String) : ParsedHttpSource() {
@ -25,7 +29,7 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
.readTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS)
.build() .build()
override fun latestUpdatesRequest(page: Int): Request { override fun latestUpdatesRequest(page: Int): Request {
// The site redirects page 1 -> url-without-page so we do this redirect early for optimization // The site redirects page 1 -> url-without-page so we do this redirect early for optimization
val builtUrl = "$baseUrl/browse?langs=$Mtlang&sort=update&page=$page" val builtUrl = "$baseUrl/browse?langs=$Mtlang&sort=update&page=$page"
return GET(builtUrl) return GET(builtUrl)
@ -45,7 +49,7 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
override fun latestUpdatesNextPageSelector() = "div.browse-pager:contains(order) a.page-link:contains(»)" override fun latestUpdatesNextPageSelector() = "div.browse-pager:contains(order) a.page-link:contains(»)"
override fun popularMangaRequest(page: Int): Request { override fun popularMangaRequest(page: Int): Request {
val builtUrl = "$baseUrl/browse?langs=$Mtlang&sort=views_w&page=$page" val builtUrl = "$baseUrl/browse?langs=$Mtlang&sort=views_w&page=$page"
return GET(builtUrl) return GET(builtUrl)
} }
@ -64,7 +68,7 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
filters.forEach { filter -> filters.forEach { filter ->
when (filter) { when (filter) {
is AuthorFilter -> { is AuthorFilter -> {
author = filter.state author = filter.state
} }
is StyleFilter -> { is StyleFilter -> {
val styleToInclude = mutableListOf<String>() val styleToInclude = mutableListOf<String>()
@ -96,7 +100,7 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
Filter.TriState.STATE_EXCLUDE -> "0" Filter.TriState.STATE_EXCLUDE -> "0"
else -> "" else -> ""
} }
if(status.isNotEmpty()) { if (status.isNotEmpty()) {
url.addQueryParameter("status", status) url.addQueryParameter("status", status)
} }
} }
@ -113,23 +117,23 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
} }
} }
is StarFilter -> { is StarFilter -> {
if(filter.state != 0) { if (filter.state != 0) {
url.addQueryParameter("stars", filter.toUriPart()) url.addQueryParameter("stars", filter.toUriPart())
} }
} }
is ChapterFilter -> { is ChapterFilter -> {
if(filter.state != 0) { if (filter.state != 0) {
url.addQueryParameter("chapters", filter.toUriPart()) url.addQueryParameter("chapters", filter.toUriPart())
} }
} }
is SortBy -> { is SortBy -> {
if(filter.state != 0) { if (filter.state != 0) {
url.addQueryParameter("sort", filter.toUriPart()) url.addQueryParameter("sort", filter.toUriPart())
} }
} }
} }
} }
return if(query.isNotBlank() || author!!.isNotBlank()) { return if (query.isNotBlank() || author!!.isNotBlank()) {
GET("$baseUrl/search?q=$query&a=$author") GET("$baseUrl/search?q=$query&a=$author")
} else GET(url.build().toString(), headers) } else GET(url.build().toString(), headers)
} }
@ -257,7 +261,7 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
val imgJson = JSONObject(script) val imgJson = JSONObject(script)
val imgNames = imgJson.names() val imgNames = imgJson.names()
for( i in 0 until imgNames.length()) { for (i in 0 until imgNames.length()) {
val imgKey = imgNames.getString(i) val imgKey = imgNames.getString(i)
val imgUrl = imgJson.getString(imgKey) val imgUrl = imgJson.getString(imgKey)
pages.add(Page(i, "", imgUrl)) pages.add(Page(i, "", imgUrl))
@ -275,111 +279,111 @@ open class Mangatensei(override val lang: String, private val Mtlang: String) :
private class StatusFilter : Filter.TriState("Completed") private class StatusFilter : Filter.TriState("Completed")
private class StarFilter : UriPartFilter("Stars", arrayOf( private class StarFilter : UriPartFilter("Stars", arrayOf(
Pair("<select>", ""), Pair("<select>", ""),
Pair("5 Stars", "5"), Pair("5 Stars", "5"),
Pair("4 Stars", "4"), Pair("4 Stars", "4"),
Pair("3 Stars", "3"), Pair("3 Stars", "3"),
Pair("2 Stars", "2"), Pair("2 Stars", "2"),
Pair("1 Stars", "1") Pair("1 Stars", "1")
)) ))
private class ChapterFilter : UriPartFilter("Chapters", arrayOf( private class ChapterFilter : UriPartFilter("Chapters", arrayOf(
Pair("<select>", ""), Pair("<select>", ""),
Pair("1 ~ 9", "1-9"), Pair("1 ~ 9", "1-9"),
Pair("10 ~ 29", "10-29"), Pair("10 ~ 29", "10-29"),
Pair("30 ~ 99", "30-99"), Pair("30 ~ 99", "30-99"),
Pair("100 ~ 199", "100-199"), Pair("100 ~ 199", "100-199"),
Pair("200+", "200"), Pair("200+", "200"),
Pair("100+", "100"), Pair("100+", "100"),
Pair("50+", "50"), Pair("50+", "50"),
Pair("10+", "10"), Pair("10+", "10"),
Pair("1+", "1") Pair("1+", "1")
)) ))
private class SortBy : UriPartFilter("Sorts By", arrayOf( private class SortBy : UriPartFilter("Sorts By", arrayOf(
Pair("<select>", ""), Pair("<select>", ""),
Pair("Totally", "views_t"), Pair("Totally", "views_t"),
Pair("365 days", "views_y"), Pair("365 days", "views_y"),
Pair("30 days", "views_m"), Pair("30 days", "views_m"),
Pair("7 days", "views_w"), Pair("7 days", "views_w"),
Pair("24 hours", "views_d"), Pair("24 hours", "views_d"),
Pair("60 minutes", "views_h"), Pair("60 minutes", "views_h"),
Pair("A-Z", "title"), Pair("A-Z", "title"),
Pair("Update time", "update"), Pair("Update time", "update"),
Pair("Add time", "create") Pair("Add time", "create")
)) ))
override fun getFilterList() = FilterList( override fun getFilterList() = FilterList(
Filter.Header("NOTE: Ignored if using text search!"), Filter.Header("NOTE: Ignored if using text search!"),
AuthorFilter(), AuthorFilter(),
Filter.Separator(), Filter.Separator(),
StatusFilter(), StatusFilter(),
StarFilter(), StarFilter(),
ChapterFilter(), ChapterFilter(),
SortBy(), SortBy(),
StyleFilter(getStyleList()), StyleFilter(getStyleList()),
DemographicFilter(getDemographicList()), DemographicFilter(getDemographicList()),
GenreFilter(getGenreList()) GenreFilter(getGenreList())
) )
private fun getStyleList() = listOf( private fun getStyleList() = listOf(
Tag("manga"), Tag("manga"),
Tag("manhwa"), Tag("manhwa"),
Tag("manhua"), Tag("manhua"),
Tag("webtoon") Tag("webtoon")
) )
private fun getDemographicList() = listOf( private fun getDemographicList() = listOf(
Tag("josei"), Tag("josei"),
Tag("seinen"), Tag("seinen"),
Tag("shoujo"), Tag("shoujo"),
Tag("shoujo ai"), Tag("shoujo ai"),
Tag("shounen"), Tag("shounen"),
Tag("shounen ai"), Tag("shounen ai"),
Tag("yaoi"), Tag("yaoi"),
Tag("yuri") Tag("yuri")
) )
private fun getGenreList() = listOf( private fun getGenreList() = listOf(
Tag("action"), Tag("action"),
Tag("adventure"), Tag("adventure"),
Tag("award winning"), Tag("award winning"),
Tag("comedy"), Tag("comedy"),
Tag("cooking"), Tag("cooking"),
Tag("demons"), Tag("demons"),
Tag("doujinshi"), Tag("doujinshi"),
Tag("drama"), Tag("drama"),
Tag("ecchi"), Tag("ecchi"),
Tag("fantasy"), Tag("fantasy"),
Tag("gender bender"), Tag("gender bender"),
Tag("harem"), Tag("harem"),
Tag("historical"), Tag("historical"),
Tag("horror"), Tag("horror"),
Tag("isekai"), Tag("isekai"),
Tag("magic"), Tag("magic"),
Tag("martial arts"), Tag("martial arts"),
Tag("mature"), Tag("mature"),
Tag("mecha"), Tag("mecha"),
Tag("medical"), Tag("medical"),
Tag("military"), Tag("military"),
Tag("music"), Tag("music"),
Tag("mystery"), Tag("mystery"),
Tag("one shot"), Tag("one shot"),
Tag("psychological"), Tag("psychological"),
Tag("reverse harem"), Tag("reverse harem"),
Tag("romance"), Tag("romance"),
Tag("school life"), Tag("school life"),
Tag("sci fi"), Tag("sci fi"),
Tag("shotacon"), Tag("shotacon"),
Tag("slice of life"), Tag("slice of life"),
Tag("smut"), Tag("smut"),
Tag("sports"), Tag("sports"),
Tag("super power"), Tag("super power"),
Tag("supernatural"), Tag("supernatural"),
Tag("tragedy"), Tag("tragedy"),
Tag("uncategorized"), Tag("uncategorized"),
Tag("vampire"), Tag("vampire"),
Tag("youkai") Tag("youkai")
) )
private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) : private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :

View File

@ -3,7 +3,54 @@ package eu.kanade.tachiyomi.extension.all.mangatensei
import eu.kanade.tachiyomi.source.Source import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.source.SourceFactory import eu.kanade.tachiyomi.source.SourceFactory
class MangatenseiFactory : SourceFactory { class MangatenseiFactory : SourceFactory {
override fun createSources(): List<Source> = getAllMangatenseiLanguages() override fun createSources(): List<Source> = listOf(
} MangatenseiArabic(),
MangatenseiBrazilian(),
MangatenseiCzech(),
MangatenseiDanish(),
MangatenseiDutch(),
MangatenseiEnglish(),
MangatenseiFilipino(),
MangatenseiFrench(),
MangatenseiGerman(),
MangatenseiGreek(),
MangatenseiHebrew(),
MangatenseiHungarian(),
MangatenseiIndonesian(),
MangatenseiItalian(),
MangatenseiMalay(),
MangatenseiPolish(),
MangatenseiPortuguese(),
MangatenseiRomanian(),
MangatenseiRussian(),
MangatenseiSpanish(),
MangatenseiThai(),
MangatenseiTurkish(),
MangatenseiVietnamese()
)
}
class MangatenseiArabic : Mangatensei("ar", "arabic")
class MangatenseiBrazilian : Mangatensei("pt-BR", "brazilian")
class MangatenseiCzech : Mangatensei("cs", "czech")
class MangatenseiDanish : Mangatensei("da", "danish")
class MangatenseiDutch : Mangatensei("nl", "dutch")
class MangatenseiEnglish : Mangatensei("en", "english")
class MangatenseiFilipino : Mangatensei("fil", "filipino")
class MangatenseiFrench : Mangatensei("fr", "french")
class MangatenseiGerman : Mangatensei("de", "german")
class MangatenseiGreek : Mangatensei("el", "greek")
class MangatenseiHebrew : Mangatensei("iw", "hebrew")
class MangatenseiHungarian : Mangatensei("hu", "hungarian")
class MangatenseiIndonesian : Mangatensei("id", "indonesian")
class MangatenseiItalian : Mangatensei("it", "italian")
class MangatenseiMalay : Mangatensei("ms", "malay")
class MangatenseiPolish : Mangatensei("pl", "polish")
class MangatenseiPortuguese : Mangatensei("pt", "portuguese")
class MangatenseiRomanian : Mangatensei("ro", "romanian")
class MangatenseiRussian : Mangatensei("ru", "russian")
class MangatenseiSpanish : Mangatensei("es", "spanish")
class MangatenseiThai : Mangatensei("th", "thai")
class MangatenseiTurkish : Mangatensei("tr", "turkish")
class MangatenseiVietnamese : Mangatensei("vi", "vietnamese")

View File

@ -1,55 +0,0 @@
package eu.kanade.tachiyomi.extension.all.mangatensei
/**
* Mangatensei languages
*/
class MangatenseiArabic : Mangatensei("ar", "arabic")
class MangatenseiBrazilian : Mangatensei("pt-BR", "brazilian")
class MangatenseiCzech : Mangatensei("cs", "czech")
class MangatenseiDanish : Mangatensei("da", "danish")
class MangatenseiDutch : Mangatensei("nl", "dutch")
class MangatenseiEnglish : Mangatensei("en", "english")
class MangatenseiFilipino : Mangatensei("fil", "filipino")
class MangatenseiFrench : Mangatensei("fr", "french")
class MangatenseiGerman : Mangatensei("de", "german")
class MangatenseiGreek : Mangatensei("el", "greek")
class MangatenseiHebrew : Mangatensei("iw", "hebrew")
class MangatenseiHungarian : Mangatensei("hu", "hungarian")
class MangatenseiIndonesian : Mangatensei("id", "indonesian")
class MangatenseiItalian : Mangatensei("it", "italian")
class MangatenseiMalay : Mangatensei("ms", "malay")
class MangatenseiPolish : Mangatensei("pl", "polish")
class MangatenseiPortuguese : Mangatensei("pt", "portuguese")
class MangatenseiRomanian : Mangatensei("ro", "romanian")
class MangatenseiRussian : Mangatensei("ru", "russian")
class MangatenseiSpanish : Mangatensei("es", "spanish")
class MangatenseiThai : Mangatensei("th", "thai")
class MangatenseiTurkish : Mangatensei("tr", "turkish")
class MangatenseiVietnamese : Mangatensei("vi", "vietnamese")
fun getAllMangatenseiLanguages() = listOf(
MangatenseiArabic(),
MangatenseiBrazilian(),
MangatenseiCzech(),
MangatenseiDanish(),
MangatenseiDutch(),
MangatenseiEnglish(),
MangatenseiFilipino(),
MangatenseiFrench(),
MangatenseiGerman(),
MangatenseiGreek(),
MangatenseiHebrew(),
MangatenseiHungarian(),
MangatenseiIndonesian(),
MangatenseiItalian(),
MangatenseiMalay(),
MangatenseiPolish(),
MangatenseiPortuguese(),
MangatenseiRomanian(),
MangatenseiRussian(),
MangatenseiSpanish(),
MangatenseiThai(),
MangatenseiTurkish(),
MangatenseiVietnamese()
)