Add multisrc theme SinMH (#11947)

* Add multisrc theme SinMH

* SinMH: optimize assets

* SinMH: add run configuration
This commit is contained in:
kasperskier 2022-05-26 18:27:33 +08:00 committed by GitHub
parent c9a32b6c26
commit fbe4571aa1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 217 additions and 1008 deletions

View File

@ -0,0 +1,17 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="SinMHGenerator" type="JetRunConfigurationType" nameIsGenerated="true">
<module name="tachiyomi-extensions.multisrc" />
<option name="VM_PARAMETERS" value="" />
<option name="PROGRAM_PARAMETERS" value="" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="false" />
<option name="ALTERNATIVE_JRE_PATH" />
<option name="PASS_PARENT_ENVS" value="true" />
<option name="MAIN_CLASS_NAME" value="eu.kanade.tachiyomi.multisrc.sinmh.SinMHGenerator" />
<option name="WORKING_DIRECTORY" value="" />
<method v="2">
<option name="Make" enabled="true" />
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktFormat" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="" />
<option name="Gradle.BeforeRunTask" enabled="true" tasks="ktLint" externalProjectPath="$PROJECT_DIR$/multisrc" vmOptions="" scriptParameters="" />
</method>
</configuration>
</component>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

View File

@ -0,0 +1,25 @@
package eu.kanade.tachiyomi.extension.zh.imitui
import eu.kanade.tachiyomi.multisrc.sinmh.SinMH
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Page
import eu.kanade.tachiyomi.source.model.SChapter
import eu.kanade.tachiyomi.source.model.SManga
import org.jsoup.nodes.Document
class Imitui : SinMH("爱米推漫画", "https://www.imitui.com") {
private val mobileUrl = "https://m.imitui.com"
override fun chapterListRequest(manga: SManga) = GET(mobileUrl + manga.url, headers)
override fun pageListRequest(chapter: SChapter) = GET(mobileUrl + chapter.url, headers)
override fun pageListParse(document: Document): List<Page> {
val pageCount = document.select("div.image-content > p").text().removePrefix("1/").toInt()
val prefix = document.location().removeSuffix(".html")
return (0 until pageCount).map { Page(it, "$prefix-${it + 1}.html") }
}
override fun imageUrlParse(document: Document): String =
document.select("div.image-content > img#image").attr("src")
}

View File

@ -0,0 +1,148 @@
package eu.kanade.tachiyomi.multisrc.sinmh
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
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 eu.kanade.tachiyomi.util.asJsoup
import okhttp3.Headers
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
import kotlin.concurrent.thread
/**
* 圣樱漫画CMS https://gitee.com/shenl/SinMH-2.0-Guide
* ref: https://github.com/kanasimi/CeJS/tree/master/application/net/work_crawler/sites
* https://github.com/kanasimi/work_crawler/blob/master/document/README.cmn-Hant-TW.md
*/
abstract class SinMH(
override val name: String,
_baseUrl: String,
override val lang: String = "zh",
) : ParsedHttpSource() {
override val baseUrl = _baseUrl
override val supportsLatest = true
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", baseUrl)
protected open val nextPageSelector = "ul.pagination > li.next:not(.disabled)"
protected open val comicItemSelector = "#contList > li"
protected open val comicItemTitleSelector = "p > a"
protected open fun mangaFromElement(element: Element) =
SManga.create().apply {
val titleElement = element.select(comicItemTitleSelector)
title = titleElement.text()
setUrlWithoutDomain(titleElement.attr("abs:href"))
thumbnail_url = element.select("img").attr("abs:src")
}
// Popular
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/click/?page=$page", headers)
override fun popularMangaNextPageSelector(): String? = nextPageSelector
override fun popularMangaSelector() = comicItemSelector
override fun popularMangaFromElement(element: Element) = mangaFromElement(element)
// Latest
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/update/?page=$page", headers)
override fun latestUpdatesNextPageSelector(): String? = nextPageSelector
override fun latestUpdatesSelector() = comicItemSelector
override fun latestUpdatesFromElement(element: Element) = mangaFromElement(element)
// Search
override fun searchMangaNextPageSelector(): String? = nextPageSelector
override fun searchMangaSelector(): String = comicItemSelector
override fun searchMangaFromElement(element: Element) = mangaFromElement(element)
override fun searchMangaRequest(page: Int, query: String, filters: FilterList) =
if (query.isNotBlank()) {
GET("$baseUrl/search/?keywords=$query&page=$page", headers)
} else {
val categories = filters.filterIsInstance<UriPartFilter>()
.joinToString("-", transform = UriPartFilter::toUriPart) + "-"
GET("$baseUrl/list/$categories/", headers)
}
// Details
override fun mangaDetailsParse(document: Document) = SManga.create().apply {
title = document.select(".book-title > h1 > span").text()
author = document.select(".detail-list strong:contains(作者) + a").text()
description = document.select("#intro-all").text().trim()
.removePrefix("漫画简介:").trim()
.removePrefix("漫画简介:").trim() // some sources have double prefix
genre = document.select(".detail-list strong:contains(类型) + a").text() + ", " +
document.select(".breadcrumb-bar a[href*=/list/]").joinToString(", ") { it.text() }
status = when (document.select(".detail-list strong:contains(状态) + a").text()) {
"连载中" -> SManga.ONGOING
"已完结" -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
thumbnail_url = document.select("p.cover > img").attr("abs:src")
// TODO: can use 更新时间2022-05-23 to set default upload date
}
// Chapters
override fun chapterListSelector() = ".chapter-body li > a:not([href*=/comic/app/])"
override fun chapterListParse(response: Response) = super.chapterListParse(response).reversed()
override fun chapterFromElement(element: Element) = SChapter.create().apply {
setUrlWithoutDomain(element.attr("abs:href"))
name = element.text()
}
// Pages
override fun pageListParse(document: Document): List<Page> {
val script = document.select("script:containsData(chapterImages)").html()
val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"")
val path = script.substringAfter("chapterPath = \"").substringBefore("\";")
// assume cover images are on the same server
val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover")
return images.mapIndexed { i, image -> Page(i, "", "$server/$path/$image") }
}
override fun imageUrlParse(document: Document): String = throw UnsupportedOperationException("Not Used.")
protected class UriPartFilter(displayName: String, values: Array<String>, private val uriParts: Array<String>) :
Filter.Select<String>(displayName, values) {
fun toUriPart(): String = uriParts[state]
}
protected data class Category(val name: String, val values: Array<String>, val uriParts: Array<String>) {
fun toUriPartFilter() = UriPartFilter(name, values, uriParts)
}
private lateinit var categories: List<Category>
protected open fun fetchCategories() {
val document = client.newCall(GET("$baseUrl/list/", headers)).execute().asJsoup()
categories = document.select(".page-main .filter-nav > .filter-item").map { element ->
val name = element.select("label").text()
val tags = element.select("a")
val values = tags.map { it.text() }.toTypedArray()
val uriParts = tags.map { it.attr("href").removePrefix("/list/").removeSuffix("/") }.toTypedArray()
Category(name, values, uriParts)
}
}
init {
thread {
fetchCategories()
}
}
override fun getFilterList() =
if (::categories.isInitialized) FilterList(
Filter.Header("如果使用文本搜索,将会忽略分类筛选"),
*categories.map(Category::toUriPartFilter).toTypedArray()
) else FilterList(
Filter.Header("分类尚未获取,请返回上一页后重试")
)
}

View File

@ -0,0 +1,27 @@
package eu.kanade.tachiyomi.multisrc.sinmh
import generator.ThemeSourceData.SingleLang
import generator.ThemeSourceGenerator
class SinMHGenerator : ThemeSourceGenerator {
override val themeClass = "SinMH"
override val themePkg = "sinmh"
override val baseVersionCode = 1
override val sources = listOf(
SingleLang(
name = "Gufeng Manhua", baseUrl = "https://www.gufengmh9.com", lang = "zh",
className = "Gufengmh", sourceName = "古风漫画网", overrideVersionCode = 5
),
SingleLang(
name = "Imitui Manhua", baseUrl = "https://www.imitui.com", lang = "zh",
className = "Imitui", sourceName = "爱米推漫画", overrideVersionCode = 2
)
)
companion object {
@JvmStatic
fun main(args: Array<String>) {
SinMHGenerator().createAll()
}
}
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,12 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Gufeng Manhua'
pkgNameSuffix = 'zh.gufengmh'
extClass = '.Gufengmh'
extVersionCode = 5
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,436 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.gufengmh
import android.net.Uri
import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.source.model.Filter
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 okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class Gufengmh : ParsedHttpSource() {
override val name: String = "古风漫画网"
override val lang: String = "zh"
override val supportsLatest: Boolean = true
override val baseUrl: String = "https://m.gufengmh9.com"
override fun headersBuilder(): Headers.Builder = Headers.Builder()
.add("Referer", baseUrl)
// Popular
override fun popularMangaRequest(page: Int): Request {
return GET("$baseUrl/list/click/?page=$page", headers)
}
override fun popularMangaNextPageSelector(): String? = "li.next"
override fun popularMangaSelector(): String = "li.list-comic"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("a.txtA").text()
setUrlWithoutDomain(element.select("a.txtA").attr("abs:href"))
thumbnail_url = element.select("mip-img").attr("abs:src")
}
// Latest
override fun latestUpdatesRequest(page: Int): Request {
return GET("$baseUrl/list/update/?page=$page", headers)
}
override fun latestUpdatesNextPageSelector(): String? = popularMangaNextPageSelector()
override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
// Search
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse(baseUrl).buildUpon()
if (query.isNotBlank()) {
uri.appendPath("search")
.appendEncodedPath("")
.appendQueryParameter("keywords", query)
.appendQueryParameter("page", page.toString())
} else {
uri.appendPath("list")
val pathBuilder = Uri.Builder()
filters.forEach {
if (it is UriFilter)
it.addToUri(pathBuilder)
}
val filterPath = pathBuilder.toString().replace("/", "-").removePrefix("-")
uri.appendEncodedPath(filterPath)
.appendEncodedPath("")
}
return GET(uri.toString(), headers)
}
override fun searchMangaNextPageSelector(): String? = popularMangaNextPageSelector()
override fun searchMangaSelector(): String = "div.itemBox, li.list-comic"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("a.title, a.txtA").text()
setUrlWithoutDomain(element.select("a.title, a.txtA").attr("abs:href"))
thumbnail_url = element.select("mip-img").attr("abs:src")
}
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select("h1.title").text()
thumbnail_url = document.select("div#Cover mip-img").attr("abs:src")
author = document.select("dt:contains(作者) + dd").text()
artist = author
genre = document.select("dt:contains(类别) + dd").text()
description = document.select("p.txtDesc").text()
}
// Chapters
override fun chapterListSelector(): String = "div.list li"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
url = element.select("a").attr("href")
name = element.select("span").text()
}
override fun chapterListParse(response: Response): List<SChapter> {
return super.chapterListParse(response).reversed()
}
// Pages
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
val script = document.select("script:containsData(chapterImages )").html()
val images = script.substringAfter("chapterImages = [\"").substringBefore("\"]").split("\",\"")
val path = script.substringAfter("chapterPath = \"").substringBefore("\";")
val server = script.substringAfter("pageImage = \"").substringBefore("/images/cover")
images.forEach {
add(Page(size, "", "$server/$path/$it"))
}
}
override fun imageUrlParse(document: Document): String = throw Exception("Not Used")
// Filters
override fun getFilterList(): FilterList {
return FilterList(
Filter.Header("如果使用文本搜索"),
Filter.Header("过滤器将被忽略"),
typefilter(),
regionfilter(),
genrefilter(),
letterfilter(),
statusfilter()
)
}
private class typefilter : UriSelectFilterPath(
"按类型",
"filtertype",
arrayOf(
Pair("", "全部"),
Pair("shaonian", "少年漫画"),
Pair("shaonv", "少女漫画"),
Pair("qingnian", "青年漫画"),
Pair("zhenrenmanhua", "真人漫画")
)
)
private class regionfilter : UriSelectFilterPath(
"按地区",
"filterregion",
arrayOf(
Pair("", "全部"),
Pair("ribenmanhua", "日本漫画"),
Pair("guochanmanhua", "国产漫画"),
Pair("gangtaimanhua", "港台漫画"),
Pair("oumeimanhua", "欧美漫画"),
Pair("hanguomanhua", "韩国漫画")
)
)
private class genrefilter : UriSelectFilterPath(
"按剧情",
"filtergenre",
arrayOf(
Pair("", "全部"),
Pair("maoxian", "冒险"),
Pair("mofa", "魔法"),
Pair("kehuan", "科幻"),
Pair("kongbu", "恐怖"),
Pair("lishi", "历史"),
Pair("jingji", "竞技"),
Pair("huanlexiang", "欢乐向"),
Pair("xifangmohuan", "西方魔幻"),
Pair("aiqing", "爱情"),
Pair("xuanyi", "悬疑"),
Pair("qihuan", "奇幻"),
Pair("qingxiaoshuo", "轻小说"),
Pair("sige", "四格"),
Pair("shengui", "神鬼"),
Pair("zhiyu", "治愈"),
Pair("xiaoyuan", "校园"),
Pair("weiniang", "伪娘"),
Pair("danmei", "耽美"),
Pair("hougong", "后宫"),
Pair("mohuan", "魔幻"),
Pair("wuxia", "武侠"),
Pair("zhichang", "职场"),
Pair("zhentan", "侦探"),
Pair("meishi", "美食"),
Pair("gedou", "格斗"),
Pair("lizhi", "励志"),
Pair("yinyuewudao", "音乐舞蹈"),
Pair("rexue", "热血"),
Pair("zhanzheng", "战争"),
Pair("gaoxiao", "搞笑"),
Pair("shenghuo", "生活"),
Pair("baihe", "百合"),
Pair("mengji", "萌系"),
Pair("jiecao", "节操"),
Pair("xingzhuanhuan", "性转换"),
Pair("yanyi", "颜艺"),
Pair("gufeng", "古风"),
Pair("xianxia", "仙侠"),
Pair("zhaiji", "宅系"),
Pair("juqing", "剧情"),
Pair("shenmo", "神魔"),
Pair("xuanhuan", "玄幻"),
Pair("chuanyue", "穿越"),
Pair("qita", "其他"),
Pair("huanxiang", "幻想"),
Pair("motong", "墨瞳"),
Pair("maimeng", "麦萌"),
Pair("manman", "漫漫"),
Pair("manhuadao", "漫画岛"),
Pair("tuili", "推理"),
Pair("dongfang", "东方"),
Pair("kuaikan", "快看"),
Pair("jizhan", "机战"),
Pair("gaoqingdanxing", "高清单行"),
Pair("xinzuo", "新作"),
Pair("tougao", "投稿"),
Pair("richang", "日常"),
Pair("shougong", "手工"),
Pair("yundong", "运动"),
Pair("weimei", "唯美"),
Pair("dushi", "都市"),
Pair("jingxian", "惊险"),
Pair("jiangshi", "僵尸"),
Pair("lianai", "恋爱"),
Pair("nuexin", "虐心"),
Pair("chunai", "纯爱"),
Pair("fuchou", "复仇"),
Pair("dongzuo", "动作"),
Pair("qita2", "其它"),
Pair("egao", "恶搞"),
Pair("mingxing", "明星"),
Pair("zhenhan", "震撼"),
Pair("anhei", "暗黑"),
Pair("naodong", "脑洞"),
Pair("xuexing", "血腥"),
Pair("youyaoqi", "有妖气"),
Pair("jijia", "机甲"),
Pair("qingchun", "青春"),
Pair("lingyi", "灵异"),
Pair("tongren", "同人"),
Pair("langman", "浪漫"),
Pair("quanmou", "权谋"),
Pair("shehui", "社会"),
Pair("gongdou", "宫斗"),
Pair("baoxiao", "爆笑"),
Pair("tiyu", "体育"),
Pair("lanmu", "栏目"),
Pair("caihong", "彩虹"),
Pair("zhentantuili", "侦探推理"),
Pair("shaonuaiqing", "少女爱情"),
Pair("gaoxiaoxiju", "搞笑喜剧"),
Pair("kongbulingyi", "恐怖灵异"),
Pair("kehuanmohuan", "科幻魔幻"),
Pair("jingjitiyu", "竞技体育"),
Pair("wuxiagedou", "武侠格斗"),
Pair("jianniang", "舰娘"),
Pair("danmeiBL", "耽美BL"),
Pair("xiee", "邪恶"),
Pair("zongheqita", "综合其它"),
Pair("qingnian", "青年"),
Pair("zhainan", "宅男"),
Pair("zazhi", "杂志"),
Pair("yinyue", "音乐"),
Pair("quancai", "全彩"),
Pair("heidao", "黑道"),
Pair("lianaidanmei", "恋爱耽美"),
Pair("rexuemaoxian", "热血冒险"),
Pair("funv", "腐女"),
Pair("gushi", "故事"),
Pair("shaonv", "少女"),
Pair("zongcai", "总裁"),
Pair("baoxiaoxiju", "爆笑喜剧"),
Pair("qitamanhua", "其他漫画"),
Pair("lianaishenghuo", "恋爱生活"),
Pair("kongbuxuanyi", "恐怖悬疑"),
Pair("danmeirensheng", "耽美人生"),
Pair("chongwu", "宠物"),
Pair("zhandou", "战斗"),
Pair("zhaohuanshou", "召唤兽"),
Pair("yineng", "异能"),
Pair("zhuangbi", "装逼"),
Pair("yishijie", "异世界"),
Pair("zhengju", "正剧"),
Pair("wenxin", "温馨"),
Pair("jingqi", "惊奇"),
Pair("jiakong", "架空"),
Pair("qingsong", "轻松"),
Pair("weilai", "未来"),
Pair("keji", "科技"),
Pair("shaonao", "烧脑"),
Pair("gaoxiaoegao", "搞笑恶搞"),
Pair("mhuaquan", "mhuaquan"),
Pair("shaonian", "少年"),
Pair("sigeduoge", "四格多格"),
Pair("bazong", "霸总"),
Pair("xiuzhen", "修真"),
Pair("gushimanhua", "故事漫画"),
Pair("huiben", "绘本"),
Pair("youxi", "游戏"),
Pair("zhenren", "真人"),
Pair("jingsong", "惊悚"),
Pair("manhua", "漫画"),
Pair("weizhongquan", "微众圈"),
Pair("yujie", "御姐"),
Pair("xiaoshuogaibian", "小说改编"),
Pair("luoli", "萝莉"),
Pair("1024manhua", "1024manhua"),
Pair("jiating", "家庭"),
Pair("shenhua", "神话"),
Pair("shishi", "史诗"),
Pair("moshi", "末世"),
Pair("yulequan", "娱乐圈"),
Pair("gandong", "感动"),
Pair("lunli", "伦理"),
Pair("zazhiquanben", "杂志全本"),
Pair("zhiyu2", "致郁"),
Pair("shangzhan", "商战"),
Pair("zhupu", "主仆"),
Pair("manhuaquan", "漫画圈"),
Pair("lianaijuqingmanhua", "恋爱、剧情漫画"),
Pair("hunai", "婚爱"),
Pair("haomen", "豪门"),
Pair("neihan", "内涵"),
Pair("xingzhuan", "性转"),
Pair("xiangcun", "乡村"),
Pair("gongting", "宫廷"),
Pair("duanzi", "段子"),
Pair("chunaimanhua", "纯爱漫画"),
Pair("nixi", "逆袭"),
Pair("hunyin", "婚姻"),
Pair("baihenvxing", "百合女性"),
Pair("shenghuomanhua", "生活漫画"),
Pair("ertong", "儿童"),
Pair("wudao", "舞蹈"),
Pair("tianchong", "甜宠"),
Pair("wengai", "文改"),
Pair("dujia", "独家"),
Pair("biaoqian", "标签"),
Pair("zhaifumanhua", "宅腐漫画"),
Pair("qinggan", "情感"),
Pair("mingkatong", "茗卡通"),
Pair("jiujie", "纠结"),
Pair("lianaimaoxiangaoxiao", "恋爱冒险搞笑"),
Pair("xiuzhenlianaijiakong", "修真恋爱架空"),
Pair("lianaigaoxiaohougong", "恋爱搞笑后宫"),
Pair("xuanyikongbu", "悬疑恐怖"),
Pair("lianaixiaoyuanshenghuo", "恋爱校园生活"),
Pair("xiuzhenlianaigufeng", "修真恋爱古风"),
Pair("shenghuoxuanyilingyi", "生活悬疑灵异"),
Pair("qingnianmanhua", "青年漫画"),
Pair("lishimanhua", "历史漫画"),
Pair("meishaonv", "美少女"),
Pair("shuangliu", "爽流"),
Pair("qiangwei", "蔷薇"),
Pair("gaozhishang", "高智商"),
Pair("xuanyituili", "悬疑推理"),
Pair("jizhi", "机智"),
Pair("donghua", "动画"),
Pair("rexuedongzuo", "热血动作"),
Pair("xiuji", "秀吉"),
Pair("AA", "AA"),
Pair("gaibian", "改编"),
Pair("juwei", "橘味")
)
)
private class letterfilter : UriSelectFilterPath(
"按字母",
"filterletter",
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 statusfilter : UriSelectFilterPath(
"按进度",
"filterstatus",
arrayOf(
Pair("", "全部"),
Pair("wanjie", "已完结"),
Pair("lianzai", "连载中")
)
)
/**
* Class that creates a select filter. Each entry in the dropdown has a name and a display name.
* If an entry is selected it is appended as a query parameter onto the end of the URI.
* If `firstIsUnspecified` is set to true, if the first entry is selected, nothing will be appended on the the URI.
*/
// vals: <name, display>
private open class UriSelectFilterPath(
displayName: String,
val uriParam: String,
val vals: Array<Pair<String, String>>,
val firstIsUnspecified: Boolean = true,
defaultValue: Int = 0
) :
Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray(), defaultValue), UriFilter {
override fun addToUri(uri: Uri.Builder) {
if (state != 0 || !firstIsUnspecified)
uri.appendPath(vals[state].first)
}
}
/**
* Represents a filter that is able to modify a URI.
*/
private interface UriFilter {
fun addToUri(uri: Uri.Builder)
}
}

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="eu.kanade.tachiyomi.extension" />

View File

@ -1,13 +0,0 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
ext {
extName = 'Imitui Manhua'
pkgNameSuffix = 'zh.imitui'
extClass = '.Imitui'
extVersionCode = 2
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

View File

@ -1,543 +0,0 @@
package eu.kanade.tachiyomi.extension.zh.imitui
import android.net.Uri
import eu.kanade.tachiyomi.network.GET
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.Request
import okhttp3.Response
import org.jsoup.nodes.Document
import org.jsoup.nodes.Element
class Imitui : ParsedHttpSource() {
override val name: String = "爱米推漫画"
override val lang: String = "zh"
override val supportsLatest: Boolean = true
override val baseUrl: String = "https://m.imitui.com"
// Popular
// Override to set hasNextPage according to page number
override fun popularMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(popularMangaSelector()).map { element ->
popularMangaFromElement(element)
}
val totalPage = document.select("input#total-page").attr("value").toInt()
val index = document.location().lastIndexOf("page=") + 5
val currentPage = document.location().substring(index).toInt()
val hasNextPage = currentPage < totalPage
return MangasPage(mangas, hasNextPage)
}
override fun popularMangaRequest(page: Int) = GET("$baseUrl/list/click/?page=$page", headers)
override fun popularMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not used.")
override fun popularMangaSelector(): String = "ul#comic-items > li.list-comic"
override fun popularMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("a.txtA").text()
setUrlWithoutDomain(element.select("a.txtA").attr("href"))
thumbnail_url = element.select("a.ImgA > img").attr("src")
}
// Latest
override fun latestUpdatesParse(response: Response): MangasPage = popularMangaParse(response)
override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/list/update/?page=$page", headers)
override fun latestUpdatesNextPageSelector(): String? = throw UnsupportedOperationException("Not used.")
override fun latestUpdatesSelector(): String = popularMangaSelector()
override fun latestUpdatesFromElement(element: Element): SManga = popularMangaFromElement(element)
// Search
// Override to set hasNextPage according to page number
override fun searchMangaParse(response: Response): MangasPage {
val document = response.asJsoup()
val mangas = document.select(searchMangaSelector()).map { element ->
searchMangaFromElement(element)
}
val totalPage = document.select("input#total-page").attr("value").toInt()
val index = document.location().lastIndexOf("page=") + 5
val currentPage = document.location().substring(index).toInt()
val hasNextPage = currentPage < totalPage
return MangasPage(mangas, hasNextPage)
}
override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request {
val uri = Uri.parse(baseUrl).buildUpon()
if (query.isNotBlank()) {
uri.appendPath("search")
.appendEncodedPath("")
.appendQueryParameter("keywords", query)
.appendQueryParameter("page", page.toString())
} else {
uri.appendPath("list")
val pathBuilder = Uri.Builder()
filters.forEach {
if (it is UriSelectFilterPath)
it.addToUri(pathBuilder)
}
val filterPath = pathBuilder.toString().replace("/", "-").removePrefix("-")
uri.appendEncodedPath(filterPath)
.appendEncodedPath("")
.appendQueryParameter("page", page.toString())
}
return GET(uri.toString(), headers)
}
override fun searchMangaNextPageSelector(): String? = throw UnsupportedOperationException("Not used.")
override fun searchMangaSelector(): String = "div.itemBox, li.list-comic"
override fun searchMangaFromElement(element: Element): SManga = SManga.create().apply {
title = element.select("a.title, a.txtA").text()
setUrlWithoutDomain(element.select("a.title, a.txtA").attr("href"))
thumbnail_url = element.select("div.itemImg > a > img, a.ImgA > img").attr("src")
}
// Details
override fun mangaDetailsParse(document: Document): SManga = SManga.create().apply {
title = document.select("div#comicName").text()
thumbnail_url = document.select("div#Cover > img").attr("abs:src")
author = document.select("div.sub_r > p:nth-child(1)").text()
artist = author
// Sometimes there is one a tag with href="/list/" and empty text, exclude it
val genreElement = document.select("div.sub_r > p.txtItme > a").not("[href=/list/]")
genre = genreElement.text().replace(" ", ", ")
description = if (document.select("p#full-des").isEmpty())
document.select("p#simple-des").text()
else
document.select("p#full-des").text()
status = when {
!document.select("p.txtItme > a[href=/list/lianzai/]").isEmpty() -> SManga.ONGOING
!document.select("p.txtItme > a[href=/list/wanjie/]").isEmpty() -> SManga.COMPLETED
else -> SManga.UNKNOWN
}
}
// Chapters
// The last element is not a real chapter, so exclude it
override fun chapterListSelector(): String = "ul#chapter-list-1 > li:nth-last-child(n+2)"
override fun chapterFromElement(element: Element): SChapter = SChapter.create().apply {
setUrlWithoutDomain(element.select("a").attr("href"))
name = element.select("span").text()
}
override fun chapterListParse(response: Response): List<SChapter> {
return super.chapterListParse(response).reversed()
}
// Pages
override fun pageListParse(document: Document): List<Page> = mutableListOf<Page>().apply {
val totalPage = document.select("div.image-content > p").text().substring(2)
val location = document.location()
add(Page(size, location))
val insertIndex = location.lastIndexOf(".html")
for (i in 2..totalPage.toInt()) {
add(Page(size, location.replaceRange(insertIndex, insertIndex, "-$i")))
}
}
override fun imageUrlParse(document: Document): String =
document.select("div.image-content > img#image").attr("src")
// Some images need referer to load
override fun imageRequest(page: Page): Request {
val newHeader = headers.newBuilder().add("Referer: https://m.imitui.com/").build()
return GET(page.imageUrl!!, newHeader)
}
// Filters
override fun getFilterList() = FilterList(
Filter.Header("如果使用文本搜索"),
Filter.Header("过滤器将被忽略"),
GenreFilter(),
ReaderFilter(),
StatusFilter(),
RegionFilter()
)
private class GenreFilter : UriSelectFilterPath(
"题材",
arrayOf(
Pair("", "全部"),
Pair("rexue", "热血"),
Pair("xuanhuan", "玄幻"),
Pair("xiuzhen", "修真"),
Pair("gufeng", "古风"),
Pair("lianai", "恋爱"),
Pair("chuanyue", "穿越"),
Pair("dushi", "都市"),
Pair("bazong", "霸总"),
Pair("xuanyi", "悬疑"),
Pair("gaoxiao", "搞笑"),
Pair("qihuan", "奇幻"),
Pair("zongcai", "总裁"),
Pair("richang", "日常"),
Pair("maoxian", "冒险"),
Pair("kehuan", "科幻"),
Pair("chunai", "纯爱"),
Pair("mohuan", "魔幻"),
Pair("zhanzheng", "战争"),
Pair("qiangwei", "蔷薇"),
Pair("wuxia", "武侠"),
Pair("shenghuo", "生活"),
Pair("dongzuo", "动作"),
Pair("hougong", "后宫"),
Pair("youxi", "游戏"),
Pair("kongbu", "恐怖"),
Pair("mangai", "漫改"),
Pair("zhenren", "真人"),
Pair("xiaoyuan", "校园"),
Pair("juqing", "剧情"),
Pair("lingyi", "灵异"),
Pair("shaonian", "少年"),
Pair("tuili", "推理"),
Pair("huaijiu", "怀旧"),
Pair("qinggan", "情感"),
Pair("ouxiang", "偶像"),
Pair("shaonv", "少女"),
Pair("dujia", "独家"),
Pair("nuexin", "虐心"),
Pair("baoxiao", "爆笑"),
Pair("lizhi", "励志"),
Pair("meishi", "美食"),
Pair("fuchou", "复仇"),
Pair("caihong", "彩虹"),
Pair("weimei", "唯美"),
Pair("zhiyu", "治愈"),
Pair("mingxing", "明星"),
Pair("naodong", "脑洞"),
Pair("mofa", "魔法"),
Pair("xiuxian", "修仙"),
Pair("zhongsheng", "重生"),
Pair("xianxia", "仙侠"),
Pair("moshi", "末世"),
Pair("yineng", "异能"),
Pair("nvzun", "女尊"),
Pair("qita", "其它"),
Pair("yanqing", "言情"),
Pair("danmei", "耽美"),
Pair("yundong", "运动"),
Pair("gongdou", "宫斗"),
Pair("guzhuang", "古装"),
Pair("meishaonv", "美少女"),
Pair("shenmo", "神魔"),
Pair("lishi", "历史"),
Pair("jingxian", "惊险"),
Pair("jingji", "竞技"),
Pair("mengxi", "萌系"),
Pair("tiyu", "体育"),
Pair("gedou", "格斗"),
Pair("jijia", "机甲"),
Pair("nuelian", "虐恋"),
Pair("shuang", ""),
Pair("fuli", "福利"),
Pair("qita2", "其他"),
Pair("xiaojiangshi", "小僵尸"),
Pair("jiangshi", "僵尸"),
Pair("langman", "浪漫"),
Pair("jinshouzhi", "金手指"),
Pair("yujie", "御姐"),
Pair("zhandou", "战斗"),
Pair("egao", "恶搞"),
Pair("shehui", "社会"),
Pair("quanmou", "权谋"),
Pair("qingchun", "青春"),
Pair("luoli", "萝莉"),
Pair("tongren", "同人"),
Pair("zhenhan", "震撼"),
Pair("riman", "日漫"),
Pair("junfa", "军阀"),
Pair("minguo", "民国"),
Pair("tegong", "特工"),
Pair("meinv", "美女"),
Pair("jiandie", "间谍"),
Pair("anhei", "暗黑"),
Pair("jiecao", "节操"),
Pair("jingdian", "经典"),
Pair("youmo", "幽默"),
Pair("tianchong", "甜宠"),
Pair("shenhua", "神话"),
Pair("riben", "日本"),
Pair("yijiyuan", "翼纪元"),
Pair("tiaoman", "条漫"),
Pair("LOL", "LOL"),
Pair("zhongtian", "种田"),
Pair("duanpian", "短篇"),
Pair("jingsong", "惊悚"),
Pair("sige", "四格"),
Pair("guoman", "国漫"),
Pair("youqudao", "有趣岛"),
Pair("mengchong", "萌宠"),
Pair("renxing", "人性"),
Pair("chonghun", "宠婚"),
Pair("xinqi", "新妻"),
Pair("xixiegui", "吸血鬼"),
Pair("shenjiemanhua", "神界漫画"),
Pair("xueyuehua", "雪月花"),
Pair("mengqishi", "梦骑士"),
Pair("shouer", "兽耳"),
Pair("shaoer", "少儿"),
Pair("baihe", "百合"),
Pair("chiji", "吃鸡"),
Pair("qiangzhan", "枪战"),
Pair("tezhongbing", "特种兵"),
Pair("xiongdi", "兄弟"),
Pair("yishi", "异世"),
Pair("xiongmei", "兄妹"),
Pair("sanciyuan", "三次元"),
Pair("meixing", "美型"),
Pair("haomen", "豪门"),
Pair("hunchong", "婚宠"),
Pair("kaigua", "开挂"),
Pair("xuexing", "血腥"),
Pair("qingsong", "轻松"),
Pair("yangcheng", "养成"),
Pair("tishen", "替身"),
Pair("nanshen", "男神"),
Pair("qingqingshu", "青青树"),
Pair("yishijie", "异世界"),
Pair("nanchuannv", "男穿女"),
Pair("hunchuan", "魂穿"),
Pair("yinan", "阴暗"),
Pair("dujitang", "毒鸡汤"),
Pair("pianyu", "片玉"),
Pair("manhuahui", "漫画会"),
Pair("longren", "龙刃"),
Pair("xihuan", "喜欢"),
Pair("zhaohuan", "召唤"),
Pair("yijie", "异界"),
Pair("henxiyou", "狠西游"),
Pair("xiyouji", "西游记"),
Pair("jianghu", "江湖"),
Pair("duantoudao", "断头岛"),
Pair("hanman", "韩漫"),
Pair("bingjiao", "病娇"),
Pair("changpian", "长篇"),
Pair("chenlan", "陈岚"),
Pair("aiqing", "爱情"),
Pair("nvqiang", "女强"),
Pair("ranxiang", "燃向"),
Pair("tianshangkong", "天上空"),
Pair("duzhiniao", "渡之鸟"),
Pair("xuezu", "血族"),
Pair("mowang", "魔王"),
Pair("keai", "可爱"),
Pair("gongting", "宫廷"),
Pair("hunlian", "婚恋"),
Pair("meng", ""),
Pair("ashuai", "阿衰"),
Pair("sanjiaolian", "三角恋"),
Pair("qianshi", "前世"),
Pair("lunhui", "轮回"),
Pair("jingqi", "惊奇"),
Pair("zhentan", "侦探"),
Pair("huanlexiang", "欢乐向"),
Pair("zhichang", "职场"),
Pair("gandong", "感动"),
Pair("jiakong", "架空"),
Pair("qingxiaoshuo", "轻小说"),
Pair("yanyi", "颜艺"),
Pair("xingzhuanhuan", "性转换"),
Pair("dongfang", "东方"),
Pair("danmeiBL", "耽美BL"),
Pair("qingsonggaoxiao", "轻松搞笑"),
Pair("tongrenmanhua", "同人漫画"),
Pair("xiaoyuangaoxiaoshenghuo", "校园搞笑生活"),
Pair("shaonvaiqing", "少女爱情"),
Pair("zhengju", "正剧"),
Pair("shaonao", "烧脑"),
Pair("zhuangbi", "装逼"),
Pair("shengui", "神鬼"),
Pair("weiniang", "伪娘"),
Pair("gaoqingdanxing", "高清单行"),
Pair("gushimanhua", "故事漫画"),
Pair("lianaishenghuoxuanhuan", "恋爱生活玄幻"),
Pair("xifangmohuan", "西方魔幻"),
Pair("jianniang", "舰娘"),
Pair("zhaixi", "宅系"),
Pair("shangzhan", "商战"),
Pair("shuangliu", "爽流"),
Pair("rexuemaoxian", "热血冒险"),
Pair("keji", "科技"),
Pair("wenxin", "温馨"),
Pair("jiating", "家庭"),
Pair("hunyin", "婚姻"),
Pair("duanzi", "段子"),
Pair("neihan", "内涵"),
Pair("jizhan", "机战"),
Pair("yulequan", "娱乐圈"),
Pair("weilai", "未来"),
Pair("chongwu", "宠物"),
Pair("bazonglianaixuanhuan", "霸总恋爱玄幻"),
Pair("gushi", "故事"),
Pair("yinyuewudao", "音乐舞蹈"),
Pair("nixi", "逆袭"),
Pair("zhaohuanshou", "召唤兽"),
Pair("kehuanmohuan", "科幻魔幻"),
Pair("jiujie", "纠结"),
Pair("lunli", "伦理"),
Pair("lianaishenghuo", "恋爱生活"),
Pair("xinzuo", "新作"),
Pair("lishimanhua", "历史漫画"),
Pair("ertong", "儿童"),
Pair("zhentantuili", "侦探推理"),
Pair("xiuzhenlianaijiakong", "修真恋爱架空"),
Pair("shougong", "手工"),
Pair("qingnian", "青年"),
Pair("qitamanhua", "其他漫画"),
Pair("zhiyu2", "致郁"),
Pair("shishi", "史诗"),
Pair("xiuji", "秀吉"),
Pair("xiangcun", "乡村"),
Pair("xingzhuan", "性转"),
Pair("hunai", "婚爱"),
Pair("siwang", "死亡"),
Pair("sishen", "死神"),
Pair("shaonan", "少男"),
Pair("xuanyijingsong", "悬疑、惊悚"),
Pair("baoxiaoxiju", "爆笑喜剧"),
Pair("dongzuogedou", "动作格斗"),
Pair("gaibian", "改编"),
Pair("AA", "AA"),
Pair("lianaidanmei", "恋爱耽美"),
Pair("heidao", "黑道"),
Pair("guiguai", "鬼怪"),
Pair("sangshi", "丧尸"),
Pair("zhupu", "主仆"),
Pair("zhiyinmanke", "知音漫客"),
Pair("maimeng", "麦萌"),
Pair("nizhuan", "逆转"),
Pair("danvzhu", "大女主"),
Pair("aimei", "暧昧"),
Pair("shenghua", "生化"),
Pair("qiwen", "奇闻"),
Pair("zhaidou", "宅斗"),
Pair("lanmu", "栏目"),
Pair("guaitan", "怪谈"),
Pair("chongai", "宠爱"),
Pair("huanxiang", "幻想"),
Pair("yizu", "异族"),
Pair("tanan", "探案"),
Pair("panni", "叛逆"),
Pair("juwei", "橘味"),
Pair("yinv", "乙女"),
Pair("lieqi", "猎奇"),
Pair("rigeng", "日更"),
Pair("manman", "漫漫"),
Pair("zhidou", "智斗"),
Pair("zhengnengliang", "正能量"),
Pair("manhuayifan", "漫画一番"),
Pair("nvwangdiankeng", "女王点坑"),
Pair("mankezhan", "漫客栈"),
Pair("samanhua", "飒漫画"),
Pair("xiaoshuogaibian", "小说改编"),
Pair("shenshi", "绅士"),
Pair("kongbuxuanyi", "恐怖悬疑"),
Pair("huiben", "绘本"),
Pair("yinyue", "音乐"),
Pair("huxian", "狐仙"),
Pair("sihoushijie", "死后世界"),
Pair("motong", "墨瞳"),
Pair("manhua", "漫画"),
Pair("mori", "末日"),
Pair("xitong", "系统"),
Pair("shenxian", "神仙"),
Pair("youyaoqi", "有妖气"),
Pair("guaiwu", "怪物"),
Pair("yaoguai", "妖怪"),
Pair("shenhao", "神豪"),
Pair("bazongdushi", "霸总.都市"),
Pair("gaotian", "高甜"),
Pair("xianzhiji", "限制级"),
Pair("dianjing", "电竞"),
Pair("unknown", "ゆり"),
Pair("xiongdiqing", "兄弟情"),
Pair("nuanmeng", "暖萌"),
Pair("haokuai", "豪快"),
Pair("wanjie", "完结"),
Pair("nvsheng", "女生"),
Pair("lianzai", "连载"),
Pair("nansheng", "男生"),
Pair("futa", "扶她"),
Pair("bianshenlongyurentishiyanshenghuaweiji", "变身;龙鱼;人体实验;生化危机"),
Pair("shenghuomanhua", "生活漫画"),
Pair("huanxi", "欢喜"),
Pair("beiou", "北欧"),
Pair("fuhei", "腹黑"),
Pair("xihuan2", "西幻"),
Pair("qinqing", "亲情"),
Pair("gudai", "古代"),
Pair("jifu", "基腐"),
Pair("langmanaiqing", "浪漫爱情"),
Pair("BL", "BL"),
Pair("qihuanmaoxian", "奇幻冒险"),
Pair("youmogaoxiao", "幽默搞笑"),
Pair("gufengchuanyue", "古风穿越"),
Pair("zongheqita", "综合其它"),
Pair("TS", "TS")
)
)
private class ReaderFilter : UriSelectFilterPath(
"读者",
arrayOf(
Pair("", "全部"),
Pair("ertong", "儿童漫画"),
Pair("shaonian", "少年漫画"),
Pair("shaonv", "少女漫画"),
Pair("qingnian", "青年漫画")
)
)
private class StatusFilter : UriSelectFilterPath(
"进度",
arrayOf(
Pair("", "全部"),
Pair("wanjie", "已完结"),
Pair("lianzai", "连载中")
)
)
private class RegionFilter : UriSelectFilterPath(
"地区",
arrayOf(
Pair("", "全部"),
Pair("riben", "日本"),
Pair("dalu", "大陆"),
Pair("hongkong", "香港"),
Pair("taiwan", "台湾"),
Pair("oumei", "欧美"),
Pair("hanguo", "韩国"),
Pair("qita", "其他")
)
)
/**
* Class that creates a select filter. Each entry in the dropdown has a name and a display name.
* If an entry is selected it is appended as a query parameter onto the end of the URI.
*/
// vals: <name, display>
private open class UriSelectFilterPath(
displayName: String,
val vals: Array<Pair<String, String>>
) : Filter.Select<String>(displayName, vals.map { it.second }.toTypedArray()) {
fun addToUri(uri: Uri.Builder) {
if (state != 0)
uri.appendPath(vals[state].first)
}
}
}