fix(wb): 国际版无独立签到活动,不再调 daily-checkin,详情页展示说明卡

This commit is contained in:
Liuxinyu176 2026-10-10 02:34:27 +08:00
parent 809b8db2d3
commit f917e5bfd6
5 changed files with 92 additions and 14 deletions

View File

@ -459,7 +459,9 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
/** 今日已签到(幂等) */ /** 今日已签到(幂等) */
ALREADY, ALREADY,
/** 签到失败(服务端给了原因,见 message) */ /** 签到失败(服务端给了原因,见 message) */
FAILED FAILED,
/** 国际站无签到活动(活动未开启/未上线,非错误) */
NO_CAMPAIGN
} }
/** /**
@ -486,6 +488,12 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
if (credential !is Credential.WorkBuddyCredential) { if (credential !is Credential.WorkBuddyCredential) {
return@withContext Result.failure(RepositoryError.InvalidCredential()) return@withContext Result.failure(RepositoryError.InvalidCredential())
} }
// 国际版没有独立签到档案:积分来自网页/客户端活跃使用,直接返回“无活动”。
if (credential.isIntlRegion()) {
return@withContext Result.success(
CheckinStatus(false, false, 0, 0, 0, "", "", CheckinOutcome.NO_CAMPAIGN)
)
}
// 两条候选路径 fallback(cpa-plugin 实测:activity-status 优先,旧路径 checkin-status 兜底) // 两条候选路径 fallback(cpa-plugin 实测:activity-status 优先,旧路径 checkin-status 兜底)
// 共享轮换状态:前一条路径轮换出的新 token 会用于下一条,且失败也先落库。 // 共享轮换状态:前一条路径轮换出的新 token 会用于下一条,且失败也先落库。
val body = authedPostFirstSuccessful( val body = authedPostFirstSuccessful(
@ -517,6 +525,12 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
if (credential !is Credential.WorkBuddyCredential) { if (credential !is Credential.WorkBuddyCredential) {
return@withContext Result.failure(RepositoryError.InvalidCredential()) return@withContext Result.failure(RepositoryError.InvalidCredential())
} }
// 国际版不调用 daily-checkin(无签到营业),直接按无活动处理,避免每次刷新都报错。
if (credential.isIntlRegion()) {
return@withContext Result.success(
CheckinStatus(false, false, 0, 0, 0, "", "", CheckinOutcome.NO_CAMPAIGN)
)
}
val body = when (val r = authedPost(credential, "/v2/billing/meter/daily-checkin")) { val body = when (val r = authedPost(credential, "/v2/billing/meter/daily-checkin")) {
is AuthedPostResult.Ok -> { is AuthedPostResult.Ok -> {
persistCheckinOutcome(credential, accountId, r.rotatedCredential) persistCheckinOutcome(credential, accountId, r.rotatedCredential)
@ -550,8 +564,15 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
?: (r["data"] as? JsonObject)?.scalar("msg", "message", "Message") ?: (r["data"] as? JsonObject)?.scalar("msg", "message", "Message")
}.getOrNull().orEmpty() }.getOrNull().orEmpty()
val status = fetchCheckinStatus(accountId).getOrNull() val status = fetchCheckinStatus(accountId).getOrNull()
// 已签到语义与上游 cpa-multi-plugins 对齐:code=10001 或文案含「已签/今日/already」。
val lowerMsg = msg.lowercase() val lowerMsg = msg.lowercase()
// 国际站无签到活动:接口可能返回 data.active=false 或「活动未开启/已过期」。
val inactive = runCatching {
val r = json.parseToJsonElement(body).jsonObject
((r["data"] as? JsonObject)?.get("active") as? JsonPrimitive)?.contentOrNull?.equals("false", true) == true
}.getOrDefault(false)
val noCampaign = inactive || msg.contains("活动未开启") || msg.contains("已过期") ||
lowerMsg.contains("not active")
// 已签到语义与上游 cpa-multi-plugins 对齐:code=10001 或文案含「已签/今日/already」。
val already = code == 10001L || msg.contains("已签") || msg.contains("已经签到") || val already = code == 10001L || msg.contains("已签") || msg.contains("已经签到") ||
msg.contains("今日") || lowerMsg.contains("already") || lowerMsg.contains("checked") || msg.contains("今日") || lowerMsg.contains("already") || lowerMsg.contains("checked") ||
lowerMsg.contains("claimed") lowerMsg.contains("claimed")
@ -566,6 +587,12 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
outcome = CheckinOutcome.SUCCESS outcome = CheckinOutcome.SUCCESS
) )
) )
noCampaign -> Result.success(
(status ?: CheckinStatus(false, false, 0, 0, 0, "")).copy(
message = msg,
outcome = CheckinOutcome.NO_CAMPAIGN
)
)
already -> Result.success( already -> Result.success(
(status ?: CheckinStatus(true, true, 0, 0, 0, "")).copy( (status ?: CheckinStatus(true, true, 0, 0, 0, "")).copy(
outcome = CheckinOutcome.ALREADY outcome = CheckinOutcome.ALREADY
@ -617,8 +644,21 @@ private fun parseResourceDetail(body: String): Map<String, String>? {
val request = Request.Builder() val request = Request.Builder()
.url(credential.originBase() + path) .url(credential.originBase() + path)
.addHeader("Authorization", "Bearer " + access) .addHeader("Authorization", "Bearer " + access)
.addHeader("Accept", "application/json") .addHeader("Accept", "application/json, text/plain, */*")
.addHeader("Content-Type", "application/json") .addHeader("Content-Type", "application/json")
.addHeader("User-Agent", "CLI/2.63.2 CodeBuddy/2.63.2")
.addHeader("Origin", credential.originBase())
.addHeader("Referer", credential.originBase() + "/profile/growth-center")
.addHeader("X-CodeBuddy-Request", "1")
.addHeader("Accept-Language", if (credential.isIntlRegion()) "en-US" else "zh-CN")
.apply {
if (credential.isIntlRegion()) {
addHeader("X-Domain", "www.workbuddy.ai")
addHeader("X-No-Enterprise-Id", "1")
} else {
addHeader("X-No-Enterprise-Id", "1")
}
}
.post("{}".toRequestBody(jsonMedia)) .post("{}".toRequestBody(jsonMedia))
.build() .build()
val resp = try { val resp = try {

View File

@ -265,17 +265,23 @@ fun ServiceDetailScreen(
} }
} }
ServiceType.WORKBUDDY -> { ServiceType.WORKBUDDY -> {
item { val wbIntl = uiState.region != null && uiState.region.equals("INTL", ignoreCase = true)
if (uiState.hasCredential) { if (wbIntl) {
CheckinCard( // 国际版没有独立签到体系:积分来自网页/客户端活跃使用,直接展示说明卡
state = checkinState, item { WorkBuddyIntlNoCheckinCard() }
todayCheckedIn = checkinInfo?.todayCheckedIn == true, } else {
streakDays = checkinInfo?.streakDays ?: 0L, item {
isTrae = false, if (uiState.hasCredential) {
onCheckin = { viewModel.checkin() } CheckinCard(
) state = checkinState,
} else { todayCheckedIn = checkinInfo?.todayCheckedIn == true,
CheckinNeedCredentialCard() streakDays = checkinInfo?.streakDays ?: 0L,
isTrae = false,
onCheckin = { viewModel.checkin() }
)
} else {
CheckinNeedCredentialCard()
}
} }
} }
// 资源明细(资源 ID 美化为「资源 #007」+ 余额;套餐/冻结/付费信息归账户信息) // 资源明细(资源 ID 美化为「资源 #007」+ 余额;套餐/冻结/付费信息归账户信息)
@ -1801,6 +1807,29 @@ private fun TraeIntlNoCheckinCard() {
} }
} }
@Composable
private fun WorkBuddyIntlNoCheckinCard() {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(20.dp),
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
) {
Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(4.dp)) {
Text(
text = stringResource(R.string.wb_intl_no_checkin_title),
style = MaterialTheme.typography.titleSmall,
fontWeight = FontWeight.SemiBold
)
Text(
text = stringResource(R.string.wb_intl_no_checkin_desc),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.outline
)
}
}
}
@Composable @Composable
internal fun WorkBuddyResourcesCard(state: State) { internal fun WorkBuddyResourcesCard(state: State) {
val balance = when (state) { val balance = when (state) {

View File

@ -228,6 +228,7 @@
<string name="checkin_success">签到成功</string> <string name="checkin_success">签到成功</string>
<string name="checkin_already">今日已签到</string> <string name="checkin_already">今日已签到</string>
<string name="checkin_already_streak">今日已签到(连签 %1$d 天)</string> <string name="checkin_already_streak">今日已签到(连签 %1$d 天)</string>
<string name="checkin_wb_no_campaign">暂无可领取的签到活动</string>
<string name="checkin_failed">签到失败,请检查凭据</string> <string name="checkin_failed">签到失败,请检查凭据</string>
<string name="checkin_unsupported">该服务不支持签到</string> <string name="checkin_unsupported">该服务不支持签到</string>
<string name="checkin_action_done">今日已签到</string> <string name="checkin_action_done">今日已签到</string>
@ -801,6 +802,8 @@
<string name="action_trae_recharge">充值 / 升级</string> <string name="action_trae_recharge">充值 / 升级</string>
<string name="trae_intl_plan_title">套餐</string> <string name="trae_intl_plan_title">套餐</string>
<string name="trae_intl_basic_used_title">基础用量已用</string> <string name="trae_intl_basic_used_title">基础用量已用</string>
<string name="wb_intl_no_checkin_title">国际版无独立签到</string>
<string name="wb_intl_no_checkin_desc">WorkBuddy 国际版积分来自网页/客户端活跃使用,没有独立签到活动。</string>
<string name="trae_intl_no_checkin_title">国际版没有每日签到</string> <string name="trae_intl_no_checkin_title">国际版没有每日签到</string>
<string name="trae_intl_no_checkin_desc">Trae 国际版按美元计费,使用月度请求额度,没有签到积分体系。</string> <string name="trae_intl_no_checkin_desc">Trae 国际版按美元计费,使用月度请求额度,没有签到积分体系。</string>
<string name="trae_intl_monthly_quota">月配额</string> <string name="trae_intl_monthly_quota">月配额</string>

View File

@ -228,6 +228,7 @@
<string name="checkin_success">簽到成功</string> <string name="checkin_success">簽到成功</string>
<string name="checkin_already">今日已簽到</string> <string name="checkin_already">今日已簽到</string>
<string name="checkin_already_streak">今日已簽到(連簽 %1$d 天)</string> <string name="checkin_already_streak">今日已簽到(連簽 %1$d 天)</string>
<string name="checkin_wb_no_campaign">暫無可領取的簽到活動</string>
<string name="checkin_failed">簽到失敗,請檢查憑證</string> <string name="checkin_failed">簽到失敗,請檢查憑證</string>
<string name="checkin_unsupported">該服務不支援簽到</string> <string name="checkin_unsupported">該服務不支援簽到</string>
<string name="checkin_action_done">今日已簽到</string> <string name="checkin_action_done">今日已簽到</string>
@ -801,6 +802,8 @@
<string name="action_trae_recharge">儲值 / 升級</string> <string name="action_trae_recharge">儲值 / 升級</string>
<string name="trae_intl_plan_title">套餐</string> <string name="trae_intl_plan_title">套餐</string>
<string name="trae_intl_basic_used_title">基礎用量已用</string> <string name="trae_intl_basic_used_title">基礎用量已用</string>
<string name="wb_intl_no_checkin_title">國際版無獨立簽到</string>
<string name="wb_intl_no_checkin_desc">WorkBuddy 國際版積分來自網頁/客戶端活躍使用,沒有獨立簽到活動。</string>
<string name="trae_intl_no_checkin_title">國際版沒有每日簽到</string> <string name="trae_intl_no_checkin_title">國際版沒有每日簽到</string>
<string name="trae_intl_no_checkin_desc">Trae 國際版按美元計費,使用月度請求額度,沒有簽到積分體系。</string> <string name="trae_intl_no_checkin_desc">Trae 國際版按美元計費,使用月度請求額度,沒有簽到積分體系。</string>
<string name="trae_intl_monthly_quota">月額度</string> <string name="trae_intl_monthly_quota">月額度</string>

View File

@ -231,6 +231,7 @@
<string name="checkin_success">Check-in successful</string> <string name="checkin_success">Check-in successful</string>
<string name="checkin_already">Already checked in today</string> <string name="checkin_already">Already checked in today</string>
<string name="checkin_already_streak">Already checked in (streak %1$d days)</string> <string name="checkin_already_streak">Already checked in (streak %1$d days)</string>
<string name="checkin_wb_no_campaign">No active check-in campaign</string>
<string name="checkin_failed">Check-in failed, please check credentials</string> <string name="checkin_failed">Check-in failed, please check credentials</string>
<string name="checkin_unsupported">This service does not support check-in</string> <string name="checkin_unsupported">This service does not support check-in</string>
<string name="checkin_action_done">Checked in today</string> <string name="checkin_action_done">Checked in today</string>
@ -806,6 +807,8 @@
<string name="action_trae_recharge">Upgrade / Top up</string> <string name="action_trae_recharge">Upgrade / Top up</string>
<string name="trae_intl_plan_title">Plan</string> <string name="trae_intl_plan_title">Plan</string>
<string name="trae_intl_basic_used_title">Basic usage used</string> <string name="trae_intl_basic_used_title">Basic usage used</string>
<string name="wb_intl_no_checkin_title">No daily check-in on international</string>
<string name="wb_intl_no_checkin_desc">WorkBuddy international credits are earned through active usage, not a check-in campaign.</string>
<string name="trae_intl_no_checkin_title">International edition has no daily check-in</string> <string name="trae_intl_no_checkin_title">International edition has no daily check-in</string>
<string name="trae_intl_no_checkin_desc">TRAE International uses USD billing with monthly request quotas instead of credit check-ins.</string> <string name="trae_intl_no_checkin_desc">TRAE International uses USD billing with monthly request quotas instead of credit check-ins.</string>
<string name="trae_intl_monthly_quota">Monthly quota</string> <string name="trae_intl_monthly_quota">Monthly quota</string>