diff --git a/src/it/mangaworld/AndroidManifest.xml b/multisrc/overrides/mangaworld/default/AndroidManifest.xml
similarity index 100%
rename from src/it/mangaworld/AndroidManifest.xml
rename to multisrc/overrides/mangaworld/default/AndroidManifest.xml
diff --git a/src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-hdpi/ic_launcher.png
similarity index 100%
rename from src/it/mangaworld/res/mipmap-hdpi/ic_launcher.png
rename to multisrc/overrides/mangaworld/default/res/mipmap-hdpi/ic_launcher.png
diff --git a/src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-mdpi/ic_launcher.png
similarity index 100%
rename from src/it/mangaworld/res/mipmap-mdpi/ic_launcher.png
rename to multisrc/overrides/mangaworld/default/res/mipmap-mdpi/ic_launcher.png
diff --git a/src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xhdpi/ic_launcher.png
similarity index 100%
rename from src/it/mangaworld/res/mipmap-xhdpi/ic_launcher.png
rename to multisrc/overrides/mangaworld/default/res/mipmap-xhdpi/ic_launcher.png
diff --git a/src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xxhdpi/ic_launcher.png
similarity index 100%
rename from src/it/mangaworld/res/mipmap-xxhdpi/ic_launcher.png
rename to multisrc/overrides/mangaworld/default/res/mipmap-xxhdpi/ic_launcher.png
diff --git a/src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png b/multisrc/overrides/mangaworld/default/res/mipmap-xxxhdpi/ic_launcher.png
similarity index 100%
rename from src/it/mangaworld/res/mipmap-xxxhdpi/ic_launcher.png
rename to multisrc/overrides/mangaworld/default/res/mipmap-xxxhdpi/ic_launcher.png
diff --git a/src/it/mangaworld/res/web_hi_res_512.png b/multisrc/overrides/mangaworld/default/res/web_hi_res_512.png
similarity index 100%
rename from src/it/mangaworld/res/web_hi_res_512.png
rename to multisrc/overrides/mangaworld/default/res/web_hi_res_512.png
diff --git a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt
similarity index 51%
rename from src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt
rename to multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt
index db5864917..0bfaaeaaf 100644
--- a/src/it/mangaworld/src/eu/kanade/tachiyomi/extension/it/mangaworld/Mangaworld.kt
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorld.kt
@@ -1,4 +1,4 @@
-package eu.kanade.tachiyomi.extension.it.mangaworld
+package eu.kanade.tachiyomi.multisrc.mangaworld
 
 import eu.kanade.tachiyomi.network.GET
 import eu.kanade.tachiyomi.source.model.Filter
@@ -16,127 +16,86 @@ import okhttp3.Request
 import okhttp3.Response
 import org.jsoup.nodes.Document
 import org.jsoup.nodes.Element
-import java.text.ParseException
+import java.lang.Exception
+import java.lang.UnsupportedOperationException
 import java.text.SimpleDateFormat
 import java.util.Locale
 
-class Mangaworld : ParsedHttpSource() {
+abstract class MangaWorld(
+    override val name: String,
+    override val baseUrl: String,
+    override val lang: String
+) : ParsedHttpSource() {
 
-    override val name = "Mangaworld"
-    override val baseUrl = "https://www.mangaworld.in"
-    override val lang = "it"
     override val supportsLatest = true
     override val client: OkHttpClient = network.cloudflareClient
 
+    companion object {
+        protected val CHAPTER_NUMBER_REGEX by lazy { Regex("""(?i)capitolo\s([0-9]+)""") }
+
+        protected val DATE_FORMATTER by lazy { SimpleDateFormat("dd MMMM yyyy", Locale.ITALY) }
+        protected val DATE_FORMATTER_2 by lazy { SimpleDateFormat("H", Locale.ITALY) }
+    }
+
     override fun popularMangaRequest(page: Int): Request {
         return GET("$baseUrl/archive?sort=most_read&page=$page", headers)
     }
     override fun latestUpdatesRequest(page: Int): Request {
-        return GET("$baseUrl/archive?sort=newest&page=$page", headers)
+        return GET("$baseUrl/?page=$page", headers)
     }
-    //    LIST SELECTOR
-    override fun popularMangaSelector() = "div.comics-grid .entry"
-    override fun latestUpdatesSelector() = popularMangaSelector()
-    override fun searchMangaSelector() = popularMangaSelector()
 
-    //    ELEMENT
+    override fun searchMangaSelector() = "div.comics-grid .entry"
+    override fun popularMangaSelector() = searchMangaSelector()
+    override fun latestUpdatesSelector() = searchMangaSelector()
+
+    override fun searchMangaFromElement(element: Element): SManga {
+        val manga = SManga.create()
+        manga.thumbnail_url = element.select("a.thumb img").attr("src")
+        element.select("a").first().let {
+            manga.setUrlWithoutDomain(it.attr("href").removeSuffix("/"))
+            manga.title = it.attr("title")
+        }
+        return manga
+    }
     override fun popularMangaFromElement(element: Element): SManga = searchMangaFromElement(element)
     override fun latestUpdatesFromElement(element: Element): SManga = searchMangaFromElement(element)
 
-    //    NEXT SELECTOR
-    //  Not needed
-    override fun popularMangaNextPageSelector(): String? = null
-    override fun latestUpdatesNextPageSelector() = popularMangaNextPageSelector()
-    override fun searchMangaNextPageSelector() = popularMangaNextPageSelector()
-    // ////////////////
+    override fun searchMangaNextPageSelector(): String? = null
+    override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
+    override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
 
-    override fun latestUpdatesParse(response: Response): MangasPage {
+    override fun searchMangaParse(response: Response): MangasPage {
         val document = response.asJsoup()
-        val mangas = document.select(popularMangaSelector()).map { element ->
-            popularMangaFromElement(element)
+        val mangas = document.select(latestUpdatesSelector()).map { element ->
+            searchMangaFromElement(element)
         }
         // nextPage is not possible because pagination is loaded after via Javascript
         // 16 is the default manga-per-page. If it is less than 16 then there's no next page
         val hasNextPage = mangas.size == 16
         return MangasPage(mangas, hasNextPage)
     }
-
-    override fun popularMangaParse(response: Response): MangasPage {
-        return latestUpdatesParse(response)
-    }
-
-    override fun searchMangaFromElement(element: Element): SManga {
-        val manga = SManga.create()
-        manga.thumbnail_url = element.select("a.thumb img").attr("src")
-        element.select("a").first().let {
-            manga.setUrlWithoutDomain(it.attr("href"))
-            manga.title = it.attr("title")
-        }
-        return manga
-    }
-
-    override fun searchMangaParse(response: Response): MangasPage {
-        return latestUpdatesParse(response)
-    }
+    override fun popularMangaParse(response: Response): MangasPage = searchMangaParse(response)
+    override fun latestUpdatesParse(response: Response): MangasPage = searchMangaParse(response)
 
     override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
         val url = "$baseUrl/archive?page=$page".toHttpUrlOrNull()!!.newBuilder()
-        val q = query
-        if (query.isNotEmpty()) {
-            url.addQueryParameter("keyword", q)
-        } else {
-            url.addQueryParameter("keyword", "")
-        }
+        url.addQueryParameter("keyword", query)
 
-        var orderBy = ""
-
-        (if (filters.isEmpty()) getFilterList() else filters).forEach { filter ->
+        filters.forEach { filter ->
             when (filter) {
-                is GenreList -> {
-                    val genreInclude = mutableListOf<String>()
-                    filter.state.forEach {
-                        if (it.state == 1) {
-                            genreInclude.add(it.id)
-                        }
+                is GenreList ->
+                    filter.state.filter { it.state }.forEach {
+                        url.addQueryParameter("genre", it.id)
                     }
-                    if (genreInclude.isNotEmpty()) {
-                        genreInclude.forEach { genre ->
-                            url.addQueryParameter("genre", genre)
-                        }
+                is StatusList ->
+                    filter.state.filter { it.state }.forEach {
+                        url.addQueryParameter("status", it.id)
                     }
-                }
-                is StatusList -> {
-                    val statuses = mutableListOf<String>()
-                    filter.state.forEach {
-                        if (it.state == 1) {
-                            statuses.add(it.id)
-                        }
+                is MTypeList ->
+                    filter.state.filter { it.state }.forEach {
+                        url.addQueryParameter("type", it.id)
                     }
-                    if (statuses.isNotEmpty()) {
-                        statuses.forEach { status ->
-                            url.addQueryParameter("status", status)
-                        }
-                    }
-                }
-
-                is MTypeList -> {
-                    val typeslist = mutableListOf<String>()
-                    filter.state.forEach {
-                        if (it.state == 1) {
-                            typeslist.add(it.id)
-                        }
-                    }
-                    if (typeslist.isNotEmpty()) {
-                        typeslist.forEach { mtype ->
-                            url.addQueryParameter("type", mtype)
-                        }
-                    }
-                }
-
-                is SortBy -> {
-                    orderBy = filter.toUriPart()
-                    url.addQueryParameter("sort", orderBy)
-                }
+                is SortBy -> url.addQueryParameter("sort", filter.toUriPart())
                 is TextField -> url.addQueryParameter(filter.key, filter.state)
             }
         }
@@ -145,82 +104,100 @@ class Mangaworld : ParsedHttpSource() {
 
     override fun mangaDetailsParse(document: Document): SManga {
         val infoElement = document.select("div.comic-info")
-        val metaData = document.select("div.comic-info").first()
+        if (infoElement.isEmpty())
+            throw Exception("Page not found")
 
         val manga = SManga.create()
-        manga.author = infoElement.select("a[href^=$baseUrl/archive?author=]").first()?.text()
-        manga.artist = infoElement.select("a[href^=$baseUrl/archive?artist=]")?.text()
+        manga.author = infoElement.select("a[href*=/archive?author=]").first()?.text()
+        manga.artist = infoElement.select("a[href*=/archive?artist=]").text()
+        manga.thumbnail_url = infoElement.select(".thumb > img").attr("src")
 
-        val genres = mutableListOf<String>()
-        metaData.select("div.meta-data a.badge").forEach { element ->
-            val genre = element.text()
-            genres.add(genre)
+        var description = document.select("div#noidungm").text()
+        val otherTitle = document.select("div.meta-data > div").first()?.text()
+        if (!otherTitle.isNullOrBlank() && otherTitle.contains("Titoli alternativi"))
+            description += "\n\n$otherTitle"
+        manga.description = description.trim()
+
+        manga.genre = infoElement.select("div.meta-data a.badge").joinToString(", ") {
+            it.text()
         }
-        manga.genre = genres.joinToString(", ")
-        manga.status = parseStatus(infoElement.select("a[href^=$baseUrl/archive?status=]").first().attr("href"))
 
-        manga.description = document.select("div#noidungm")?.text()
-        manga.thumbnail_url = document.select(".comic-info .thumb > img").attr("src")
+        val status = infoElement.select("a[href*=/archive?status=]").first()?.text()
+        manga.status = parseStatus(status)
 
         return manga
     }
 
-    private fun parseStatus(element: String): Int = when {
-        element.lowercase().contains("ongoing") -> SManga.ONGOING
-        element.lowercase().contains("completed") -> SManga.COMPLETED
-        else -> SManga.UNKNOWN
+    protected fun parseStatus(element: String?): Int {
+        if (element.isNullOrEmpty())
+            return SManga.UNKNOWN
+        return when (element.lowercase()) {
+            "in corso" -> SManga.ONGOING
+            "finito" -> SManga.COMPLETED
+            "in pausa" -> SManga.ON_HIATUS
+            "cancellato" -> SManga.CANCELLED
+            else -> SManga.UNKNOWN
+        }
     }
 
     override fun chapterListSelector() = ".chapters-wrapper .chapter"
 
     override fun chapterFromElement(element: Element): SChapter {
-        val urlElement = element.select("a.chap").first()
         val chapter = SChapter.create()
-        chapter.setUrlWithoutDomain(getUrl(urlElement))
-        chapter.name = urlElement.select("span.d-inline-block").first().text()
-        chapter.date_upload = element.select(".chap-date").last()?.text()?.let {
-            try {
-                SimpleDateFormat("dd MMMM yyyy", Locale.ITALY).parse(it).time
-            } catch (e: ParseException) {
-                SimpleDateFormat("H", Locale.ITALY).parse(it).time
-            }
-        } ?: 0
+
+        val url = element.select("a.chap").first()?.attr("href")
+            ?: throw throw Exception("Url not found")
+        chapter.setUrlWithoutDomain(fixChapterUrl(url))
+
+        val name = element.select("span.d-inline-block").first()?.text() ?: ""
+        chapter.name = name
+
+        val date = parseChapterDate(element.select(".chap-date").last()?.text())
+        chapter.date_upload = date
+
+        val number = parseChapterNumber(name)
+        if (number != null)
+            chapter.chapter_number = number
         return chapter
     }
 
-    private fun getUrl(urlElement: Element): String {
-        var url = urlElement.attr("href")
+    protected fun fixChapterUrl(url: String?): String {
+        if (url.isNullOrEmpty())
+            return ""
+        val params = url.split("?").let { if (it.size > 1) it[1] else "" }
         return when {
-            url.endsWith("?style=list") -> url
-            else -> "$url?style=list"
+            params.contains("style=list") -> url
+            params.contains("style=pages") ->
+                url.replace("style=pages", "style=list")
+            params.isEmpty() -> "$url?style=list"
+            else -> "$url&style=list"
         }
     }
 
-    override fun prepareNewChapter(chapter: SChapter, manga: SManga) {
-        val basic = Regex("""Capitolo\s([0-9]+)""")
-        when {
-            basic.containsMatchIn(chapter.name) -> {
-                basic.find(chapter.name)?.let {
-                    chapter.chapter_number = it.groups[1]?.value!!.toFloat()
-                }
-            }
+    protected fun parseChapterDate(string: String?): Long {
+        if (string == null)
+            return 0L
+        return runCatching { DATE_FORMATTER.parse(string)?.time }.getOrNull()
+            ?: runCatching { DATE_FORMATTER_2.parse(string)?.time }.getOrNull() ?: 0L
+    }
+
+    protected fun parseChapterNumber(string: String): Float? {
+        return CHAPTER_NUMBER_REGEX.find(string)?.let {
+            it.groups[1]?.value?.toFloat()
         }
     }
 
     override fun pageListParse(document: Document): List<Page> {
         val pages = mutableListOf<Page>()
-        var i = 0
-        document.select("div#page img.page-image").forEach { element ->
-            val url = element.attr("src")
-            i++
-            if (url.length != 0) {
-                pages.add(Page(i, "", url))
-            }
+        document.select("div#page img.page-image").forEachIndexed { i, it ->
+            val url = it.attr("src")
+            if (url.isNotEmpty())
+                pages.add(Page(i, imageUrl = url))
         }
         return pages
     }
 
-    override fun imageUrlParse(document: Document) = ""
+    override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
 
     override fun imageRequest(page: Page): Request {
         val imgHeader = Headers.Builder().apply {
@@ -229,52 +206,40 @@ class Mangaworld : ParsedHttpSource() {
         }.build()
         return GET(page.imageUrl!!, imgHeader)
     }
-    //    private class Status : Filter.TriState("Completed")
-    private class TextField(name: String, val key: String) : Filter.Text(name)
-    private class SortBy : UriPartFilter(
-        "Ordina per",
-        arrayOf(
-            Pair("Rilevanza", ""),
-            Pair("Più recenti", "newest"),
-            Pair("Meno recenti", "oldest"),
-            Pair("A-Z", "a-z"),
-            Pair("Z-A", "z-a"),
-            Pair("Più letti", "most_read"),
-            Pair("Meno letti", "less_read")
-        )
-    )
-    private class Genre(name: String, val id: String = name) : Filter.TriState(name)
-    private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres)
-    private class MType(name: String, val id: String = name) : Filter.TriState(name)
-    private class MTypeList(types: List<MType>) : Filter.Group<MType>("Tipologia", types)
-    private class Status(name: String, val id: String = name) : Filter.TriState(name)
-    private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses)
 
     override fun getFilterList() = FilterList(
-        TextField("Anno di rilascio", "year"),
+        TextField("Anno di uscita", "year"),
         SortBy(),
         StatusList(getStatusList()),
         GenreList(getGenreList()),
         MTypeList(getTypesList())
     )
-    private fun getStatusList() = listOf(
-        Status("In corso", "ongoing"),
-        Status("Finito", "completed"),
-        Status("Droppato", "dropped"),
-        Status("In pausa", "paused"),
-        Status("Cancellato", "canceled")
+
+    private class SortBy : UriPartFilter(
+        "Ordina per",
+        arrayOf(
+            Pair("Rilevanza", ""),
+            Pair("Più letti", "most_read"),
+            Pair("Meno letti", "less_read"),
+            Pair("Più recenti", "newest"),
+            Pair("Meno recenti", "oldest"),
+            Pair("A-Z", "a-z"),
+            Pair("Z-A", "z-a")
+        )
     )
 
-    private fun getTypesList() = listOf(
-        MType("Manga", "manga"),
-        MType("Manhua", "manhua"),
-        MType("Manhwa", "manhwa"),
-        MType("Oneshot", "oneshot"),
-        MType("Thai", "thai"),
-        MType("Vietnamita", "vietnamese")
-    )
+    private class TextField(name: String, val key: String) : Filter.Text(name)
 
-    private fun getGenreList() = listOf(
+    class Genre(name: String, val id: String = name) : Filter.CheckBox(name)
+    private class GenreList(genres: List<Genre>) : Filter.Group<Genre>("Generi", genres)
+
+    class MType(name: String, val id: String = name) : Filter.CheckBox(name)
+    private class MTypeList(types: List<MType>) : Filter.Group<MType>("Tipologia", types)
+
+    class Status(name: String, val id: String = name) : Filter.CheckBox(name)
+    private class StatusList(statuses: List<Status>) : Filter.Group<Status>("Stato", statuses)
+
+    protected fun getGenreList() = listOf(
         Genre("Adulti", "adulti"),
         Genre("Arti Marziali", "arti-marziali"),
         Genre("Avventura", "avventura"),
@@ -312,6 +277,22 @@ class Mangaworld : ParsedHttpSource() {
         Genre("Yaoi", "yaoi"),
         Genre("Yuri", "yuri")
     )
+    protected fun getTypesList() = listOf(
+        MType("Manga", "manga"),
+        MType("Manhua", "manhua"),
+        MType("Manhwa", "manhwa"),
+        MType("Oneshot", "oneshot"),
+        MType("Thai", "thai"),
+        MType("Vietnamita", "vietnamese")
+    )
+    protected fun getStatusList() = listOf(
+        Status("In corso", "ongoing"),
+        Status("Finito", "completed"),
+        Status("Droppato", "dropped"),
+        Status("In pausa", "paused"),
+        Status("Cancellato", "canceled")
+    )
+
     private open class UriPartFilter(displayName: String, val vals: Array<Pair<String, String>>) :
         Filter.Select<String>(displayName, vals.map { it.first }.toTypedArray()) {
         fun toUriPart() = vals[state].second
diff --git a/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt
new file mode 100644
index 000000000..4e114c244
--- /dev/null
+++ b/multisrc/src/main/java/eu/kanade/tachiyomi/multisrc/mangaworld/MangaWorldGenerator.kt
@@ -0,0 +1,23 @@
+package eu.kanade.tachiyomi.multisrc.mangaworld
+
+import generator.ThemeSourceData.SingleLang
+import generator.ThemeSourceGenerator
+
+class MangaWorldGenerator : ThemeSourceGenerator {
+
+    override val themePkg = "mangaworld"
+    override val themeClass = "MangaWorld"
+    override val baseVersionCode: Int = 1
+
+    override val sources = listOf(
+        SingleLang("Mangaworld", "https://www.mangaworld.in", "it", isNsfw = false, pkgName = "mangaworld", overrideVersionCode = 5),
+        SingleLang("MangaworldAdult", "https://www.mangaworldadult.com", "it", isNsfw = true),
+    )
+
+    companion object {
+        @JvmStatic
+        fun main(args: Array<String>) {
+            MangaWorldGenerator().createAll()
+        }
+    }
+}
diff --git a/src/it/mangaworld/build.gradle b/src/it/mangaworld/build.gradle
deleted file mode 100644
index ad2ef9b7c..000000000
--- a/src/it/mangaworld/build.gradle
+++ /dev/null
@@ -1,12 +0,0 @@
-apply plugin: 'com.android.application'
-apply plugin: 'kotlin-android'
-
-ext {
-    extName = 'Mangaworld'
-    pkgNameSuffix = 'it.mangaworld'
-    extClass = '.Mangaworld'
-    extVersionCode = 5
-    isNsfw = true
-}
-
-apply from: "$rootDir/common.gradle"