Skip to content
This repository was archived by the owner on Jul 18, 2026. It is now read-only.

Commit 599629d

Browse files
committed
支持getchu的新番预告
1 parent 53dd4e9 commit 599629d

27 files changed

Lines changed: 1931 additions & 48 deletions

app/src/main/java/com/yenaly/han1meviewer/Constants.kt

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ val LOCAL_DATE_TIME_FORMAT = LocalDateTime.Format {
2727
// 网络基本设置
2828

2929
const val USER_AGENT =
30-
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Mobile Safari/537.36"
30+
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Mobile Safari/537.36"
31+
const val DESKTOP_USER_AGENT =
32+
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"
3133

3234
// 設置發佈日期年份,在搜索的tag裏
3335

@@ -41,37 +43,6 @@ const val SEARCH_YEAR_RANGE_START = 1990
4143
*/
4244
const val SEARCH_YEAR_RANGE_END = BuildConfig.SEARCH_YEAR_RANGE_END
4345

44-
// intent傳值用名稱
45-
46-
const val FROM_DOWNLOAD = "FROM_DOWNLOAD"
47-
48-
const val VIDEO_CODE = "VIDEO_CODE"
49-
50-
//@Deprecated("Use [ADVANCED_SEARCH_MAP] instead")
51-
//const val FROM_VIDEO_TAG = "FROM_VIDEO_TAG"
52-
53-
/**
54-
* 接受类型 [AdvancedSearchMap] 或者 [String]
55-
*/
56-
const val ADVANCED_SEARCH_MAP = "ADVANCED_SEARCH_MAP"
57-
58-
const val COMMENT_ID = "COMMENT_ID"
59-
60-
const val DATE_CODE = "DATE_CODE"
61-
62-
//const val CSRF_TOKEN = "CSRF_TOKEN"
63-
64-
// Result Code
65-
66-
//const val LOGIN_TO_MAIN_ACTIVITY = 0
67-
68-
// 给rv传值,判断布局需要wrap_content还是match_parent,不填则为默认
69-
// 设置布局为MATCH_PARENT可以使rv在GridLayoutManager下能居中,反之不能
70-
71-
const val VIDEO_LAYOUT_WRAP_CONTENT = 1
72-
73-
const val VIDEO_LAYOUT_MATCH_PARENT = 2
74-
7546
const val VIDEO_COMMENT_PREFIX = "video"
7647

7748
const val PREVIEW_COMMENT_PREFIX = "preview"
@@ -124,3 +95,4 @@ const val UPDATE_NOTIFICATION_CHANNEL = "update_channel"
12495
// File
12596

12697
const val FILE_PROVIDER_AUTHORITY = "${BuildConfig.APPLICATION_ID}.fileProvider"
98+
const val GETCHU_BASE_URL = "https://www.getchu.com/"
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package com.yenaly.han1meviewer.logic
2+
3+
import android.util.Log
4+
import com.yenaly.han1meviewer.EMPTY_STRING
5+
import com.yenaly.han1meviewer.logic.NetworkRepo.handleException
6+
import com.yenaly.han1meviewer.logic.NetworkRepo.throwRequestException
7+
import com.yenaly.han1meviewer.logic.network.HanimeNetwork
8+
import com.yenaly.han1meviewer.logic.state.WebsiteState
9+
import kotlinx.coroutines.Dispatchers
10+
import kotlinx.coroutines.flow.catch
11+
import kotlinx.coroutines.flow.flow
12+
import kotlinx.coroutines.flow.flowOn
13+
import okhttp3.ResponseBody
14+
import retrofit2.Response
15+
import java.nio.charset.Charset
16+
import kotlin.runCatching
17+
18+
object GetchuNetworkRepo {
19+
val GETCHU_CHARSET: Charset = Charset.forName("EUC-JP")
20+
fun getGetchuPreview(date: String) = websiteIOFlow(
21+
request = {
22+
HanimeNetwork.getchuService.getPreviewList(
23+
year = date.take(4),
24+
month = date.takeLast(2),
25+
)
26+
},
27+
bodyToString = { it.getchuString() },
28+
action = { GetchuParser.getchuPreview(it, date) }
29+
)
30+
fun getGetchuPreviewDetail(id: String) = websiteIOFlow(
31+
request = { HanimeNetwork.getchuService.getPreviewDetail(id) },
32+
bodyToString = { it.getchuString() },
33+
) { body ->
34+
val detailState = GetchuParser.getchuPreviewDetail(body, id)
35+
if (detailState !is WebsiteState.Success) return@websiteIOFlow detailState
36+
37+
val parentId = body.extractGetchuSeriesParentId() ?: return@websiteIOFlow detailState
38+
runCatching {
39+
val response = HanimeNetwork.getchuService.getSeriesItems(parentIdArray = parentId)
40+
if (!response.isSuccessful) return@runCatching emptyList()
41+
response.body()?.getchuString()?.let(GetchuParser::getchuSeriesItems).orEmpty()
42+
}.getOrDefault(emptyList()).let { seriesItems ->
43+
Log.d(
44+
"GetchuPreviewParser",
45+
"series ajax id=$id parentId=$parentId items=${seriesItems.size}"
46+
)
47+
if (seriesItems.isEmpty()) {
48+
detailState
49+
} else {
50+
val detail = detailState.info
51+
val mergedSeriesItems = (detail.seriesItems + seriesItems)
52+
.distinctBy { it.id }
53+
.filterNot { it.id == id }
54+
WebsiteState.Success(
55+
detail.copy(
56+
seriesItems = mergedSeriesItems,
57+
relatedItems = mergedSeriesItems,
58+
)
59+
)
60+
}
61+
}
62+
}
63+
private fun <T> websiteIOFlow(
64+
request: suspend () -> Response<ResponseBody>,
65+
permittedSuccessCode: IntArray? = null,
66+
bodyToString: (ResponseBody) -> String = ResponseBody::string,
67+
action: suspend (String) -> WebsiteState<T>,
68+
) = flow {
69+
val requestResult = request.invoke()
70+
val resultBody = requestResult.body()?.let(bodyToString)
71+
val permitted = permittedSuccessCode?.contains(requestResult.code()) == true
72+
if ((permitted || requestResult.isSuccessful)) {
73+
emit(action.invoke(resultBody ?: EMPTY_STRING))
74+
} else {
75+
requestResult.throwRequestException()
76+
}
77+
}.catch { e ->
78+
emit(WebsiteState.Error(handleException(e)))
79+
}.flowOn(Dispatchers.IO)
80+
81+
private fun ResponseBody.getchuString(): String {
82+
return bytes().toString(GETCHU_CHARSET)
83+
}
84+
85+
private fun String.extractGetchuSeriesParentId(): String? {
86+
return Regex("[\"']parent_id_array[\"']\\s*:\\s*[\"']([^\"']+)[\"']", RegexOption.IGNORE_CASE)
87+
.find(this)
88+
?.groupValues
89+
?.getOrNull(1)
90+
?.takeIf { it.isNotBlank() }
91+
}
92+
}

0 commit comments

Comments
 (0)