diff --git a/README.md b/README.md
index 9647cdd..dcc39a0 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
# CreditDaddy
-**AI 编程工具多账号本地管理 + 每日积分自动领取(领鸡蛋)助手:Qoder · WorkBuddy · ZCode · mirasim · 妙手 · Trae · MiniMax · ModelScope · 豆包 Doubao · 讯飞 Loomy,集成 10Router 额度总览、用量同步与网页会话额度卡(Token Harbor / 小米 MiMo / River / ModelScope 魔搭 / LongCat)**
+**AI 编程工具多账号本地管理 + 每日积分自动领取(领鸡蛋)助手:Qoder · WorkBuddy · ZCode · mirasim · 妙手 · Trae · MiniMax · ModelScope · 豆包 Doubao · 讯飞 Loomy · Meoo 秒悟,集成 10Router 额度总览、用量同步与网页会话额度卡(Token Harbor / 小米 MiMo / River / ModelScope 魔搭 / LongCat)**
[](https://github.com/techysy/CreditDaddy/releases/latest)
[](https://github.com/techysy/CreditDaddy/actions/workflows/ci.yml)
@@ -144,6 +144,7 @@
| River AI | WorkOS localStorage JWT 变体(三源收割) | `$25` 免费额度 `balance_usd`,会话过期自动续期 |
| ModelScope 魔搭(issue #25) | 会话 cookie(双源) | 魔粒余额,卡面拆「今日发放 / 长期」两腿;每日额度登录自动发放、无签到端点 |
| LongCat 美团(issue #29) | 美团 SSO 会话 cookie | Token 资源包:Balance 汇总 + 逐包明细腿(官方消耗顺序、赠送包徽注、到期日) |
+| Meoo 秒悟 | meoo.com 会话 cookie(阿里云 SSO 手机号 + 短信登录) | 每日首登积分(`/api/mc/entitlement/init` 触发,当前 +2000/天、约 6~7 天过期)自动领取 + 积分余额;会话失效(`NEED_LOGIN`)提示重新捕获;首版不推 10Router 卡 |
> 注:办公小浣熊曾按 issue #27 调研接入,因 `raccoon_token` 异步写回时序不可靠已于 v1.5.0 回滚移除。
@@ -343,6 +344,7 @@ CreditDaddy/
│ ├── localDetect.js # 客户端已安装环境侦测
│ ├── logger.js # 内存环形日志与按日归档
│ ├── longcatClient.js # LongCat Token 资源包与身份请求
+│ ├── meooClient.js # Meoo 秒悟每日首登积分与积分余额
│ ├── mirasimClient.js # mirasim 套餐 / 额度请求
│ ├── mirasimLocal.js # 本机 mirasim 凭据解密与切号
│ ├── minimaxAuth.js # MiniMax 浏览器登录(OAuth 设备码 + S256 PKCE)
diff --git a/package.json b/package.json
index acb7767..f1c3cac 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "creditdaddy",
"version": "1.7.2",
- "description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax / ModelScope / 豆包 Doubao / 讯飞 Loomy / WPS 灵犀多账号本地管理与每日积分自动签到助手,及 10Router 供应商 Cookie 会话捕获与外部配额(Token Harbor / 小米 MiMo / River / ModelScope 魔搭 / LongCat)",
+ "description": "CreditDaddy — Qoder / WorkBuddy / ZCode / mirasim / 妙手 / Trae / MiniMax / ModelScope / 豆包 Doubao / 讯飞 Loomy / WPS 灵犀 / Meoo 秒悟多账号本地管理与每日积分自动签到助手,及 10Router 供应商 Cookie 会话捕获与外部配额(Token Harbor / 小米 MiMo / River / ModelScope 魔搭 / LongCat)",
"homepage": "https://github.com/techysy/CreditDaddy",
"repository": {
"type": "git",
diff --git a/scripts/release-check.sh b/scripts/release-check.sh
index 9304ea5..c1adfba 100644
--- a/scripts/release-check.sh
+++ b/scripts/release-check.sh
@@ -246,7 +246,7 @@ import('./src/constants.js').then(({ PROVIDERS, productOf }) => {
" 2>/dev/null || true)
if [ -n "$PROVIDERS" ]; then
# catpaw 在文案里叫「妙手」,单独给别名
- alias_of() { case "$1" in catpaw) echo "妙手";; tokenharbor) echo "Token Harbor";; 'xiaomi-mimo') echo "MiMo";; lingxi) echo "灵犀";; *) echo "$1";; esac; }
+ alias_of() { case "$1" in catpaw) echo "妙手";; tokenharbor) echo "Token Harbor";; 'xiaomi-mimo') echo "MiMo";; lingxi) echo "灵犀";; meoo) echo "秒悟";; *) echo "$1";; esac; }
ROOT_DESC=$(node -p "require('./package.json').description")
STALE=0
while IFS= read -r product; do
diff --git a/src/checkin.js b/src/checkin.js
index 678b479..95d7520 100644
--- a/src/checkin.js
+++ b/src/checkin.js
@@ -35,7 +35,7 @@ const ZCODE_FIRST_DELAY_MS = 2 * 60 * 1000;
// 签到日 = 对应时点起的日历日:dayKey(now, provider) = (now + (8 - 刷新小时)h) 的 UTC 日期
const DAY_REFRESH_HOUR_UTC8 = { qoder: 10, 'qoder-cn': 10 };
// 领取日志的积分单位:默认 Credits(Qoder/WorkBuddy 系),网页会话线各自带单位
-const CHECKIN_UNIT = { lingxi: '灵点', doubao: '额度' };
+const CHECKIN_UNIT = { lingxi: '灵点', doubao: '额度', meoo: '秒悟积分' };
let timerHandle = null;
let zcodeTimerHandle = null;
diff --git a/src/constants.js b/src/constants.js
index 467c342..93ef0f5 100644
--- a/src/constants.js
+++ b/src/constants.js
@@ -35,7 +35,7 @@ export const WEB_USAGE_PATH = '/api/v2/me/usages/big_model_credits';
export const JOB_TOKEN_EXCHANGE_PATH = '/api/v1/jobToken/exchange';
// 账号的 provider = 产品 + 区域。新增产品时在此登记,并在 providers.js 挂上对应实现。
-export const PROVIDERS = ['qoder', 'qoder-cn', 'workbuddy', 'workbuddy-intl', 'zcode', 'mirasim', 'catpaw', 'trae', 'trae-intl', 'minimax', 'minimax-global', 'tokenharbor', 'xiaomi-mimo', 'river', 'modelscope', 'longcat', 'doubao', 'loomy', 'lingxi'];
+export const PROVIDERS = ['qoder', 'qoder-cn', 'workbuddy', 'workbuddy-intl', 'zcode', 'mirasim', 'catpaw', 'trae', 'trae-intl', 'minimax', 'minimax-global', 'tokenharbor', 'xiaomi-mimo', 'river', 'modelscope', 'longcat', 'doubao', 'loomy', 'lingxi', 'meoo'];
export const PROVIDER_LABEL = {
qoder: 'Qoder 国际版',
'qoder-cn': 'Qoder 国内版',
@@ -56,6 +56,7 @@ export const PROVIDER_LABEL = {
doubao: '豆包',
loomy: '讯飞 Loomy',
lingxi: 'WPS 灵犀',
+ meoo: 'Meoo 秒悟',
};
/** provider → 产品线(qoder / workbuddy / zcode / mirasim / catpaw / trae / minimax / tokenharbor / xiaomi-mimo / river) */
export const productOf = (provider) => {
@@ -74,6 +75,7 @@ export const productOf = (provider) => {
if (p === 'doubao') return 'doubao';
if (p === 'loomy') return 'loomy';
if (p === 'lingxi') return 'lingxi';
+ if (p === 'meoo') return 'meoo';
return 'qoder';
};
/** provider 是否国内版 */
diff --git a/src/cookieSites.js b/src/cookieSites.js
index 5b866a0..d324eb2 100644
--- a/src/cookieSites.js
+++ b/src/cookieSites.js
@@ -184,6 +184,22 @@ export const COOKIE_SITES = [
accountName: 'WPS 灵犀',
hint: '每日自动签到(+100 灵点,七天一轮第七天 200)+ 推送 10Router 出余额卡,Cookie 只存本机。多号分别捕获即按 WPS 账号分行并存。',
},
+ {
+ id: 'meoo',
+ name: 'Meoo 秒悟',
+ provider: 'meoo',
+ label: 'Meoo 秒悟',
+ loginUrl: 'https://meoo.com',
+ homeUrl: 'https://meoo.com',
+ // 探测 = 权益接口本身(2026-11-13 实测):带会话 cookie 返 { success:true, data:{ userPointInfo… } };
+ // 未登录 401 { errCode:'NEED_LOGIN' }。身份走 /api/v1/user/me 的 user_id(多号归户)。
+ // 每日首登积分由 entitlement/init 触发(网页端登录后同款调用),CD 每日代调一次。
+ verify: 'api',
+ cookieUrls: ['https://meoo.com'],
+ accountName: 'Meoo 秒悟',
+ no10rSync: true, // 首版不推 10Router 外部配额卡(积分只在 Meoo 内消费)
+ hint: '登录 meoo.com(阿里云账号手机号 + 短信)后捕获会话;CD 每日自动领取首登积分(当前 +2000/天,约 6~7 天后过期)并展示积分余额。会话失效(NEED_LOGIN)时请重新登录捕获,Cookie 只存本机。',
+ },
// ⚠ 码道 CodeArts(issue #40)已于 2026-10-30 撤下,不再提供:
// 华为云控制台会话受闲置超时 + WAF 指印(HWWAFSESID / browserCheckResult)绑定,
// 浏览器里能靠 SPA 持续续期,本机无法续 —— 实测新鲜会话当日内即失效(网关回登录页),
diff --git a/src/daemon.js b/src/daemon.js
index 0fa127e..3b5171c 100644
--- a/src/daemon.js
+++ b/src/daemon.js
@@ -387,6 +387,36 @@ async function reprobeCookieAccount(site, a) {
logger.info('COOKIE-SITE', '{name} 就地重探通过({identity},签到{signed},灵点 {balance})', { name: site.name, identity: uid ? 'uid ' + uidMasked : '无 uid', signed: st.checkedIn ? '已签' : '未签', balance: Number.isFinite(st.balance) ? st.balance : '—' });
return finish({ id: a.id, name: a.name, ok: true, identity: uid ? `uid:${uidMasked}` : null, balance: Number.isFinite(st.balance) ? st.balance : null, quotaSnapshot: Number.isFinite(st.balance) ? { used: 0, total: st.balance, isCreditBalance: true, at: now } : null });
}
+ if (site.id === 'meoo') {
+ // Meoo 秒悟:权益接口即探测(只读,不调 entitlement/init——发放动作只走签到线),
+ // 刷新积分余额快照;身份走 user/me 的 user_id(老行缺失时回填 + 名字升级)
+ const { fetchMeooEntitlement, fetchMeooIdentity, maskMeooUserId } = await import('./meooClient.js');
+ const ent = await fetchMeooEntitlement(cookie).catch((e) => ({ auth: true, ok: false, code: e?.message || 'network' }));
+ if (!ent.auth) return finish({ id: a.id, name: a.name, ok: false, error: 'Meoo 会话已失效(NEED_LOGIN),请重新登录捕获' });
+ if (!ent.ok) return finish({ id: a.id, name: a.name, ok: false, error: '探测失败,请稍后再试或重新登录捕获' });
+ const ident = await fetchMeooIdentity(cookie).catch(() => null);
+ const userId = ident?.ok ? ident.userId : null;
+ const masked = userId ? maskMeooUserId(userId) : null;
+ const bal = Number.isFinite(ent.balance) ? ent.balance : null;
+ await withAccounts((list) => {
+ const hit = list.find((x) => x.id === a.id);
+ if (!hit) return;
+ hit.meta = {
+ ...(hit.meta || {}),
+ cookieSiteSession: { cookie, capturedAt: hit.meta?.cookieSiteSession?.capturedAt || now },
+ ...(userId && !hit.meta?.identityHash ? { identityHash: identityHashOf(`uid:${userId}`) } : {}),
+ ...(ent.grants ? { meooGrants: { grants: ent.grants, at: now } } : {}),
+ ...(bal !== null ? { quotaSnapshot: { used: 0, total: bal, isCreditBalance: true, at: now } } : {}),
+ };
+ if (userId) {
+ hit.uid = String(userId);
+ if (canAutoRename(site, hit)) hit.name = `${site.accountName}(${masked})`;
+ }
+ hit.updatedAt = now;
+ });
+ logger.info('COOKIE-SITE', '{name} 就地重探通过({identity},积分 {balance})', { name: site.name, identity: masked ? 'uid ' + masked : '无 uid', balance: bal ?? '—' });
+ return finish({ id: a.id, name: a.name, ok: true, identity: masked ? `uid:${masked}` : null, balance: bal, quotaSnapshot: bal !== null ? { used: 0, total: bal, isCreditBalance: true, at: now } : null });
+ }
if (site.id === 'doubao') {
// 豆包(issue #32):detail 接口一次拿全(会话有效性 + 签到状态 + 累计点数),
// 重探 = 刷新额度快照;无 10R 同步(豆包不推外部配额卡,错误文案不再误导)
@@ -764,7 +794,7 @@ async function handleApi(req, res, url) {
// 单位标注:各家余额单位互异(Token / USD / CNY / 魔粒 / 积分)且都不是「积分」
// 口径——带 unit 返回,面板 creditSum 按 unit 有无决定是否计入「剩余积分」合计
//(有单位 = 不同量纲,不相加)。HTML 探针站(tokenharbor)是百分比,同样排除。
- const UNIT_BY_SITE = { 'xiaomi-mimo': 'CNY', 'river': 'USD', 'longcat': 'Token', 'modelscope': '魔粒', 'lingxi': '灵点' };
+ const UNIT_BY_SITE = { 'xiaomi-mimo': 'CNY', 'river': 'USD', 'longcat': 'Token', 'modelscope': '魔粒', 'lingxi': '灵点', 'meoo': '秒悟积分' };
const unit = UNIT_BY_SITE[site.id] || (hasTotal ? '' : '%');
return json(res, 200, { quota: {
total: hasTotal ? total : 100,
@@ -1280,11 +1310,13 @@ async function handleApi(req, res, url) {
msBalance: a.meta?.msBalance || null,
longcatPacks: a.meta?.longcatPacks || null,
lingxiPacks: a.meta?.lingxiPacks || null, // 智点资源包快照(issue #53)
+ meooGrants: a.meta?.meooGrants || null, // Meoo 积分逐笔发放快照(带到期日)
lastProbe: a.meta?.lastProbe || null, // 呼吸灯数据源:ok / warn / dead + 失败原因
}));
const latest = rows.reduce((t, r) => (r.capturedAt && (!t || r.capturedAt > t) ? r.capturedAt : t), null);
return {
id: site.id, name: site.name, label: site.label, loginUrl: site.loginUrl, hint: site.hint,
+ ...(site.no10rSync === true ? { no10rSync: true } : {}),
accountId: rows[0]?.id || null, capturedAt: latest, accounts: rows,
};
}),
@@ -1495,6 +1527,37 @@ async function handleApi(req, res, url) {
}
}
+ // Meoo 秒悟:权益接口即探测(带 cookie 返 success:true,未登录 401 NEED_LOGIN)。
+ // 只读——不在捕获时调 entitlement/init,首登积分发放统一走签到线。身份 = user/me 的 user_id。
+ let meooUserId = null;
+ let meooMasked = null;
+ let meooGrants = null;
+ if (site.id === 'meoo') {
+ try {
+ const { fetchMeooEntitlement, fetchMeooIdentity, maskMeooUserId } = await import('./meooClient.js');
+ if (!cookie) {
+ logger.warn('COOKIE-SITE', '{name} 未读到会话 cookie', { name: site.name });
+ return json(res, 400, { error: '未读到 Meoo 登录会话,请在登录窗完成登录后再关闭' });
+ }
+ const ent = await fetchMeooEntitlement(cookie);
+ if (!ent.auth) {
+ logger.warn('COOKIE-SITE', '{name} 权益接口探测被拒:{code}', { name: site.name, code: ent.code });
+ return json(res, 401, { error: '会话不可用,请重新登录该站点后再捕获(探测 401/403)' });
+ }
+ if (!ent.ok) return json(res, 502, { error: '探测站点失败:' + (ent.code || '') });
+ const ident = await fetchMeooIdentity(cookie).catch(() => null);
+ meooUserId = ident?.ok ? ident.userId : null;
+ meooMasked = meooUserId ? maskMeooUserId(meooUserId) : null;
+ if (ent.grants) meooGrants = { grants: ent.grants, at: now };
+ identity = meooUserId ? `uid:${meooUserId}` : null; // 有 user_id → 哈希归户;无 → 单例兜底
+ quotaSnapshot = { used: 0, total: Number(ent.balance) || 0, isCreditBalance: true };
+ logger.info('COOKIE-SITE', '{name} 探测通过({identity},积分 {balance})', { name: site.name, identity: meooUserId ? 'uid ' + meooMasked : '无 uid(单例)', balance: Number.isFinite(ent.balance) ? ent.balance : '—' });
+ } catch (meooErr) {
+ logger.warn('COOKIE-SITE', '{name} 权益接口抓取异常:{err}', { name: site.name, err: meooErr.message });
+ return json(res, 502, { error: '探测站点失败:' + (meooErr.message || '') });
+ }
+ }
+
const ih = identityHashOf(mimoUserId || riverSession?.user?.id || msSession?.id || lcSession?.userId || identity);
const quotaMeta = quotaSnapshot ? { quotaSnapshot: { ...quotaSnapshot, at: now } } : {};
const cred = riverSession ? riverSession.accessToken : cookie; // 落盘的凭据主体
@@ -1511,6 +1574,7 @@ async function handleApi(req, res, url) {
else if (site.id === 'modelscope') accName = `ModelScope (${identity || '会话'})`;
else if (site.id === 'longcat') accName = `LongCat (${identity || '会话'})`;
else if (site.id === 'lingxi') accName = site.accountName + (lxUidMasked ? `(${lxUidMasked})` : ''); // uid 后缀脱敏(前3后3),归户仍用原始 uid
+ else if (site.id === 'meoo') accName = site.accountName + (meooMasked ? `(${meooMasked})` : ''); // user_id 脱敏后缀,归户仍用原始值
else accName = site.accountName + (identity ? `(${identity})` : '');
// 自动行名只在「没手改过且现名属自动名家族」时写入(meta.renamed + isAutoGeneratedName 双闸)
const autoName = (row) => { if (canAutoRename(site, row)) row.name = accName; };
@@ -1538,6 +1602,8 @@ async function handleApi(req, res, url) {
...(lxSnapshot ? { lingxiPacks: lxSnapshot } : {}),
};
if (lcSession?.userId) { hit.uid = String(lcSession.userId); autoName(hit); }
+ if (meooUserId) { hit.uid = String(meooUserId); autoName(hit); }
+ if (meooGrants) hit.meta.meooGrants = meooGrants;
hit.updatedAt = now;
accountId = hit.id;
} else {
@@ -1546,7 +1612,7 @@ async function handleApi(req, res, url) {
provider: site.provider,
token: cred,
expiresAt: site.id === 'river' ? (riverSession?.expiresAt || undefined) : undefined,
- uid: mimoUserId || riverUid || (msSession?.id ? String(msSession.id) : undefined) || (lcSession?.userId ? String(lcSession.userId) : undefined) || (lxUid ? String(lxUid) : undefined) || undefined,
+ uid: mimoUserId || riverUid || (msSession?.id ? String(msSession.id) : undefined) || (lcSession?.userId ? String(lcSession.userId) : undefined) || (lxUid ? String(lxUid) : undefined) || (meooUserId ? String(meooUserId) : undefined) || undefined,
source: 'web-session',
meta: {
cookieSiteId: site.id,
@@ -1561,6 +1627,7 @@ async function handleApi(req, res, url) {
...(lcPacks ? { longcatPacks: longcatSnapshot(lcPacks) } : {}),
...(lcSession ? { longcatSession: lcSession } : {}),
...(lxSnapshot ? { lingxiPacks: lxSnapshot } : {}),
+ ...(meooGrants ? { meooGrants } : {}),
},
});
acc.verified = true;
@@ -1623,6 +1690,7 @@ async function handleApi(req, res, url) {
// ——失败说明会话可能已死,恰好要把 message 状态卡推上去让 10R 卡面可见。
const touchedSources = new Set();
const sourceOf = async (a) => {
+ if (site.no10rSync === true) return null; // 不推 10R 卡的站点(如 Meoo)不进推送名单
if (site.id === 'tokenharbor') return 'tokenharbor';
try {
const m = await import(
@@ -2065,6 +2133,7 @@ async function handleApi(req, res, url) {
trae: dayKey(Date.now(), 'trae'),
minimax: dayKey(Date.now(), 'minimax'),
lingxi: dayKey(Date.now(), 'lingxi'), // 网页会话签到线(#53):0 点翻日,徽标/isDone 的日界比对用
+ meoo: dayKey(Date.now(), 'meoo'), // Meoo 首登积分线:同为 0 点翻日
},
scheduler: await getSchedulerInfo(),
riskIdentity: riskIdentityAvailable(),
diff --git a/src/i18n/en.json b/src/i18n/en.json
index 9940c1a..dc5ea97 100644
--- a/src/i18n/en.json
+++ b/src/i18n/en.json
@@ -2001,5 +2001,28 @@
"回调里的 AuthCode 已过期,请重新发起": "The AuthCode in the callback has expired — please start again",
"回调里没有 AuthCode(授权页协议可能又变了),请重新发起或更新 CreditDaddy": "No AuthCode in the callback (the authorize-page protocol may have changed again) — please start again or update CreditDaddy",
"MiniMax Code 国内版": "MiniMax Code (CN)",
- "MiniMax Code 国际版": "MiniMax Code (International)"
+ "MiniMax Code 国际版": "MiniMax Code (International)",
+ "秒悟积分": "Meoo points",
+ "Meoo 秒悟": "Meoo",
+ "登录 meoo.com(阿里云账号手机号 + 短信)后捕获会话;CD 每日自动领取首登积分(当前 +2000/天,约 6~7 天后过期)并展示积分余额。会话失效(NEED_LOGIN)时请重新登录捕获,Cookie 只存本机。": "Sign in to meoo.com (Aliyun account, phone + SMS) and capture the session; CD claims the daily first-login points automatically (currently +2000/day, expiring after about 6–7 days) and shows the points balance. If the session expires (NEED_LOGIN), sign in and capture again. Cookies stay on this machine only.",
+ "Meoo 会话已失效(NEED_LOGIN),请重新登录捕获": "Meoo session expired (NEED_LOGIN); please sign in and capture again",
+ "{name} 就地重探通过({identity},积分 {balance})": "{name} re-probe passed ({identity}, points {balance})",
+ "未读到 Meoo 登录会话,请在登录窗完成登录后再关闭": "No Meoo login session found; please finish signing in before closing the window",
+ "{name} 权益接口探测被拒:{code}": "{name} entitlement probe rejected: {code}",
+ "{name} 探测通过({identity},积分 {balance})": "{name} probe passed ({identity}, points {balance})",
+ "{name} 权益接口抓取异常:{err}": "{name} entitlement fetch error: {err}",
+ "Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获": "Meoo session missing; sign in to meoo.com on the Web Sessions page and capture",
+ "Meoo 首登积分领取失败:{err}": "Meoo first-login points claim failed: {err}",
+ "Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获": "Meoo session expired ({code}); sign in to meoo.com again on the Web Sessions page and capture",
+ "Meoo 账号权益仍在初始化,稍后自动重试": "Meoo account entitlements are still initializing; will retry automatically",
+ "Meoo 首登积分领取成功": "Meoo first-login points claimed",
+ "首登积分已发放(+{n} 积分)": "First-login points granted (+{n} points)",
+ "今日首登积分已发放": "Today's first-login points already granted",
+ "Meoo 积分查询失败:{err}": "Meoo points query failed: {err}",
+ "每日首登": "Daily first login",
+ "邀请奖励": "Invite reward",
+ "订阅": "Subscription",
+ "积分余额": "Points balance",
+ "积分余额 ": "Points balance ",
+ "(最早 ": " (earliest expires "
}
diff --git a/src/i18n/ja.json b/src/i18n/ja.json
index 5b59c67..ff632c9 100644
--- a/src/i18n/ja.json
+++ b/src/i18n/ja.json
@@ -2001,5 +2001,28 @@
"回调里的 AuthCode 已过期,请重新发起": "コールバックの AuthCode が期限切れです。再度実行してください",
"回调里没有 AuthCode(授权页协议可能又变了),请重新发起或更新 CreditDaddy": "コールバックに AuthCode がありません(認可ページのプロトコルがまた変わった可能性があります)。再度実行するか CreditDaddy を更新してください",
"MiniMax Code 国内版": "MiniMax Code 国内版",
- "MiniMax Code 国际版": "MiniMax Code 国際版"
+ "MiniMax Code 国际版": "MiniMax Code 国際版",
+ "秒悟积分": "Meoo ポイント",
+ "Meoo 秒悟": "Meoo 秒悟",
+ "登录 meoo.com(阿里云账号手机号 + 短信)后捕获会话;CD 每日自动领取首登积分(当前 +2000/天,约 6~7 天后过期)并展示积分余额。会话失效(NEED_LOGIN)时请重新登录捕获,Cookie 只存本机。": "meoo.com に(Aliyun アカウント・電話番号 + SMS で)ログインしてセッションを取得します。CD が毎日の初回ログインポイントを自動受け取り(現在 +2000/日、約 6〜7 日で失効)、ポイント残高を表示します。セッション失効(NEED_LOGIN)時は再ログインして取得し直してください。Cookie はこの端末にのみ保存されます。",
+ "Meoo 会话已失效(NEED_LOGIN),请重新登录捕获": "Meoo セッションが失効しました(NEED_LOGIN)。再ログインして取得してください",
+ "{name} 就地重探通过({identity},积分 {balance})": "{name} その場で再検査 OK({identity}、ポイント {balance})",
+ "未读到 Meoo 登录会话,请在登录窗完成登录后再关闭": "Meoo のログインセッションが読み取れません。ログインを完了してからウィンドウを閉じてください",
+ "{name} 权益接口探测被拒:{code}": "{name} 権益 API の検査が拒否されました: {code}",
+ "{name} 探测通过({identity},积分 {balance})": "{name} 検査 OK({identity}、ポイント {balance})",
+ "{name} 权益接口抓取异常:{err}": "{name} 権益 API の取得エラー: {err}",
+ "Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获": "Meoo セッションがありません。「Web セッション」ページで meoo.com にログインして取得してください",
+ "Meoo 首登积分领取失败:{err}": "Meoo 初回ログインポイントの受け取りに失敗: {err}",
+ "Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获": "Meoo セッションが失効しました({code})。「Web セッション」ページで meoo.com に再ログインして取得してください",
+ "Meoo 账号权益仍在初始化,稍后自动重试": "Meoo アカウントの権益はまだ初期化中です。後で自動的に再試行します",
+ "Meoo 首登积分领取成功": "Meoo 初回ログインポイントを受け取りました",
+ "首登积分已发放(+{n} 积分)": "初回ログインポイント付与(+{n} ポイント)",
+ "今日首登积分已发放": "本日の初回ログインポイントは付与済み",
+ "Meoo 积分查询失败:{err}": "Meoo ポイントの照会に失敗: {err}",
+ "每日首登": "毎日の初回ログイン",
+ "邀请奖励": "招待報酬",
+ "订阅": "サブスクリプション",
+ "积分余额": "ポイント残高",
+ "积分余额 ": "ポイント残高 ",
+ "(最早 ": "(最短 "
}
diff --git a/src/i18n/ko.json b/src/i18n/ko.json
index 8be3df5..1da1be3 100644
--- a/src/i18n/ko.json
+++ b/src/i18n/ko.json
@@ -2001,5 +2001,28 @@
"回调里的 AuthCode 已过期,请重新发起": "콜백의 AuthCode가 만료되었습니다. 다시 시작해 주세요",
"回调里没有 AuthCode(授权页协议可能又变了),请重新发起或更新 CreditDaddy": "콜백에 AuthCode가 없습니다(인가 페이지 프로토콜이 다시 변경된 것 같습니다). 다시 시작하거나 CreditDaddy를 업데이트해 주세요",
"MiniMax Code 国内版": "MiniMax Code 국내판",
- "MiniMax Code 国际版": "MiniMax Code 국제판"
+ "MiniMax Code 国际版": "MiniMax Code 국제판",
+ "秒悟积分": "Meoo 포인트",
+ "Meoo 秒悟": "Meoo",
+ "登录 meoo.com(阿里云账号手机号 + 短信)后捕获会话;CD 每日自动领取首登积分(当前 +2000/天,约 6~7 天后过期)并展示积分余额。会话失效(NEED_LOGIN)时请重新登录捕获,Cookie 只存本机。": "meoo.com에 (Aliyun 계정, 휴대폰 + SMS) 로그인한 뒤 세션을 캡처하세요. CD가 매일 첫 로그인 포인트를 자동으로 받고(현재 +2000/일, 약 6~7일 후 만료) 포인트 잔액을 표시합니다. 세션이 만료되면(NEED_LOGIN) 다시 로그인해 캡처하세요. 쿠키는 이 기기에만 저장됩니다.",
+ "Meoo 会话已失效(NEED_LOGIN),请重新登录捕获": "Meoo 세션이 만료되었습니다(NEED_LOGIN). 다시 로그인해 캡처하세요",
+ "{name} 就地重探通过({identity},积分 {balance})": "{name} 재검사 통과({identity}, 포인트 {balance})",
+ "未读到 Meoo 登录会话,请在登录窗完成登录后再关闭": "Meoo 로그인 세션을 읽지 못했습니다. 로그인을 완료한 뒤 창을 닫으세요",
+ "{name} 权益接口探测被拒:{code}": "{name} 권익 API 검사 거부: {code}",
+ "{name} 探测通过({identity},积分 {balance})": "{name} 검사 통과({identity}, 포인트 {balance})",
+ "{name} 权益接口抓取异常:{err}": "{name} 권익 API 조회 오류: {err}",
+ "Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获": "Meoo 세션이 없습니다. 「웹 세션」 페이지에서 meoo.com에 로그인해 캡처하세요",
+ "Meoo 首登积分领取失败:{err}": "Meoo 첫 로그인 포인트 수령 실패: {err}",
+ "Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获": "Meoo 세션이 만료되었습니다({code}). 「웹 세션」 페이지에서 meoo.com에 다시 로그인해 캡처하세요",
+ "Meoo 账号权益仍在初始化,稍后自动重试": "Meoo 계정 권익이 아직 초기화 중입니다. 잠시 후 자동으로 재시도합니다",
+ "Meoo 首登积分领取成功": "Meoo 첫 로그인 포인트 수령 성공",
+ "首登积分已发放(+{n} 积分)": "첫 로그인 포인트 지급(+{n} 포인트)",
+ "今日首登积分已发放": "오늘의 첫 로그인 포인트는 이미 지급됨",
+ "Meoo 积分查询失败:{err}": "Meoo 포인트 조회 실패: {err}",
+ "每日首登": "매일 첫 로그인",
+ "邀请奖励": "초대 보상",
+ "订阅": "구독",
+ "积分余额": "포인트 잔액",
+ "积分余额 ": "포인트 잔액 ",
+ "(最早 ": "(가장 이른 만료 "
}
diff --git a/src/i18n/zh-TW.json b/src/i18n/zh-TW.json
index 4b6e3ce..a681ba2 100644
--- a/src/i18n/zh-TW.json
+++ b/src/i18n/zh-TW.json
@@ -2001,5 +2001,28 @@
"回调里的 AuthCode 已过期,请重新发起": "回調裡的 AuthCode 已過期,請重新發起",
"回调里没有 AuthCode(授权页协议可能又变了),请重新发起或更新 CreditDaddy": "回調裡沒有 AuthCode(授權頁協議可能又變了),請重新發起或更新 CreditDaddy",
"MiniMax Code 国内版": "MiniMax Code 國內版",
- "MiniMax Code 国际版": "MiniMax Code 國際版"
+ "MiniMax Code 国际版": "MiniMax Code 國際版",
+ "秒悟积分": "秒悟積分",
+ "Meoo 秒悟": "Meoo 秒悟",
+ "登录 meoo.com(阿里云账号手机号 + 短信)后捕获会话;CD 每日自动领取首登积分(当前 +2000/天,约 6~7 天后过期)并展示积分余额。会话失效(NEED_LOGIN)时请重新登录捕获,Cookie 只存本机。": "登入 meoo.com(阿里雲帳號手機號 + 簡訊)後擷取工作階段;CD 每日自動領取首登積分(目前 +2000/天,約 6~7 天後過期)並顯示積分餘額。工作階段失效(NEED_LOGIN)時請重新登入擷取,Cookie 只存本機。",
+ "Meoo 会话已失效(NEED_LOGIN),请重新登录捕获": "Meoo 工作階段已失效(NEED_LOGIN),請重新登入擷取",
+ "{name} 就地重探通过({identity},积分 {balance})": "{name} 就地重探通過({identity},積分 {balance})",
+ "未读到 Meoo 登录会话,请在登录窗完成登录后再关闭": "未讀到 Meoo 登入工作階段,請在登入視窗完成登入後再關閉",
+ "{name} 权益接口探测被拒:{code}": "{name} 權益介面探測被拒:{code}",
+ "{name} 探测通过({identity},积分 {balance})": "{name} 探測通過({identity},積分 {balance})",
+ "{name} 权益接口抓取异常:{err}": "{name} 權益介面抓取異常:{err}",
+ "Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获": "Meoo 工作階段缺失,請在「網頁工作階段」頁登入 meoo.com 後擷取",
+ "Meoo 首登积分领取失败:{err}": "Meoo 首登積分領取失敗:{err}",
+ "Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获": "Meoo 工作階段已失效({code}),請到「網頁工作階段」頁重新登入 meoo.com 並擷取",
+ "Meoo 账号权益仍在初始化,稍后自动重试": "Meoo 帳號權益仍在初始化,稍後自動重試",
+ "Meoo 首登积分领取成功": "Meoo 首登積分領取成功",
+ "首登积分已发放(+{n} 积分)": "首登積分已發放(+{n} 積分)",
+ "今日首登积分已发放": "今日首登積分已發放",
+ "Meoo 积分查询失败:{err}": "Meoo 積分查詢失敗:{err}",
+ "每日首登": "每日首登",
+ "邀请奖励": "邀請獎勵",
+ "订阅": "訂閱",
+ "积分余额": "積分餘額",
+ "积分余额 ": "積分餘額 ",
+ "(最早 ": "(最早 "
}
diff --git a/src/meooClient.js b/src/meooClient.js
new file mode 100644
index 0000000..efde2e2
--- /dev/null
+++ b/src/meooClient.js
@@ -0,0 +1,250 @@
+/**
+ * Meoo 秒悟(meoo.com,阿里云)每日首登积分 + 积分余额抓取。
+ *
+ * 接口(2026-11-13 网页端 bundle 与本机实测):
+ * GET https://meoo.com/api/mc/entitlement/init
+ * 网页前端登录后调用它触发「每日首次登录」积分发放(当前 2000 积分/天)。
+ * → { success: true, data: { initialized, dailyLoginGranted, dailyLoginGrantAmount, inviteRewardArrivals } }
+ * initialized=false 时前端按 500/1000/2000/4000ms 退避重查(账号权益尚在初始化)。
+ * GET https://meoo.com/api/mc/entitlement/get
+ * → data.userPointInfo.account.availableBalance(积分余额)、data.planCode、
+ * data.benefits[](typeCode=daily_login 的 quota = 每日首登额度)
+ * GET https://meoo.com/api/v1/user/me
+ * → data.user_id / username / email;未登录 data.need_login=true
+ *
+ * 凭据 = meoo.com 网页会话 Cookie(阿里云 SSO 手机号 + 短信登录后由浏览器写入),
+ * 不支持 Meoo CLI 的 OAuth Bearer(/api/mc/* 一律 NEED_LOGIN)。会话寿命未知——
+ * 失效统一表现为 HTTP 401 或 { success:false, errCode:'NEED_LOGIN' | 'LOGIN_INVALID' },
+ * 按「会话已失效,请重新登录捕获」处理,不重试、不猜。
+ *
+ * 首版边界:首登积分 + 余额展示;每日首登积分约 6~7 天后过期、Free 套餐无月度积分;
+ * 不推 10Router 外部配额卡(site.no10rSync),不做网关。
+ */
+
+import { fetchJsonRace } from './zcodeClient.js';
+import { FETCH_TIMEOUT_MS } from './constants.js';
+import { t } from './i18n.js';
+
+export const MEOO_BASE = 'https://meoo.com';
+export const MEOO_INIT_URL = `${MEOO_BASE}/api/mc/entitlement/init`;
+export const MEOO_ENTITLEMENT_URL = `${MEOO_BASE}/api/mc/entitlement/get`;
+export const MEOO_USER_URL = `${MEOO_BASE}/api/v1/user/me`;
+
+/** 前端同款退避(毫秒):initialized=false 时最多再查这几次 */
+export const MEOO_INIT_RETRY_MS = [500, 1000, 2000];
+
+const AUTH_CODES = new Set(['NEED_LOGIN', 'LOGIN_INVALID', 'LOGIN_REQUIRED', 'NOT_LOGIN', 'UNAUTHORIZED']);
+
+function meooHeaders(cookie) {
+ return {
+ Cookie: String(cookie || '').trim(),
+ Referer: `${MEOO_BASE}/`,
+ Origin: MEOO_BASE,
+ Accept: 'application/json, text/plain, */*',
+ 'User-Agent':
+ 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36',
+ };
+}
+
+/**
+ * GET 一个 Meoo 网页端点,归一成 { auth, ok, status, data, code }。
+ * auth=false = 会话失效(401/403 或业务码 NEED_LOGIN 类,或 user/me 的 need_login=true)。
+ */
+async function meooGet(url, cookie, fetchImpl) {
+ const doIt = fetchImpl || ((u, init) => fetchJsonRace(u, { headers: init.headers, method: 'GET', timeoutMs: FETCH_TIMEOUT_MS }));
+ const r = await doIt(url, { headers: meooHeaders(cookie) });
+ const text = await r.text().catch(() => '');
+ let body = null;
+ try { body = text ? JSON.parse(text) : null; } catch { body = null; }
+ const code = String(body?.errCode ?? body?.error?.code ?? body?.code ?? '');
+ if (r.status === 401 || r.status === 403 || AUTH_CODES.has(code) || body?.data?.need_login === true) {
+ return { auth: false, ok: false, status: r.status, code: code || `HTTP ${r.status}` };
+ }
+ if (!r.ok) return { auth: true, ok: false, status: r.status, code: code || `HTTP ${r.status}`, message: body?.errMsg || body?.message || '' };
+ if (!body || typeof body !== 'object') return { auth: true, ok: false, status: r.status, code: 'non-json' };
+ if (body.success === false) return { auth: true, ok: false, status: r.status, code: code || 'success=false', message: body.errMsg || body.message || '' };
+ return { auth: true, ok: true, status: r.status, data: body.data ?? body };
+}
+
+function authError(msg) {
+ const e = new Error(msg);
+ e.auth = true;
+ return e;
+}
+
+/**
+ * 用户 id 展示脱敏(与灵犀 uid 同口径):≥7 位前 3 后 3,4~6 位前 1 后 1,更短全掩。
+ * 只用于行名等显示位;归户哈希 / 账号 uid 仍用原始值。
+ */
+export function maskMeooUserId(id) {
+ const s = String(id ?? '');
+ if (s.length >= 7) return s.slice(0, 3) + '****' + s.slice(-3);
+ if (s.length >= 4) return s.slice(0, 1) + '****' + s.slice(-1);
+ return '****';
+}
+
+/**
+ * 身份:GET /api/v1/user/me。
+ * @returns {Promise<{auth: boolean, ok?: boolean, userId?: string|null, code?: string}>}
+ * username / email 可能是手机号,不回传(只取稳定 user_id 做归户)。
+ */
+export async function fetchMeooIdentity(cookie, { fetchImpl } = {}) {
+ try {
+ const r = await meooGet(MEOO_USER_URL, cookie, fetchImpl);
+ if (!r.auth) return { auth: false, code: r.code };
+ if (!r.ok) return { auth: true, ok: false, code: r.code };
+ const id = r.data?.user_id ?? r.data?.userId ?? r.data?.open_id ?? null;
+ return { auth: true, ok: true, userId: id != null && String(id) ? String(id) : null };
+ } catch (e) {
+ return { auth: true, ok: false, code: e?.message || 'network' };
+ }
+}
+
+/** 把一条 activeGrants 记录拍平成 { source, remaining, total, effectiveAt, expireAt } 列表(形状尽力而为) */
+function flattenGrants(grants) {
+ const out = [];
+ for (const g of Array.isArray(grants) ? grants : []) {
+ const source = String(g?.sourceType || g?.source || '');
+ const details = Array.isArray(g?.grantDetails) && g.grantDetails.length ? g.grantDetails : [g];
+ for (const d of details) {
+ const remaining = Number(d?.remainingAmount ?? d?.remaining ?? d?.availableAmount);
+ if (!Number.isFinite(remaining)) continue;
+ const total = Number(d?.grantAmount ?? d?.amount ?? d?.totalAmount);
+ out.push({
+ source,
+ remaining,
+ total: Number.isFinite(total) ? total : remaining,
+ effectiveAt: d?.effectiveDate || null,
+ expireAt: d?.expiresDate || d?.expireDate || null,
+ });
+ }
+ }
+ return out;
+}
+
+/**
+ * 权益与积分余额:GET /api/mc/entitlement/get。
+ * @returns {Promise<{auth: boolean, ok?: boolean, balance?: number, planCode?: string, dailyLoginQuota?: number,
+ * grants?: Array<{source, remaining, total, effectiveAt, expireAt}>, code?: string}>}
+ */
+export async function fetchMeooEntitlement(cookie, { fetchImpl } = {}) {
+ try {
+ const r = await meooGet(MEOO_ENTITLEMENT_URL, cookie, fetchImpl);
+ if (!r.auth) return { auth: false, code: r.code };
+ if (!r.ok) return { auth: true, ok: false, code: r.code };
+ const d = r.data || {};
+ const upi = d.userPointInfo || {};
+ const out = { auth: true, ok: true };
+ const bal = Number(upi.account?.availableBalance);
+ if (Number.isFinite(bal)) out.balance = bal;
+ if (d.planCode) out.planCode = String(d.planCode).toLowerCase();
+ const daily = Array.isArray(d.benefits) ? d.benefits.find((b) => b?.typeCode === 'daily_login') : null;
+ const dq = Number(daily?.quota);
+ if (Number.isFinite(dq) && dq > 0) out.dailyLoginQuota = dq;
+ const grants = flattenGrants(upi.activeGrants);
+ if (grants.length) out.grants = grants;
+ return out;
+ } catch (e) {
+ return { auth: true, ok: false, code: e?.message || 'network' };
+ }
+}
+
+function isoOrNull(v) {
+ if (!v) return null;
+ const d = new Date(v);
+ return Number.isFinite(d.getTime()) ? d.toISOString() : null;
+}
+
+const sleepMs = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
+
+/**
+ * 每日首登积分:调 entitlement/init(网页端登录后同款调用)。
+ * - dailyLoginGranted=true 且金额>0 → checked-in(+N 积分)
+ * - initialized=true 但本次没有发放 → already(今日已发放,多为网页端已登录过)
+ * - initialized 一直为 false(前端同款退避后仍未就绪)→ failed,下轮自动重试
+ * - NEED_LOGIN / 401 → failed,提示重新登录捕获(不抛,签到轮记一次失败即可)
+ * @returns {Promise<{ status: 'checked-in'|'already'|'failed', message: string, claimedAmount?: number, amount?: number, balanceAfter?: number, authExpired?: boolean }>}
+ */
+export async function checkinMeoo(account, ctx = {}) {
+ const cookie = account?.meta?.cookieSiteSession?.cookie || account?.token;
+ if (!cookie) {
+ return { status: 'failed', authExpired: true, message: t('Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获') };
+ }
+ const fetchImpl = ctx?.fetchImpl;
+ const sleep = ctx?.sleep || sleepMs;
+ let r;
+ try {
+ r = await meooGet(MEOO_INIT_URL, cookie, fetchImpl);
+ for (const ms of MEOO_INIT_RETRY_MS) {
+ if (!r.auth || !r.ok || r.data?.initialized) break;
+ await sleep(ms);
+ r = await meooGet(MEOO_INIT_URL, cookie, fetchImpl);
+ }
+ } catch (e) {
+ return { status: 'failed', message: t('Meoo 首登积分领取失败:{err}', { err: e?.message || 'network' }) };
+ }
+ if (!r.auth) {
+ return { status: 'failed', authExpired: true, message: t('Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获', { code: r.code || 'NEED_LOGIN' }) };
+ }
+ if (!r.ok) {
+ return { status: 'failed', message: t('Meoo 首登积分领取失败:{err}', { err: [r.code, r.message].filter(Boolean).join(' ').slice(0, 120) }) };
+ }
+ const data = r.data || {};
+ if (!data.initialized) {
+ return { status: 'failed', message: t('Meoo 账号权益仍在初始化,稍后自动重试') };
+ }
+ // 余额旁路(失败不影响领取结果)
+ let balanceAfter;
+ try {
+ const ent = await fetchMeooEntitlement(cookie, { fetchImpl });
+ if (ent.ok && Number.isFinite(ent.balance)) balanceAfter = ent.balance;
+ } catch { /* 余额拿不到不阻断 */ }
+ const amount = Number(data.dailyLoginGrantAmount);
+ if (data.dailyLoginGranted && Number.isFinite(amount) && amount > 0) {
+ ctx?.log?.(t('Meoo 首登积分领取成功'));
+ return {
+ status: 'checked-in',
+ message: t('首登积分已发放(+{n} 积分)', { n: amount }),
+ claimedAmount: amount,
+ amount,
+ ...(balanceAfter !== undefined ? { balanceAfter } : {}),
+ };
+ }
+ return {
+ status: 'already',
+ message: t('今日首登积分已发放'),
+ ...(balanceAfter !== undefined ? { balanceAfter } : {}),
+ };
+}
+
+/**
+ * 积分余额(quota 口径:remaining = 可用积分;parts 按发放来源逐笔,带到期日)
+ */
+export async function fetchMeooQuota(account, ctx = {}) {
+ const cookie = account?.meta?.cookieSiteSession?.cookie || account?.token;
+ if (!cookie) throw authError(t('Meoo 会话缺失,请在「网页会话」页登录 meoo.com 后捕获'));
+ const ent = await fetchMeooEntitlement(cookie, { fetchImpl: ctx?.fetchImpl });
+ if (!ent.auth) throw authError(t('Meoo 会话已失效({code}),请到「网页会话」页重新登录 meoo.com 并捕获', { code: ent.code || 'NEED_LOGIN' }));
+ if (!ent.ok) throw new Error(t('Meoo 积分查询失败:{err}', { err: ent.code || '' }));
+ const balance = Number.isFinite(ent.balance) ? ent.balance : 0;
+ const SOURCE_LABEL = { DAILY_LOGIN: '每日首登', INVITE_REWARD: '邀请奖励', SUBSCRIPTION: '订阅' };
+ const parts = (ent.grants || []).map((g) => ({
+ name: t(SOURCE_LABEL[g.source] || '积分包'),
+ total: g.total,
+ used: Math.max(0, g.total - g.remaining),
+ remaining: g.remaining,
+ expiresAt: isoOrNull(g.expireAt),
+ recurring: false,
+ unit: '秒悟积分',
+ }));
+ return {
+ total: balance,
+ used: 0,
+ remaining: balance,
+ exceeded: false,
+ plan: ent.planCode ? `Meoo ${ent.planCode}` : 'Meoo',
+ planExpiresAt: null,
+ parts: parts.length ? parts : [{ name: t('积分余额'), total: balance, used: 0, remaining: balance, recurring: true, unit: '秒悟积分' }],
+ empty: balance <= 0,
+ };
+}
diff --git a/src/panel.html b/src/panel.html
index 4ec28ed..ca4a4b0 100644
--- a/src/panel.html
+++ b/src/panel.html
@@ -1303,7 +1303,7 @@
const CHECKIN_PRODUCTS = new Set(['qoder', 'workbuddy', 'trae', 'minimax']);
// 网页会话卡支持「立即签到」的站点:provider 在 providers.js 挂了 checkin 且非 entryOnly
// (豆包虽挂了 checkin 实现,但定位是纯打开入口、CD 不代签,不在此列)
-const FREE_CHECKIN_SITES = new Set(['lingxi']);
+const FREE_CHECKIN_SITES = new Set(['lingxi', 'meoo']);
const CLIENT_LABEL = { qoder: 'Qoder', workbuddy: 'WorkBuddy', zcode: 'ZCode', mirasim: 'mirasim', catpaw: '妙手', trae: 'Trae', minimax: 'MiniMax', loomy: 'Loomy' };
// 产品页顶部改用「有效套餐 N/M」而不是「剩余积分」的产品(单位不是积分:Token / % / Credits)。
// ⚠️ 只影响**产品页头部文案**;「能不能并进仪表盘剩余积分合计」由下方的 CREDIT_SUM_EXCLUDED
@@ -3039,6 +3039,18 @@
return '' + T('智点余额 ') + Number(bal).toLocaleString() + '' + detail;
}
}
+ if (site.id === 'meoo') {
+ // 积分余额 + 最早到期日(每日首登积分约 6~7 天后过期)
+ const bal = acc.quota?.total;
+ if (Number.isFinite(bal)) {
+ const grants = acc.meooGrants?.grants;
+ const firstExp = Array.isArray(grants) ? (grants.map((g) => g.expireAt).filter(Boolean).sort()[0] || '') : '';
+ const detail = firstExp
+ ? '' + T('(最早 ') + esc(String(firstExp).slice(0, 10)) + T(' 到期)') + ''
+ : '';
+ return '' + T('积分余额 ') + Number(bal).toLocaleString() + '' + detail;
+ }
+ }
if (site.id === 'tokenharbor') {
const used = acc.quota?.used;
if (Number.isFinite(used)) {
diff --git a/src/providers.js b/src/providers.js
index 02e75fa..31abc8d 100644
--- a/src/providers.js
+++ b/src/providers.js
@@ -21,6 +21,7 @@ const isTraeIntl = (account) => account?.provider === 'trae-intl' || Boolean(acc
import { fetchMiniMaxQuota, checkinMiniMax, verifyMiniMaxAccount } from './minimaxClient.js';
import { fetchDoubaoStatus, checkinDoubao } from './doubaoClient.js';
import { checkinLingxi, fetchLingxiQuota } from './lingxiClient.js';
+import { checkinMeoo, fetchMeooQuota } from './meooClient.js';
import { fetchLoomyQuota, verifyLoomyAccount } from './loomyClient.js';
/** 从 userinfo 响应中挑一个可读的显示名 */
export function displayNameFrom(ui) {
@@ -256,6 +257,14 @@ const PRODUCTS = {
checkin: (account) => checkinLingxi(account),
quota: (account) => fetchLingxiQuota(account),
},
+ meoo: {
+ label: 'Meoo 秒悟',
+ // 网页会话捕获线:凭据是 meoo.com 会话 cookie(meta.cookieSiteSession.cookie,阿里云 SSO 登录)。
+ // 「签到」= 调网页端同款 entitlement/init 触发每日首登积分(2000/天,约 6~7 天后过期);
+ // NEED_LOGIN 记失败并提示重新捕获。额度 = 积分余额(独立口径,unit '秒悟积分' 不进剩余积分合计)。
+ checkin: (account) => checkinMeoo(account),
+ quota: (account) => fetchMeooQuota(account),
+ },
loomy: {
label: '讯飞 Loomy',
quota: (account) => fetchLoomyQuota(account),
diff --git a/test/meoo.test.js b/test/meoo.test.js
new file mode 100644
index 0000000..11e80ee
--- /dev/null
+++ b/test/meoo.test.js
@@ -0,0 +1,159 @@
+import { test } from 'node:test';
+import assert from 'node:assert/strict';
+import {
+ checkinMeoo, fetchMeooQuota, fetchMeooEntitlement, fetchMeooIdentity, maskMeooUserId,
+ MEOO_INIT_URL, MEOO_ENTITLEMENT_URL, MEOO_USER_URL,
+} from '../src/meooClient.js';
+import { getSite } from '../src/cookieSites.js';
+import { PROVIDERS, productOf } from '../src/constants.js';
+import { productImpl } from '../src/providers.js';
+
+const res = (status, body) => ({ status, ok: status >= 200 && status < 300, text: async () => JSON.stringify(body) });
+const NEED_LOGIN = { success: false, errCode: 'NEED_LOGIN', errMsg: 'need login' };
+
+const entBody = (balance = 4000, extra = {}) => ({
+ success: true,
+ data: {
+ planCode: 'FREE',
+ benefits: [{ typeCode: 'daily_login', quota: 2000 }, { typeCode: 'model_credits', quota: 0, used: 0 }],
+ userPointInfo: { account: { availableBalance: balance, totalGranted: 6000, totalConsumed: 2000 }, ...extra },
+ },
+});
+
+/** 按 URL 分发的假 fetch;init 支持按调用次序返回不同响应 */
+function mockFetch({ init = [], ent = res(200, entBody()), user = res(200, { success: true, data: { user_id: '1234567890' } }) } = {}) {
+ const calls = [];
+ let i = 0;
+ const fn = async (url, initOpts) => {
+ calls.push({ url, headers: initOpts?.headers });
+ if (url === MEOO_INIT_URL) { const r = init[Math.min(i, init.length - 1)]; i++; return r; }
+ if (url === MEOO_ENTITLEMENT_URL) return ent;
+ if (url === MEOO_USER_URL) return user;
+ throw new Error(`unexpected url: ${url}`);
+ };
+ fn.calls = calls;
+ return fn;
+}
+
+const account = { meta: { cookieSiteSession: { cookie: 'session=test' } } };
+const noSleep = async () => {};
+
+test('首登发放:dailyLoginGranted=true → checked-in +2000,并带回余额', async () => {
+ const f = mockFetch({ init: [res(200, { success: true, data: { initialized: true, dailyLoginGranted: true, dailyLoginGrantAmount: 2000, inviteRewardArrivals: [] } })] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'checked-in');
+ assert.equal(r.claimedAmount, 2000);
+ assert.equal(r.balanceAfter, 4000);
+ assert.match(r.message, /\+2000/);
+ // 会话 cookie 原样带上,Referer/Origin 为 meoo.com
+ assert.equal(f.calls[0].headers.Cookie, 'session=test');
+ assert.equal(f.calls[0].headers.Origin, 'https://meoo.com');
+});
+
+test('当日已初始化且无发放 → already(不报虚假 +N)', async () => {
+ const f = mockFetch({ init: [res(200, { success: true, data: { initialized: true, dailyLoginGranted: false, dailyLoginGrantAmount: 0 } })] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'already');
+ assert.equal(r.claimedAmount, undefined);
+});
+
+test('initialized=false 按前端同款退避重查,就绪后发放', async () => {
+ const f = mockFetch({ init: [
+ res(200, { success: true, data: { initialized: false } }),
+ res(200, { success: true, data: { initialized: true, dailyLoginGranted: true, dailyLoginGrantAmount: 2000 } }),
+ ] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'checked-in');
+ assert.equal(f.calls.filter((c) => c.url === MEOO_INIT_URL).length, 2);
+});
+
+test('initialized 一直 false → failed(下轮自动重试),重查次数有上限', async () => {
+ const f = mockFetch({ init: [res(200, { success: true, data: { initialized: false } })] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'failed');
+ assert.ok(f.calls.filter((c) => c.url === MEOO_INIT_URL).length <= 4);
+});
+
+test('NEED_LOGIN(401)→ failed + authExpired,提示重新登录捕获,不抛异常', async () => {
+ const f = mockFetch({ init: [res(401, NEED_LOGIN)] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'failed');
+ assert.equal(r.authExpired, true);
+ assert.match(r.message, /NEED_LOGIN/);
+ assert.match(r.message, /重新登录/);
+});
+
+test('HTTP 200 但业务码 NEED_LOGIN 同样判会话失效', async () => {
+ const f = mockFetch({ init: [res(200, NEED_LOGIN)] });
+ const r = await checkinMeoo(account, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'failed');
+ assert.equal(r.authExpired, true);
+});
+
+test('无会话 cookie → failed,不发请求', async () => {
+ const f = mockFetch({ init: [res(200, {})] });
+ const r = await checkinMeoo({ meta: {} }, { fetchImpl: f, sleep: noSleep });
+ assert.equal(r.status, 'failed');
+ assert.equal(f.calls.length, 0);
+});
+
+test('权益接口:余额 / 套餐 / 每日首登额度 / 逐笔发放(带到期日)', async () => {
+ const ent = res(200, entBody(4000, { activeGrants: [
+ { sourceType: 'DAILY_LOGIN', grantDetails: [
+ { remainingAmount: 2000, grantAmount: 2000, effectiveDate: '2026-11-12', expiresDate: '2026-11-19' },
+ { remainingAmount: 0, grantAmount: 2000, effectiveDate: '2026-11-11', expiresDate: '2026-11-18' },
+ ] },
+ { sourceType: 'INVITE_REWARD', grantDetails: [{ remainingAmount: 2000, grantAmount: 2000, expiresDate: '2026-12-01' }] },
+ ] }));
+ const e = await fetchMeooEntitlement('session=test', { fetchImpl: mockFetch({ ent }) });
+ assert.equal(e.auth, true);
+ assert.equal(e.balance, 4000);
+ assert.equal(e.planCode, 'free');
+ assert.equal(e.dailyLoginQuota, 2000);
+ assert.equal(e.grants.length, 3);
+ assert.deepEqual(e.grants[0], { source: 'DAILY_LOGIN', remaining: 2000, total: 2000, effectiveAt: '2026-11-12', expireAt: '2026-11-19' });
+
+ const q = await fetchMeooQuota(account, { fetchImpl: mockFetch({ ent }) });
+ assert.equal(q.remaining, 4000);
+ assert.equal(q.parts.length, 3);
+ assert.equal(q.parts[1].used, 2000);
+ assert.ok(q.parts[0].expiresAt.startsWith('2026-11-19'));
+ assert.equal(q.parts[0].unit, '秒悟积分');
+});
+
+test('权益接口无逐笔明细时 quota 兜底单行余额', async () => {
+ const q = await fetchMeooQuota(account, { fetchImpl: mockFetch() });
+ assert.equal(q.total, 4000);
+ assert.equal(q.parts.length, 1);
+ assert.equal(q.empty, false);
+});
+
+test('quota 遇 NEED_LOGIN 抛 auth 错误', async () => {
+ await assert.rejects(
+ () => fetchMeooQuota(account, { fetchImpl: mockFetch({ ent: res(401, NEED_LOGIN) }) }),
+ (e) => e.auth === true && /NEED_LOGIN/.test(e.message),
+ );
+});
+
+test('身份:user/me 取 user_id;need_login=true 判失效;展示脱敏', async () => {
+ const ok = await fetchMeooIdentity('session=test', { fetchImpl: mockFetch() });
+ assert.equal(ok.userId, '1234567890');
+ const dead = await fetchMeooIdentity('session=test', { fetchImpl: mockFetch({ user: res(200, { success: true, data: { need_login: true } }) }) });
+ assert.equal(dead.auth, false);
+ assert.equal(maskMeooUserId('1234567890'), '123****890');
+ assert.equal(maskMeooUserId('12345'), '1****5');
+ assert.equal(maskMeooUserId('12'), '****');
+});
+
+test('接线:站点表 / provider / 产品线 / 签到实现均已登记', () => {
+ const site = getSite('meoo');
+ assert.ok(site);
+ assert.equal(site.provider, 'meoo');
+ assert.equal(site.verify, 'api');
+ assert.equal(site.no10rSync, true);
+ assert.deepEqual(site.cookieUrls, ['https://meoo.com']);
+ assert.ok(PROVIDERS.includes('meoo'));
+ assert.equal(productOf('meoo'), 'meoo');
+ assert.equal(typeof productImpl('meoo').checkin, 'function');
+ assert.equal(typeof productImpl('meoo').quota, 'function');
+});