Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 98 additions & 1 deletion src/functions/activities/api/UrlReward.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { randomUUID } from 'node:crypto'
import { URLs } from '../../../constants/urls'
import { BING_APP_USER_AGENT } from '../../../constants/userAgents'
import type { BasePromotion } from '../../../interface/DashboardData'
import { BaseActivity } from '../BaseActivity'

Expand All @@ -22,10 +24,12 @@ export class UrlReward extends BaseActivity {

const live = await this.bot.browser.func.ensureOffer(offerId)
if (!live) {
// 页面快照缺失:回退到 App dapi 官方通道上报(V4-china / geo=CN 下每日任务在页面快照中无 offerId)
if (await this.dapiReportActivity(promotion)) return
this.bot.logger.warn(
this.bot.isMobile,
'URL-REWARD',
`跳过 ${offerId}:页面快照中不存在,即使重新获取 /earn 和 /dashboard 后也没有`
`跳过 ${offerId}:页面快照中不存在(dapi 上报亦未成功),即使重新获取 /earn 和 /dashboard 后也没有`
)
return
}
Expand Down Expand Up @@ -147,4 +151,97 @@ export class UrlReward extends BaseActivity {
await this.runUrlReward(promotion, false)
return true
}
/**
* App dapi 通道上报(Microsoft Rewards 每日任务官方上报接口):
* POST prod.rewardsplatform.microsoft.com/dapi/me/activities
* 使用登录时获取的 MBI_SSL accessToken(this.bot.accessToken)。
* 请求格式对照 BingSapphire App:attributes.offerid + amount + type:101 + channel + id + meta + country。
* meta 为 App 端签名,实测服务端不校验(空串可接受)。
*/
private async dapiReportActivity(promotion: BasePromotion): Promise<boolean> {
const offerId = promotion.offerId
const token = this.bot.accessToken
if (!token) {
this.bot.logger.warn(this.bot.isMobile, 'URL-REWARD', `dapi 上报跳过 ${offerId}:无 accessToken(未走移动端登录)`)
return false
}

this.bot.logger.info(
this.bot.isMobile,
'URL-REWARD',
`dapi 通道上报 | offerId=${offerId} | type=101 | currentBalance=${this.bot.userData.currentPoints}`
)

try {
const oldBalance = this.bot.userData.currentPoints
const request = {
url: URLs.platform.activities,
method: 'POST' as const,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${token}`,
'User-Agent': BING_APP_USER_AGENT,
'X-Rewards-Country': this.bot.userData.geoLocale,
'X-Rewards-Language': this.bot.userData.langCode,
'X-Rewards-IsMobile': 'true',
Accept: '*/*'
},
data: {
attributes: { offerid: offerId },
amount: 1,
type: 101,
risk_context: {},
channel: 'SAAndroid',
id: randomUUID(),
meta: '',
country: 'cn'
},
retries: 1
}

const response = await this.bot.http.request<{ code?: number; response?: { balance?: number } }>(
request,
false
)
const code = response.data?.code
const newBalance = response.data?.response?.balance ?? (await this.bot.browser.func.getCurrentPoints())
const gainedPoints = newBalance - oldBalance

this.bot.logger.info(
this.bot.isMobile,
'URL-REWARD',
`dapi 上报响应 | offerId=${offerId} | http=${response.status} | code=${code} | pointsGained=${gainedPoints} | balance=${newBalance} | raw=${JSON.stringify(response.data ?? '').slice(0, 600)}`
)

if (code === 0) {
if (gainedPoints > 0) {
this.bot.userData.currentPoints = newBalance
this.bot.userData.gainedPoints = (this.bot.userData.gainedPoints ?? 0) + gainedPoints
}
this.bot.logger.info(
this.bot.isMobile,
'URL-REWARD',
`dapi 上报完成 | offerId=${offerId} | pointsGained=${gainedPoints}`,
'green'
)
return true
}

this.bot.logger.warn(
this.bot.isMobile,
'URL-REWARD',
`dapi 上报未成功 | offerId=${offerId} | http=${response.status} | code=${code} | raw=${JSON.stringify(response.data ?? '').slice(0, 600)}`
)
return false
} catch (error) {
this.bot.logger.error(
this.bot.isMobile,
'URL-REWARD',
`dapi 上报出错 | offerId=${offerId} | message=${error instanceof Error ? error.message : String(error)}`
)
return false
}
}


}