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);""" + } +}