diff --git a/src/en/likemanga/AndroidManifest.xml b/src/en/likemanga/AndroidManifest.xml
new file mode 100644
index 000000000..8072ee00d
--- /dev/null
+++ b/src/en/likemanga/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/en/likemanga/build.gradle b/src/en/likemanga/build.gradle
new file mode 100644
index 000000000..a1fe18997
--- /dev/null
+++ b/src/en/likemanga/build.gradle
@@ -0,0 +1,12 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'LikeManga'
+ pkgNameSuffix = 'en.likemanga'
+ extClass = '.LikeManga'
+ extVersionCode = 1
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..0992d9728
Binary files /dev/null and b/src/en/likemanga/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..4159c59d5
Binary files /dev/null and b/src/en/likemanga/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..f55409dd9
Binary files /dev/null and b/src/en/likemanga/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7a367aa38
Binary files /dev/null and b/src/en/likemanga/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png b/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..7c485d4aa
Binary files /dev/null and b/src/en/likemanga/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/en/likemanga/res/web_hi_res_512.png b/src/en/likemanga/res/web_hi_res_512.png
new file mode 100644
index 000000000..c15b6655d
Binary files /dev/null and b/src/en/likemanga/res/web_hi_res_512.png differ
diff --git a/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt
new file mode 100644
index 000000000..c98d5e1bc
--- /dev/null
+++ b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeManga.kt
@@ -0,0 +1,276 @@
+package eu.kanade.tachiyomi.extension.en.likemanga
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.interceptor.rateLimit
+import eu.kanade.tachiyomi.source.model.Filter
+import eu.kanade.tachiyomi.source.model.FilterList
+import eu.kanade.tachiyomi.source.model.MangasPage
+import eu.kanade.tachiyomi.source.model.Page
+import eu.kanade.tachiyomi.source.model.SChapter
+import eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import eu.kanade.tachiyomi.util.asJsoup
+import kotlinx.serialization.json.Json
+import kotlinx.serialization.json.jsonObject
+import kotlinx.serialization.json.jsonPrimitive
+import okhttp3.HttpUrl.Companion.toHttpUrl
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.Jsoup
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import uy.kohesive.injekt.injectLazy
+import java.text.SimpleDateFormat
+import java.util.Locale
+
+class LikeManga : ParsedHttpSource() {
+
+ override val name = "LikeManga"
+
+ override val lang = "en"
+
+ override val baseUrl = "https://likemanga.io"
+
+ override val supportsLatest = true
+
+ override val client = network.cloudflareClient.newBuilder()
+ .rateLimit(1, 2)
+ .build()
+
+ override fun headersBuilder() = super.headersBuilder()
+ .add("Referer", "$baseUrl/")
+
+ private val json: Json by injectLazy()
+
+ override fun popularMangaRequest(page: Int): Request {
+ return searchMangaRequest(page, "", FilterList(SortFilter("top-manga")))
+ }
+
+ override fun popularMangaParse(response: Response) = searchMangaParse(response)
+ override fun popularMangaFromElement(element: Element) = searchMangaFromElement(element)
+ override fun popularMangaSelector() = searchMangaSelector()
+ override fun popularMangaNextPageSelector() = searchMangaNextPageSelector()
+
+ override fun latestUpdatesRequest(page: Int): Request {
+ return searchMangaRequest(page, "", FilterList(SortFilter("lastest-chap")))
+ }
+
+ override fun latestUpdatesParse(response: Response) = searchMangaParse(response)
+ override fun latestUpdatesFromElement(element: Element) = searchMangaFromElement(element)
+ override fun latestUpdatesSelector() = searchMangaSelector()
+ override fun latestUpdatesNextPageSelector() = searchMangaNextPageSelector()
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addQueryParameter("act", "searchadvance")
+ filters.forEach { filter ->
+ when (filter) {
+ is GenreFilter -> {
+ filter.checked?.forEach {
+ addQueryParameter("f[genres][]", it)
+ }
+ }
+ is ChapterCountFilter -> {
+ filter.selected?.let {
+ addQueryParameter("f[min_num_chapter]", it)
+ }
+ }
+ is StatusFilter -> {
+ filter.selected?.let {
+ addQueryParameter("f[status]", it)
+ }
+ }
+ is SortFilter -> {
+ filter.selected?.let {
+ addQueryParameter("f[sortby]", it)
+ }
+ }
+ else -> {}
+ }
+ }
+ if (query.isNotEmpty()) {
+ addQueryParameter("f[keyword]", query.trim())
+ }
+ if (page > 1) {
+ addQueryParameter("pageNum", page.toString())
+ }
+ }.build()
+
+ return GET(url, headers)
+ }
+
+ private var genresList: List> = emptyList()
+
+ private fun parseGenres(document: Document): List> {
+ return document.selectFirst("div.search_genres")
+ ?.select("div.form-check")
+ .orEmpty()
+ .mapNotNull {
+ val label = it.selectFirst("label")
+ ?.text()?.trim() ?: return@mapNotNull null
+
+ val value = it.selectFirst("input")
+ ?.attr("value") ?: return@mapNotNull null
+
+ Pair(label, value)
+ }
+ }
+
+ override fun getFilterList(): FilterList {
+ val filters: MutableList> = mutableListOf(
+ SortFilter(),
+ StatusFilter(),
+ ChapterCountFilter(),
+ )
+
+ filters += if (genresList.isEmpty()) {
+ listOf(
+ Filter.Separator(),
+ Filter.Header("Press 'reset' to attempt to show Genres"),
+ )
+ } else {
+ listOf(
+ GenreFilter("Genre", genresList),
+ )
+ }
+
+ return FilterList(filters)
+ }
+
+ override fun searchMangaParse(response: Response): MangasPage {
+ if (genresList.isEmpty()) {
+ val document = response.peekBody(Long.MAX_VALUE).string()
+ .let { Jsoup.parse(it, response.request.url.toString()) }
+
+ genresList = parseGenres(document)
+ }
+
+ return super.searchMangaParse(response)
+ }
+
+ override fun searchMangaFromElement(element: Element) = SManga.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
+ thumbnail_url = element.selectFirst("img")?.imgAttr()
+ title = element.select(".title-manga").text()
+ }
+
+ override fun searchMangaSelector() = "div.card-body div.card"
+ override fun searchMangaNextPageSelector() = "ul.pagination a:contains(ยป)"
+
+ override fun mangaDetailsParse(document: Document) = SManga.create().apply {
+ title = document.select("#title-detail-manga").text()
+ thumbnail_url = document.selectFirst(".detail-info img")?.imgAttr()
+ description = document.selectFirst("#summary_shortened")?.text()?.trim()
+ genre = document.select(".list-info a[href*=/genres/]").joinToString { it.text() }
+ status = document.selectFirst(".list-info .status p:nth-child(2)")?.text().parseStatus()
+ author = document.selectFirst(".list-info .author p:nth-child(2)")?.text()
+ ?.takeUnless { it.trim() == "Updating" }
+ }
+
+ private fun String?.parseStatus(): Int {
+ if (this == null) return SManga.UNKNOWN
+
+ return when {
+ contains("Complete", true) -> SManga.COMPLETED
+ contains("In process", true) -> SManga.ONGOING
+ contains("Pause", true) -> SManga.ON_HIATUS
+ else -> SManga.UNKNOWN
+ }
+ }
+
+ override fun chapterListParse(response: Response): List {
+ val document = response.use { it.asJsoup() }
+
+ val chapters = document.select(chapterListSelector())
+ .map(::chapterFromElement)
+ .toMutableList()
+
+ val lastPage = document.select("div.chapters_pagination a:not(.next)").last()
+ ?.attr("onclick")
+ ?.substringAfter("(")
+ ?.substringBefore(")")
+ ?.toIntOrNull()
+ ?: return chapters
+
+ val id = document.select("#title-detail-manga").attr("data-manga")
+ .toIntOrNull() ?: return chapters
+
+ for (page in 2..lastPage) {
+ chapters.addAll(fetchAjaxChapterList(id, page))
+ }
+
+ return chapters
+ }
+
+ private fun fetchAjaxChapterList(id: Int, page: Int): List {
+ val request = ajaxChapterListRequest(id, page)
+ val response = client.newCall(request).execute()
+
+ if (!response.isSuccessful) {
+ response.close()
+ return emptyList()
+ }
+
+ return ajaxChapterListParse(response)
+ }
+
+ private fun ajaxChapterListRequest(id: Int, page: Int): Request {
+ val url = baseUrl.toHttpUrl().newBuilder().apply {
+ addQueryParameter("act", "ajax")
+ addQueryParameter("code", "load_list_chapter")
+ addQueryParameter("manga_id", id.toString())
+ addQueryParameter("page_num", page.toString())
+ addQueryParameter("chap_id", "0")
+ addQueryParameter("keyword", "")
+ }.build()
+
+ return GET(url, headers)
+ }
+
+ private fun ajaxChapterListParse(response: Response): List {
+ val responseJson = response.use { json.parseToJsonElement(it.body.string()) }.jsonObject
+ val htmlString = responseJson["list_chap"]!!.jsonPrimitive.content
+ val document = Jsoup.parseBodyFragment(htmlString, response.request.url.toString())
+
+ return document.select(chapterListSelector())
+ .map(::chapterFromElement)
+ }
+
+ override fun chapterFromElement(element: Element) = SChapter.create().apply {
+ setUrlWithoutDomain(element.selectFirst("a")!!.attr("href"))
+ name = element.select("a").text()
+ date_upload = element.selectFirst(".chapter-release-date")?.text().parseDate()
+ }
+
+ override fun chapterListSelector() = ".wp-manga-chapter"
+
+ private fun String?.parseDate(): Long {
+ return runCatching {
+ dateFormat.parse(this!!)!!.time
+ }.getOrDefault(0L)
+ }
+
+ override fun pageListParse(document: Document): List {
+ return document.select(".reading-detail img:not(noscript img)").mapIndexed { i, img ->
+ Page(i, "", img.imgAttr())
+ }
+ }
+
+ private fun Element.imgAttr(): String? {
+ return when {
+ hasAttr("data-cfsrc") -> attr("abs:data-cfsrc")
+ hasAttr("data-src") -> attr("abs:data-src")
+ hasAttr("data-lazy-src") -> attr("abs:data-lazy-src")
+ hasAttr("srcset") -> attr("abs:srcset").substringBefore(" ")
+ else -> attr("abs:src")
+ }
+ }
+
+ override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not Used")
+
+ companion object {
+ val dateFormat by lazy {
+ SimpleDateFormat("MMMM dd, yyyy", Locale.ENGLISH)
+ }
+ }
+}
diff --git a/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt
new file mode 100644
index 000000000..21946c782
--- /dev/null
+++ b/src/en/likemanga/src/eu/kanade/tachiyomi/extension/en/likemanga/LikeMangaFilters.kt
@@ -0,0 +1,71 @@
+package eu.kanade.tachiyomi.extension.en.likemanga
+
+import eu.kanade.tachiyomi.source.model.Filter
+
+abstract class SelectFilter(
+ name: String,
+ private val options: List>,
+ defaultValue: String? = null,
+) : Filter.Select(
+ name,
+ options.map { it.first }.toTypedArray(),
+ options.indexOfFirst { it.second == defaultValue }.takeIf { it != -1 } ?: 0,
+) {
+ val selected get() = options[state].second.takeUnless { it.isEmpty() }
+}
+
+class CheckBoxFilter(
+ name: String,
+ val value: String,
+) : Filter.CheckBox(name)
+
+class GenreFilter(
+ name: String,
+ genres: List>,
+) : Filter.Group(
+ name,
+ genres.map { CheckBoxFilter(it.first, it.second) },
+) {
+ val checked get() = state.filter { it.state }.map { it.value }.takeUnless { it.isEmpty() }
+}
+
+class SortFilter(default: String? = null) : SelectFilter(
+ "Sort By",
+ listOf(
+ Pair("", ""),
+ Pair("Lasted update", "lastest-chap"),
+ Pair("Lasted manga", "lastest-manga"),
+ Pair("Top all", "top-manga"),
+ Pair("Top month", "top-month"),
+ Pair("Top week", "top-week"),
+ Pair("Top day", "top-day"),
+ Pair("Follow", "follow"),
+ Pair("Comments", "comment"),
+ Pair("Number of Chapters", "num-chap"),
+ ),
+ default,
+)
+
+class StatusFilter : SelectFilter(
+ "Status",
+ listOf(
+ Pair("All", ""),
+ Pair("Complete", "Complete"),
+ Pair("In process", "In process"),
+ Pair("Pause", "Pause"),
+ ),
+)
+
+class ChapterCountFilter : SelectFilter(
+ "Number of Chapters",
+ listOf(
+ Pair("", ""),
+ Pair(">= 0 chapter", "1"),
+ Pair(">= 50 chapter", "50"),
+ Pair(">= 100 chapter", "100"),
+ Pair(">= 200 chapter", "200"),
+ Pair(">= 300 chapter", "300"),
+ Pair(">= 400 chapter", "400"),
+ Pair(">= 500 chapter", "500"),
+ ),
+)