Add Xgmn Source (#10067)

* init

* complete

* complete

* fix

* apply commit

* move to all
This commit is contained in:
Hualiang 2025-08-11 10:49:33 +08:00 committed by Draff
parent f9e6919908
commit d514463edd
Signed by: Draff
GPG Key ID: E8A89F3211677653
8 changed files with 243 additions and 0 deletions

View File

@ -0,0 +1,8 @@
ext {
extName = 'XGMN'
extClass = '.XGMN'
extVersionCode = 1
isNsfw = true
}
apply from: "$rootDir/common.gradle"

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

View File

@ -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<String>(
"分类(搜索时无效)",
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<String>(
// "秀人系列",
// 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<String>(
// "名站系列",
// 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<String>("丝袜美腿", arrayOf("丝袜美腿", "美腿宝贝", "蜜丝", "妖精社")) {
// val value get() = arrayOf("siwametui", "LEGBABY", "MissLeg", "YaoJingShe")[state]
// }
//
// class JingpinFilter : Filter.Select<String>(
// "精品散图",
// arrayOf(
// "性感尤物", "国产美女", "港台美女", "日韩美女",
// "欧美美女", "丝袜美腿", "内衣尤物", "Cosplay",
// ),
// ) {
// val value
// get() = arrayOf(
// "Xgyw", "Guochanmeinv", "Gangtaimeinv", "Rihanmeinv",
// "Oumeimeinv", "Siwameitui", "Neiyiyouwu", "Cosplay",
// )[state]
// }

View File

@ -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"))}"
}
}