diff --git a/lib-multisrc/mangabox/build.gradle.kts b/lib-multisrc/mangabox/build.gradle.kts index b45873b53..ede652be5 100644 --- a/lib-multisrc/mangabox/build.gradle.kts +++ b/lib-multisrc/mangabox/build.gradle.kts @@ -2,4 +2,4 @@ plugins { id("lib-multisrc") } -baseVersionCode = 5 +baseVersionCode = 6 diff --git a/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt index df3f5165b..c070e7fbd 100644 --- a/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt +++ b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBox.kt @@ -1,7 +1,11 @@ package eu.kanade.tachiyomi.multisrc.mangabox import android.annotation.SuppressLint +import android.content.SharedPreferences +import androidx.preference.ListPreference +import androidx.preference.PreferenceScreen import eu.kanade.tachiyomi.network.GET +import eu.kanade.tachiyomi.source.ConfigurableSource import eu.kanade.tachiyomi.source.model.Filter import eu.kanade.tachiyomi.source.model.FilterList import eu.kanade.tachiyomi.source.model.Page @@ -9,42 +13,144 @@ import eu.kanade.tachiyomi.source.model.SChapter import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.online.ParsedHttpSource import eu.kanade.tachiyomi.util.asJsoup +import keiyoushi.utils.getPreferencesLazy +import keiyoushi.utils.tryParse import okhttp3.Headers +import okhttp3.HttpUrl import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Interceptor import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.Response +import okio.IOException import org.jsoup.nodes.Document import org.jsoup.nodes.Element -import java.text.ParseException import java.text.SimpleDateFormat -import java.util.Calendar import java.util.Locale -import java.util.concurrent.TimeUnit +import java.util.TimeZone +import java.util.regex.Pattern -// Based off of Mangakakalot 1.2.8 abstract class MangaBox( override val name: String, - override val baseUrl: String, + private val mirrorEntries: Array<String>, override val lang: String, - private val dateformat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yy", Locale.ENGLISH), -) : ParsedHttpSource() { + private val dateFormat: SimpleDateFormat = SimpleDateFormat( + "MMM-dd-yyyy HH:mm", + Locale.ENGLISH, + ).apply { + timeZone = TimeZone.getTimeZone("UTC") + }, +) : ParsedHttpSource(), ConfigurableSource { override val supportsLatest = true + override val baseUrl: String get() = mirror + override val client: OkHttpClient = network.cloudflareClient.newBuilder() - .connectTimeout(15, TimeUnit.SECONDS) - .readTimeout(30, TimeUnit.SECONDS) + .addInterceptor(::useAltCdnInterceptor) .build() + private fun SharedPreferences.getMirrorPref(): String = + getString(PREF_USE_MIRROR, mirrorEntries[0])!! + + private val preferences: SharedPreferences by getPreferencesLazy { + // if current mirror is not in mirrorEntries, set default + if (getMirrorPref() !in mirrorEntries.map { "${URL_PREFIX}$it" }) { + edit().putString(PREF_USE_MIRROR, "${URL_PREFIX}${mirrorEntries[0]}").apply() + } + } + + private var mirror = "" + get() { + if (field.isNotEmpty()) { + return field + } + + field = preferences.getMirrorPref() + return field + } + + private val cdnSet = + MangaBoxLinkedCdnSet() // Stores all unique CDNs that the extension can use to retrieve chapter images + + private class MangaBoxFallBackTag // Custom empty class tag to use as an identifier that the specific request is fallback-able + + private fun HttpUrl.getBaseUrl(): String = + "${URL_PREFIX}${this.host}${ + when (this.port) { + 80, 443 -> "" + else -> ":${this.port}" + } + }" + + private fun useAltCdnInterceptor(chain: Interceptor.Chain): Response { + val request = chain.request() + val requestTag = request.tag(MangaBoxFallBackTag::class.java) + val originalResponse: Response? = try { + chain.proceed(request) + } catch (e: IOException) { + if (requestTag == null) { + throw e + } else { + null + } + } + + if (requestTag == null || originalResponse?.isSuccessful == true) { + requestTag?.let { + // Move working cdn to first so it gets priority during iteration + cdnSet.moveItemToFirst(request.url.getBaseUrl()) + } + + return originalResponse!! + } + + // Close the original response if it's not successful + originalResponse?.close() + + for (cdnUrl in cdnSet) { + var tryResponse: Response? = null + + try { + val newUrl = cdnUrl.toHttpUrl().newBuilder() + .encodedPath(request.url.encodedPath) + .fragment(request.url.fragment) + .build() + + // Create a new request with the updated URL + val newRequest = request.newBuilder() + .url(newUrl) + .build() + + // Proceed with the new request + tryResponse = chain.proceed(newRequest) + + // Check if the response is successful + if (tryResponse.isSuccessful) { + // Move working cdn to first so it gets priority during iteration + cdnSet.moveItemToFirst(newRequest.url.getBaseUrl()) + + return tryResponse + } + + tryResponse.close() + } catch (_: IOException) { + tryResponse?.close() + } + } + + // If all CDNs fail, throw an error + return throw IOException("All CDN attempts failed.") + } + override fun headersBuilder(): Headers.Builder = super.headersBuilder() - .add("Referer", baseUrl) // for covers + .add("Referer", "$baseUrl/") - open val popularUrlPath = "manga_list?type=topview&category=all&state=all&page=" + open val popularUrlPath = "manga-list/hot-manga?page=" - open val latestUrlPath = "manga_list?type=latest&category=all&state=all&page=" + open val latestUrlPath = "manga-list/latest-manga?page=" - open val simpleQueryPath = "search/" + open val simpleQueryPath = "search/story/" override fun popularMangaSelector() = "div.truyen-list > div.list-truyen-item-wrap" @@ -58,10 +164,11 @@ abstract class MangaBox( return GET("$baseUrl/$latestUrlPath$page", headers) } - protected fun mangaFromElement(element: Element, urlSelector: String = "h3 a"): SManga { + private fun mangaFromElement(element: Element, urlSelector: String = "h3 a"): SManga { return SManga.create().apply { element.select(urlSelector).first()!!.let { - url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain + url = it.attr("abs:href") + .substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain title = it.text() } thumbnail_url = element.select("img").first()!!.attr("abs:src") @@ -72,62 +179,47 @@ abstract class MangaBox( override fun latestUpdatesFromElement(element: Element): SManga = mangaFromElement(element) - override fun popularMangaNextPageSelector() = "div.group_page, div.group-page a:not([href]) + a:not(:contains(Last))" + override fun popularMangaNextPageSelector() = + "div.group_page, div.group-page a:not([href]) + a:not(:contains(Last))" override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector() override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) { - GET("$baseUrl/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers) + return if (query.isNotBlank()) { + val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder() + .addPathSegment(normalizeSearchQuery(query)) + .addQueryParameter("page", page.toString()) + .build() + + return GET(url, headers) } else { - val url = baseUrl.toHttpUrl().newBuilder() - if (getAdvancedGenreFilters().isNotEmpty()) { - url.addPathSegment("advanced_search") - url.addQueryParameter("page", page.toString()) - url.addQueryParameter("keyw", normalizeSearchQuery(query)) - var genreInclude = "" - var genreExclude = "" - filters.forEach { filter -> - when (filter) { - is KeywordFilter -> filter.toUriPart()?.let { url.addQueryParameter("keyt", it) } - is SortFilter -> url.addQueryParameter("orby", filter.toUriPart()) - is StatusFilter -> url.addQueryParameter("sts", filter.toUriPart()) - is AdvGenreFilter -> { - filter.state.forEach { if (it.isIncluded()) genreInclude += "_${it.id}" } - filter.state.forEach { if (it.isExcluded()) genreExclude += "_${it.id}" } - } - else -> {} - } - } - url.addQueryParameter("g_i", genreInclude) - url.addQueryParameter("g_e", genreExclude) - } else { - url.addPathSegment("manga_list") - url.addQueryParameter("page", page.toString()) - filters.forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) - is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) - is GenreFilter -> url.addQueryParameter("category", filter.toUriPart()) - else -> {} - } + val url = "$baseUrl/genre".toHttpUrl().newBuilder() + url.addQueryParameter("page", page.toString()) + filters.forEach { filter -> + when (filter) { + is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) + is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) + is GenreFilter -> url.addPathSegment(filter.toUriPart()!!) + else -> {} } } + GET(url.build(), headers) } } - override fun searchMangaSelector() = ".panel_story_list .story_item" + override fun searchMangaSelector() = ".panel_story_list .story_item, div.list-truyen-item-wrap" override fun searchMangaFromElement(element: Element) = mangaFromElement(element) - override fun searchMangaNextPageSelector() = "a.page_select + a:not(.page_last), a.page-select + a:not(.page-last)" + override fun searchMangaNextPageSelector() = + "a.page_select + a:not(.page_last), a.page-select + a:not(.page-last)" open val mangaDetailsMainSelector = "div.manga-info-top, div.panel-story-info" open val thumbnailSelector = "div.manga-info-pic img, span.info-image img" - open val descriptionSelector = "div#noidungm, div#panel-story-info-description" + open val descriptionSelector = "div#noidungm, div#panel-story-info-description, div#contentBox" override fun mangaDetailsRequest(manga: SManga): Request { if (manga.url.startsWith("http")) { @@ -146,11 +238,15 @@ abstract class MangaBox( return SManga.create().apply { document.select(mangaDetailsMainSelector).firstOrNull()?.let { infoElement -> title = infoElement.select("h1, h2").first()!!.text() - author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td a").eachText().joinToString() - status = parseStatus(infoElement.select("li:contains(status), td:containsOwn(status) + td").text()) + author = infoElement.select("li:contains(author) a, td:containsOwn(author) + td a") + .eachText().joinToString() + status = parseStatus( + infoElement.select("li:contains(status), td:containsOwn(status) + td").text(), + ) genre = infoElement.select("div.manga-info-top li:contains(genres)").firstOrNull() ?.select("a")?.joinToString { it.text() } // kakalot - ?: infoElement.select("td:containsOwn(genres) + td a").joinToString { it.text() } // nelo + ?: infoElement.select("td:containsOwn(genres) + td a") + .joinToString { it.text() } // nelo } ?: checkForRedirectMessage(document) description = document.select(descriptionSelector).firstOrNull()?.ownText() ?.replace("""^$title summary:\s""".toRegex(), "") @@ -199,44 +295,23 @@ abstract class MangaBox( protected open val alternateChapterDateSelector = String() - protected fun Element.selectDateFromElement(): Element { + private fun Element.selectDateFromElement(): Element { val defaultChapterDateSelector = "span" - return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select(alternateChapterDateSelector).last()!! + return this.select(defaultChapterDateSelector).lastOrNull() ?: this.select( + alternateChapterDateSelector, + ).last()!! } override fun chapterFromElement(element: Element): SChapter { return SChapter.create().apply { element.select("a").let { - url = it.attr("abs:href").substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain + url = it.attr("abs:href") + .substringAfter(baseUrl) // intentionally not using setUrlWithoutDomain name = it.text() scanlator = it.attr("abs:href").toHttpUrl().host // show where chapters are actually from } - date_upload = parseChapterDate(element.selectDateFromElement().text(), scanlator!!) ?: 0 - } - } - - private fun parseChapterDate(date: String, host: String): Long? { - return if ("ago" in date) { - val value = date.split(' ')[0].toIntOrNull() - val cal = Calendar.getInstance() - when { - value != null && "min" in date -> cal.apply { add(Calendar.MINUTE, -value) } - value != null && "hour" in date -> cal.apply { add(Calendar.HOUR_OF_DAY, -value) } - value != null && "day" in date -> cal.apply { add(Calendar.DATE, -value) } - else -> null - }?.timeInMillis - } else { - try { - if (host.contains("manganato", ignoreCase = true)) { - // Nelo's date format - SimpleDateFormat("MMM dd,yy", Locale.ENGLISH).parse(date) - } else { - dateformat.parse(date) - } - } catch (e: ParseException) { - null - }?.time + date_upload = dateFormat.tryParse(element.selectDateFromElement().attr("title")) } } @@ -247,26 +322,59 @@ abstract class MangaBox( return super.pageListRequest(chapter) } - open val pageListSelector = "div#vungdoc img, div.container-chapter-reader img" + private fun extractArray(scriptContent: String, arrayName: String): List<String> { + val pattern = Pattern.compile("$arrayName\\s*=\\s*\\[([^]]+)]") + val matcher = pattern.matcher(scriptContent) + val arrayValues = mutableListOf<String>() + + if (matcher.find()) { + val arrayContent = matcher.group(1) + val values = arrayContent?.split(",") + if (values != null) { + for (value in values) { + arrayValues.add( + value.trim() + .removeSurrounding("\"") + .replace("\\/", "/") + .removeSuffix("/"), + ) + } + } + } + + return arrayValues + } override fun pageListParse(document: Document): List<Page> { - return document.select(pageListSelector) - // filter out bad elements for mangakakalots - .filterNot { it.attr("src").endsWith("log") } - .mapIndexed { i, element -> - val url = element.attr("abs:src").let { src -> - if (src.startsWith("https://convert_image_digi.mgicdn.com")) { - "https://images.weserv.nl/?url=" + src.substringAfter("//") - } else { - src - } - } - Page(i, document.location(), url) + val element = document.select("head > script").lastOrNull() + ?: return emptyList() + val cdns = + extractArray(element.html(), "cdns") + extractArray(element.html(), "backupImage") + val chapterImages = extractArray(element.html(), "chapterImages") + + // Add all parsed cdns to set + cdnSet.addAll(cdns) + + return chapterImages.mapIndexed { i, imagePath -> + val parsedUrl = cdns[0].toHttpUrl().run { + newBuilder() + .encodedPath( + "/$imagePath".replace( + "//", + "/", + ), + ) // replace ensures that there's at least one trailing slash prefix + .build() + .toString() } + + Page(i, document.location(), parsedUrl) + } } override fun imageRequest(page: Page): Request { - return GET(page.imageUrl!!, headersBuilder().set("Referer", page.url).build()) + return GET(page.imageUrl!!, headers).newBuilder() + .tag(MangaBoxFallBackTag::class.java, MangaBoxFallBackTag()).build() } override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException() @@ -282,47 +390,27 @@ abstract class MangaBox( str = str.replace("[ùúụủũưừứựửữ]".toRegex(), "u") str = str.replace("[ỳýỵỷỹ]".toRegex(), "y") str = str.replace("đ".toRegex(), "d") - str = str.replace("""!|@|%|\^|\*|\(|\)|\+|=|<|>|\?|/|,|\.|:|;|'| |"|&|#|\[|]|~|-|$|_""".toRegex(), "_") + str = str.replace( + """!|@|%|\^|\*|\(|\)|\+|=|<|>|\?|/|,|\.|:|;|'| |"|&|#|\[|]|~|-|$|_""".toRegex(), + "_", + ) str = str.replace("_+_".toRegex(), "_") str = str.replace("""^_+|_+$""".toRegex(), "") return str } - override fun getFilterList() = if (getAdvancedGenreFilters().isNotEmpty()) { - FilterList( - KeywordFilter(getKeywordFilters()), - SortFilter(getSortFilters()), - StatusFilter(getStatusFilters()), - AdvGenreFilter(getAdvancedGenreFilters()), - ) - } else { - FilterList( - Filter.Header("NOTE: Ignored if using text search!"), - Filter.Separator(), - SortFilter(getSortFilters()), - StatusFilter(getStatusFilters()), - GenreFilter(getGenreFilters()), - ) - } - - // Technically, only Sort, Status, and Genre need to be non-private for Mangakakalot and Manganato, but I'll include Keyword to make it uniform. - protected class KeywordFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Keyword search ", vals) - protected class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals) - protected class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals) - protected class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", vals) - - // For advanced search, specifically tri-state genres - private class AdvGenreFilter(vals: List<AdvGenre>) : Filter.Group<AdvGenre>("Category", vals) - class AdvGenre(val id: String?, name: String) : Filter.TriState(name) - - // keyt query parameter - private fun getKeywordFilters(): Array<Pair<String?, String>> = arrayOf( - Pair(null, "Everything"), - Pair("title", "Title"), - Pair("alternative", "Alt title"), - Pair("author", "Author"), + override fun getFilterList() = FilterList( + Filter.Header("NOTE: Ignored if using text search!"), + Filter.Separator(), + SortFilter(getSortFilters()), + StatusFilter(getStatusFilters()), + GenreFilter(getGenreFilters()), ) + private class SortFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Order by", vals) + private class StatusFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Status", vals) + private class GenreFilter(vals: Array<Pair<String?, String>>) : UriPartFilter("Category", vals) + private fun getSortFilters(): Array<Pair<String?, String>> = arrayOf( Pair("latest", "Latest"), Pair("newest", "Newest"), @@ -338,53 +426,72 @@ abstract class MangaBox( open fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf( Pair("all", "ALL"), - Pair("2", "Action"), - Pair("3", "Adult"), - Pair("4", "Adventure"), - Pair("6", "Comedy"), - Pair("7", "Cooking"), - Pair("9", "Doujinshi"), - Pair("10", "Drama"), - Pair("11", "Ecchi"), - Pair("12", "Fantasy"), - Pair("13", "Gender bender"), - Pair("14", "Harem"), - Pair("15", "Historical"), - Pair("16", "Horror"), - Pair("45", "Isekai"), - Pair("17", "Josei"), - Pair("44", "Manhua"), - Pair("43", "Manhwa"), - Pair("19", "Martial arts"), - Pair("20", "Mature"), - Pair("21", "Mecha"), - Pair("22", "Medical"), - Pair("24", "Mystery"), - Pair("25", "One shot"), - Pair("26", "Psychological"), - Pair("27", "Romance"), - Pair("28", "School life"), - Pair("29", "Sci fi"), - Pair("30", "Seinen"), - Pair("31", "Shoujo"), - Pair("32", "Shoujo ai"), - Pair("33", "Shounen"), - Pair("34", "Shounen ai"), - Pair("35", "Slice of life"), - Pair("36", "Smut"), - Pair("37", "Sports"), - Pair("38", "Supernatural"), - Pair("39", "Tragedy"), - Pair("40", "Webtoons"), - Pair("41", "Yaoi"), - Pair("42", "Yuri"), + Pair("action", "Action"), + Pair("adult", "Adult"), + Pair("adventure", "Adventure"), + Pair("comedy", "Comedy"), + Pair("cooking", "Cooking"), + Pair("doujinshi", "Doujinshi"), + Pair("drama", "Drama"), + Pair("ecchi", "Ecchi"), + Pair("fantasy", "Fantasy"), + Pair("gender-bender", "Gender bender"), + Pair("harem", "Harem"), + Pair("historical", "Historical"), + Pair("horror", "Horror"), + Pair("isekai", "Isekai"), + Pair("josei", "Josei"), + Pair("manhua", "Manhua"), + Pair("manhwa", "Manhwa"), + Pair("martial-arts", "Martial arts"), + Pair("mature", "Mature"), + Pair("mecha", "Mecha"), + Pair("medical", "Medical"), + Pair("mystery", "Mystery"), + Pair("one-shot", "One shot"), + Pair("psychological", "Psychological"), + Pair("romance", "Romance"), + Pair("school-life", "School life"), + Pair("sci-fi", "Sci fi"), + Pair("seinen", "Seinen"), + Pair("shoujo", "Shoujo"), + Pair("shoujo-ai", "Shoujo ai"), + Pair("shounen", "Shounen"), + Pair("shounen-ai", "Shounen ai"), + Pair("slice-of-life", "Slice of life"), + Pair("smut", "Smut"), + Pair("sports", "Sports"), + Pair("supernatural", "Supernatural"), + Pair("tragedy", "Tragedy"), + Pair("webtoons", "Webtoons"), + Pair("yaoi", "Yaoi"), + Pair("yuri", "Yuri"), ) - // To be overridden if using tri-state genres - protected open fun getAdvancedGenreFilters(): List<AdvGenre> = emptyList() - open class UriPartFilter(displayName: String, private val vals: Array<Pair<String?, String>>) : Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) { fun toUriPart() = vals[state].first } + + override fun setupPreferenceScreen(screen: PreferenceScreen) { + ListPreference(screen.context).apply { + key = PREF_USE_MIRROR + title = "Mirror" + entries = mirrorEntries + entryValues = mirrorEntries.map { "${URL_PREFIX}$it" }.toTypedArray() + setDefaultValue(entryValues[0]) + summary = "%s" + + setOnPreferenceChangeListener { _, newValue -> + // Update values + mirror = newValue as String + true + } + }.let(screen::addPreference) + } + + companion object { + private const val PREF_USE_MIRROR = "pref_use_mirror" + private const val URL_PREFIX = "https://" + } } diff --git a/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBoxLinkedCdnSet.kt b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBoxLinkedCdnSet.kt new file mode 100644 index 000000000..5344fefd5 --- /dev/null +++ b/lib-multisrc/mangabox/src/eu/kanade/tachiyomi/multisrc/mangabox/MangaBoxLinkedCdnSet.kt @@ -0,0 +1,20 @@ +package eu.kanade.tachiyomi.multisrc.mangabox + +class MangaBoxLinkedCdnSet : LinkedHashSet<String>() { + fun moveItemToFirst(item: String) { + // Lock the object to avoid multi threading issues + synchronized(this) { + if (this.contains(item) && this.first() != item) { + // Remove the item from the current set + this.remove(item) + // Create a new list with the item at the first position + val newItems = mutableListOf(item) + // Add the remaining items + newItems.addAll(this) + // Clear the current set and add all items from the new list + this.clear() + this.addAll(newItems) + } + } + } +} diff --git a/src/en/mangabat/build.gradle b/src/en/mangabat/build.gradle index 9362f144e..9be3bca0e 100644 --- a/src/en/mangabat/build.gradle +++ b/src/en/mangabat/build.gradle @@ -2,8 +2,8 @@ ext { extName = 'Mangabat' extClass = '.Mangabat' themePkg = 'mangabox' - baseUrl = 'https://h.mangabat.com' - overrideVersionCode = 5 + baseUrl = 'https://www.mangabats.com' + overrideVersionCode = 6 isNsfw = true } diff --git a/src/en/mangabat/src/eu/kanade/tachiyomi/extension/en/mangabat/Mangabat.kt b/src/en/mangabat/src/eu/kanade/tachiyomi/extension/en/mangabat/Mangabat.kt index 7a95b4b34..3eb6275db 100644 --- a/src/en/mangabat/src/eu/kanade/tachiyomi/extension/en/mangabat/Mangabat.kt +++ b/src/en/mangabat/src/eu/kanade/tachiyomi/extension/en/mangabat/Mangabat.kt @@ -1,17 +1,11 @@ package eu.kanade.tachiyomi.extension.en.mangabat import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox -import eu.kanade.tachiyomi.network.GET -import okhttp3.Request -import java.text.SimpleDateFormat -import java.util.Locale -class Mangabat : MangaBox("Mangabat", "https://h.mangabat.com", "en", SimpleDateFormat("MMM dd,yy", Locale.ENGLISH)) { - override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/manga-list-all/$page?type=topview", headers) - override fun popularMangaSelector() = "div.list-story-item" - override val latestUrlPath = "manga-list-all/" - override fun searchMangaSelector() = "div.list-story-item" - override fun getAdvancedGenreFilters(): List<AdvGenre> = getGenreFilters() - .drop(1) - .map { AdvGenre(it.first, it.second) } -} +class Mangabat : MangaBox( + "Mangabat", + arrayOf( + "www.mangabats.com", + ), + "en", +) diff --git a/src/en/mangairo/build.gradle b/src/en/mangairo/build.gradle deleted file mode 100644 index 5fdaf3882..000000000 --- a/src/en/mangairo/build.gradle +++ /dev/null @@ -1,10 +0,0 @@ -ext { - extName = 'Mangairo' - extClass = '.Mangairo' - themePkg = 'mangabox' - baseUrl = 'https://h.mangairo.com' - overrideVersionCode = 4 - isNsfw = true -} - -apply from: "$rootDir/common.gradle" diff --git a/src/en/mangairo/res/mipmap-hdpi/ic_launcher.png b/src/en/mangairo/res/mipmap-hdpi/ic_launcher.png deleted file mode 100644 index 7ab1b9070..000000000 Binary files a/src/en/mangairo/res/mipmap-hdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangairo/res/mipmap-mdpi/ic_launcher.png b/src/en/mangairo/res/mipmap-mdpi/ic_launcher.png deleted file mode 100644 index df8c10a58..000000000 Binary files a/src/en/mangairo/res/mipmap-mdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangairo/res/mipmap-xhdpi/ic_launcher.png b/src/en/mangairo/res/mipmap-xhdpi/ic_launcher.png deleted file mode 100644 index 683d30f8b..000000000 Binary files a/src/en/mangairo/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangairo/res/mipmap-xxhdpi/ic_launcher.png b/src/en/mangairo/res/mipmap-xxhdpi/ic_launcher.png deleted file mode 100644 index 545a0404f..000000000 Binary files a/src/en/mangairo/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangairo/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/mangairo/res/mipmap-xxxhdpi/ic_launcher.png deleted file mode 100644 index 07c031464..000000000 Binary files a/src/en/mangairo/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ diff --git a/src/en/mangairo/src/eu/kanade/tachiyomi/extension/en/mangairo/Mangairo.kt b/src/en/mangairo/src/eu/kanade/tachiyomi/extension/en/mangairo/Mangairo.kt deleted file mode 100644 index a18a4b039..000000000 --- a/src/en/mangairo/src/eu/kanade/tachiyomi/extension/en/mangairo/Mangairo.kt +++ /dev/null @@ -1,32 +0,0 @@ -package eu.kanade.tachiyomi.extension.en.mangairo - -import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.SManga -import okhttp3.Request -import org.jsoup.nodes.Element -import java.text.SimpleDateFormat -import java.util.Locale - -class Mangairo : MangaBox("Mangairo", "https://h.mangairo.com", "en", SimpleDateFormat("MMM-dd-yy", Locale.ENGLISH)) { - override val popularUrlPath = "manga-list/type-topview/ctg-all/state-all/page-" - override fun popularMangaSelector() = "div.story-item" - override val latestUrlPath = "manga-list/type-latest/ctg-all/state-all/page-" - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return GET("$baseUrl/list/$simpleQueryPath${normalizeSearchQuery(query)}?page=$page", headers) - } - - override fun searchMangaSelector() = "div.story-item" - override fun searchMangaFromElement(element: Element): SManga = mangaFromElement(element, "h2 a") - override fun searchMangaNextPageSelector() = "div.group-page a.select + a:not(.go-p-end)" - override val mangaDetailsMainSelector = "${super.mangaDetailsMainSelector}, div.story_content" - override val thumbnailSelector = "${super.thumbnailSelector}, div.story_info_left img" - override val descriptionSelector = "${super.descriptionSelector}, div#story_discription p" - override fun chapterListSelector() = "${super.chapterListSelector()}, div#chapter_list li" - override val alternateChapterDateSelector = "p" - override val pageListSelector = "${super.pageListSelector}, div.panel-read-story img" - - // will have to write a separate searchMangaRequest to get filters working for this source - override fun getFilterList() = FilterList() -} diff --git a/src/en/mangakakalot/build.gradle b/src/en/mangakakalot/build.gradle index fe16447db..b2c85b4c6 100644 --- a/src/en/mangakakalot/build.gradle +++ b/src/en/mangakakalot/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Mangakakalot' themePkg = 'mangabox' baseUrl = 'https://www.mangakakalot.gg' - overrideVersionCode = 4 + overrideVersionCode = 5 isNsfw = true } diff --git a/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt b/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt index ab998e042..79fae52d1 100644 --- a/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt +++ b/src/en/mangakakalot/src/eu/kanade/tachiyomi/extension/en/mangakakalot/Mangakakalot.kt @@ -1,115 +1,12 @@ package eu.kanade.tachiyomi.extension.en.mangakakalot import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -class Mangakakalot : MangaBox("Mangakakalot", "https://www.mangakakalot.gg", "en") { - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yyyy HH:mm", Locale.ENGLISH) - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "$baseUrl/") // for covers - override val popularUrlPath = "manga-list/hot-manga?page=" - override val latestUrlPath = "manga-list/latest-manga?page=" - override val simpleQueryPath = "search/story/" - override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) { - val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder() - .addPathSegment(normalizeSearchQuery(query)) - .addQueryParameter("page", page.toString()) - .build() - - return GET(url, headers) - } else { - val url = "$baseUrl/genre".toHttpUrl().newBuilder() - url.addQueryParameter("page", page.toString()) - filters.forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) - is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) - is GenreFilter -> url.addPathSegment(filter.toUriPart()!!) - else -> {} - } - } - - GET(url.build(), headers) - } - } - - override fun chapterFromElement(element: Element): SChapter { - // parse on title attribute rather than the value - val dateUploadAttr: Long? = try { - dateFormat.parse(element.selectDateFromElement().attr("title"))?.time - } catch (e: ParseException) { - null - } - - return super.chapterFromElement(element).apply { - date_upload = dateUploadAttr ?: date_upload - } - } - - override val descriptionSelector = "div#contentBox" - - override fun imageRequest(page: Page): Request { - return if (page.url.contains(baseUrl)) { - GET(page.imageUrl!!, headersBuilder().build()) - } else { // Avoid 403 errors on non-migrated mangas - super.imageRequest(page) - } - } - - override fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf( - Pair("all", "ALL"), - Pair("action", "Action"), - Pair("adult", "Adult"), - Pair("adventure", "Adventure"), - Pair("comedy", "Comedy"), - Pair("cooking", "Cooking"), - Pair("doujinshi", "Doujinshi"), - Pair("drama", "Drama"), - Pair("ecchi", "Ecchi"), - Pair("fantasy", "Fantasy"), - Pair("gender-bender", "Gender bender"), - Pair("harem", "Harem"), - Pair("historical", "Historical"), - Pair("horror", "Horror"), - Pair("isekai", "Isekai"), - Pair("josei", "Josei"), - Pair("manhua", "Manhua"), - Pair("manhwa", "Manhwa"), - Pair("martial-arts", "Martial arts"), - Pair("mature", "Mature"), - Pair("mecha", "Mecha"), - Pair("medical", "Medical"), - Pair("mystery", "Mystery"), - Pair("one-shot", "One shot"), - Pair("psychological", "Psychological"), - Pair("romance", "Romance"), - Pair("school-life", "School life"), - Pair("sci-fi", "Sci fi"), - Pair("seinen", "Seinen"), - Pair("shoujo", "Shoujo"), - Pair("shoujo-ai", "Shoujo ai"), - Pair("shounen", "Shounen"), - Pair("shounen-ai", "Shounen ai"), - Pair("slice-of-life", "Slice of life"), - Pair("smut", "Smut"), - Pair("sports", "Sports"), - Pair("supernatural", "Supernatural"), - Pair("tragedy", "Tragedy"), - Pair("webtoons", "Webtoons"), - Pair("yaoi", "Yaoi"), - Pair("yuri", "Yuri"), - ) -} +class Mangakakalot : MangaBox( + "Mangakakalot", + arrayOf( + "www.mangakakalot.gg", + "www.mangakakalove.com", + ), + "en", +) diff --git a/src/en/manganelo/build.gradle b/src/en/manganelo/build.gradle index 0293ff2ed..cdd46a3a2 100644 --- a/src/en/manganelo/build.gradle +++ b/src/en/manganelo/build.gradle @@ -3,7 +3,7 @@ ext { extClass = '.Manganato' themePkg = 'mangabox' baseUrl = 'https://www.natomanga.com' - overrideVersionCode = 3 + overrideVersionCode = 4 isNsfw = true } diff --git a/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt b/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt index 4a877b629..718f450a5 100644 --- a/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt +++ b/src/en/manganelo/src/eu/kanade/tachiyomi/extension/en/manganelo/Manganato.kt @@ -1,117 +1,16 @@ package eu.kanade.tachiyomi.extension.en.manganelo import eu.kanade.tachiyomi.multisrc.mangabox.MangaBox -import eu.kanade.tachiyomi.network.GET -import eu.kanade.tachiyomi.source.model.FilterList -import eu.kanade.tachiyomi.source.model.Page -import eu.kanade.tachiyomi.source.model.SChapter -import okhttp3.Headers -import okhttp3.HttpUrl.Companion.toHttpUrl -import okhttp3.Request -import org.jsoup.nodes.Element -import java.text.ParseException -import java.text.SimpleDateFormat -import java.util.Locale -class Manganato : MangaBox("Manganato", "https://www.natomanga.com", "en") { +class Manganato : MangaBox( + "Manganato", + arrayOf( + "www.natomanga.com", + "www.nelomanga.com", + "www.manganato.gg", + ), + "en", +) { + override val id: Long = 1024627298672457456 - - private val dateFormat: SimpleDateFormat = SimpleDateFormat("MMM-dd-yyyy HH:mm", Locale.ENGLISH) - - override fun headersBuilder(): Headers.Builder = super.headersBuilder().set("Referer", "$baseUrl/") // for covers - override val popularUrlPath = "manga-list/hot-manga?page=" - override val latestUrlPath = "manga-list/latest-manga?page=" - override val simpleQueryPath = "search/story/" - override fun searchMangaSelector() = "${super.searchMangaSelector()}, div.list-truyen-item-wrap" - - override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { - return if (query.isNotBlank() && getAdvancedGenreFilters().isEmpty()) { - val url = "$baseUrl/$simpleQueryPath".toHttpUrl().newBuilder() - .addPathSegment(normalizeSearchQuery(query)) - .addQueryParameter("page", page.toString()) - .build() - - return GET(url, headers) - } else { - val url = "$baseUrl/genre".toHttpUrl().newBuilder() - url.addQueryParameter("page", page.toString()) - filters.forEach { filter -> - when (filter) { - is SortFilter -> url.addQueryParameter("type", filter.toUriPart()) - is StatusFilter -> url.addQueryParameter("state", filter.toUriPart()) - is GenreFilter -> url.addPathSegment(filter.toUriPart()!!) - else -> {} - } - } - - GET(url.build(), headers) - } - } - - override fun chapterFromElement(element: Element): SChapter { - // parse on title attribute rather than the value - val dateUploadAttr: Long? = try { - dateFormat.parse(element.selectDateFromElement().attr("title"))?.time - } catch (e: ParseException) { - null - } - - return super.chapterFromElement(element).apply { - date_upload = dateUploadAttr ?: date_upload - } - } - - override val descriptionSelector = "div#contentBox" - - override fun imageRequest(page: Page): Request { - return if (page.url.contains(baseUrl)) { - GET(page.imageUrl!!, headersBuilder().build()) - } else { // Avoid 403 errors on non-migrated mangas - super.imageRequest(page) - } - } - - override fun getGenreFilters(): Array<Pair<String?, String>> = arrayOf( - Pair("all", "ALL"), - Pair("action", "Action"), - Pair("adult", "Adult"), - Pair("adventure", "Adventure"), - Pair("comedy", "Comedy"), - Pair("cooking", "Cooking"), - Pair("doujinshi", "Doujinshi"), - Pair("drama", "Drama"), - Pair("ecchi", "Ecchi"), - Pair("fantasy", "Fantasy"), - Pair("gender-bender", "Gender bender"), - Pair("harem", "Harem"), - Pair("historical", "Historical"), - Pair("horror", "Horror"), - Pair("isekai", "Isekai"), - Pair("josei", "Josei"), - Pair("manhua", "Manhua"), - Pair("manhwa", "Manhwa"), - Pair("martial-arts", "Martial arts"), - Pair("mature", "Mature"), - Pair("mecha", "Mecha"), - Pair("medical", "Medical"), - Pair("mystery", "Mystery"), - Pair("one-shot", "One shot"), - Pair("psychological", "Psychological"), - Pair("romance", "Romance"), - Pair("school-life", "School life"), - Pair("sci-fi", "Sci fi"), - Pair("seinen", "Seinen"), - Pair("shoujo", "Shoujo"), - Pair("shoujo-ai", "Shoujo ai"), - Pair("shounen", "Shounen"), - Pair("shounen-ai", "Shounen ai"), - Pair("slice-of-life", "Slice of life"), - Pair("smut", "Smut"), - Pair("sports", "Sports"), - Pair("supernatural", "Supernatural"), - Pair("tragedy", "Tragedy"), - Pair("webtoons", "Webtoons"), - Pair("yaoi", "Yaoi"), - Pair("yuri", "Yuri"), - ) }