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