@magpie-community/opencode-qoder-auth 0.2.6 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -0
- package/index.mjs +50 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -151,6 +151,27 @@ Neither magpie's built-in Qoder CN nor this plugin has been checked against
|
|
|
151
151
|
every kind of qoder.cn account. If a sign-in or a chat fails, please open
|
|
152
152
|
an issue with the error.
|
|
153
153
|
|
|
154
|
+
## Daily check-in
|
|
155
|
+
|
|
156
|
+
Qoder gives credits for a daily claim (the Qoder client's campaign
|
|
157
|
+
`CLAIM_BENEFIT`, 100 Credits a day as reported), on both sites. magpie can
|
|
158
|
+
press it once a day for each account when its Qoder check-in switch is on
|
|
159
|
+
(Settings). It asks these pages through this plugin's fetch (since 0.2.7),
|
|
160
|
+
which sends them as the account, on the device token (`Bearer`,
|
|
161
|
+
`Cosy-ClientType: 10`) as usage is read; a refused device token is
|
|
162
|
+
rotated once and the page asked again.
|
|
163
|
+
|
|
164
|
+
- **List:** `GET <openapi>/sash/api/v1/me/campaigns`: `campaigns[]` with
|
|
165
|
+
`campaignId`, `actionType`, `claimStatus` (`CLAIMABLE`, `CLAIMED`),
|
|
166
|
+
`startAt`, `endAt`, `benefit.amount`.
|
|
167
|
+
- **Claim:** `POST <openapi>/sash/api/v1/me/campaigns/{id}/claim`, body
|
|
168
|
+
`{}`: `status` `CLAIMED` (or under `data`).
|
|
169
|
+
- `<openapi>` is `openapi.qoder.sh` (Qoder) or `openapi.qoder.com.cn`
|
|
170
|
+
(Qoder CN). Other pages on those hosts are still refused.
|
|
171
|
+
|
|
172
|
+
Taken from [wallechfox/qoder-checkin](https://github.com/wallechfox/qoder-checkin),
|
|
173
|
+
which a user reports works. Not yet tried here with a real Qoder account.
|
|
174
|
+
|
|
154
175
|
## Not here
|
|
155
176
|
|
|
156
177
|
- Qoder's usage and quota display.
|
package/index.mjs
CHANGED
|
@@ -953,6 +953,33 @@ async function fetchUsage(site, deviceToken) {
|
|
|
953
953
|
return env
|
|
954
954
|
}
|
|
955
955
|
|
|
956
|
+
// CAMPAIGNS is where Qoder lists the account's campaigns — the daily
|
|
957
|
+
// credits among them (actionType CLAIM_BENEFIT) — and claims one
|
|
958
|
+
// (POST …/{id}/claim), as Qoder's client does, on the device token.
|
|
959
|
+
const CAMPAIGNS = "/sash/api/v1/me/campaigns"
|
|
960
|
+
|
|
961
|
+
// campaignPage: url is the site's campaigns page or a claim under it, which
|
|
962
|
+
// the account's fetch sends as the account rather than as a chat.
|
|
963
|
+
function campaignPage(site, url) {
|
|
964
|
+
try {
|
|
965
|
+
const u = new URL(url)
|
|
966
|
+
return u.origin === new URL(site.openapi).origin && (u.pathname === CAMPAIGNS || /^\/sash\/api\/v1\/me\/campaigns\/[^/]+\/claim$/.test(u.pathname))
|
|
967
|
+
} catch {
|
|
968
|
+
return false
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
// campaignCall is url asked with the device token; Qoder's answer comes back
|
|
973
|
+
// as it is.
|
|
974
|
+
async function campaignCall(url, method, body, deviceToken, signal) {
|
|
975
|
+
return fetch(url, {
|
|
976
|
+
method,
|
|
977
|
+
headers: { Accept: "application/json", "Content-Type": "application/json", Authorization: `Bearer ${deviceToken}`, "Cosy-ClientType": "10", "User-Agent": "Qoder" },
|
|
978
|
+
body: method === "GET" ? undefined : body || "{}",
|
|
979
|
+
signal: signal ?? AbortSignal.timeout(20_000),
|
|
980
|
+
})
|
|
981
|
+
}
|
|
982
|
+
|
|
956
983
|
// refreshDevice trades the device refresh token for a new pair; it rotates
|
|
957
984
|
// too, so the caller saves it.
|
|
958
985
|
async function refreshDevice(site, refresh, chat = false) {
|
|
@@ -1174,6 +1201,27 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1174
1201
|
}
|
|
1175
1202
|
}
|
|
1176
1203
|
|
|
1204
|
+
// campaign is a campaigns page (the daily check-in) asked as the account:
|
|
1205
|
+
// a refused device token is rotated once, as usage does, and asked again
|
|
1206
|
+
const campaign = async (getAuth, input, url, init) => {
|
|
1207
|
+
const body = await bodyText(input, init)
|
|
1208
|
+
let cred
|
|
1209
|
+
try {
|
|
1210
|
+
cred = await fresh(getAuth)
|
|
1211
|
+
} catch (e) {
|
|
1212
|
+
return signedInError(e)
|
|
1213
|
+
}
|
|
1214
|
+
const method = String(init.method ?? input?.method ?? "GET").toUpperCase()
|
|
1215
|
+
try {
|
|
1216
|
+
let res = await campaignCall(url, method, body, cred.deviceToken, init.signal)
|
|
1217
|
+
if (res.status === 401 || res.status === 403) res = await campaignCall(url, method, body, await deviceToken(getAuth, cred.deviceToken), init.signal)
|
|
1218
|
+
return signed(res, renewed(cred))
|
|
1219
|
+
} catch (e) {
|
|
1220
|
+
if (e?.expired) return signedInError(e)
|
|
1221
|
+
return errorResponse({ status: 502, message: e?.message ?? String(e) })
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1177
1225
|
const signedInError = (e) =>
|
|
1178
1226
|
errorResponse({
|
|
1179
1227
|
status: e instanceof SignInGone ? 401 : 502,
|
|
@@ -1244,6 +1292,7 @@ const makePlugin = (site) => async ({ client }) => {
|
|
|
1244
1292
|
// every chat completion written as Qoder's own request
|
|
1245
1293
|
async fetch(input, init = {}) {
|
|
1246
1294
|
const url = typeof input === "string" ? input : input instanceof URL ? input.href : input.url
|
|
1295
|
+
if (campaignPage(site, url)) return campaign(getAuth, input, url, init)
|
|
1247
1296
|
if (!/\/chat\/completions$/.test(new URL(url).pathname))
|
|
1248
1297
|
return errorResponse({ status: 404, message: "only chat completions are served" })
|
|
1249
1298
|
let chat
|
|
@@ -1309,4 +1358,4 @@ export const QoderAuthPlugin = makePlugin(SITES.qoder)
|
|
|
1309
1358
|
export const QoderCNAuthPlugin = makePlugin(SITES["qoder-cn"])
|
|
1310
1359
|
|
|
1311
1360
|
// for tests
|
|
1312
|
-
export const _internal = { parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
|
1361
|
+
export const _internal = { campaignPage, parseUsage, gfmt, when, failure, modelInfo, qoderBody, SITES, decodeBody, events }
|
package/package.json
CHANGED