From f7d1a97b5b99d35e8573fb21ac02fb3b9f8c0c6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=94=A1=E5=8F=8A?= <522caiji@gmail.com> Date: Wed, 30 Sep 2026 01:21:25 +0800 Subject: [PATCH] fix(qoder): send machine headers for daily check-in --- changelog/unreleased/qoder-cn-daily-checkin.md | 7 +++++++ worker/src/checkin.mjs | 18 ++++++++++++++++++ worker/test/checkin.test.mjs | 12 ++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 changelog/unreleased/qoder-cn-daily-checkin.md diff --git a/changelog/unreleased/qoder-cn-daily-checkin.md b/changelog/unreleased/qoder-cn-daily-checkin.md new file mode 100644 index 0000000..6757327 --- /dev/null +++ b/changelog/unreleased/qoder-cn-daily-checkin.md @@ -0,0 +1,7 @@ +### English + +- Fix Qoder CN daily check-in by sending the machine identity headers required by the campaign service. + +### 中文 + +- 修复 Qoder 国内版每日签到,补齐活动服务要求的机器身份请求头。 diff --git a/worker/src/checkin.mjs b/worker/src/checkin.mjs index 3eeb8e5..0bc5325 100644 --- a/worker/src/checkin.mjs +++ b/worker/src/checkin.mjs @@ -69,6 +69,22 @@ function rewardFrom(campaign) { return benefit.amount; } +async function machineHeaders(auth) { + let machineId = auth.machineId; + if (typeof auth.getMachineId === "function") { + try { + machineId = await auth.getMachineId(); + } catch { + machineId = ""; + } + } + if (typeof machineId !== "string" || !machineId) return {}; + return { + "Cosy-MachineId": machineId, + "Cosy-MachineToken": machineId, + }; +} + export function createQoderCheckin({ region, getAuthManager, fetchImpl = (...args) => globalThis.fetch(...args) }) { let pending; @@ -85,6 +101,7 @@ export function createQoderCheckin({ region, getAuthManager, fetchImpl = (...arg } catch { throw new Error("qoder_checkin_auth_refresh_failed"); } + const machineHeadersValue = await machineHeaders(auth); async function request(path, method, refreshed = false) { const user = auth.getUserInfo(); @@ -101,6 +118,7 @@ export function createQoderCheckin({ region, getAuthManager, fetchImpl = (...arg "User-Agent": "Qoder", "Cosy-ClientType": "10", "Cosy-Version": "0.3.4", + ...machineHeadersValue, Origin: endpoint.origin, Referer: `${endpoint.base}/growth-page/activity-iframe`, }, diff --git a/worker/test/checkin.test.mjs b/worker/test/checkin.test.mjs index 02ba9b7..0c1f85d 100644 --- a/worker/test/checkin.test.mjs +++ b/worker/test/checkin.test.mjs @@ -13,6 +13,7 @@ function fixture(responses, options = {}) { getUserInfo: () => ({ security_oauth_token: refreshed ? "new-test-token" : "test-token" }), refreshTokenIfNeeded: async () => {}, forceRefreshToken: async () => { refreshed++; }, + getMachineId: async () => options.machineId, }; const checkin = createQoderCheckin({ region: "cn", @@ -66,12 +67,23 @@ test("CN check-in lists campaigns then claims the CLAIM_BENEFIT campaign", async assert.equal(calls[1].init.headers["User-Agent"], "Qoder"); assert.equal(calls[1].init.headers["Cosy-ClientType"], "10"); assert.equal(calls[1].init.headers["Cosy-Version"], "0.3.4"); + assert.equal(calls[1].init.headers["Cosy-MachineId"], undefined); + assert.equal(calls[1].init.headers["Cosy-MachineToken"], undefined); assert.equal(calls[1].init.headers.Referer, "https://openapi.qoder.com.cn/growth-page/activity-iframe"); assert.equal(calls[1].init.redirect, "manual"); assert.ok(calls[1].init.signal instanceof AbortSignal); assert.ok(!calls.some((call) => String(call.url).includes("daily-check-in"))); }); +test("CN check-in sends the machine identity headers used by the desktop client", async () => { + const { checkin, calls } = fixture([json(listed(credit())), json({ status: "CLAIMED" })], { machineId: "machine-test-id" }); + await checkin(); + assert.equal(calls[0].init.headers["Cosy-MachineId"], "machine-test-id"); + assert.equal(calls[0].init.headers["Cosy-MachineToken"], "machine-test-id"); + assert.equal(calls[1].init.headers["Cosy-MachineId"], "machine-test-id"); + assert.equal(calls[1].init.headers["Cosy-MachineToken"], "machine-test-id"); +}); + test("VIEW_DETAILS campaigns are never claimed", async () => { const { checkin, calls } = fixture([json(listed(details()))]); assert.equal((await checkin()).status, "skipped");