diff --git a/app/build.gradle b/app/build.gradle index b6892dd..160e461 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,21 +6,27 @@ apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' -def keystorePropertiesFile = rootProject.file(".travis/keystore.properties") -def keystoreProperties = new Properties() -keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) -def keyStoreFile = file(keystoreProperties['storeFileLocal']) -if (!keyStoreFile.exists()){ - keyStoreFile = file(keystoreProperties['storeFileCI']) -} +//def keystorePropertiesFile = rootProject.file(".travis/keystore.properties") +//def keystoreProperties = new Properties() +//keystoreProperties.load(new FileInputStream(keystorePropertiesFile)) +//def keyStoreFile = file(keystoreProperties['storeFileLocal']) +//if (!keyStoreFile.exists()){ +// keyStoreFile = file(keystoreProperties['storeFileCI']) +//} android { + lintOptions { + checkReleaseBuilds false + abortOnError false + } signingConfigs { - release { - keyAlias keystoreProperties['keyAlias'] - keyPassword keystoreProperties['keyPwd'] - storeFile keyStoreFile - storePassword keystoreProperties['storePwd'] + myConfig { + storeFile file(RELEASE_STORE_FILE) + storePassword RELEASE_STORE_PASSWORD + keyAlias RELEASE_KEY_ALIAS + keyPassword RELEASE_KEY_PASSWORD + v1SigningEnabled true + v2SigningEnabled true } } compileSdkVersion 28 @@ -28,8 +34,8 @@ android { applicationId "top.rechinx.meow" minSdkVersion 16 targetSdkVersion 27 - versionCode 6 - versionName "1.0.6" + versionCode 8 + versionName "1.0.8" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true multiDexEnabled true @@ -38,7 +44,7 @@ android { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - signingConfig signingConfigs.release + signingConfig signingConfigs.myConfig } debug { applicationIdSuffix ".debug" diff --git a/app/gradle.properties b/app/gradle.properties new file mode 100644 index 0000000..a5af447 --- /dev/null +++ b/app/gradle.properties @@ -0,0 +1,5 @@ +# RELEASE_STORE_FILE = "..\\key.properties.jks" +RELEASE_STORE_FILE=.\\key.properties.jks +RELEASE_KEY_PASSWORD=android +RELEASE_KEY_ALIAS=key0 +RELEASE_STORE_PASSWORD=android \ No newline at end of file diff --git a/app/key.properties.jks b/app/key.properties.jks new file mode 100644 index 0000000..d0f08ec Binary files /dev/null and b/app/key.properties.jks differ diff --git a/app/src/main/java/top/rechinx/meow/core/source/SourceManager.kt b/app/src/main/java/top/rechinx/meow/core/source/SourceManager.kt index ae0c8e2..f799ba7 100644 --- a/app/src/main/java/top/rechinx/meow/core/source/SourceManager.kt +++ b/app/src/main/java/top/rechinx/meow/core/source/SourceManager.kt @@ -1,7 +1,8 @@ package top.rechinx.meow.core.source import android.content.Context -import top.rechinx.meow.core.source.internal.Dmzj +import top.rechinx.meow.core.source.internal.* +import top.rechinx.meowo.core.source.internal.Tohomh class SourceManager(private val context: Context) { @@ -36,7 +37,16 @@ class SourceManager(private val context: Context) { } private fun createInternalSources(): List = listOf( - Dmzj() + Dmzj(), + ManHuaLou(), + Tohomh(), + Bilibili(), + //U17www(), + Tencent(), + ManHuaTai(), + DongMan(), + U17(), + ManHuaDui() //Shuhui(), //EHentai() ) diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/Bilibili.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/Bilibili.kt new file mode 100644 index 0000000..745d5fd --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/Bilibili.kt @@ -0,0 +1,248 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.* +import org.json.JSONObject +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* +import java.util.zip.ZipInputStream +import kotlin.experimental.xor + +class Bilibili:HttpSource() { + + override val name = "Bilibili" + override val baseUrl = "https://manga.bilibili.com" + private val cleanRegex = Regex("") + + private fun POST(url: String, json:String) = Request.Builder() + .url(url) + .post(RequestBody.create(MediaType.parse("application/json; charset=utf-8"), json)) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request { + if("" != keyword){ + return POST("$baseUrl/twirp/comic.v1.Comic/Search?device=pc&platform=web", + "{\"key_word\":\"$keyword\",\"page_num\":$page,\"page_size\":9}") + } else{ + val json = filters.map { (it as UriPartFilter).getUrl() }.joinToString(",") + return POST("$baseUrl/twirp/comic.v1.Comic/ClassPage?device=pc&platform=web", + "{$json,\"page_num\":$page,\"page_size\":18}") + } + } + + private fun commonMangaParse(response: Response): PagedList { + val json = JSONObject(response.body()!!.string()) + if(response.request().url().toString().contains("Search")){ + val data = json.getJSONObject("data") + val arr = data.getJSONArray("list") + val ret = ArrayList() + for (i in 0 until arr.length()) { + val info = arr.getJSONObject(i) + ret.add(SManga.create().apply { + title = info.getString("title").replace(cleanRegex, "") + thumbnail_url = info.getString("vertical_cover") + url = info.getInt("id").toString() + + val tmp = ArrayList() + val styles = info.getJSONArray("styles") + for (j in 0 until styles.length()) { + tmp.add(styles.getString(j)) + } + genre = tmp.joinToString(", ") + + tmp.clear() + val authors = info.getJSONArray("author_name") + for (j in 0 until authors.length()) { + tmp.add(authors.getString(j)) + } + author = tmp.joinToString(", ").replace(cleanRegex, "") + }) + } + return PagedList(ret, true) + } + else{ + val arr = json.getJSONArray("data") + val ret = ArrayList(arr.length()) + for (i in 0 until arr.length()) { + val info = arr.getJSONObject(i) + ret.add(SManga.create().apply { + title = info.getString("title") + thumbnail_url = info.getString("vertical_cover") + url = info.getInt("season_id").toString() + }) + } + return PagedList(ret, true) + } + } + + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun popularMangaRequest(page: Int): Request = POST("$baseUrl/twirp/comic.v1.Comic/ClassPage?device=pc&platform=web", + "{\"style_id\":-1,\"area_id\":-1,\"is_finish\":-1,\"order\":0,\"page_num\":$page,\"page_size\":18,\"is_free\":-1}") + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = POST("$baseUrl/twirp/comic.v2.Comic/ComicDetail?device=pc&platform=web","{\"comic_id\":$url}") + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + val json = JSONObject(response.body()!!.string()) + val data = json.getJSONObject("data") + title = data.getString("title") + thumbnail_url = data.getString("vertical_cover") + url = data.getInt("id").toString() + + val tmp = ArrayList() + val authors = data.getJSONArray("author_name") + for (j in 0 until authors.length()) { + tmp.add(authors.getString(j)) + } + author = tmp.joinToString(", ") + + status = when(data.getInt("is_finish")) { + 1 -> SManga.COMPLETED + 0 -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + tmp.clear() + val styles = data.getJSONArray("styles") + for (j in 0 until styles.length()) { + tmp.add(styles.getString(j)) + } + genre = tmp.joinToString(", ") + + description = data.getString("evaluate") + } + + override fun chaptersRequest(page: Int, url: String): Request = POST("$baseUrl/twirp/comic.v2.Comic/ComicDetail?device=pc&platform=web","{\"comic_id\":$url}") + + override fun chaptersParse(response: Response): PagedList { + val json = JSONObject(response.body()!!.string()) + val data = json.getJSONObject("data") + val list = data.getJSONArray("ep_list") + + val ret = ArrayList() + for (i in 0 until list.length()) { + val chapter = list.getJSONObject(i) + ret.add(SChapter.create().apply { + name = (when(chapter.getBoolean("is_locked")){ + true -> "🔒" + chapter.getString("short_title") + false -> chapter.getString("short_title") + }) + url = chapter.getInt("id").toString() + }) + } + return PagedList(ret, false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = POST("$baseUrl/twirp/comic.v1.Comic/GetImageIndex?device=pc&platform=web","{\"ep_id\":${chapter.url}}") + + + override fun mangaPagesParse(response: Response): List { + val json = JSONObject(response.body()!!.string()) + if("" != json.getString("msg")){ + val ret = ArrayList() + ret.add(MangaPage(0, "", "https://i0.hdslb.com/bfs/activity-plat/cover/20171017/496nrnmz9x.png")) + return ret + } + + val data = json.getJSONObject("data") + val path = data.getString("path") + var host = "https://i0.hdslb.com" + if(data.has("host") && ""!=data.getString("host")){ + host = data.getString("host") + } + val m = Regex("^/bfs/manga/(\\d+)/(\\d+)").find(path)!! + val cid = m.groupValues[1].toInt() + val epid = m.groupValues[2].toInt() + + var bytes = client.newCall(Request.Builder() + .url(host+path) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build()).execute().body()!!.bytes() + bytes = bytes.sliceArray(9 until bytes.size) + + val key = ByteArray(8) + key[0] = epid.toByte() + key[1] = epid.shr(8).toByte() + key[2] = epid.shr(16).toByte() + key[3] = epid.shr(24).toByte() + key[4] = cid.toByte() + key[5] = cid.shr(8).toByte() + key[6] = cid.shr(16).toByte() + key[7] = cid.shr(24).toByte() + for (i in 0 until bytes.size){ + bytes[i] = bytes[i].xor(key[i % 8]) + } + + val zis = ZipInputStream(bytes.inputStream()) + zis.nextEntry + val pics = JSONObject(zis.bufferedReader().use { it.readText() }).getJSONArray("pics") + val tokens = JSONObject(client.newCall(POST("$baseUrl/twirp/comic.v1.Comic/ImageToken?device=pc&platform=web", + "{\"urls\":\"${pics.toString().replace("\"","\\\"")}\"}")) + .execute().body()!!.string()).getJSONArray("data") + val ret = ArrayList() + for (i in 0 until tokens.length()) { + val token = tokens.getJSONObject(i) + ret.add(MangaPage(i, "", "${token.getString("url")}?token=${token.getString("token")}")) + } + return ret + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList(StylesFilter(), AreasFilter(), StatusFilter(), PricesFilter(), OrdersFilter()) + + private open class UriPartFilter(displayName: String, val vals: Array>, + defaultValue: Int = 0) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray(), defaultValue){ + open fun getUrl() = vals[state].second + } + + private class StylesFilter : UriPartFilter("题材", arrayOf( + Pair("全部", "\"style_id\":-1"), + Pair("冒险", "\"style_id\":1013"), + Pair("热血", "\"style_id\":999"), + Pair("搞笑", "\"style_id\":994"), + Pair("恋爱", "\"style_id\":995"), + Pair("少女", "\"style_id\":1026"), + Pair("日常", "\"style_id\":1020"), + Pair("校园", "\"style_id\":1001"), + Pair("运动", "\"style_id\":1010"), + Pair("正能量", "\"style_id\":1028"), + Pair("治愈", "\"style_id\":1007"), + Pair("古风", "\"style_id\":997"), + Pair("玄幻", "\"style_id\":1016"), + Pair("奇幻", "\"style_id\":998"), + Pair("惊奇", "\"style_id\":996"), + Pair("悬疑", "\"style_id\":1023"), + Pair("都市", "\"style_id\":1002"), + Pair("总裁", "\"style_id\":1004") + )) + + private class AreasFilter : UriPartFilter("地区", arrayOf( + Pair("全部", "\"area_id\":-1"), + Pair("大陆", "\"area_id\":1"), + Pair("日本", "\"area_id\":2"), + Pair("其他", "\"area_id\":5") + )) + + private class StatusFilter : UriPartFilter("进度", arrayOf( + Pair("全部", "\"is_finish\":-1"), + Pair("连载", "\"is_finish\":0"), + Pair("完结", "\"is_finish\":1"), + Pair("新上架", "\"is_finish\":2") + )) + + private class PricesFilter : UriPartFilter("收费", arrayOf( + Pair("全部", "\"is_free\":-1"), + Pair("免费", "\"is_free\":1"), + Pair("付费", "\"is_free\":2") + )) + + private class OrdersFilter : UriPartFilter("排序", arrayOf( + Pair("人气推荐", "\"order\":0"), + Pair("更新时间", "\"order\":1"), + Pair("追漫人数", "\"order\":2") + )) +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/Dmzj.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/Dmzj.kt index 0d626ce..805497f 100644 --- a/app/src/main/java/top/rechinx/meow/core/source/internal/Dmzj.kt +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/Dmzj.kt @@ -16,7 +16,7 @@ class Dmzj: HttpSource() { private fun GET(url: String) = Request.Builder() .url(url) - .addHeader("User-Agent", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 ") + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") .build() override fun searchMangaRequest(query: String, page: Int, filterList: FilterList): Request { diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/DongMan.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/DongMan.kt new file mode 100644 index 0000000..c995f60 --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/DongMan.kt @@ -0,0 +1,83 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.Request +import okhttp3.Response +import org.jsoup.Jsoup +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* +import java.net.URI +import java.util.ArrayList + +class DongMan:HttpSource() { + override val name = "咚漫" + override val baseUrl = "https://www.dongmanmanhua.cn" + + private fun GET(url: String) = Request.Builder() + .url(URI(baseUrl).resolve(url).toString()) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request + = GET("$baseUrl/search?keyword=$keyword&page=$page") + private fun commonMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".card_lst li").map { node -> SManga.create().apply { + title = node.selectFirst(".subj").text() + thumbnail_url = node.selectFirst("img").attr("src") + url = node.selectFirst("a").attr("href") + author = node.selectFirst(".author").text() + genre = node.selectFirst(".genre").text() + } } + return PagedList(ret, true) + } + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl) + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + Jsoup.parse(response.body()!!.string()).let { + title = it.selectFirst("h1").text() + author = it.selectFirst(".author").text() + thumbnail_url = it.selectFirst(".other_card img").attr("src") + genre = it.selectFirst(".genre").text().replace(" ",", ") + description = it.selectFirst(".summary").text() + } + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + Jsoup.parse(response.body()!!.string()).let{ + val href = it.selectFirst("#_listUl a").attr("href") + val split = href.lastIndexOf("=") + 1 + val half = href.substring(0,split) + val ret = ArrayList() + for(i in href.substring(split).toInt() downTo 1){ + ret.add(SChapter.create().apply { + name = i.toString() + url = half + i + }) + } + return PagedList(ret, false) + } + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(chapter.url!!) + + override fun mangaPagesParse(response: Response): List { + val doc = Jsoup.parse(response.body()!!.string()) + return doc.select("#_viewerBox img").mapIndexed { i, node -> + MangaPage(i, "", node.attr("data-url")) + } + } + + override fun headersBuilder() = super.headersBuilder().add("Referer", baseUrl)!! + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList() +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaDui.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaDui.kt new file mode 100644 index 0000000..f9e0057 --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaDui.kt @@ -0,0 +1,95 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.Request +import okhttp3.Response + +import org.jsoup.Jsoup + +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* + + +class ManHuaDui:HttpSource() { + + override val name: String = "漫画堆" + + override val baseUrl: String = "https://m.manhuadui.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.87 Mobile Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request + = GET("https://m.manhuadui.com/search/?keywords=$keyword&page=$page") + + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + private fun commonMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".itemBox,.list-comic").map { node -> SManga.create().apply { + title = node.selectFirst(".title").text() + thumbnail_url = node.selectFirst("img").attr("src") + url = node.selectFirst(".title").attr("href") + description = node.selectFirst(".pd,.date").text() + author = node.selectFirst(".txtItme").text() + } } + return PagedList(ret, true) + } + + override fun popularMangaRequest(page: Int): Request = GET("https://m.manhuadui.com/update/$page/") + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + val dom = Jsoup.parse(response.body()!!.string()) + title = dom.selectFirst("h1").text() + thumbnail_url = dom.selectFirst("#Cover img").attr("src") + author = dom.selectFirst(".txtItme").text().substringAfter(":").trim() +// status = when(infoElement.selectFirst("span:nth-of-type(1) span").text()) { +// "连载中" -> SManga.ONGOING +// "完结" -> SManga.COMPLETED +// else -> SManga.UNKNOWN +// } +// genre = infoElement.select(".tip a") +// .map{ node -> node.text() }.joinToString(", ") + description = dom.select("#full-des").text() + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select("#chapter-list-1 a").map { node -> SChapter.create().apply { + name = node.text() + url = "https://m.manhuadui.com${node.attr("href")}" + } } + return PagedList(ret.reversed(), false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(chapter.url!!) + + override fun mangaPagesParse(response: Response): List { + val doc = Jsoup.parse(response.body()!!.string()) + val info = doc.select(".BarTit").text() + val reg = Regex("\\d+/(\\d+)").find(info) + if(reg != null){ + val len = reg.groupValues[1].toInt() + val ret = ArrayList(len) + val sp = response.request().url().url().toString().split(".html") + for ( i in 1 .. len){ + ret.add(MangaPage(i, "${sp[0]}-$i.html", "")) + } + return ret + } + return ArrayList() + } + + override fun imageUrlParse(response: Response): String + = Jsoup.parse(response.body()!!.string()).select("mip-img").attr("src") + + override fun getFilterList(): FilterList = FilterList() + +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaLou.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaLou.kt new file mode 100644 index 0000000..201e95d --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaLou.kt @@ -0,0 +1,351 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.Request +import okhttp3.Response +import org.json.JSONArray + +import org.jsoup.Jsoup + +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* +import java.text.SimpleDateFormat +import java.util.Locale + + +class ManHuaLou:HttpSource() { + + override val name: String = "漫画楼" + + override val baseUrl: String = "https://www.manhualou.com" + + override fun getFilterList(): FilterList + = FilterList(Types(), Orgins(), Plots(), Letters(), Progresses()) + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request{ + if("" != keyword){ + return GET("$baseUrl/search/?keywords=$keyword&page=$page") + } else{ + val params = filters.map { (it as UriPartFilter).getUrl() }.joinToString("-") + if("" != params){ + return GET("$baseUrl/list/$params/$page/") + } + return GET("$baseUrl/list_$page/") + } + } + + override fun searchMangaParse(response: Response): PagedList + = commonMangaParse(response) + + private fun commonMangaParse(response: Response): PagedList { + val res = response.body()!!.string() + val doc = Jsoup.parse(res) + val ret = doc.select("#contList li").map { node -> SManga.create().apply { + title = node.selectFirst("p").text() + thumbnail_url = node.selectFirst("img").attr("src") + url = node.selectFirst("a").attr("href") + } } + return PagedList(ret, doc.selectFirst(".next") != null && doc.selectFirst(".next.disabled") == null) + } + override fun popularMangaRequest(page: Int): Request + = GET("$baseUrl/list_$page/") + + override fun popularMangaParse(response: Response): PagedList + = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request + = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + val res = response.body()!!.string() + val doc = Jsoup.parse(res) + title = doc.selectFirst(".book-title").text() + thumbnail_url = doc.selectFirst(".cover .pic").attr("src") + author = doc.selectFirst("ul.detail-list.cf > li:nth-of-type(2) > span:nth-of-type(2) > a").text() + status = when(doc.selectFirst("li.status a").text()) { + "已完结" -> SManga.COMPLETED + "连载中" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + genre = doc.select("ul.detail-list.cf > li:nth-child(2) > span:nth-child(1) > a") + .map{ node -> node.text() }.joinToString(", ") + description = doc.selectFirst("#intro-all > p").text() + } + + override fun chaptersRequest(page: Int, url: String): Request + = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".chapter-body.clearfix a").map { node -> SChapter.create().apply { + name = node.text() + url = node.attr("href") + } } + doc.selectFirst("li.status .red").text().let { + ret[0].date_updated = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).parse(it).time + } + return PagedList(ret.reversed(), false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request + = GET(baseUrl + chapter.url) + + override fun mangaPagesParse(response: Response): List { + val res = response.body()!!.string() + val arr = JSONArray(Regex("chapterImages\\s*=\\s*([^;]*)").find(res)!!.groupValues[1]) + val ret = ArrayList(arr.length()) + for (i in 0 until arr.length()) { + ret.add(MangaPage(i, "", "https://restp.dongqiniqin.com/"+arr.getString(i))) + } + return ret + } + + private open class UriPartFilter(displayName: String, val vals: Array>, + defaultValue: Int = 0) : + Filter.Select(displayName, vals.map { it.first }.toTypedArray(), defaultValue){ + open fun getUrl() = vals[state].second + } + private class Types :UriPartFilter("类型", arrayOf( + Pair("全部", ""), + Pair("儿童漫画", "ertong"), + Pair("少年漫画", "shaonian"), + Pair("少女漫画", "shaonv"), + Pair("青年漫画", "qingnian") + + )) + private class Orgins :UriPartFilter("地区", arrayOf( + Pair("全部", ""), + Pair("日本", "riben"), + Pair("大陆", "dalu"), + Pair("香港", "hongkong"), + Pair("台湾", "taiwan"), + Pair("欧美", "oumei"), + Pair("韩国", "hanguo"), + Pair("其他", "qita") + )) + private class Plots :UriPartFilter("剧情", arrayOf( + Pair("全部", ""), + Pair("爱情", "aiqing"), + Pair("少女爱情", "shaonvaiqing"), + Pair("欢乐向", "huanlexiang"), + Pair("耽美", "danmei"), + Pair("东方", "dongfang"), + Pair("其他", "qita"), + Pair("冒险", "maoxian"), + Pair("奇幻", "qihuan"), + Pair("性转换", "xingzhuanhuan"), + Pair("节操", "jiecao"), + Pair("舰娘", "jianniang"), + Pair("四格", "sige"), + Pair("科幻", "kehuan"), + Pair("校园", "xiaoyuan"), + Pair("竞技", "jingji"), + Pair("萌系", "mengxi"), + Pair("机战", "jizhan"), + Pair("后宫", "hougong"), + Pair("格斗", "gedou"), + Pair("百合", "baihe"), + Pair("魔幻", "mohuan"), + Pair("动作格斗", "dongzuogedou"), + Pair("魔法", "mofa"), + Pair("生活", "shenghuo"), + Pair("轻小说", "qingxiaoshuo"), + Pair("神鬼", "shengui"), + Pair("悬疑", "xuanyi"), + Pair("美食", "meishi"), + Pair("伪娘", "weiniang"), + Pair("治愈", "zhiyu"), + Pair("颜艺", "yanyi"), + Pair("恐怖", "kongbu"), + Pair("职场", "zhichang"), + Pair("热血", "rexue"), + Pair("侦探", "zhentan"), + Pair("搞笑", "gaoxiao"), + Pair("音乐舞蹈", "yinyuewudao"), + Pair("历史", "lishi"), + Pair("战争", "zhanzheng"), + Pair("励志", "lizhi"), + Pair("高清单行", "gaoqingdanxing"), + Pair("西方魔幻", "xifangmohuan"), + Pair("宅系", "zhaixi"), + Pair("魔幻神话", "mohuanshenhua"), + Pair("校园青春", "xiaoyuanqingchun"), + Pair("综合其它", "zongheqita"), + Pair("轻松搞笑", "qingsonggaoxiao"), + Pair("体育竞技", "tiyujingji"), + Pair("同人漫画", "tongrenmanhua"), + Pair("布卡漫画", "bukamanhua"), + Pair("科幻未来", "kehuanweilai"), + Pair("悬疑探案", "xuanyitanan"), + Pair("短篇漫画", "duanpianmanhua"), + Pair("萌", "meng"), + Pair("侦探推理", "zhentantuili"), + Pair("其它漫画", "qitamanhua"), + Pair("武侠格斗", "wuxiagedou"), + Pair("科幻魔幻", "kehuanmohuan"), + Pair("耽美BL", "danmeiBL"), + Pair("青春", "qingchun"), + Pair("恋爱", "lianai"), + Pair("神魔", "shenmo"), + Pair("恐怖鬼怪", "kongbuguiguai"), + Pair("青年漫画", "qingnianmanhua"), + Pair("四格漫画", "sigemanhua"), + Pair("搞笑喜剧", "gaoxiaoxiju"), + Pair("玄幻", "xuanhuan"), + Pair("动作", "dongzuo"), + Pair("武侠", "wuxia"), + Pair("穿越", "chuanyue"), + Pair("同人", "tongren"), + Pair("架空", "jiakong"), + Pair("霸总", "bazong"), + Pair("萝莉", "luoli"), + Pair("总裁", "zongcai"), + Pair("古风", "gufeng"), + Pair("推理", "tuili"), + Pair("恐怖灵异", "kongbulingyi"), + Pair("修真", "xiuzhen"), + Pair("灵异", "lingyi"), + Pair("真人", "zhenren"), + Pair("历史漫画", "lishimanhua"), + Pair("漫改", "mangai"), + Pair("剧情", "juqing"), + Pair("美少女", "meishaonv"), + Pair("故事", "gushi"), + Pair("都市", "dushi"), + Pair("社会", "shehui"), + Pair("竞技体育", "jingjitiyu"), + Pair("少女", "shaonv"), + Pair("御姐", "yujie"), + Pair("运动", "yundong"), + Pair("杂志", "zazhi"), + Pair("吸血", "xixie"), + Pair("泡泡", "paopao"), + Pair("彩虹", "caihong"), + Pair("恋爱生活", "lianaishenghuo"), + Pair("修真热血玄幻", "xiuzhenrexuexuanhuan"), + Pair("恋爱玄幻", "lianaixuanhuan"), + Pair("生活悬疑灵异", "shenghuoxuanyilingyi"), + Pair("霸总生活", "bazongshenghuo"), + Pair("恋爱生活玄幻", "lianaishenghuoxuanhuan"), + Pair("架空后宫古风", "jiakonghougonggufeng"), + Pair("生活悬疑古风", "shenghuoxuanyigufeng"), + Pair("恋爱热血玄幻", "lianairexuexuanhuan"), + Pair("恋爱校园生活", "lianaixiaoyuanshenghuo"), + Pair("玄幻动作", "xuanhuandongzuo"), + Pair("玄幻科幻", "xuanhuankehuan"), + Pair("恋爱生活励志", "lianaishenghuolizhi"), + Pair("悬疑恐怖", "xuanyikongbu"), + Pair("游戏", "youxi"), + Pair("恋爱生活科幻", "lianaishenghuokehuan"), + Pair("修真灵异动作", "xiuzhenlingyidongzuo"), + Pair("恋爱校园玄幻", "lianaixiaoyuanxuanhuan"), + Pair("热血动作", "rexuedongzuo"), + Pair("恋爱科幻", "lianaikehuan"), + Pair("恋爱搞笑玄幻", "lianaigaoxiaoxuanhuan"), + Pair("恋爱后宫古风", "lianaihougonggufeng"), + Pair("恋爱搞笑穿越", "lianaigaoxiaochuanyue"), + Pair("搞笑热血", "gaoxiaorexue"), + Pair("修真恋爱架空", "xiuzhenlianaijiakong"), + Pair("搞笑古风穿越", "gaoxiaogufengchuanyue"), + Pair("霸总恋爱生活", "bazonglianaishenghuo"), + Pair("恋爱古风穿越", "lianaigufengchuanyue"), + Pair("玄幻古风", "xuanhuangufeng"), + Pair("校园搞笑生活", "xiaoyuangaoxiaoshenghuo"), + Pair("恋爱校园", "lianaixiaoyuan"), + Pair("热血玄幻", "rexuexuanhuan"), + Pair("恋爱生活悬疑", "lianaishenghuoxuanyi"), + Pair("唯美", "weimei"), + Pair("霸总恋爱", "bazonglianai"), + Pair("悬疑动作", "xuanyidongzuo"), + Pair("搞笑生活", "gaoxiaoshenghuo"), + Pair("热血架空", "rexuejiakong"), + Pair("恋爱校园搞笑", "lianaixiaoyuangaoxiao"), + Pair("校园生活动作", "xiaoyuanshenghuodongzuo"), + Pair("恋爱搞笑生活", "lianaigaoxiaoshenghuo"), + Pair("修真热血动作", "xiuzhenrexuedongzuo"), + Pair("热血玄幻动作", "rexuexuanhuandongzuo"), + Pair("恋爱搞笑励志", "lianaigaoxiaolizhi"), + Pair("搞笑生活玄幻", "gaoxiaoshenghuoxuanhuan"), + Pair("恋爱搞笑科幻", "lianaigaoxiaokehuan"), + Pair("悬疑古风", "xuanyigufeng"), + Pair("恋爱架空古风", "lianaijiakonggufeng"), + Pair("热血科幻战争", "rexuekehuanzhanzheng"), + Pair("生活悬疑", "shenghuoxuanyi"), + Pair("修真玄幻", "xiuzhenxuanhuan"), + Pair("霸总恋爱玄幻", "bazonglianaixuanhuan"), + Pair("搞笑生活励志", "gaoxiaoshenghuolizhi"), + Pair("恋爱校园竞技", "lianaixiaoyuanjingji"), + Pair("冒险热血玄幻", "maoxianrexuexuanhuan"), + Pair("冒险热血", "maoxianrexue"), + Pair("恋爱冒险古风", "lianaimaoxiangufeng"), + Pair("恋爱搞笑古风", "lianaigaoxiaogufeng"), + Pair("恋爱古风", "lianaigufeng"), + Pair("霸总恋爱搞笑", "bazonglianaigaoxiao"), + Pair("恋爱玄幻古风", "lianaixuanhuangufeng"), + Pair("搞笑生活穿越", "gaoxiaoshenghuochuanyue"), + Pair("恋爱搞笑后宫", "lianaigaoxiaohougong"), + Pair("恋爱冒险玄幻", "lianaimaoxianxuanhuan"), + Pair("恋爱搞笑悬疑", "lianaigaoxiaoxuanyi"), + Pair("恋爱玄幻穿越", "lianaixuanhuanchuanyue"), + Pair("生活玄幻", "shenghuoxuanhuan"), + Pair("校园冒险搞笑", "xiaoyuanmaoxiangaoxiao"), + Pair("恋爱生活古风", "lianaishenghuogufeng"), + Pair("恋爱搞笑架空", "lianaigaoxiaojiakong"), + Pair("冒险热血动作", "maoxianrexuedongzuo"), + Pair("爆笑", "baoxiao"), + Pair("热血玄幻悬疑", "rexuexuanhuanxuanyi"), + Pair("恋爱冒险搞笑", "lianaimaoxiangaoxiao"), + Pair("修真生活玄幻", "xiuzhenshenghuoxuanhuan"), + Pair("恋爱悬疑", "lianaixuanyi"), + Pair("恋爱校园励志", "lianaixiaoyuanlizhi"), + Pair("修真恋爱古风", "xiuzhenlianaigufeng"), + Pair("复仇", "fuchou"), + Pair("虐心", "nuexin"), + Pair("纯爱", "chunai"), + Pair("蔷薇", "qiangwei"), + Pair("震撼", "zhenhan"), + Pair("惊悚", "jingsong") + )) + private class Letters :UriPartFilter("字母", arrayOf( + Pair("全部", ""), + Pair("A", "a"), + Pair("B", "b"), + Pair("C", "c"), + Pair("D", "d"), + Pair("E", "e"), + Pair("F", "f"), + Pair("G", "g"), + Pair("H", "h"), + Pair("I", "i"), + Pair("J", "j"), + Pair("K", "k"), + Pair("L", "l"), + Pair("M", "m"), + Pair("N", "n"), + Pair("O", "o"), + Pair("P", "p"), + Pair("Q", "q"), + Pair("R", "r"), + Pair("S", "s"), + Pair("T", "t"), + Pair("U", "u"), + Pair("V", "v"), + Pair("W", "w"), + Pair("X", "x"), + Pair("Y", "y"), + Pair("Z", "z"), + Pair("其他", "1") + )) + private class Progresses :UriPartFilter("进度", arrayOf( + Pair("全部", ""), + Pair("已完结", "wanjie"), + Pair("连载中", "lianzai") + )) + +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaTai.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaTai.kt new file mode 100644 index 0000000..5333e7a --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/ManHuaTai.kt @@ -0,0 +1,115 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.Request +import okhttp3.Response +import org.json.JSONObject +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* + +class ManHuaTai:HttpSource() { + override val name = "漫画台" + override val baseUrl = "http://getcomicinfo-globalapi.yyhao.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request + = GET("$baseUrl/app_api/v5/getsortlist/?search_key=$keyword&page=$page") + + private fun commonMangaParse(response: Response): PagedList{ + val json = JSONObject(response.body()!!.string()) + val data = json.getJSONArray("data") + val ret = ArrayList() + for(i in 0 until data.length()){ + ret.add(SManga.create().apply { + val manga = data.getJSONObject(i) + val id = manga.getInt("comic_id").toString() + title = manga.getString("comic_name") + thumbnail_url = "http://image.mhxk.com/mh/$id.jpg" + genre = manga.getString("comic_type") + url = "$baseUrl/app_api/v5/getcomicinfo_body/?comic_id=$id" + }) + } + return PagedList(ret, true) + } + + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun popularMangaRequest(page: Int): Request + = GET("$baseUrl/app_api/v5/getsortlist/?page=$page") + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + JSONObject(response.body()!!.string()).let { + title = it.getString("comic_name") + thumbnail_url = it.getJSONArray("cover_list").getString(0) + author = it.getString("comic_author") + status = when(it.getInt("comic_status")) { + 0 -> SManga.COMPLETED + 1 -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + val types = it.getJSONObject("comic_type") + val tmp = ArrayList() + for (key in types.keys()) { + tmp.add(types.getString(key)) + } + genre = tmp.joinToString(", ") + + description = it.getString("comic_desc") + } + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val json = JSONObject(response.body()!!.string()) + val chapters = json.getJSONArray("comic_chapter") + val ret = ArrayList() + for(i in 0 until chapters.length()){ + val chapter = chapters.getJSONObject(i) + ret.add(SChapter.create().apply { + name = when(chapter.has("isbuy") && chapter.getInt("isbuy") == 1) { + true -> "💰" + else -> "" + } + when(chapter.getInt("islock")) { + 1 -> "🔒" + else -> "" + } + chapter.getString("chapter_name") + + url = response.request().url().toString()+"&chapter="+i.toString() + chapter_number = i.toString() + date_updated = 1000 * chapter.getLong("create_date") + }) + } + return PagedList(ret, false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(chapter.url!!) + + override fun mangaPagesParse(response: Response): List { + JSONObject(response.body()!!.string()) + .getJSONArray("comic_chapter") + .getJSONObject(response.request().url().queryParameter("chapter")!!.toInt()) + .let{ + val rule = it.getJSONObject("chapter_image").getString("high") + val ext = rule.split("$$")[1] + val server = "https://mhpic."+it.getString("chapter_domain")+rule.split("$$")[0] + val ret = ArrayList() + for(i in 1 .. it.getInt("end_num")){ + ret.add(MangaPage(i, "", server+i.toString()+ext)) + } + return ret + } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList() +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/Tencent.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/Tencent.kt new file mode 100644 index 0000000..b799f09 --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/Tencent.kt @@ -0,0 +1,94 @@ +package top.rechinx.meow.core.source.internal + +import android.util.Base64 +import okhttp3.Request +import okhttp3.Response +import org.json.JSONArray +import org.jsoup.Jsoup +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* + +class Tencent:HttpSource() { + override val name = "Tencent" + override val baseUrl = "https://ac.qq.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request = GET("$baseUrl/Comic/searchList?search=$keyword&page=$page") + + override fun searchMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".mod_book_list li").map { node -> SManga.create().apply { + title = node.selectFirst("h4").text() + thumbnail_url = node.selectFirst("img").attr("data-original") + url = baseUrl + node.selectFirst("a").attr("href") + } } + return PagedList(ret, true/*doc.selectFirst(".mod_page_next") != null*/) + } + + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/Comic/all/search/time/page/$page") + + override fun popularMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".ret-search-list li").map { node -> SManga.create().apply { + title = node.selectFirst("h3").text() + thumbnail_url = node.selectFirst("img").attr("data-original") + url = baseUrl+ node.selectFirst("a").attr("href") + author = node.selectFirst(".ret-works-author").text() + } } + return PagedList(ret, true/*doc.selectFirst(".mod_page_next") != null*/) + } + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + Jsoup.parse(response.body()!!.string()).selectFirst(".works-intro-wr").let { + title = it.selectFirst("h2").text() + thumbnail_url = it.selectFirst(".works-cover img").attr("src") + author = it.selectFirst(".works-intro-digi em").text() +// status = when(it.selectFirst(".comic_infor_status span").text()) { +// "已完结" -> SManga.COMPLETED +// "连载中" -> SManga.ONGOING +// else -> SManga.UNKNOWN +// } + genre = it.select(".works-intro-tags a") + .map{ node -> node.text() }.joinToString(", ") + description = it.selectFirst(".works-intro-short").text() + } + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".chapter-page-all span").map { node -> SChapter.create().apply { + name = when(node.selectFirst("i").className()){ + "ui-icon-pay" -> "🔒" + node.text() + else -> node.text() + } + url = node.selectFirst("a").attr("href") + } } + return PagedList(ret.reversed(), false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(baseUrl + chapter.url!!) + + override fun mangaPagesParse(response: Response): List { + val res = response.body()!!.string() + val DATA = res.substringAfter("DATA = '").substringBefore("'") + val pic = String(Base64.decode(DATA.substring(DATA.length%4), Base64.NO_WRAP)) + val arr = JSONArray(pic.substringAfter("\"picture\":").substringBefore("]")+"]") + val ret = ArrayList() + for (i in 0 until arr.length()) { + ret.add(MangaPage(i, "", arr.getJSONObject(i).getString("url"))) + } + return ret + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList() +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/Tohomh.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/Tohomh.kt new file mode 100644 index 0000000..ec4a624 --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/Tohomh.kt @@ -0,0 +1,94 @@ +package top.rechinx.meowo.core.source.internal + +import okhttp3.Request +import okhttp3.Response +import org.json.JSONObject +import org.jsoup.Jsoup +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* +import java.text.SimpleDateFormat +import java.util.Locale + +class Tohomh:HttpSource() { + + override val name = "Tohomh123" + + override val baseUrl = "https://www.tohomh123.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request + = GET("$baseUrl/action/Search?keyword=$keyword&page=$page") + + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + private fun commonMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select("div.mh-item").map { node -> SManga.create().apply { + title = node.selectFirst("h2 a").text() + thumbnail_url = node.selectFirst("p.mh-cover").attr("style").substringAfter("(").substringBefore(")") + url = baseUrl + node.selectFirst("h2 a").attr("href") + } } + return PagedList(ret, doc.selectFirst("div.page-pagination a:contains(>)") != null) + } + + override fun popularMangaRequest(page: Int): Request = GET("$baseUrl/f-1-------hits--$page.html") + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + val infoElement = Jsoup.parse(response.body()!!.string()).selectFirst(".banner_detail_form") + title = infoElement.selectFirst("h1").text() + thumbnail_url = infoElement.selectFirst(".cover img").attr("src") + author = infoElement.selectFirst(".subtitle").text().substringAfter(":").trim() + status = when(infoElement.selectFirst("span:nth-of-type(1) span").text()) { + "连载中" -> SManga.ONGOING + "完结" -> SManga.COMPLETED + else -> SManga.UNKNOWN + } + genre = infoElement.select(".tip a") + .map{ node -> node.text() }.joinToString(", ") + description = infoElement.select("p.content").text() + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select("ul#detail-list-select-1 a").map { node -> SChapter.create().apply { + name = node.text() + url = node.attr("href") + } } + doc.select(".banner_detail_form span:nth-of-type(3)").text() + .substringAfter(":").trim().let { + ret[0].date_updated = SimpleDateFormat("yyyy-MM-dd", Locale.CHINA).parse(it).time + } + return PagedList(ret, false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(baseUrl + chapter.url) + + override fun mangaPagesParse(response: Response): List { + val doc = Jsoup.parse(response.body()!!.string()) + val script = doc.select("script:containsData(did)").first().data() + val did = script.substringAfter("did=").substringBefore(";") + val sid = script.substringAfter("sid=").substringBefore(";") + val lastPage = script.substringAfter("pcount =").substringBefore(";").trim().toInt() + val ret = ArrayList(lastPage) + for (i in 1..lastPage) { + ret.add(MangaPage(i, "$baseUrl/action/play/read?did=$did&sid=$sid&iid=$i", "")) + } + return ret + } + + override fun imageUrlParse(response: Response): String + = JSONObject(response.body()!!.string()).getString("Code") + + override fun getFilterList(): FilterList = FilterList() + +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/U17.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/U17.kt new file mode 100644 index 0000000..7b67b44 --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/U17.kt @@ -0,0 +1,139 @@ +package top.rechinx.meow.core.source.internal + +import okhttp3.Request +import okhttp3.Response +import org.json.JSONObject +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* + +class U17:HttpSource() { + override val name = "U17" + override val baseUrl = "http://app.u17.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request + = GET("$baseUrl/v3/appV3_3/android/phone/search/searchResult?q=$keyword&page=$page") + + private fun commonMangaParse(response: Response): PagedList{ + val json = JSONObject(response.body()!!.string()) + val data = json.getJSONObject("data").getJSONObject("returnData") + val comics = data.getJSONArray("comics") + val ret = ArrayList() + for(i in 0 until comics.length()){ + ret.add(SManga.create().apply { + val manga = comics.getJSONObject(i) + title = manga.getString("name") + author = manga.getString("author") + thumbnail_url = manga.getString("cover") + + genre = manga.getJSONArray("tags").join(", ") + + url = when(manga.has("comicId")){ + true -> "$baseUrl/v3/appV3_3/android/phone/comic/detail_static_new?&comicid=${manga.getInt("comicId")}" + else -> "$baseUrl/v3/appV3_3/android/phone/comic/detail_static_new?&comicid=${manga.getInt("comic_id")}" + } + + description = manga.getString("description") + }) + } + return PagedList(ret, data.has("hasMore") && data.getBoolean("hasMore")) + } + + override fun searchMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun popularMangaRequest(page: Int): Request + = GET("$baseUrl/v3/appV3_3/android/phone/list/conditionScreenlists?page=$page") + + override fun popularMangaParse(response: Response): PagedList = commonMangaParse(response) + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + JSONObject(response.body()!!.string()) + .getJSONObject("data") + .getJSONObject("returnData") + .getJSONObject("comic") + .let { + title = it.getString("name") + thumbnail_url = it.getString("cover") + author = it.getJSONObject("author").getString("name") + status = when(it.getInt("series_status")) { + 1 -> SManga.COMPLETED + 0 -> SManga.ONGOING + else -> SManga.UNKNOWN + } + + genre = (it.getJSONArray("tagList").join(", ") + ", " + + it.getJSONArray("theme_ids").join(", ")).replace("\"","") + + description = it.getString("description") + } + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + JSONObject(response.body()!!.string()) + .getJSONObject("data") + .getJSONObject("returnData") + .getJSONArray("chapter_list") + .let { + val ret = ArrayList() + for(i in (it.length()-1) downTo 0){ + val chapter = it.getJSONObject(i) + ret.add(SChapter.create().apply { + name = when(chapter.getInt("type")) { + 2 -> "🔒" + 3 -> "🔓" + else -> "" + } + chapter.getString("name") + url = chapter.getString("chapter_id") + chapter_number = chapter.getString("index") + date_updated = 1000 * chapter.getLong("pass_time") + }) + } + return PagedList(ret, false) + } + } + + override fun mangaPagesRequest(chapter: SChapter): Request + = GET("$baseUrl/v3/appV3_3/android/phone/comic/chapterNew?chapter_id=${chapter.url}") + + override fun mangaPagesParse(response: Response): List { + JSONObject(response.body()!!.string()) + .getJSONObject("data") + .getJSONObject("returnData") + .let{ + val ret = ArrayList() + + if(it.has("image_list")){ + val images = it.getJSONArray("image_list") + for(i in 0 until images.length()){ + ret.add(MangaPage(i, "", images.getJSONObject(i).getString("location"))) + } + } + if(it.has("free_image_list")){ + val images = it.getJSONArray("free_image_list") + for(i in 0 until images.length()){ + ret.add(MangaPage(i, "", images.getJSONObject(i).getString("location"))) + } + } + /*if(it.has("unlock_image")){ + val images = it.getJSONArray("unlock_image") + for(i in 0 until images.length()){ + ret.add(MangaPage(i, "", images.getJSONObject(i).getString("blur_image_url"))) + } + }*/ + + return ret + } + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList() +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/core/source/internal/U17www.kt b/app/src/main/java/top/rechinx/meow/core/source/internal/U17www.kt new file mode 100644 index 0000000..9a26d0d --- /dev/null +++ b/app/src/main/java/top/rechinx/meow/core/source/internal/U17www.kt @@ -0,0 +1,95 @@ +package top.rechinx.meow.core.source.internal + +import android.util.Base64 +import okhttp3.Request +import okhttp3.Response +import org.json.JSONObject +import org.jsoup.Jsoup +import top.rechinx.meow.core.source.HttpSource +import top.rechinx.meow.core.source.model.* + +class U17www:HttpSource() { + + override val name = "U17www" + override val baseUrl = "http://www.u17.com" + + private fun GET(url: String) = Request.Builder() + .url(url) + .addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36") + .build() + + override fun searchMangaRequest(keyword: String, page: Int, filters: FilterList): Request = GET("http://so.u17.com/all/$keyword/m0_p$page.html") + + override fun searchMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".comiclist li").map { node -> SManga.create().apply { + title = node.selectFirst("h3 a").text() + thumbnail_url = node.selectFirst("img").attr("src") + url = node.selectFirst("a").attr("href") + author = node.select("h3>a")?.text() + } } + return PagedList(ret, doc.selectFirst(".next") != null && doc.selectFirst(".next.over") == null) + } + + override fun popularMangaRequest(page: Int): Request = GET(baseUrl) + + override fun popularMangaParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select(".comic_all li").map { node -> SManga.create().apply { + title = node.selectFirst("a[title]").attr("title") + thumbnail_url = node.selectFirst("img").attr("xsrc") + url = node.selectFirst("a").attr("href") + } } + return PagedList(ret, doc.selectFirst(".next") != null && doc.selectFirst(".next.over") == null) + } + + override fun mangaInfoRequest(url: String): Request = GET(url) + + override fun mangaInfoParse(response: Response): SManga = SManga.create().apply { + Jsoup.parse(response.body()!!.string()).selectFirst(".comic_info").let { + title = it.selectFirst("h1").text() + thumbnail_url = it.selectFirst(".cover img").attr("src") + author = it.selectFirst(".author_info .info a").text() + status = when(it.selectFirst(".comic_infor_status span").text()) { + "已完结" -> SManga.COMPLETED + "连载中" -> SManga.ONGOING + else -> SManga.UNKNOWN + } + genre = it.select(".class_tag") + .map{ node -> node.text() }.joinToString(", ") + description = it.selectFirst(".words").text() + } + } + + override fun chaptersRequest(page: Int, url: String): Request = GET(url) + + override fun chaptersParse(response: Response): PagedList { + val doc = Jsoup.parse(response.body()!!.string()) + val ret = doc.select("#chapter li").map { node -> SChapter.create().apply { + name = when(node.selectFirst("a").className()){ + "vip_chapter" -> "🔒" + node.text() + else -> node.text() + } + url = node.selectFirst("a").attr("href") + } } + return PagedList(ret.reversed(), false) + } + + override fun mangaPagesRequest(chapter: SChapter): Request = GET(chapter.url!!) + + override fun mangaPagesParse(response: Response): List { + val res = response.body()!!.string() + val list = JSONObject(Regex("image_list\\s*:\\s*(.*?\\}\\})").find(res)!!.groupValues[1]) + val ret = ArrayList() + var i = 0 + list.keys().forEach { + ret.add(MangaPage(i, "", String(Base64.decode(list.getJSONObject(it).getString("src"), Base64.NO_WRAP)))) + i++ + } + return ret + } + + override fun imageUrlParse(response: Response): String = throw UnsupportedOperationException("Unused method was called somehow!") + + override fun getFilterList(): FilterList = FilterList() +} \ No newline at end of file diff --git a/app/src/main/java/top/rechinx/meow/ui/result/ResultPresenter.kt b/app/src/main/java/top/rechinx/meow/ui/result/ResultPresenter.kt index 883c527..b35dce7 100644 --- a/app/src/main/java/top/rechinx/meow/ui/result/ResultPresenter.kt +++ b/app/src/main/java/top/rechinx/meow/ui/result/ResultPresenter.kt @@ -68,12 +68,14 @@ class ResultPresenter(val query: String): BasePresenter(), KoinC try { if(it.list.isEmpty()) throw Exception() for(item in it.list) { - val manga = Manga() - manga.copyFrom(item) - manga.sourceId = source.id - manga.sourceName = source.name - emitter.onNext(manga) - Thread.sleep(Random().nextInt(200).toLong()) + if(item.title != null && item.title!!.contains(query)){ + val manga = Manga() + manga.copyFrom(item) + manga.sourceId = source.id + manga.sourceName = source.name + emitter.onNext(manga) + Thread.sleep(Random().nextInt(200).toLong()) + } } emitter.onComplete() } catch (e: Exception) { diff --git a/app/src/main/res/layout/fragment_about.xml b/app/src/main/res/layout/fragment_about.xml index 3721c19..2e542ad 100644 --- a/app/src/main/res/layout/fragment_about.xml +++ b/app/src/main/res/layout/fragment_about.xml @@ -85,5 +85,28 @@ android:textColor="?android:attr/textColorSecondary" android:text="@string/about_resource_url"/> + + + + \ No newline at end of file diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index d45501b..37e459b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -22,6 +22,7 @@ 检查更新 点击检查更新 源代码 + 分支 加载更多章节 正在解析 暂停中 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7362979..0033c04 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,6 +26,8 @@ Click to check update Source code https://github.com/ReChinX/Meow + Fork + https://github.com/mabDc/Meow Ongoing Completed Unknown diff --git a/build.gradle b/build.gradle index 95ce6f5..a114b43 100644 --- a/build.gradle +++ b/build.gradle @@ -1,13 +1,13 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.10' + ext.kotlin_version = '1.3.50' repositories { google() jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.0' + classpath 'com.android.tools.build:gradle:3.5.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 418447c..ebd6045 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Feb 07 14:42:10 CST 2019 +#Sat Nov 09 14:44:07 CST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip