Some chinese sources update (#1022)
Some checks failed
CI / Prepare job (push) Successful in 18s
CI / Build individual modules (push) Failing after 4m11s
CI / Publish repo (push) Has been skipped

Close #957

Checklist:

- [x] Updated `extVersionCode` value in `build.gradle` for individual extensions
- [ ] Updated `overrideVersionCode` or `baseVersionCode` as needed for all multisrc extensions
- [x] Referenced all related issues in the PR body (e.g. "Closes #xyz")
- [ ] Added the `isNsfw = true` flag in `build.gradle` when appropriate
- [x] Have not changed source names
- [ ] Have explicitly kept the `id` if a source's name or language were changed
- [x] Have tested the modifications by compiling and running the extension through Android Studio
- [ ] Have removed `web_hi_res_512.png` when adding a new extension
- [ ] Have made sure all the icons are in png format

Co-authored-by: AlphaBoom <30779939+AlphaBoom@users.noreply.github.com>
Co-authored-by: ZhendongWu <30779939+AlphaBoom@users.noreply.github.com>
Reviewed-on: #1022
Co-authored-by: AlphaBoom <alphaboom@noreply.localhost>
Co-committed-by: AlphaBoom <alphaboom@noreply.localhost>
This commit is contained in:
AlphaBoom 2025-06-20 01:05:04 -05:00 committed by AlmightyHak
parent f97d742c40
commit 1018982a2e
4 changed files with 50 additions and 29 deletions

View file

@ -1,7 +1,7 @@
ext {
extName = 'Hanime1'
extClass = '.Hanime1'
extVersionCode = 4
extVersionCode = 5
isNsfw = true
}

View file

@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.animeextension.zh.hanime1
import android.app.Application
import android.content.SharedPreferences
import android.util.Log
import androidx.preference.ListPreference
import androidx.preference.PreferenceScreen
import eu.kanade.tachiyomi.animesource.ConfigurableAnimeSource
@ -16,12 +17,15 @@ import eu.kanade.tachiyomi.network.GET
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonElement
import kotlinx.serialization.json.jsonArray
import kotlinx.serialization.json.jsonObject
import kotlinx.serialization.json.jsonPrimitive
import okhttp3.Cookie
@ -65,14 +69,32 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
}
override fun animeDetailsParse(response: Response): SAnime {
val jsoup = response.asJsoup()
val doc = response.asJsoup()
return SAnime.create().apply {
genre = jsoup.select(".single-video-tag").not("[data-toggle]").eachText().joinToString()
author = jsoup.select("#video-artist-name").text()
jsoup.select("script[type=application/ld+json]").first()?.data()?.let {
genre = doc.select(".single-video-tag").not("[data-toggle]").eachText().joinToString()
author = doc.select("#video-artist-name").text()
doc.select("script[type=application/ld+json]").first()?.data()?.let {
val info = json.decodeFromString<JsonElement>(it).jsonObject
title = info["name"]!!.jsonPrimitive.content
description = info["description"]!!.jsonPrimitive.content
thumbnail_url = info["thumbnailUrl"]?.jsonArray?.get(0)?.jsonPrimitive?.content
}
val type = doc.select("a#video-artist-name + a").text().trim()
if (type == "裏番" || type == "泡麵番") {
// Use the series cover image for bangumi entries instead of the episode image.
runBlocking {
try {
val animesPage =
getSearchAnime(
1,
title,
AnimeFilterList(GenreFilter(arrayOf("", type)).apply { state = 1 }),
)
thumbnail_url = animesPage.animes.first().thumbnail_url
} catch (e: Exception) {
Log.e(name, "Failed to get bangumi cover image")
}
}
}
}
}
@ -137,7 +159,7 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
override fun searchAnimeParse(response: Response): AnimesPage {
val jsoup = response.asJsoup()
val nodes = jsoup.select("div.search-doujin-videos.hidden-xs")
val nodes = jsoup.select("div.search-doujin-videos.hidden-xs:not(:has(a[target=_blank]))")
val list = if (nodes.isNotEmpty()) {
nodes.map {
SAnime.create().apply {
@ -216,11 +238,12 @@ class Hanime1 : AnimeHttpSource(), ConfigurableAnimeSource {
return chain.proceed(chain.request())
}
@OptIn(DelicateCoroutinesApi::class)
private fun updateFilters() {
filterUpdateState = FilterUpdateState.UPDATING
val exceptionHandler =
CoroutineExceptionHandler { _, _ -> filterUpdateState = FilterUpdateState.FAILED }
CoroutineScope(Dispatchers.IO + exceptionHandler).launch {
GlobalScope.launch(Dispatchers.IO + exceptionHandler) {
val jsoup = client.newCall(GET("$baseUrl/search")).awaitSuccess().asJsoup()
val genreList = jsoup.select("div.genre-option div.hentai-sort-options").eachText()
val sortList =

View file

@ -1,7 +1,7 @@
ext {
extName = 'Xfani'
extClass = '.Xfani'
extVersionCode = 5
extVersionCode = 6
}
apply from: "$rootDir/common.gradle"

View file

@ -20,8 +20,9 @@ import eu.kanade.tachiyomi.network.POST
import eu.kanade.tachiyomi.network.awaitSuccess
import eu.kanade.tachiyomi.util.asJsoup
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import kotlinx.serialization.json.jsonObject
@ -53,7 +54,7 @@ enum class FilterUpdateState {
class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
override val baseUrl: String
get() = "https://dick.xfani.com"
get() = "https://dm.xifanacg.com"
override val lang: String
get() = "zh"
override val name: String
@ -117,10 +118,16 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
}
override fun animeDetailsParse(response: Response): SAnime {
val jsoup = response.asJsoup()
val doc = response.asJsoup()
return SAnime.create().apply {
description = jsoup.select("#height_limit.text").text()
title = jsoup.select(".slide-info-title").text()
description = doc.select("#height_limit.text").text()
title = doc.select(".slide-info-title").text()
author = doc.select(".slide-info:contains(导演 :)").text().removePrefix("导演 :")
.removeSuffix(",")
artist = doc.select(".slide-info:contains(演员 :)").text().removePrefix("演员 :")
.removeSuffix(",")
genre = doc.select(".slide-info:contains(类型 :)").text().removePrefix("类型 :")
.removeSuffix(",").replace(",", ", ")
}
}
@ -234,19 +241,13 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
return vodListToAnimePageList(response)
}
val jsoup = response.asJsoup()
val items = jsoup.select("div.public-list-box.search-box.flex.rel")
val items = jsoup.select("div.search-list")
val animeList = items.map { item ->
SAnime.create().apply {
title = item.select(".thumb-txt").text()
url = item.select("div.left.public-list-bj a.public-list-exp").attr("href")
title = item.select("div.detail-info > a").text()
url = item.select("div.detail-info > a").attr("href")
thumbnail_url =
item.select("div.left.public-list-bj img[data-src]").attr("data-src")
author = item.select("div.thumb-actor").text().removeSuffix("/")
artist = item.select("div.thumb-director").text().removeSuffix("/")
description = item.select(".thumb-blurb").text()
genre = item.select("div.thumb-else").text()
val statusString = item.select("div.left.public-list-bj .public-list-prb").text()
status = STATUS_STR_MAPPING.getOrElse(statusString) { SAnime.ONGOING }
item.select("div.detail-pic img[data-src]").attr("data-src")
}
}
val tip = jsoup.select("div.pages div.page-tip").text()
@ -259,12 +260,13 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
return numbers.size == 2 && numbers[0] != numbers[1]
}
@OptIn(DelicateCoroutinesApi::class)
private fun updateFilter() {
filterState = FilterUpdateState.UPDATING
val handler = CoroutineExceptionHandler { _, _ ->
filterState = FilterUpdateState.FAILED
}
CoroutineScope(Dispatchers.IO + handler).launch {
GlobalScope.launch(Dispatchers.IO + handler) {
val jsoup = client.newCall(GET("$baseUrl/show/1/html")).awaitSuccess().asJsoup()
// update class and year filter type
val classList = jsoup.select("li[data-type=class]").eachAttr("data-val")
@ -393,9 +395,5 @@ class Xfani : AnimeHttpSource(), ConfigurableAnimeSource {
const val PREF_KEY_FILTER_YEAR = "PREF_KEY_FILTER_YEAR"
const val DEFAULT_VIDEO_SOURCE = "0"
val STATUS_STR_MAPPING = mapOf(
"已完结" to SAnime.COMPLETED,
)
}
}