diff --git a/src/zh/baozimanhua/AndroidManifest.xml b/src/zh/baozimanhua/AndroidManifest.xml
new file mode 100644
index 000000000..2b35feb12
--- /dev/null
+++ b/src/zh/baozimanhua/AndroidManifest.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/zh/baozimanhua/build.gradle b/src/zh/baozimanhua/build.gradle
new file mode 100644
index 000000000..9393d7dc0
--- /dev/null
+++ b/src/zh/baozimanhua/build.gradle
@@ -0,0 +1,13 @@
+apply plugin: 'com.android.application'
+apply plugin: 'kotlin-android'
+apply plugin: 'kotlinx-serialization'
+
+ext {
+ extName = 'Baozimanhua'
+ pkgNameSuffix = 'zh.baozimanhua'
+ extClass = '.Baozimanhua'
+ extVersionCode = 1
+ libVersion = '1.2'
+}
+
+apply from: "$rootDir/common.gradle"
diff --git a/src/zh/baozimanhua/res/mipmap-hdpi/ic_launcher.png b/src/zh/baozimanhua/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 000000000..dbd8c099c
Binary files /dev/null and b/src/zh/baozimanhua/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/src/zh/baozimanhua/res/mipmap-mdpi/ic_launcher.png b/src/zh/baozimanhua/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 000000000..6dc41ceda
Binary files /dev/null and b/src/zh/baozimanhua/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/src/zh/baozimanhua/res/mipmap-xhdpi/ic_launcher.png b/src/zh/baozimanhua/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 000000000..8e77a7ad4
Binary files /dev/null and b/src/zh/baozimanhua/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/src/zh/baozimanhua/res/mipmap-xxhdpi/ic_launcher.png b/src/zh/baozimanhua/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 000000000..db1285618
Binary files /dev/null and b/src/zh/baozimanhua/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/src/zh/baozimanhua/res/mipmap-xxxhdpi/ic_launcher.png b/src/zh/baozimanhua/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 000000000..05e91a040
Binary files /dev/null and b/src/zh/baozimanhua/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/src/zh/baozimanhua/res/web_hi_res_512.png b/src/zh/baozimanhua/res/web_hi_res_512.png
new file mode 100644
index 000000000..107ec6f1b
Binary files /dev/null and b/src/zh/baozimanhua/res/web_hi_res_512.png differ
diff --git a/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozimanhua.kt b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozimanhua.kt
new file mode 100644
index 000000000..007b44ae5
--- /dev/null
+++ b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/Baozimanhua.kt
@@ -0,0 +1,307 @@
+package eu.kanade.tachiyomi.extension.zh.baozimanhua
+
+import eu.kanade.tachiyomi.network.GET
+import eu.kanade.tachiyomi.network.asObservableSuccess
+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 okhttp3.OkHttpClient
+import okhttp3.Request
+import okhttp3.Response
+import org.jsoup.nodes.Document
+import org.jsoup.nodes.Element
+import rx.Observable
+
+class Baozimanhua : ParsedHttpSource() {
+
+ override val name = "Baozimanhua"
+
+ override val baseUrl = "https://cn.baozimh.com"
+
+ override val lang = "zh"
+
+ override val supportsLatest = true
+
+ override val client: OkHttpClient = network.cloudflareClient
+
+ override fun chapterListSelector(): String = "div.pure-g[id^=chapter] > div"
+
+ override fun chapterListParse(response: Response): List {
+ val document = response.asJsoup()
+ val chapters = document.select(chapterListSelector()).map { element ->
+ chapterFromElement(element)
+ }
+ // chapters are listed oldest to newest in the source
+ return chapters.reversed()
+ }
+
+ override fun chapterFromElement(element: Element): SChapter {
+ return SChapter.create().apply {
+ url = element.select("a").attr("href").trim()
+ name = element.text()
+ }
+ }
+
+ override fun popularMangaSelector(): String = "div.pure-g div a.comics-card__poster"
+
+ override fun popularMangaFromElement(element: Element): SManga {
+ return SManga.create().apply {
+ url = element.attr("href")!!.trim()
+ title = element.attr("title")!!.trim()
+ thumbnail_url = element.select("> amp-img").attr("src")!!.trim()
+ }
+ }
+
+ override fun popularMangaNextPageSelector() = throw java.lang.UnsupportedOperationException("Not used.")
+
+ override fun popularMangaRequest(page: Int): Request = GET("https://www.baozimh.com/classify", headers)
+
+ override fun popularMangaParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ val mangas = document.select(popularMangaSelector()).map { element ->
+ popularMangaFromElement(element)
+ }
+ return MangasPage(mangas, mangas.size == 36)
+ }
+
+ override fun latestUpdatesSelector(): String = "div.pure-g div a.comics-card__poster"
+
+ override fun latestUpdatesFromElement(element: Element): SManga {
+ return popularMangaFromElement(element)
+ }
+
+ override fun latestUpdatesNextPageSelector() = throw java.lang.UnsupportedOperationException("Not used.")
+
+ override fun latestUpdatesRequest(page: Int): Request = GET("$baseUrl/list/new", headers)
+
+ override fun latestUpdatesParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ val mangas = document.select(popularMangaSelector()).map { element ->
+ popularMangaFromElement(element)
+ }
+ return MangasPage(mangas, mangas.size == 12)
+ }
+
+ override fun mangaDetailsParse(document: Document): SManga {
+ return SManga.create().apply {
+ title = document.select("h1.comics-detail__title").text().trim()
+ thumbnail_url = document.select("div.pure-g div > amp-img").attr("src").trim()
+ author = document.select("h2.comics-detail__author").text().trim()
+ description = document.select("p.comics-detail__desc").text().trim()
+ status = when (document.selectFirst("div.tag-list > span.tag").text().trim()) {
+ "连载中" -> SManga.ONGOING
+ "已完结" -> SManga.COMPLETED
+ "連載中" -> SManga.ONGOING
+ "已完結" -> SManga.COMPLETED
+ else -> SManga.UNKNOWN
+ }
+ }
+ }
+
+ override fun pageListParse(document: Document): List {
+ val pages = document.select("div.comic-contain > amp-img").mapIndexed() { index, element ->
+ Page(index, imageUrl = element.attr("src").trim())
+ }
+ return pages
+ }
+
+ override fun imageUrlParse(document: Document) = throw UnsupportedOperationException("Not used.")
+
+ override fun searchMangaSelector() = throw java.lang.UnsupportedOperationException("Not used.")
+
+ override fun searchMangaFromElement(element: Element) = throw java.lang.UnsupportedOperationException("Not used.")
+
+ override fun searchMangaNextPageSelector() = throw java.lang.UnsupportedOperationException("Not used.")
+
+ override fun fetchSearchManga(page: Int, query: String, filters: FilterList): Observable {
+ return if (query.startsWith(ID_SEARCH_PREFIX)) {
+ val id = query.removePrefix(ID_SEARCH_PREFIX)
+ client.newCall(searchMangaByIdRequest(id))
+ .asObservableSuccess()
+ .map { response -> searchMangaByIdParse(response, id) }
+ } else {
+ super.fetchSearchManga(page, query, filters)
+ }
+ }
+
+ private fun searchMangaByIdRequest(id: String) = GET("$baseUrl/comic/$id", headers)
+
+ private fun searchMangaByIdParse(response: Response, id: String): MangasPage {
+ val sManga = mangaDetailsParse(response)
+ sManga.url = "/comic/$id"
+ return MangasPage(listOf(sManga), false)
+ }
+
+ override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
+ // impossible to search a manga and use the filters
+ return if (query.isNotEmpty()) {
+ GET("$baseUrl/search?q=$query", headers)
+ } else {
+ lateinit var tag: String
+ lateinit var region: String
+ lateinit var status: String
+ lateinit var start: String
+ filters.forEach { filter ->
+ when (filter) {
+ is TagFilter -> {
+ tag = filter.toUriPart()
+ }
+ is RegionFilter -> {
+ region = filter.toUriPart()
+ }
+ is StatusFilter -> {
+ status = filter.toUriPart()
+ }
+ is StartFilter -> {
+ start = filter.toUriPart()
+ }
+ }
+ }
+ GET("$baseUrl/classify?type=$tag®ion=$region&state=$status&filter=$start&page=$page")
+ }
+ }
+
+ override fun searchMangaParse(response: Response): MangasPage {
+ val document = response.asJsoup()
+ // Normal search
+ return if (response.request.url.encodedPath.startsWith("search?")) {
+ val mangas = document.select(popularMangaSelector()).map { element ->
+ popularMangaFromElement(element)
+ }
+ MangasPage(mangas, false)
+ // Filter search
+ } else {
+ val mangas = document.select(popularMangaSelector()).map { element ->
+ popularMangaFromElement(element)
+ }
+ MangasPage(mangas, mangas.size == 36)
+ }
+ }
+
+ override fun getFilterList() = FilterList(
+ Filter.Header("注意:不影響按標題搜索"),
+ TagFilter(),
+ RegionFilter(),
+ StatusFilter(),
+ StartFilter()
+ )
+
+ private open class UriPartFilter(displayName: String, val vals: Array>) :
+ Filter.Select(displayName, vals.map { it.first }.toTypedArray()) {
+ fun toUriPart() = vals[state].second
+ }
+
+ private class TagFilter : UriPartFilter(
+ "标签",
+ arrayOf(
+ Pair("全部", "all"),
+ Pair("都市", "dushi"),
+ Pair("冒险", "mouxian"),
+ Pair("热血", "rexie"),
+ Pair("恋爱", "lianai"),
+ Pair("耽美", "danmei"),
+ Pair("武侠", "wuxia"),
+ Pair("格斗", "gedou"),
+ Pair("科幻", "kehuan"),
+ Pair("魔幻", "mohuan"),
+ Pair("推理", "tuili"),
+ Pair("玄幻", "xuanhuan"),
+ Pair("日常", "richang"),
+ Pair("生活", "shenghuo"),
+ Pair("搞笑", "gaoxiao"),
+ Pair("校园", "xiaoyuan"),
+ Pair("奇幻", "qihuan"),
+ Pair("萌系", "mengxi"),
+ Pair("穿越", "chuanyue"),
+ Pair("后宫", "hougong"),
+ Pair("战争", "zhanzheng"),
+ Pair("历史", "lishi"),
+ Pair("剧情", "juqing"),
+ Pair("同人", "tongren"),
+ Pair("竞技", "jingji"),
+ Pair("励志", "lizhi"),
+ Pair("治愈", "zhiyu"),
+ Pair("机甲", "jijia"),
+ Pair("纯爱", "chunai"),
+ Pair("美食", "meishi"),
+ Pair("恶搞", "egao"),
+ Pair("虐心", "nuexin"),
+ Pair("动作", "dongzuo"),
+ Pair("惊险", "liangxian"),
+ Pair("唯美", "weimei"),
+ Pair("复仇", "fuchou"),
+ Pair("脑洞", "naodong"),
+ Pair("宫斗", "gongdou"),
+ Pair("运动", "yundong"),
+ Pair("灵异", "lingyi"),
+ Pair("古风", "gufeng"),
+ Pair("权谋", "quanmou"),
+ Pair("节操", "jiecao"),
+ Pair("明星", "mingxing"),
+ Pair("暗黑", "anhei"),
+ Pair("社会", "shehui"),
+ Pair("音乐舞蹈", "yinlewudao"),
+ Pair("东方", "dongfang"),
+ Pair("AA", "aa"),
+ Pair("悬疑", "xuanyi"),
+ Pair("轻小说", "qingxiaoshuo"),
+ Pair("霸总", "bazong"),
+ Pair("萝莉", "luoli"),
+ Pair("战斗", "zhandou"),
+ Pair("惊悚", "liangsong"),
+ Pair("百合", "yuri"),
+ Pair("大女主", "danuzhu"),
+ Pair("幻想", "huanxiang"),
+ Pair("少女", "shaonu"),
+ Pair("少年", "shaonian"),
+ Pair("性转", "xingzhuanhuan"),
+ Pair("重生", "zhongsheng"),
+ Pair("韩漫", "hanman"),
+ Pair("其它", "qita")
+ )
+ )
+
+ private class RegionFilter : UriPartFilter(
+ "地区",
+ arrayOf(
+ Pair("全部", "all"),
+ Pair("国漫", "cn"),
+ Pair("日本", "jp"),
+ Pair("韩国", "kr"),
+ Pair("欧美", "en")
+ )
+ )
+
+ private class StatusFilter : UriPartFilter(
+ "进度",
+ arrayOf(
+ Pair("全部", "all"),
+ Pair("连载中", "serial"),
+ Pair("已完结", "pub")
+ )
+ )
+
+ private class StartFilter : UriPartFilter(
+ "标题开头",
+ arrayOf(
+ Pair("全部", "*"),
+ Pair("ABCD", "ABCD"),
+ Pair("EFGH", "EFGH"),
+ Pair("IJKL", "IJKL"),
+ Pair("MNOP", "MNOP"),
+ Pair("QRST", "QRST"),
+ Pair("UVW", "UVW"),
+ Pair("XYZ", "XYZ"),
+ Pair("0-9", "0-9")
+ )
+ )
+
+ companion object {
+ const val ID_SEARCH_PREFIX = "id:"
+ }
+}
diff --git a/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/BaozimanhuaUrlActivity.kt b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/BaozimanhuaUrlActivity.kt
new file mode 100644
index 000000000..fa4419e3b
--- /dev/null
+++ b/src/zh/baozimanhua/src/eu/kanade/tachiyomi/extension/zh/baozimanhua/BaozimanhuaUrlActivity.kt
@@ -0,0 +1,38 @@
+package eu.kanade.tachiyomi.extension.zh.baozimanhua
+
+import android.app.Activity
+import android.content.ActivityNotFoundException
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import kotlin.system.exitProcess
+
+class BaozimanhuaUrlActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val pathSegments = intent?.data?.pathSegments
+ if (pathSegments != null && pathSegments.size > 1) {
+ val id = if (pathSegments[1].equals("chapter")) {
+ pathSegments[2]
+ } else {
+ pathSegments[1]
+ }
+ val mainIntent = Intent().apply {
+ action = "eu.kanade.tachiyomi.SEARCH"
+ putExtra("query", "${Baozimanhua.ID_SEARCH_PREFIX}$id")
+ putExtra("filter", packageName)
+ }
+
+ try {
+ startActivity(mainIntent)
+ } catch (e: ActivityNotFoundException) {
+ Log.e("BaoziUrlActivity", e.toString())
+ }
+ } else {
+ Log.e("BaoziUrlActivity", "could not parse uri from intent $intent")
+ }
+
+ finish()
+ exitProcess(0)
+ }
+}