diff --git a/src/zh/maofly/AndroidManifest.xml b/src/zh/maofly/AndroidManifest.xml
new file mode 100644
index 000000000..30deb7f79
--- /dev/null
+++ b/src/zh/maofly/AndroidManifest.xml
@@ -0,0 +1,2 @@
+
+
diff --git a/src/zh/maofly/build.gradle b/src/zh/maofly/build.gradle
new file mode 100644
index 000000000..50e46a933
--- /dev/null
+++ b/src/zh/maofly/build.gradle
@@ -0,0 +1,13 @@
+
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+
+ext {
+ extName = 'Maofly'
+ pkgNameSuffix = 'zh.maofly'
+ extClass = '.Maofly'
+ extVersionCode = 1
+}
+
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..7c276e1ef
Binary files /dev/null and b/src/zh/maofly/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..5e1e7c879
Binary files /dev/null and b/src/zh/maofly/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..023e50c4d
Binary files /dev/null and b/src/zh/maofly/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..394b51683
Binary files /dev/null and b/src/zh/maofly/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..805e84b22
Binary files /dev/null and b/src/zh/maofly/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/zh/maofly/res/web_hi_res_512.png b/src/zh/maofly/res/web_hi_res_512.png
new file mode 100644
index 000000000..abeef0d7e
Binary files /dev/null and b/src/zh/maofly/res/web_hi_res_512.png differ
diff --git a/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt b/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt
new file mode 100644
index 000000000..f92e52f3c
--- /dev/null
+++ b/src/zh/maofly/src/eu/kanade/tachiyomi/extension/zh/maofly/Maofly.kt
@@ -0,0 +1,96 @@
+package eu.kanade.tachiyomi.extension.zh.maofly
+
+import android.net.Uri
+import com.squareup.duktape.Duktape
+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 eu.kanade.tachiyomi.source.model.SManga
+import eu.kanade.tachiyomi.source.online.ParsedHttpSource
+import okhttp3.Headers
+import okhttp3.Request
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+
+class Maofly : ParsedHttpSource() {
+ override val name: String = "漫画猫"
+ override val lang: String = "zh"
+ override val supportsLatest: Boolean = false
+ override val baseUrl: String = "https://www.maofly.com"
+ override fun headersBuilder(): Headers.Builder = super.headersBuilder().add("Referer", baseUrl)
+
+ // Popular
+
+ override fun popularMangaRequest(page: Int) = GET("$baseUrl/list-page-$page.html", headers)
+ override fun popularMangaNextPageSelector(): String? = "li.page-item > a:contains(下一页)"
+ override fun popularMangaSelector(): String = "div.comic-main-section > div.comic-book-unit"
+ override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
+ title = element.selectFirst("h2 > a").text()
+ setUrlWithoutDomain(element.selectFirst("h2 > a").attr("href"))
+ thumbnail_url = element.selectFirst("img").attr("src")
+ }
+
+ // Latest
+
+ override fun latestUpdatesRequest(page: Int) = throw UnsupportedOperationException("Not used.")
+ override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used.")
+ override fun latestUpdatesSelector(): String = throw UnsupportedOperationException("Not used.")
+ override fun latestUpdatesFromElement(element: Element): SManga = throw UnsupportedOperationException("Not used.")
+
+ // Search
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ val uri = Uri.parse(baseUrl).buildUpon()
+ .appendPath("search.html")
+ .appendQueryParameter("q", query)
+ .appendQueryParameter("page", page.toString())
+ return GET(uri.toString(), headers)
+ }
+
+ override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
+ override fun searchMangaSelector(): String = "div.comic-main-section > div > div"
+ override fun searchMangaFromElement(element: Element): SManga = popularMangaFromElement(element)
+
+ // Details
+
+ override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
+ title = document.selectFirst("h1.comic-title").text().dropLast(1).drop(1)
+ thumbnail_url = document.selectFirst("td.comic-cover > img").attr("abs:src")
+ author = document.selectFirst("td.pub-duration").text()
+ artist = author
+ description = document.selectFirst("div.comic-info > p.comic_story").text()
+ genre = document.select("div.comic-info > ul.tags > li").eachText().joinToString(", ")
+ status = when {
+ !document.select("td.comic-titles > a:contains(已完结)").isEmpty() -> SManga.COMPLETED
+ !document.select("td.comic-titles > a:contains(连载中)").isEmpty() -> SManga.ONGOING
+ else -> SManga.UNKNOWN
+ }
+ }
+
+ // Chapters
+
+ override fun chapterListSelector(): String = "div#comic-book-list li.sort_div"
+ override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
+ setUrlWithoutDomain(element.select("a").attr("href"))
+ name = element.select("a").text()
+ }
+
+ // Pages
+
+ override fun pageListParse(document: Document): List {
+ val script = document.selectFirst("script:containsData(img_data)").data()
+ val imgData = script.substringAfter("img_data = ").substringBefore("\n")
+ val decoded = Duktape.create().use { it.evaluate("${LZSTRING}LZString.decompressFromBase64($imgData)").toString() }
+ val imgServer = document.selectFirst("div.vg-r-data").attr("data-chapter-domain")
+ return decoded.split(",").mapIndexed { i, url ->
+ Page(i, "", "$imgServer/uploads/$url")
+ }
+ }
+
+ override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not used.")
+
+ companion object {
+ const val LZSTRING = """var LZString=function(){function o(o,r){if(!t[o]){t[o]={};for(var n=0;ne;e++){var s=r.charCodeAt(e);n[2*e]=s>>>8,n[2*e+1]=s%256}return n},decompressFromUint8Array:function(o){if(null===o||void 0===o)return i.decompress(o);for(var n=new Array(o.length/2),e=0,t=n.length;t>e;e++)n[e]=256*o[2*e]+o[2*e+1];var s=[];return n.forEach(function(o){s.push(r(o))}),i.decompress(s.join(""))},compressToEncodedURIComponent:function(o){return null==o?"":i._compress(o,6,function(o){return e.charAt(o)})},decompressFromEncodedURIComponent:function(r){return null==r?"":""==r?null:(r=r.replace(/ /g,"+"),i._decompress(r.length,32,function(n){return o(e,r.charAt(n))}))},compress:function(o){return i._compress(o,16,function(o){return r(o)})},_compress:function(o,r,n){if(null==o)return"";var e,t,i,s={},p={},u="",c="",a="",l=2,f=3,h=2,d=[],m=0,v=0;for(i=0;ie;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++),s[c]=f++,a=String(u)}if(""!==a){if(Object.prototype.hasOwnProperty.call(p,a)){if(a.charCodeAt(0)<256){for(e=0;h>e;e++)m<<=1,v==r-1?(v=0,d.push(n(m)),m=0):v++;for(t=a.charCodeAt(0),e=0;8>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}else{for(t=1,e=0;h>e;e++)m=m<<1|t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t=0;for(t=a.charCodeAt(0),e=0;16>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1}l--,0==l&&(l=Math.pow(2,h),h++),delete p[a]}else for(t=s[a],e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;l--,0==l&&(l=Math.pow(2,h),h++)}for(t=2,e=0;h>e;e++)m=m<<1|1&t,v==r-1?(v=0,d.push(n(m)),m=0):v++,t>>=1;for(;;){if(m<<=1,v==r-1){d.push(n(m));break}v++}return d.join("")},decompress:function(o){return null==o?"":""==o?null:i._decompress(o.length,32768,function(r){return o.charCodeAt(r)})},_decompress:function(o,n,e){var t,i,s,p,u,c,a,l,f=[],h=4,d=4,m=3,v="",w=[],A={val:e(0),position:n,index:1};for(i=0;3>i;i+=1)f[i]=i;for(p=0,c=Math.pow(2,2),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(t=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;l=r(p);break;case 2:return""}for(f[3]=l,s=l,w.push(l);;){if(A.index>o)return"";for(p=0,c=Math.pow(2,m),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;switch(l=p){case 0:for(p=0,c=Math.pow(2,8),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 1:for(p=0,c=Math.pow(2,16),a=1;a!=c;)u=A.val&A.position,A.position>>=1,0==A.position&&(A.position=n,A.val=e(A.index++)),p|=(u>0?1:0)*a,a<<=1;f[d++]=r(p),l=d-1,h--;break;case 2:return w.join("")}if(0==h&&(h=Math.pow(2,m),m++),f[l])v=f[l];else{if(l!==d)return null;v=s+s.charAt(0)}w.push(v),f[d++]=s+v.charAt(0),h--,s=v,0==h&&(h=Math.pow(2,m),m++)}}};return i}();"function"==typeof define&&define.amd?define(function(){return LZString}):"undefined"!=typeof module&&null!=module&&(module.exports=LZString);"""
+ }
+}