diff --git a/src/all/xgmn/build.gradle b/src/all/xgmn/build.gradle new file mode 100644 index 000000000..46d43b2af --- /dev/null +++ b/src/all/xgmn/build.gradle @@ -0,0 +1,8 @@ +ext { + extName = 'XGMN' + extClass = '.XGMN' + extVersionCode = 1 + isNsfw = true +} + +apply from: "$rootDir/common.gradle" diff --git a/src/all/xgmn/res/mipmap-hdpi/ic_launcher.png b/src/all/xgmn/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 000000000..1e6518a26 Binary files /dev/null and b/src/all/xgmn/res/mipmap-hdpi/ic_launcher.png differ diff --git a/src/all/xgmn/res/mipmap-mdpi/ic_launcher.png b/src/all/xgmn/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 000000000..b0316c25a Binary files /dev/null and b/src/all/xgmn/res/mipmap-mdpi/ic_launcher.png differ diff --git a/src/all/xgmn/res/mipmap-xhdpi/ic_launcher.png b/src/all/xgmn/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 000000000..fc7cbd0bf Binary files /dev/null and b/src/all/xgmn/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/src/all/xgmn/res/mipmap-xxhdpi/ic_launcher.png b/src/all/xgmn/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 000000000..dd6409e18 Binary files /dev/null and b/src/all/xgmn/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/src/all/xgmn/res/mipmap-xxxhdpi/ic_launcher.png b/src/all/xgmn/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 000000000..87586a551 Binary files /dev/null and b/src/all/xgmn/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/Filters.kt b/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/Filters.kt new file mode 100644 index 000000000..b7def7009 --- /dev/null +++ b/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/Filters.kt @@ -0,0 +1,86 @@ +package eu.kanade.tachiyomi.extension.all.xgmn + +import eu.kanade.tachiyomi.source.model.Filter +import eu.kanade.tachiyomi.source.model.FilterList + +fun buildFilterList(): FilterList { + return FilterList( + CategoryFilter(), + ) +} + +class CategoryFilter : Filter.Select( + "分类(搜索时无效)", + arrayOf( + "秀人网", "美媛馆", "尤物馆", "爱蜜社", "蜜桃社", "优星馆", "嗲囡囡", + "魅妍社", "兔几盟", "影私荟", "星乐园", "顽味生活", "模范学院", + "花の颜", "御女郎", "网红馆", "尤蜜荟", "薄荷叶", "瑞丝馆", "模特联盟", + "花漾", "星颜社", "画语界", "推女郎", "尤果网", "青豆客", "头条女神", + "果团网", "喵糖映画", "爱尤物", "波萝社", "猎女神", "尤蜜", "潘多拉", + "Artgravia", "DJAWA", "丝袜美腿", "美腿宝贝", "蜜丝", "妖精社", + "性感尤物", "国产美女", "港台美女", "日韩美女", "欧美美女", "丝袜美腿", + "内衣尤物", "Cosplay", + ), +) { + override fun toString() = arrayOf( + "Xiuren/", "MyGirl/", "YouWu/", "IMiss/", "MiiTao/", "Uxing/", "FeiLin/", + "MiStar/", "Tukmo/", "WingS/", "LeYuan/", "Taste/", "MFStar/", "Huayan/", + "DKGirl/", "Candy/", "YouMi/", "MintYe/", "Micat/", "Mtmeng/", "HuaYang/", + "XingYan/", "XiaoYu/", "Tuigirl/", "Ugirls/", "Tgod/", "TouTiao/", "Girlt/", + "Mtcos/", "Aiyouwu/", "BoLoli/", "Slady/", "YouMei/", "Pdl/", "Artgravia/", + "DJAWA/", "Siwameitui/", "LEGBABY/", "MissLeg/", "YaoJingShe/", "Xgyw/", + "Guochanmeinv/", "Gangtaimeinv/", "Rihanmeinv/", "Oumeimeinv/", + "Siwameitui/", "Neiyiyouwu/", "Cosplay/", + )[state] +} + +// class XiurenFilter : Filter.Select( +// "秀人系列", +// arrayOf( +// "秀人网", "美媛馆", "尤物馆", "爱蜜社", "蜜桃社", "优星馆", "嗲囡囡", +// "魅妍社", "兔几盟", "影私荟", "星乐园", "顽味生活", "模范学院", +// "花の颜", "御女郎", "网红馆", "尤蜜荟", "薄荷叶", "瑞丝馆", "模特联盟", +// "花漾", "星颜社", "画语界", +// ), +// ) { +// val value +// get() = arrayOf( +// "Xiuren", "MyGirl", "YouWu", "IMiss", "MiiTao", "Uxing", "FeiLin", +// "MiStar", "Tukmo", "WingS", "LeYuan", "Taste", "MFStar", "Huayan", +// "DKGirl", "Candy", "YouMi", "MintYe", "Micat", "Mtmeng", "HuaYang", +// "XingYan", "XiaoYu", +// )[state] +// } +// +// class MingzhanFilter : Filter.Select( +// "名站系列", +// arrayOf( +// "推女郎", "尤果网", "青豆客", "头条女神", "果团网", "喵糖映画", +// "爱尤物", "波萝社", "猎女神", "尤蜜", "潘多拉", "Artgravia", "DJAWA", +// ), +// ) { +// val value +// get() = arrayOf( +// "Tuigirl", "Ugirls", "Tgod", "TouTiao", "Girlt", "Mtcos", "Aiyouwu", +// "BoLoli", "Slady", "YouMei", "Pdl", "Artgravia", "DJAWA", +// )[state] +// } +// +// class SiwameituiFilter : +// Filter.Select("丝袜美腿", arrayOf("丝袜美腿", "美腿宝贝", "蜜丝", "妖精社")) { +// val value get() = arrayOf("siwametui", "LEGBABY", "MissLeg", "YaoJingShe")[state] +// } +// +// class JingpinFilter : Filter.Select( +// "精品散图", +// arrayOf( +// "性感尤物", "国产美女", "港台美女", "日韩美女", +// "欧美美女", "丝袜美腿", "内衣尤物", "Cosplay", +// ), +// ) { +// val value +// get() = arrayOf( +// "Xgyw", "Guochanmeinv", "Gangtaimeinv", "Rihanmeinv", +// "Oumeimeinv", "Siwameitui", "Neiyiyouwu", "Cosplay", +// )[state] +// } diff --git a/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/XGMN.kt b/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/XGMN.kt new file mode 100644 index 000000000..82b2dd331 --- /dev/null +++ b/src/all/xgmn/src/eu/kanade/tachiyomi/extension/all/xgmn/XGMN.kt @@ -0,0 +1,149 @@ +package eu.kanade.tachiyomi.extension.all.xgmn + +import eu.kanade.tachiyomi.network.GET +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.model.UpdateStrategy +import eu.kanade.tachiyomi.source.online.HttpSource +import eu.kanade.tachiyomi.util.asJsoup +import keiyoushi.utils.tryParse +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.Request +import okhttp3.Response +import java.text.SimpleDateFormat +import java.util.Locale + +class XGMN : HttpSource() { + override val baseUrl get() = redirectUrl ?: "http://xgmn8.vip" + override val lang = "all" + override val name = "性感美女" + override val supportsLatest = true + private var redirectUrl: String? = null + + companion object { + val ID_REGEX = Regex("\\d+(?=\\.html)") + val PAGE_SIZE_REGEX = Regex("\\d+(?=P)") + val DATE_FORMAT = SimpleDateFormat("yyyy.MM.dd", Locale.CHINA) + } + + private fun getUrlWithoutDomain(url: String): String { + val prefix = listOf("http://", "https://").firstOrNull(url::startsWith) + return url.substringAfter(prefix ?: "").substringAfter('/') + } + + // Popular Page + + override fun popularMangaRequest(page: Int) = GET("$baseUrl/top.html", headers) + + override fun popularMangaParse(response: Response) = response.asJsoup().let { doc -> + redirectUrl = redirectUrl ?: doc.location().toHttpUrl().let { "${it.scheme}://${it.host}" } + MangasPage( + doc.select(".related_box").map { + SManga.create().apply { + thumbnail_url = it.selectFirst("img")?.absUrl("src") + it.selectFirst("a")!!.let { + title = it.attr("title") + setUrlWithoutDomain(it.absUrl("href")) + } + } + }, + false, + ) + } + + // Latest Page + + override fun latestUpdatesRequest(page: Int) = GET("$baseUrl/new.html", headers) + + override fun latestUpdatesParse(response: Response) = popularMangaParse(response) + + // Search Page + + override fun getFilterList() = buildFilterList() + + override fun searchMangaRequest(page: Int, query: String, filters: FilterList): Request { + val url = baseUrl.toHttpUrl().newBuilder() + if (query.isNotBlank()) { + url.addPathSegments("/plus/search/index.asp") + .addQueryParameter("keyword", query) + .addQueryParameter("p", page.toString()) + } else { + url.addPathSegments(filters.first().toString()) + if (page > 1) url.addPathSegment("page_$page.html") + } + return GET(url.build()) + } + + override fun searchMangaParse(response: Response) = + if (response.request.url.pathSegments.contains("search")) { + val doc = response.asJsoup() + redirectUrl = redirectUrl ?: doc.location().toHttpUrl().let { "${it.scheme}://${it.host}" } + val current = doc.selectFirst(".current")!!.text().toInt() + MangasPage( + doc.select(".node > p > a").map { + SManga.create().apply { + title = it.text() + setUrlWithoutDomain(it.absUrl("href")) + thumbnail_url = "$baseUrl/uploadfile/pic/${ID_REGEX.find(url)?.value}.jpg" + } + }, + current < doc.select(".list .pagination a").size, + ) + } else { + popularMangaParse(response) + } + + // Manga Detail Page + + override fun mangaDetailsParse(response: Response) = response.asJsoup().let { doc -> + redirectUrl = redirectUrl ?: doc.location().toHttpUrl().let { "${it.scheme}://${it.host}" } + SManga.create().apply { + author = doc.selectFirst(".item-2")?.text()?.substringAfter("模特:") + update_strategy = UpdateStrategy.ONLY_FETCH_ONCE + status = SManga.COMPLETED + } + } + + // Manga Detail Page / Chapters Page (Separate) + + override fun chapterListParse(response: Response) = response.asJsoup().let { doc -> + redirectUrl = redirectUrl ?: doc.location().toHttpUrl().let { "${it.scheme}://${it.host}" } + listOf( + SChapter.create().apply { + setUrlWithoutDomain(doc.selectFirst(".current")!!.absUrl("href")) + name = doc.selectFirst(".article-title")!!.text() + chapter_number = 1F + date_upload = DATE_FORMAT.tryParse( + doc.selectFirst(".item-1")?.text()?.substringAfter("更新:"), + ) + }, + ) + } + + // Manga View Page + + override fun pageListParse(response: Response) = response.asJsoup().let { doc -> + val prefix = doc.selectFirst(".current")!!.absUrl("href").substringBeforeLast(".html") + val total = PAGE_SIZE_REGEX.find(doc.selectFirst(".article-title")!!.text())!!.value + val size = doc.select(".article-content > p[style] > img").size + List(total.toInt()) { + Page( + it, + prefix + (it / size).let { v -> if (v == 0) "" else "_$v" } + ".html#${it % size + 1}", + ) + } + } + + // Image + + override fun imageUrlParse(response: Response): String { + val seq = response.request.url.fragment!! + val url = response.asJsoup() + .selectXpath("//*[contains(@class,'article-content')]/p[@*[contains(.,'center')]]/img[position()=$seq]") + .first() ?: throw Exception("没找到图片") + return "$baseUrl/${getUrlWithoutDomain(url.absUrl("src"))}" + } +}