creditdaddy 1.5.2 → 1.6.2
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/build.json +3 -3
- package/package.json +1 -1
- package/src/assets/donate-alipay.svg +1 -0
- package/src/assets/donate-ifdian.svg +1 -0
- package/src/assets/donate-wechat.svg +1 -0
- package/src/checkin.js +11 -10
- package/src/constants.js +3 -1
- package/src/cookieSites.js +30 -11
- package/src/daemon.js +2062 -1961
- package/src/doubaoClient.js +168 -0
- package/src/externalQuota.js +113 -9
- package/src/formatDuration.js +17 -0
- package/src/i18n/en.json +1734 -1755
- package/src/i18n/ja.json +1734 -1755
- package/src/i18n/ko.json +1734 -1755
- package/src/i18n/zh-TW.json +1734 -1755
- package/src/longcatClient.js +24 -15
- package/src/minimaxClient.js +29 -0
- package/src/minimaxGateway.js +459 -419
- package/src/mirasimClient.js +3 -0
- package/src/panel.html +393 -126
- package/src/providers.js +39 -3
- package/src/qoderApp.js +10 -1
- package/src/tenrouter.js +27 -3
- package/src/traeClient.js +23 -15
- package/src/traeGateway.js +52 -4
- package/src/traeLocal.js +14 -4
- package/src/usageSync.js +10 -2
- package/src/workbuddyClient.js +0 -45
- package/src/workbuddyLocal.js +40 -0
- package/src/zcodeAutoClaim.js +21 -4
- package/src/zcodeClient.js +8 -0
- package/src/zcodeGateway.js +763 -616
package/src/panel.html
CHANGED
|
@@ -224,6 +224,8 @@
|
|
|
224
224
|
.credit { display: inline-flex; align-items: center; gap: 4px; font-variant-numeric: tabular-nums; }
|
|
225
225
|
.credit b { font-weight: 700; }
|
|
226
226
|
.credit svg { width: 13px; height: 13px; color: rgb(var(--accent-rgb)); }
|
|
227
|
+
.credit.pending b { color: var(--text-3); }
|
|
228
|
+
.seg.pending-ghost { background: repeating-linear-gradient(90deg, color-mix(in srgb, var(--text-3) 30%, transparent) 0 6px, transparent 6px 10px); }
|
|
227
229
|
.dim { color: var(--text-3); }
|
|
228
230
|
.claim-plan { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 10px 2px; border-bottom: 1px solid var(--border); }
|
|
229
231
|
.claim-plan:last-of-type { border-bottom: 0; }
|
|
@@ -472,11 +474,6 @@
|
|
|
472
474
|
</div>
|
|
473
475
|
<div class="hint" data-i18n="作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。">作用于全部产品的定时签到轮(ZCode 活动领取在自身页签单独配置);修改立即生效,下一轮按新间隔执行。</div>
|
|
474
476
|
</div>
|
|
475
|
-
<div class="field">
|
|
476
|
-
<label data-i18n="WorkBuddy 国际版 活跃请求">WorkBuddy 国际版 活跃请求</label>
|
|
477
|
-
<label class="check"><input type="checkbox" id="cfg-wb-activity"> <span data-i18n="定期发送活跃请求(保持资格,不保证奖励)">定期发送活跃请求(保持资格,不保证奖励)</span></label>
|
|
478
|
-
<div class="hint" data-i18n="关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。">关闭后本机不再对国际版账号自动/手动发活跃请求;卡片与仪表盘的「活跃」入口一同隐藏。</div>
|
|
479
|
-
</div>
|
|
480
477
|
<div class="field">
|
|
481
478
|
<label data-i18n="面板访问密码">面板访问密码</label>
|
|
482
479
|
<div id="sec-block"></div>
|
|
@@ -769,6 +766,35 @@
|
|
|
769
766
|
</div>
|
|
770
767
|
</div>
|
|
771
768
|
|
|
769
|
+
<!-- 捐赠支持(面向国内用户:爱发电 / 支付宝 / 微信;渠道与 10Router 一致,UI 照 CD 设计语言) -->
|
|
770
|
+
<div class="mask" id="m-donate">
|
|
771
|
+
<div class="modal">
|
|
772
|
+
<div class="modal-title"><span data-i18n="捐赠支持">捐赠支持</span><button class="close" data-close>✕</button></div>
|
|
773
|
+
<div class="hint" data-i18n="如果 CreditDaddy 对你有帮助,欢迎支持一下开发——免费渠道的每一行代码都是咖啡驱动的。">如果 CreditDaddy 对你有帮助,欢迎支持一下开发——免费渠道的每一行代码都是咖啡驱动的。</div>
|
|
774
|
+
<div style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-top:14px">
|
|
775
|
+
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
|
|
776
|
+
<span style="font-size:26px">⚡</span>
|
|
777
|
+
<div style="font-weight:700">AiFaDian</div>
|
|
778
|
+
<div class="hint" data-i18n="爱发电 · Power me ⚡">爱发电 · Power me ⚡</div>
|
|
779
|
+
<img src="/assets/donate-ifdian.svg?v=3" alt="AiFaDian" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
|
|
780
|
+
</div>
|
|
781
|
+
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
|
|
782
|
+
<span style="font-size:26px">🅰️</span>
|
|
783
|
+
<div style="font-weight:700">Alipay</div>
|
|
784
|
+
<div class="hint" data-i18n="支付宝扫码打赏">支付宝扫码打赏</div>
|
|
785
|
+
<img src="/assets/donate-alipay.svg?v=3" alt="Alipay" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
|
|
786
|
+
</div>
|
|
787
|
+
<div style="display:flex;flex-direction:column;align-items:center;gap:8px;padding:14px 10px;border:1px solid var(--border-subtle);border-radius:10px">
|
|
788
|
+
<span style="font-size:26px">💚</span>
|
|
789
|
+
<div style="font-weight:700">WeChat Pay</div>
|
|
790
|
+
<div class="hint" data-i18n="微信扫码打赏">微信扫码打赏</div>
|
|
791
|
+
<img src="/assets/donate-wechat.svg?v=3" alt="WeChat Pay" style="width:140px;height:140px;margin-top:auto;border-radius:8px;padding:6px;flex:none">
|
|
792
|
+
</div>
|
|
793
|
+
</div>
|
|
794
|
+
<div class="modal-actions"><button class="btn primary" data-close data-i18n="关闭">关闭</button></div>
|
|
795
|
+
</div>
|
|
796
|
+
</div>
|
|
797
|
+
|
|
772
798
|
<!-- 10Router 设置 -->
|
|
773
799
|
<div class="mask" id="m-10r">
|
|
774
800
|
<div class="modal">
|
|
@@ -777,9 +803,6 @@
|
|
|
777
803
|
<div class="field" style="margin-top:12px"><label data-i18n="10Router 地址">10Router 地址</label><input class="input mono" id="tr-endpoint" placeholder="http://127.0.0.1:20128(站点根地址,不要带 /v1)"></div>
|
|
778
804
|
<div class="field"><label data-i18n="虚拟 key">虚拟 key</label><input class="input mono" id="tr-key" placeholder="sk-…" autocomplete="off"></div>
|
|
779
805
|
<div class="field"><label data-i18n="10Router 面板密码(可选)">10Router 面板密码(可选)</label><input class="input mono" id="tr-admin-pw" type="password" placeholder="配置后「同步账号」走 OAuth 通道(带 refreshToken 自动续期);留空保持不变" autocomplete="new-password"></div>
|
|
780
|
-
<div class="field"><label data-i18n="账号同步">账号同步</label>
|
|
781
|
-
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap"><button class="btn" id="tr-sync-accounts" data-i18n="同步账号到 10R">同步账号到 10R</button><span class="hint" data-i18n="把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持">把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接;zcode 待反代就绪后支持</span></div>
|
|
782
|
-
</div>
|
|
783
806
|
<div class="field"><label data-i18n="显示">显示</label>
|
|
784
807
|
<div><label class="check"><input type="checkbox" id="tr-hide-empty"> 「全部」视图隐藏没有额度 / 余额为 0 的供应商汇总卡</label></div>
|
|
785
808
|
</div>
|
|
@@ -807,9 +830,9 @@
|
|
|
807
830
|
<!-- 免费额度池:推送设置 -->
|
|
808
831
|
<div class="mask" id="m-free-settings">
|
|
809
832
|
<div class="modal sm">
|
|
810
|
-
<div class="modal-title"><span data-i18n="
|
|
833
|
+
<div class="modal-title"><span data-i18n="额度卡片推送设置">额度卡片推送设置</span><button class="close" data-close>✕</button></div>
|
|
811
834
|
<div class="field">
|
|
812
|
-
<label class="check"><input type="checkbox" id="free-push-auto"> <span data-i18n="
|
|
835
|
+
<label class="check"><input type="checkbox" id="free-push-auto"> <span data-i18n="自动推送额度卡片到 10Router(Token Harbor / MiniMax / LongCat 等)">自动推送额度卡片到 10Router(Token Harbor / MiniMax / LongCat 等)</span></label>
|
|
813
836
|
</div>
|
|
814
837
|
<div class="field">
|
|
815
838
|
<label data-i18n="推送频率">推送频率</label>
|
|
@@ -821,7 +844,7 @@
|
|
|
821
844
|
<option value="240" data-i18n="每 4 小时">每 4 小时</option>
|
|
822
845
|
</select>
|
|
823
846
|
</div>
|
|
824
|
-
<div class="hint" data-i18n="把 CreditDaddy
|
|
847
|
+
<div class="hint" data-i18n="把 CreditDaddy 解析的额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。">把 CreditDaddy 解析的额度数字(不含凭据)推到 10Router 配额页出卡;卡片在 10Router 具 7 天有效期。点「立即同步一次」或 10Router 页「刷新额度」也会立即推送。</div>
|
|
825
848
|
<div class="hint" id="free-settings-status" style="margin-top:8px"></div>
|
|
826
849
|
<div class="modal-actions">
|
|
827
850
|
<button class="btn" style="margin-right:auto" id="free-settings-push-now" data-i18n="立即推送一次">立即推送一次</button>
|
|
@@ -840,15 +863,26 @@
|
|
|
840
863
|
</div>
|
|
841
864
|
</div>
|
|
842
865
|
|
|
843
|
-
|
|
866
|
+
<!-- 10Router 账号管理:账号同步 + 网页会话 Cookie 捕获(原「Cookie 会话」按钮与「设置」里的账号同步合并) -->
|
|
867
|
+
<div class="mask" id="m-am">
|
|
844
868
|
<div class="modal">
|
|
845
|
-
<div class="modal-title"><span data-i18n="
|
|
846
|
-
<div class="
|
|
847
|
-
|
|
869
|
+
<div class="modal-title"><span data-i18n="账号管理">账号管理</span><button class="close" data-close>✕</button></div>
|
|
870
|
+
<div class="field"><label data-i18n="账号同步">账号同步</label>
|
|
871
|
+
<div class="hint" style="margin-bottom:8px" data-i18n="把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接。">把本机 Qoder / WorkBuddy 账号推为 10Router 上游连接。</div>
|
|
872
|
+
<input class="input mono" id="am-pw" type="password" autocomplete="new-password">
|
|
873
|
+
<div style="display:flex;gap:8px;align-items:center;margin-top:8px;flex-wrap:wrap">
|
|
874
|
+
<button class="btn" id="am-sync"><span class="dot"></span><span class="lbl">同步账号到 10R</span></button>
|
|
875
|
+
<span class="hint" id="am-sync-msg"></span>
|
|
876
|
+
</div>
|
|
877
|
+
<div style="margin-top:10px" id="am-sync-channels"></div>
|
|
878
|
+
</div>
|
|
879
|
+
<div class="field" style="margin-top:14px"><label data-i18n="网页会话">网页会话</label>
|
|
880
|
+
<div class="hint" data-i18n="为「额度只在网页 dashboard」的供应商捕获登录会话(cookie),同步给 10Router 点亮配额卡片;凭据全程只存本机(账号库加密)。">为「额度只在网页 dashboard」的供应商捕获登录会话(cookie),同步给 10Router 点亮配额卡片;凭据全程只存本机(账号库加密)。</div>
|
|
881
|
+
<div id="cookie-sites-body" style="margin-top:10px;display:grid;gap:8px"></div>
|
|
882
|
+
</div>
|
|
848
883
|
<div class="modal-actions"><button class="btn" data-close data-i18n="关闭">关闭</button></div>
|
|
849
884
|
</div>
|
|
850
885
|
</div>
|
|
851
|
-
|
|
852
886
|
<!-- 通用对话框 -->
|
|
853
887
|
<div class="mask" id="m-dialog">
|
|
854
888
|
<div class="modal sm">
|
|
@@ -969,6 +1003,8 @@ const I = {
|
|
|
969
1003
|
play: svg('<polygon points="6 4 20 12 6 20 6 4"/>'),
|
|
970
1004
|
refresh: svg('<path d="M21 12a9 9 0 1 1-2.64-6.36L21 8"/><path d="M21 3v5h-5"/>'),
|
|
971
1005
|
trash: svg('<path d="M3 6h18"/><path d="M8 6V4h8v2"/><path d="M19 6l-1 14H6L5 6"/>'),
|
|
1006
|
+
eye: svg('<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/>'),
|
|
1007
|
+
eyeOff: svg('<path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/>'),
|
|
972
1008
|
plus: svg('<path d="M12 5v14M5 12h14"/>'),
|
|
973
1009
|
upload: svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M17 8l-5-5-5 5"/><path d="M12 3v12"/>'),
|
|
974
1010
|
download: svg('<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/>'),
|
|
@@ -999,7 +1035,7 @@ const $ = (id) => document.getElementById(id);
|
|
|
999
1035
|
const state = { accounts: [], quotas: {}, status: null, busy: {}, view: 'dash', region: '', addProduct: '', logTab: 'run', logLevel: 'all', locale: 'zh-CN' };
|
|
1000
1036
|
const VIEWS = ['dash', 'qoder', 'workbuddy', 'zcode', 'mirasim', 'catpaw', 'trae', 'minimax', 'free', 'tenrouter'];
|
|
1001
1037
|
// 10Router 集成:config 来自 /api/tenrouter,quotas 来自 /api/tenrouter/quotas
|
|
1002
|
-
const tr = { config: null, quotas: null, error: null, loading: false, syncing: false };
|
|
1038
|
+
const tr = { config: null, quotas: null, error: null, loading: false, syncing: false, accountsSyncing: false };
|
|
1003
1039
|
const PRODUCTS = [
|
|
1004
1040
|
{ id: 'qoder', label: 'Qoder' },
|
|
1005
1041
|
{ id: 'workbuddy', label: 'WorkBuddy' },
|
|
@@ -1023,6 +1059,9 @@ const COMPONENTS = [
|
|
|
1023
1059
|
{ id: 'free', label: '网页会话' },
|
|
1024
1060
|
{ id: 'tenrouter', label: '10Router' },
|
|
1025
1061
|
];
|
|
1062
|
+
// 捐赠二维码随日夜主题切换(顶层定义:applyTheme 的调用早于原块级定义,块内 function 不可达)
|
|
1063
|
+
// 捐赠二维码统一低饱和金黄(透明底),日夜主题通用,无需随主题切换
|
|
1064
|
+
function applyDonateQrTheme() { /* 统一金码:无主题切换 */ }
|
|
1026
1065
|
const HIDDEN_COMP_KEY = 'qd_hidden_components';
|
|
1027
1066
|
const DEFAULT_HIDDEN_COMP = ['catpaw']; // 妙手默认收起,右上角设置里可打开
|
|
1028
1067
|
state.hidden = (() => {
|
|
@@ -1250,7 +1289,6 @@ async function loadSettings() {
|
|
|
1250
1289
|
try {
|
|
1251
1290
|
const d = await api('/api/settings');
|
|
1252
1291
|
state.sec = { enabled: Boolean(d.panelKeyEnabled), loaded: true };
|
|
1253
|
-
state.wbActivity = d.workbuddyActivity !== false; // WB 国际版活跃请求总开关(默认开)
|
|
1254
1292
|
if (!d.panelKeyEnabled) setKey(''); // 关闭后清掉本地缓存的旧密钥
|
|
1255
1293
|
if (d.locale) renderLocale(d.locale);
|
|
1256
1294
|
} catch (e) { /* 401(密码开启但未输入)等状态由 promptKey 流程处理 */ }
|
|
@@ -1283,15 +1321,6 @@ async function applyDictFromServer() {
|
|
|
1283
1321
|
document.getElementById('set-locale')?.addEventListener('change', (ev) => setLocale(ev.target.value));
|
|
1284
1322
|
|
|
1285
1323
|
// ── 通用设置:全局自动签到(开关 + 频率,落盘立刻重调度)、日志默认显示方式(本机偏好)──
|
|
1286
|
-
const wbActOn = () => state.wbActivity !== false;
|
|
1287
|
-
$('cfg-wb-activity').addEventListener('change', async (e) => {
|
|
1288
|
-
try {
|
|
1289
|
-
await api('/api/settings', { method: 'PUT', json: { workbuddyActivity: e.target.checked } });
|
|
1290
|
-
await loadSettings();
|
|
1291
|
-
render(); renderStatus();
|
|
1292
|
-
toast(T('已保存'), true);
|
|
1293
|
-
} catch (err) { toast(err.message, false); }
|
|
1294
|
-
});
|
|
1295
1324
|
function fillAutoCheckin() {
|
|
1296
1325
|
const sc = state.status && state.status.scheduler;
|
|
1297
1326
|
if (!sc) return;
|
|
@@ -1329,6 +1358,7 @@ applyLogsDisplay(logsDisplayDefault());
|
|
|
1329
1358
|
// 由 renderThemeMode() 反映到菜单里的分段控件(主题只有那一个入口)。
|
|
1330
1359
|
function applyTheme(t) {
|
|
1331
1360
|
document.documentElement.dataset.theme = t;
|
|
1361
|
+
if (typeof applyDonateQrTheme === 'function') { try { applyDonateQrTheme(t); } catch {} }
|
|
1332
1362
|
}
|
|
1333
1363
|
function initTheme() {
|
|
1334
1364
|
let t = null;
|
|
@@ -1425,6 +1455,7 @@ function initTheme() {
|
|
|
1425
1455
|
// 需要跟随系统走菜单里的三态分段控件。
|
|
1426
1456
|
const themeBtn = $('btn-theme');
|
|
1427
1457
|
function resolvedTheme() { return document.documentElement.dataset.theme === 'dark' ? 'dark' : 'light'; }
|
|
1458
|
+
|
|
1428
1459
|
function renderThemeButton() {
|
|
1429
1460
|
if (!themeBtn) return;
|
|
1430
1461
|
const cur = resolvedTheme();
|
|
@@ -1455,20 +1486,28 @@ function initTheme() {
|
|
|
1455
1486
|
else window.open(url, '_blank');
|
|
1456
1487
|
};
|
|
1457
1488
|
|
|
1458
|
-
//
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
if (window.creditdaddy && typeof window.creditdaddy.shellOpen === 'function')
|
|
1462
|
-
window.creditdaddy.shellOpen('https://github.com/sponsors/techysy');
|
|
1463
|
-
else window.open('https://github.com/sponsors/techysy', '_blank');
|
|
1464
|
-
};
|
|
1489
|
+
// 捐赠:弹窗展示三渠道(爱发电 / 支付宝 / 微信——国内用户为主,与 10Router 一致);
|
|
1490
|
+
// Sponsors 按钮跳转(桌面版走 IPC 桥,网页直接开浏览器)
|
|
1491
|
+
$('donate-btn').onclick = () => { closeHmenu(); openModal('m-donate'); };
|
|
1465
1492
|
}
|
|
1466
1493
|
|
|
1467
1494
|
// ── 状态 / 渲染 ──
|
|
1468
|
-
//
|
|
1495
|
+
// 统一「领鸡蛋」语义:全部签到产品线 = 领取积分(workbuddy-intl 的「活跃」语义随活跃领取线删除)
|
|
1469
1496
|
function statusOf(a) {
|
|
1470
|
-
if (!CHECKIN_PRODUCTS.has(a.product))
|
|
1471
|
-
|
|
1497
|
+
if (!CHECKIN_PRODUCTS.has(a.product)) {
|
|
1498
|
+
// ZCode 无「每日签到」口径,但活动领取的结果必须落到徽标上:
|
|
1499
|
+
// 否则卡片上唯一能证明「领到了」的东西只有额度数字,而活动额度常带
|
|
1500
|
+
// effective_at(当天稍晚到账),在那之前卡片看起来毫无变化。
|
|
1501
|
+
if (a.product === 'zcode') {
|
|
1502
|
+
const r = a.lastResult;
|
|
1503
|
+
const fresh = r && r.at && Date.now() - new Date(r.at).getTime() < 26 * 3600e3;
|
|
1504
|
+
if (r && fresh && r.status === 'checked-in') return { cls: 'ok', text: T('已领取'), msg: r.message };
|
|
1505
|
+
if (r && fresh && r.status === 'already') return { cls: 'ok', text: T('已领过'), msg: r.message };
|
|
1506
|
+
if (r && fresh && r.status === 'failed') return { cls: 'fail', text: T('领取失败'), msg: r.message, err: true };
|
|
1507
|
+
}
|
|
1508
|
+
return { cls: '', text: '', msg: '' };
|
|
1509
|
+
}
|
|
1510
|
+
if (a.provider === 'workbuddy-intl') return { cls: '', text: '', msg: '' }; // 国际版无签到动作(活跃领取线已删),不显示徽标
|
|
1472
1511
|
const byProd = (state.status && state.status.todayByProduct) || {};
|
|
1473
1512
|
const today = byProd[a.product] || (state.status && state.status.today);
|
|
1474
1513
|
const done = state.status && state.status.todayDone && state.status.todayDone[a.id] === today;
|
|
@@ -1476,15 +1515,13 @@ function statusOf(a) {
|
|
|
1476
1515
|
const fresh = r && r.at && state.status && (Date.now() - new Date(r.at).getTime() < 26 * 3600e3);
|
|
1477
1516
|
if (r && fresh && r.status === 'limited' && done) return { cls: 'limited', text: T('本机已领'), msg: r.message };
|
|
1478
1517
|
if (done) {
|
|
1479
|
-
if (intl) return { cls: 'ok', text: T('今日活跃'), msg: r && r.message ? r.message : '' };
|
|
1480
1518
|
const amt = r && r.status === 'checked-in' && fresh && r.amount ? ' +' + fmtNum(r.amount) : '';
|
|
1481
|
-
const streak = r &&
|
|
1519
|
+
const streak = r && r.streakDays ? T(' · 连续 ') + r.streakDays + T(' 天') : '';
|
|
1482
1520
|
return { cls: 'ok', text: T('今日已领') + amt + streak, msg: r && r.message && !['checked-in', 'already'].includes(r.status) ? r.message : '' };
|
|
1483
1521
|
}
|
|
1484
|
-
|
|
1485
|
-
if (r && fresh && r.status === '
|
|
1486
|
-
|
|
1487
|
-
return { cls: 'pending', text: intl ? T('未活跃') : T('未领取'), msg: '' };
|
|
1522
|
+
if (r && fresh && r.status === 'failed') return { cls: 'fail', text: T('领取失败'), msg: r.message, err: true };
|
|
1523
|
+
if (r && fresh && r.status === 'no-activity') return { cls: 'pending', text: T('待领取'), msg: r.message };
|
|
1524
|
+
return { cls: 'pending', text: T('未领取'), msg: '' };
|
|
1488
1525
|
}
|
|
1489
1526
|
|
|
1490
1527
|
function expiryCell(a) {
|
|
@@ -1567,8 +1604,11 @@ function creditHtml(a) {
|
|
|
1567
1604
|
const planBadge = q.plan ? '<span class="badge">' + esc(T(q.plan)) + T(' 套餐</span>') : '';
|
|
1568
1605
|
const expLabel = q.planExpiresAt ? '<span class="dim">' + fmtTime(q.planExpiresAt).slice(0, 10) + T(' 到期</span>') : '';
|
|
1569
1606
|
// 状态采用温和的徽章,避免整行大红字制造恐慌
|
|
1607
|
+
// pill 不再用 mirasimClient 拼好的 bottleneckLabel(上游窗口名原文 + '已用尽' 拼串,
|
|
1608
|
+
// 英文界面会出「30d已用尽」这类混语)——改面板侧按词典分别 T():窗口名走词条
|
|
1609
|
+
// ('5小时窗口'→'5h window' 等,上游新名如 '30d' 拉丁原样),「已用尽」单独走词条。
|
|
1570
1610
|
const topStatus = isExhausted
|
|
1571
|
-
? '<span class="pill pending">' + esc(T(q.
|
|
1611
|
+
? '<span class="pill pending">' + esc(q.bottleneck ? T(q.bottleneck.name) + T('已用尽') : T('额度已用尽')) + '</span>'
|
|
1572
1612
|
: T('<span class="lbl">可用额度</span><b>') + fmtNum(q.remaining) + '%</b>';
|
|
1573
1613
|
|
|
1574
1614
|
const fmtUnits = (n) => (n >= 10000 ? (n / 10000).toFixed(2) + T('万') : fmtNum(Math.round(n)));
|
|
@@ -1591,15 +1631,32 @@ function creditHtml(a) {
|
|
|
1591
1631
|
|
|
1592
1632
|
const unit = q.unit ? ' ' + T(q.unit) : '';
|
|
1593
1633
|
if (q.unit) {
|
|
1634
|
+
// 待生效权益单独计数:活动发的额度要到生效点才进余额,把它算成「已用完」会让人以为丢了
|
|
1635
|
+
const pendingParts = q.parts.filter((x) => x.pending);
|
|
1636
|
+
const effParts = q.parts.filter((x) => !x.pending);
|
|
1594
1637
|
const items = q.parts.map((x) => {
|
|
1595
1638
|
const base = esc(T(x.name)) + ' ' + fmtNum(x.remaining) + '/' + fmtNum(x.total) + (x.unit ? ' ' + esc(T(x.unit)) : '');
|
|
1596
1639
|
if (x.pending && x.startsAt) return base + T('(') + fmtTime(x.startsAt).slice(5, 16) + T(' 生效)');
|
|
1597
1640
|
return base + (x.expiresAt ? T('(') + fmtTime(x.expiresAt).slice(5, 16) + (x.recurring ? T(' 重置)') : T(' 到期)')) : '');
|
|
1598
1641
|
}).join(' · ');
|
|
1642
|
+
const atText = q.pendingAt ? fmtTime(q.pendingAt) : '';
|
|
1643
|
+
const isToday = atText.slice(0, 10) === fmtTime(new Date().toISOString()).slice(0, 10);
|
|
1644
|
+
const when = atText ? (isToday ? atText.slice(11) : atText.slice(0, 10) + ' ' + atText.slice(11)) : '';
|
|
1645
|
+
const pendingTotal = q.pendingTotal || pendingParts.reduce((s, x) => s + (x.total || 0), 0);
|
|
1646
|
+
const pendingLine = '<div class="credit-line"><span class="credit pending">' + T('<span class="lbl">待生效</span>')
|
|
1647
|
+
+ '<b>' + fmtNum(pendingTotal) + '</b>' + esc(unit)
|
|
1648
|
+
+ '</span><span class="credit dim" title="' + items + '">'
|
|
1649
|
+
+ (q.plan ? esc(q.plan) + ' · ' : '') + (when ? when + ' ' + T('到账') : T('即将到账')) + '</span></div>';
|
|
1599
1650
|
const pct = q.total > 0 ? Math.min(1, q.remaining / q.total) : 0;
|
|
1651
|
+
const mainLine = () => T('<div class="credit-line"><span class="credit"><span class="lbl">剩余</span><b>') + fmtNum(q.remaining) + '</b>' + esc(unit) + '</span>'
|
|
1652
|
+
+ '<span class="credit dim" title="' + items + '">' + (q.plan ? esc(q.plan) + ' · ' : '') + items + '</span></div>';
|
|
1653
|
+
// 全部待生效(活动刚发、还没到点):主行显示待生效,别只显示一个 0
|
|
1654
|
+
if (pendingParts.length && !effParts.length) {
|
|
1655
|
+
return pendingLine + '<div class="bar"><span class="seg pending-ghost" style="flex:1 1 0"></span></div>';
|
|
1656
|
+
}
|
|
1600
1657
|
// 满额(100%)不再渲染灰色「已使用」尾段——它只占 0 宽也会造成几像素的灰边
|
|
1601
|
-
return
|
|
1602
|
-
+
|
|
1658
|
+
return mainLine()
|
|
1659
|
+
+ (pendingParts.length ? pendingLine : '')
|
|
1603
1660
|
+ '<div class="bar"><span class="seg" style="flex:' + pct.toFixed(4) + ' 1 0"></span>'
|
|
1604
1661
|
+ (pct < 0.9999 ? '<span class="seg used" style="flex:' + (1 - pct).toFixed(4) + ' 1 0"></span>' : '')
|
|
1605
1662
|
+ '</div>';
|
|
@@ -1631,7 +1688,9 @@ function cardHtml(a) {
|
|
|
1631
1688
|
+ T('<span class="name" data-act="rename" title="点击修改备注名">') + esc(a.name || T('(未命名)')) + '</span>'
|
|
1632
1689
|
+ provBadges(a.provider, true, a.flavor)
|
|
1633
1690
|
+ (isCurrent ? '<span class="pill cur" title="' + esc(CLIENT_LABEL[a.product] ? T(CLIENT_LABEL[a.product]) : '') + T(' 客户端当前登录的账号">客户端当前</span>') : '')
|
|
1634
|
-
+ (a.product === 'zcode' && isGatewayCurrent(a) ?
|
|
1691
|
+
+ (a.product === 'zcode' && isGatewayCurrent(a) ? (zgwAccountCount() > 1
|
|
1692
|
+
? T('<span class="pill gw" title="体验包接口最近一次补全用的账号({n} 个 ZCode 账号按轮转分摊请求,每个请求换一个,与客户端登录无关)">接口使用中</span>', { n: zgwAccountCount() })
|
|
1693
|
+
: T('<span class="pill gw" title="体验包接口最近一次补全用的账号(多账号时按轮转自动选择,与客户端登录无关)">接口使用中</span>')) : '')
|
|
1635
1694
|
+ (a.product === 'minimax' && isMiniMaxGatewayCurrent(a) ? T('<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>') : '')
|
|
1636
1695
|
+ (a.product === 'trae' && isTraeGatewayCurrent(a) ? T('<span class="pill gw" title="本地接口最近一次补全用的账号(多账号时按轮换自动选择,与客户端登录无关)">接口使用中</span>') : '')
|
|
1637
1696
|
+ (a.isPat ? '<span class="badge">PAT</span>' : '')
|
|
@@ -1640,7 +1699,7 @@ function cardHtml(a) {
|
|
|
1640
1699
|
+ (st.text ? '<span class="pill ' + st.cls + '" title="' + esc(a.lastResult ? fmtTime(a.lastResult.at) + ' ' + (a.lastResult.message || a.lastResult.status) : '') + '">' + esc(st.text) + '</span>' : '')
|
|
1641
1700
|
+ '</div><div class="ops">'
|
|
1642
1701
|
+ (a.product === 'zcode' ? T('<button class="icon-btn" data-act="claim" title="活动领取(billing/preview → claim,可能需过一次验证码)">') + I.gift + '</button>' : '')
|
|
1643
|
-
+ (canCheckin &&
|
|
1702
|
+
+ (canCheckin && a.provider !== 'workbuddy-intl' ? '<button class="icon-btn' + (state.busy[a.id] ? ' busy' : '') + '" data-act="checkin" title="' + T('立即领取') + '">' + (state.busy[a.id] ? I.refresh : I.play) + '</button>' : '')
|
|
1644
1703
|
+ (a.qoderWebDead ? '<button class="icon-btn" data-act="webrenew" title="' + T('网页会话已失效——打开登录窗口重新授权,关闭窗口时自动补 cookie(API token 不受影响)') + '">' + I.swap + '</button>' : '')
|
|
1645
1704
|
+ (a.canSwitch && !isCurrent && st.cls !== 'fail' ? T('<button class="icon-btn" data-act="switch" title="切换 ') + (CLIENT_LABEL[a.product] ? T(CLIENT_LABEL[a.product]) : prodLabel(a.product)) + T(' 客户端到此账号">') + I.swap + '</button>' : '')
|
|
1646
1705
|
+ T('<button class="icon-btn" data-act="quota" title="刷新积分">') + I.refresh + '</button>'
|
|
@@ -1672,6 +1731,10 @@ function isGatewayCurrent(a) {
|
|
|
1672
1731
|
const last = state.zGatewayStats && state.zGatewayStats.lastAccount;
|
|
1673
1732
|
return Boolean(last) && (a.name === last || a.uid === last || a.id === last);
|
|
1674
1733
|
}
|
|
1734
|
+
/** 网关账号总数(多账号时徽标注明「轮转中」,避免用户以为只有一个号在服役) */
|
|
1735
|
+
function zgwAccountCount() {
|
|
1736
|
+
return state.accounts.filter((a) => a.product === 'zcode').length;
|
|
1737
|
+
}
|
|
1675
1738
|
/** MiniMax 本地接口使用中判定 */
|
|
1676
1739
|
function isMiniMaxGatewayCurrent(a) {
|
|
1677
1740
|
const last = state.mGatewayStats && state.mGatewayStats.lastAccount;
|
|
@@ -1689,8 +1752,12 @@ function isClientCurrent(a) {
|
|
|
1689
1752
|
if (!s) return false;
|
|
1690
1753
|
if (a.product === 'workbuddy') return Boolean(a.uid && s.workbuddyCurrentUid && s.workbuddyCurrentUid === a.uid);
|
|
1691
1754
|
if (a.product === 'qoder') {
|
|
1755
|
+
// 服务端按「本机 auth.v1.dat 解出的 token」对齐出账号 id(token 优先、uid 兜底)——
|
|
1756
|
+
// 只看 uid 的话客户端静默刷新 token 后会把别的同 uid 记录误当成当前账号
|
|
1692
1757
|
const cur = s.qoderCurrent || {};
|
|
1693
|
-
|
|
1758
|
+
if (cur[a.provider]) return String(cur[a.provider]) === String(a.id);
|
|
1759
|
+
const uid = s.qoderCurrentUid || {};
|
|
1760
|
+
return Boolean(a.uid && uid[a.provider] && String(uid[a.provider]) === String(a.uid));
|
|
1694
1761
|
}
|
|
1695
1762
|
if (a.product === 'mirasim') return Boolean(a.uid && s.mirasimCurrentUid && s.mirasimCurrentUid === a.uid);
|
|
1696
1763
|
if (a.product === 'catpaw') return Boolean(a.uid && s.catpawCurrentUid && s.catpawCurrentUid === a.uid);
|
|
@@ -1752,11 +1819,9 @@ function renderProd() {
|
|
|
1752
1819
|
$('s-count').textContent = acc.length;
|
|
1753
1820
|
$('s-done-wrap').style.display = canCheckin ? 'contents' : 'none';
|
|
1754
1821
|
if (prod === 'workbuddy') {
|
|
1755
|
-
// 国内版 =
|
|
1822
|
+
// 国内版 = 每日签到送积分;国际版无签到动作(活跃领取线已删),只统计国内
|
|
1756
1823
|
const cnAcc = acc.filter((a) => a.provider === 'workbuddy');
|
|
1757
|
-
|
|
1758
|
-
$('s-done-wrap').innerHTML = (cnAcc.length ? T('<div class="stat"><span title="国内版每日签到送积分">领取(国内)</span><strong>') + cnAcc.filter(isDone).length + ' / ' + cnAcc.length + '</strong></div><div class="divider"></div>' : '')
|
|
1759
|
-
+ (intlAcc.length ? T('<div class="stat"><span title="国际版定期发送活跃请求保持资格;活跃不等于必有奖励">活跃(国际)</span><strong>') + intlAcc.filter(isDone).length + ' / ' + intlAcc.length + '</strong></div><div class="divider"></div>' : '');
|
|
1824
|
+
$('s-done-wrap').innerHTML = (cnAcc.length ? T('<div class="stat"><span title="国内版每日签到送积分">领取(国内)</span><strong>') + cnAcc.filter(isDone).length + ' / ' + cnAcc.length + '</strong></div><div class="divider"></div>' : '');
|
|
1760
1825
|
} else {
|
|
1761
1826
|
// 从 WorkBuddy 页切回来时 s-done 可能被 innerHTML 换没了,先重建结构再写值
|
|
1762
1827
|
if (!$('s-done')) $('s-done-wrap').innerHTML = T('<div class="stat"><span>今日已领</span><strong id="s-done"></strong></div><div class="divider"></div>');
|
|
@@ -1773,8 +1838,7 @@ function renderProd() {
|
|
|
1773
1838
|
document.querySelectorAll('#region button').forEach((x) => x.classList.toggle('active', x.dataset.r === state.region));
|
|
1774
1839
|
$('btn-checkin').style.display = canCheckin ? '' : 'none';
|
|
1775
1840
|
if (!$('btn-checkin').disabled) {
|
|
1776
|
-
|
|
1777
|
-
$('btn-checkin').innerHTML = I.play + (prod === 'workbuddy' && hasIntl ? T('领取 / 活跃全部') : T('领取全部 ') + prodLabel(prod));
|
|
1841
|
+
$('btn-checkin').innerHTML = I.play + T('领取全部 ') + prodLabel(prod);
|
|
1778
1842
|
}
|
|
1779
1843
|
// 顶栏产品专属入口:ZCode 设置组、Trae 打开客户端,带状态灯(运行中绿点呼吸)
|
|
1780
1844
|
const dot = (on, pulse) => '<span class="dot' + (on ? ' on' : '') + (on && pulse ? ' pulse' : '') + '"></span>';
|
|
@@ -1842,23 +1906,19 @@ function renderDash() {
|
|
|
1842
1906
|
const credit = creditSum(state.accounts.filter((a) => a.product !== 'zcode'));
|
|
1843
1907
|
const byProd = orderedVisibleComps().filter((id) => id !== 'tenrouter').map((id) => prodLabel(id) + ' ' + ofProduct(id).length).join(' · ');
|
|
1844
1908
|
const creditByProd = ['qoder', 'workbuddy', 'minimax'].filter(compVisible).map((id) => { const c = creditSum(ofProduct(id)); return prodLabel(id) + ' ' + (c.known ? fmtNum(c.sum) : '-'); }).join(' · ');
|
|
1845
|
-
//
|
|
1846
|
-
const intlAcc = signable.filter((a) => a.provider === 'workbuddy-intl' && wbActOn());
|
|
1847
|
-
const domAcc = signable.filter((a) => a.provider !== 'workbuddy-intl');
|
|
1848
|
-
const doneDom = domAcc.filter(isDone).length;
|
|
1849
|
-
const doneIntl = intlAcc.filter(isDone).length;
|
|
1909
|
+
// 今日进度:国际版 WorkBuddy 无签到动作(活跃领取线已删),signable 天然只剩可领取账号
|
|
1850
1910
|
$('kpis').innerHTML =
|
|
1851
1911
|
kpi(T('账号'), state.accounts.length, byProd)
|
|
1852
|
-
+ kpi(T('今日领取') +
|
|
1853
|
-
? doneDom + ' <small>/ ' + domAcc.length + '</small><small class="kpi-side">' + T(' · 活跃 {n} / {m}', { n: doneIntl, m: intlAcc.length }) + '</small>'
|
|
1854
|
-
: done + ' <small>/ ' + signable.length + '</small>',
|
|
1912
|
+
+ kpi(T('今日领取'), done + ' <small>/ ' + signable.length + '</small>',
|
|
1855
1913
|
!signable.length ? T('暂无可领取的账号')
|
|
1856
|
-
: (
|
|
1914
|
+
: (done === signable.length ? T('今天全部签完')
|
|
1857
1915
|
: T('待办 ') + (signable.length - done) + (failed ? T(' · 失败 ') + failed : '')),
|
|
1858
|
-
T('领取 =
|
|
1916
|
+
T('领取 = 每日签到'))
|
|
1859
1917
|
+ kpi(T('剩余积分'), credit.loading ? '…' : (credit.known ? fmtNum(credit.sum) : '-'), credit.loading ? T('正在读取各账号额度…') : creditByProd)
|
|
1860
1918
|
+ kpi(T('下次自动领取'), sch.ticking ? T('领取中…') : sch.nextTickAt ? fmtTime(sch.nextTickAt).slice(11) : '-',
|
|
1861
|
-
sch.lastTick
|
|
1919
|
+
sch.lastTick
|
|
1920
|
+
? T('上一轮 ') + fmtTime(sch.lastTick.at).slice(5) + ' · ' + esc(sch.lastTick.summaryKey ? T(sch.lastTick.summaryKey, sch.lastTick.summaryArgs) : (sch.lastTick.summary || ''))
|
|
1921
|
+
: T('每 ~2 小时一轮'));
|
|
1862
1922
|
|
|
1863
1923
|
// 仪表盘产品卡按组件设置里的顺序渲染(与标签栏同步),10Router 汇总卡按其顺序插入
|
|
1864
1924
|
const prodCardHtml = (p) => {
|
|
@@ -1870,16 +1930,10 @@ function renderDash() {
|
|
|
1870
1930
|
const d = list.filter(isDone).length;
|
|
1871
1931
|
const c = creditSum(list);
|
|
1872
1932
|
if (p.id === 'workbuddy') {
|
|
1873
|
-
// 国内 =
|
|
1933
|
+
// 国内 = 每日领取;国际无签到动作(活跃领取线已删),统计只覆盖国内账号
|
|
1874
1934
|
const cnList = list.filter((a) => a.provider === 'workbuddy');
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
if (cnList.length) parts.push(cnList.filter(isDone).length + ' / ' + cnList.length + T('(国内)'));
|
|
1878
|
-
if (intlList.length) parts.push(intlList.filter(isDone).length + ' / ' + intlList.length + T('(国际 · 不保证奖励)'));
|
|
1879
|
-
rows += T('<span class="lbl">领取 / 活跃</span><span class="val" title="') + esc(parts.join(T(',')) + (intlList.length ? T('(国际为活跃请求,不保证奖励)') : '')) + '">'
|
|
1880
|
-
+ (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T(' 国内') : '')
|
|
1881
|
-
+ (cnList.length && intlList.length ? ' · ' : '')
|
|
1882
|
-
+ (intlList.length ? intlList.filter(isDone).length + ' / ' + intlList.length + T(' 国际') : '')
|
|
1935
|
+
rows += T('<span class="lbl">领取</span><span class="val" title="') + esc(cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T('(国内)') : '') + '">'
|
|
1936
|
+
+ (cnList.length ? cnList.filter(isDone).length + ' / ' + cnList.length + T(' 国内') : T('—'))
|
|
1883
1937
|
+ '</span>';
|
|
1884
1938
|
} else if (p.id === 'minimax') {
|
|
1885
1939
|
const planned = list.filter((a) => { const q = state.quotas[a.id]; return q && !q.error && !q.empty; }).length;
|
|
@@ -1944,7 +1998,7 @@ function renderDash() {
|
|
|
1944
1998
|
if (a.verified === false) att.push(Object.assign({ pill: 'fail', text: T('token 无效') }, base));
|
|
1945
1999
|
else if (ex.label === '已过期') att.push(Object.assign({ pill: 'fail', text: T('token 已过期') }, base));
|
|
1946
2000
|
else if (ex.warn) att.push(Object.assign({ pill: 'limited', text: ex.label.slice(5, 10) + T(' 过期') }, base));
|
|
1947
|
-
if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text:
|
|
2001
|
+
if (st.cls === 'fail') att.push(Object.assign({ pill: 'fail', text: T('领取失败'), title: st.msg }, base));
|
|
1948
2002
|
}
|
|
1949
2003
|
for (const c of (tr.quotas && tr.quotas.connections) || []) {
|
|
1950
2004
|
if (!compVisible('tenrouter')) break;
|
|
@@ -1970,8 +2024,6 @@ function renderDash() {
|
|
|
1970
2024
|
? recent.map((a) => {
|
|
1971
2025
|
const r = a.lastResult;
|
|
1972
2026
|
let [cls, txt] = RES[r.status] || ['pending', r.status];
|
|
1973
|
-
// 国际版 WorkBuddy 是保持活跃(不保证奖励),不叫「领取」
|
|
1974
|
-
if (a.provider === 'workbuddy-intl' && r.status === 'checked-in') txt = T('活跃达成');
|
|
1975
2027
|
const label = r.status === 'checked-in' && r.amount ? txt + ' +' + fmtNum(r.amount) : txt;
|
|
1976
2028
|
return '<div class="item" data-view="' + a.product + '" title="' + esc(r.message || '') + '"><span class="item-main"><span class="item-name">' + esc(a.name || a.email || a.id) + '</span>' + provBadges(a.provider) + '</span>'
|
|
1977
2029
|
+ '<span class="item-side"><span class="pill ' + cls + '">' + esc(label) + '</span><span class="item-time">' + fmtTime(r.at).slice(5) + '</span></span></div>';
|
|
@@ -2001,11 +2053,11 @@ function renderStatus() {
|
|
|
2001
2053
|
} else {
|
|
2002
2054
|
chip.innerHTML = '';
|
|
2003
2055
|
}
|
|
2004
|
-
chip.title = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at) + T(':') + sch.lastTick.summary : '';
|
|
2056
|
+
chip.title = sch.lastTick ? T('上一轮 ') + fmtTime(sch.lastTick.at) + T(':') + (sch.lastTick.summaryKey ? T(sch.lastTick.summaryKey, sch.lastTick.summaryArgs) : sch.lastTick.summary || '') : '';
|
|
2005
2057
|
// 产品相关提示只在对应标签页出现
|
|
2006
2058
|
const hasIntl = state.view === 'qoder' && state.region !== 'cn' && state.accounts.some((a) => a.provider === 'qoder');
|
|
2007
2059
|
const n = $('notice');
|
|
2008
|
-
n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || state.view === 'trae' || state.view === 'minimax' || (hasIntl && Boolean(s.riskIdentity)));
|
|
2060
|
+
n.classList.toggle('info', state.view === 'zcode' || state.view === 'mirasim' || state.view === 'catpaw' || state.view === 'trae' || state.view === 'minimax' || (state.view === 'workbuddy' && s.workbuddyClient && s.workbuddyClient.installed === false) || (hasIntl && Boolean(s.riskIdentity)));
|
|
2009
2061
|
if (state.view === 'minimax') {
|
|
2010
2062
|
n.style.display = '';
|
|
2011
2063
|
n.innerHTML = T('<div class="z-row"><span class="z-label">每日签到</span><span class="z-text">每日签到领 800~2000 积分 + 400 连签加成。支持自动检测本机 MiniMax Code 凭据与 Bearer Token 导入,自带标准 Anthropic 本地网关。</span></div>');
|
|
@@ -2030,6 +2082,10 @@ function renderStatus() {
|
|
|
2030
2082
|
n.innerHTML = T('<div class="z-row"><span class="z-label">活动领取</span><span class="z-text">活动包积分,每日 0 点左右刷新(无每日签到)。自动领取')
|
|
2031
2083
|
+ (ac ? T('每 ') + ziv + T(' 分钟检查资格') + (zwin > 0 ? T(',本次运行 ') + zwin + T(' 分钟') : T(',持续运行')) : T('未开启(右上角设置轮询计划)'))
|
|
2032
2084
|
+ T(';礼物图标 = 手动临时领。</span></div>');
|
|
2085
|
+
} else if (state.view === 'workbuddy' && s.workbuddyClient && s.workbuddyClient.installed === false) {
|
|
2086
|
+
// WorkBuddy 客户端未安装:切换线是死路(写凭据没人应用),进页就明说而不是切了才失败
|
|
2087
|
+
n.style.display = '';
|
|
2088
|
+
n.innerHTML = T('<div class="z-row"><span class="z-label">客户端</span><span class="z-text">本机未安装 WorkBuddy / CodeBuddy 客户端:Token 导入、额度读取与每日领取不受影响;但「切换账号」需要本机客户端(切换是写入客户端凭据由其应用)。请先安装并登录客户端,再「本机导入」。</span></div>');
|
|
2033
2089
|
} else if (hasIntl && !s.riskIdentity && umidOffer(s)) {
|
|
2034
2090
|
n.style.display = '';
|
|
2035
2091
|
const busy = s.umid.installing || umidBusy;
|
|
@@ -2082,6 +2138,30 @@ async function installUmid() {
|
|
|
2082
2138
|
$('notice').addEventListener('click', (e) => { if (e.target.closest('#btn-umid')) installUmid(); });
|
|
2083
2139
|
// 覆盖 #notice(各产品横幅)与 #z-actions(ZCode 工具栏按钮组)的点击
|
|
2084
2140
|
document.addEventListener('click', async (e) => {
|
|
2141
|
+
// 免费额度页:站点卡隐藏 / 全部显示(拖拽排序在 renderFreePool 里经 bindCardDrag 绑定)
|
|
2142
|
+
const hideBtn = e.target.closest('[data-free-hide]');
|
|
2143
|
+
if (hideBtn) {
|
|
2144
|
+
state.hiddenFreeSites.add(hideBtn.dataset.freeHide);
|
|
2145
|
+
saveHiddenFreeSites();
|
|
2146
|
+
renderFreePool();
|
|
2147
|
+
return;
|
|
2148
|
+
}
|
|
2149
|
+
const doubaoBtn = e.target.closest('[data-open-doubao]');
|
|
2150
|
+
if (doubaoBtn) {
|
|
2151
|
+
if (window.creditdaddy && typeof window.creditdaddy.openDoubaoClient === 'function') {
|
|
2152
|
+
window.creditdaddy.openDoubaoClient().then((r) => {
|
|
2153
|
+
if (r && r.ok) toast(T('已启动豆包客户端'), true);
|
|
2154
|
+
else toast((r && r.error) || T('无法启动豆包客户端'), false);
|
|
2155
|
+
}).catch((err) => toast(String(err && err.message || err), false));
|
|
2156
|
+
} else toast(T('需要桌面版 CreditDaddy'), false);
|
|
2157
|
+
return;
|
|
2158
|
+
}
|
|
2159
|
+
if (e.target.closest('#free-show-all')) {
|
|
2160
|
+
state.hiddenFreeSites.clear();
|
|
2161
|
+
saveHiddenFreeSites();
|
|
2162
|
+
renderFreePool();
|
|
2163
|
+
return;
|
|
2164
|
+
}
|
|
2085
2165
|
if (e.target.closest('#zt-open')) {
|
|
2086
2166
|
try {
|
|
2087
2167
|
const r = await window.creditdaddy.openZcodeClient();
|
|
@@ -2488,30 +2568,78 @@ async function loadCookieSites(force) {
|
|
|
2488
2568
|
return state.cookieSites;
|
|
2489
2569
|
}
|
|
2490
2570
|
|
|
2571
|
+
// 免费额度页站点卡:顺序 / 显隐(本机持久化;拖拽复用 bindCardDrag,眼睛按钮切换显隐)
|
|
2572
|
+
const FREE_ORDER_KEY = 'qd_free_site_order';
|
|
2573
|
+
const FREE_HIDDEN_KEY = 'qd_hidden_free_sites';
|
|
2574
|
+
state.freeOrder = (() => {
|
|
2575
|
+
let arr = [];
|
|
2576
|
+
try { arr = JSON.parse(localStorage.getItem(FREE_ORDER_KEY) || '[]'); } catch {}
|
|
2577
|
+
return Array.isArray(arr) ? arr : [];
|
|
2578
|
+
})();
|
|
2579
|
+
function saveFreeOrder() { try { localStorage.setItem(FREE_ORDER_KEY, JSON.stringify(state.freeOrder)); } catch {} }
|
|
2580
|
+
state.hiddenFreeSites = (() => {
|
|
2581
|
+
let list = [];
|
|
2582
|
+
try { list = JSON.parse(localStorage.getItem(FREE_HIDDEN_KEY) || '[]'); } catch {}
|
|
2583
|
+
return new Set(Array.isArray(list) ? list : []);
|
|
2584
|
+
})();
|
|
2585
|
+
function saveHiddenFreeSites() { try { localStorage.setItem(FREE_HIDDEN_KEY, JSON.stringify([...state.hiddenFreeSites])); } catch {} }
|
|
2586
|
+
const freeVisible = (id) => !state.hiddenFreeSites.has(id);
|
|
2587
|
+
|
|
2588
|
+
function orderedFreeSites(sites) {
|
|
2589
|
+
const ids = sites.map((s) => s.id);
|
|
2590
|
+
const arr = state.freeOrder.filter((id) => ids.includes(id));
|
|
2591
|
+
for (const id of ids) if (!arr.includes(id)) arr.push(id);
|
|
2592
|
+
state.freeOrder = arr;
|
|
2593
|
+
return arr.map((id) => sites.find((s) => s.id === id)).filter(Boolean);
|
|
2594
|
+
}
|
|
2595
|
+
|
|
2491
2596
|
function renderFreePool() {
|
|
2492
2597
|
const body = $('free-pool-body');
|
|
2493
2598
|
if (!body) return;
|
|
2494
2599
|
const sites = state.cookieSites;
|
|
2495
2600
|
const canCapture = Boolean(window.creditdaddy && typeof window.creditdaddy.openAuthWindow === 'function');
|
|
2496
2601
|
if (!sites) { body.innerHTML = T('<div class="dim">加载中…</div>'); loadCookieSites().then(() => renderFreePool()); return; }
|
|
2497
|
-
const
|
|
2602
|
+
const enabledSites = sites.filter((s) => s.disabled !== true);
|
|
2603
|
+
const totalAccounts = enabledSites.reduce((sum, s) => sum + (s.accounts?.length || 0), 0);
|
|
2498
2604
|
if ($('free-total-count')) $('free-total-count').textContent = totalAccounts;
|
|
2499
|
-
if ($('free-sites-count')) $('free-sites-count').textContent =
|
|
2605
|
+
if ($('free-sites-count')) $('free-sites-count').textContent = enabledSites.length;
|
|
2500
2606
|
if ($('cnt-free')) $('cnt-free').textContent = totalAccounts;
|
|
2501
2607
|
|
|
2502
|
-
if (!
|
|
2608
|
+
if (!enabledSites.length) {
|
|
2503
2609
|
body.innerHTML = T('<div class="muted-empty">暂无可捕获的供应商</div>');
|
|
2504
2610
|
return;
|
|
2505
2611
|
}
|
|
2506
2612
|
|
|
2507
|
-
|
|
2613
|
+
const hiddenCount = enabledSites.filter((s) => !freeVisible(s.id)).length;
|
|
2614
|
+
const showHiddenBar = hiddenCount > 0;
|
|
2615
|
+
const visibleSites = orderedFreeSites(sites).filter((s) => freeVisible(s.id) && s.disabled !== true);
|
|
2616
|
+
|
|
2617
|
+
body.innerHTML = (showHiddenBar
|
|
2618
|
+
? T('<div class="hint" style="grid-column:1/-1;display:flex;align-items:center;gap:8px">') + T('{n} 个站点已隐藏', { n: hiddenCount })
|
|
2619
|
+
+ ' <button class="btn" id="free-show-all" style="padding:2px 10px;font-size:12px">' + T('全部显示') + '</button></div>'
|
|
2620
|
+
: '')
|
|
2621
|
+
+ visibleSites.map((site) => {
|
|
2622
|
+
// entryOnly 站点(豆包):纯快捷入口卡——只出「打开」按钮与说明,无捕获/账号
|
|
2623
|
+
if (site.entryOnly === true) {
|
|
2624
|
+
const openBtn = canCapture
|
|
2625
|
+
? '<button class="btn primary" data-open-doubao="1">' + I.play + T('打开豆包') + '</button>'
|
|
2626
|
+
: T('<button class="btn" disabled title="需要桌面版 CreditDaddy">打开豆包</button>');
|
|
2627
|
+
return '<div class="card" style="display:flex;flex-direction:column;justify-content:flex-start;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow)">'
|
|
2628
|
+
+ '<div style="display:flex;align-items:center;justify-content:space-between;gap:10px">'
|
|
2629
|
+
+ '<span style="font-size:14px;font-weight:700">' + esc(site.label || site.name) + '</span>' + openBtn + '</div>'
|
|
2630
|
+
+ (site.hint ? '<div class="hint" style="margin:2px 0 0">' + esc(T(site.hint)) + '</div>' : '')
|
|
2631
|
+
+ '</div>';
|
|
2632
|
+
}
|
|
2508
2633
|
const rows = site.accounts || [];
|
|
2509
2634
|
const stateTxt = rows.length
|
|
2510
2635
|
? T('<span class="badge ok">已捕获 ') + rows.length + T(' 个会话</span>')
|
|
2511
2636
|
: T('<span class="badge limited">未捕获</span>');
|
|
2512
|
-
const
|
|
2513
|
-
|
|
2514
|
-
|
|
2637
|
+
const siteDisabled = site.disabled === true;
|
|
2638
|
+
const btn = siteDisabled
|
|
2639
|
+
? T('<button class="btn" disabled title="网页 cookie 无法参与该站活动(活动校验客户端环境)">网页版(暂不可用)</button>')
|
|
2640
|
+
: canCapture
|
|
2641
|
+
? '<button class="btn primary" data-capture-site="' + esc(site.id) + '">' + I.plus + T('网页版') + '</button>'
|
|
2642
|
+
: T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">网页版</button>');
|
|
2515
2643
|
// 「粘贴捕获」:部分站点(小米账号 SSO)登录态挂设备指纹,CD 无痕窗也会被静默 SSO
|
|
2516
2644
|
// 拉回同一账号——多号并存走系统浏览器登录新号后手动粘 cookie(不依赖登录窗)。
|
|
2517
2645
|
const pasteBtn = ' <button class="btn" data-paste-capture="' + esc(site.id) + '" title="' + T('在其他浏览器登录该站点后,把会话 cookie 粘贴进来捕获(多账号并存的可靠路径)') + '">' + T('粘贴捕获') + '</button>';
|
|
@@ -2605,28 +2733,44 @@ function renderFreePool() {
|
|
|
2605
2733
|
+ '</div>'
|
|
2606
2734
|
+ '<div style="display:flex;align-items:center;gap:8px;flex:none">'
|
|
2607
2735
|
+ '<span class="dim" style="font-size:11.5px">' + (acc.capturedAt ? esc(fmtTime(acc.capturedAt)) : '–') + '</span>'
|
|
2736
|
+
+ (site.id === 'doubao' && canCapture ? '<button class="icon-btn" data-open-doubao="1" title="' + T('打开豆包客户端(签到请在客户端内完成)') + '">' + I.play + '</button>' : '')
|
|
2608
2737
|
+ '<button class="icon-btn" data-act="reprobe" data-site="' + esc(site.id) + '" data-id="' + esc(acc.id) + '" title="' + T('就地重探(免登录,用已存会话刷新身份与额度)') + '">' + I.refresh + '</button>'
|
|
2609
2738
|
+ '<button class="icon-btn del" data-act="del" data-id="' + esc(acc.id) + '" title="' + T('删除此账号') + '">' + I.trash + '</button>'
|
|
2610
2739
|
+ '</div></div>'
|
|
2611
2740
|
+ (metricHtml ? '<div style="display:flex;align-items:center;gap:6px;flex-wrap:wrap;padding-left:14px">' + metricHtml + '</div>' : '')
|
|
2612
2741
|
+ '</div>';
|
|
2613
2742
|
}).join('') + '</div>'
|
|
2614
|
-
: T('<div class="muted-empty" style="padding:16px 0 6px"
|
|
2743
|
+
: T('<div class="muted-empty" style="padding:16px 0 6px">还没有该站点的账号,点右上角「网页版」登录后自动捕获</div>');
|
|
2615
2744
|
|
|
2616
2745
|
// justify-content 不用 space-between:网格行高被最高卡撑齐后,单行账号会被推到卡底(沉底)
|
|
2617
|
-
|
|
2746
|
+
const eyeBtn = '<button class="icon-btn" data-free-hide="' + esc(site.id) + '" title="' + T('隐藏此站点卡片(「全部显示」可恢复)') + '">' + I.eyeOff + '</button>';
|
|
2747
|
+
return '<div class="card" draggable="true" data-id="' + esc(site.id) + '" style="display:flex;flex-direction:column;justify-content:flex-start;gap:8px;padding:14px 16px;background:var(--surface);border:1px solid var(--border-subtle);border-radius:14px;box-shadow:var(--shadow)">'
|
|
2618
2748
|
+ '<div><div style="display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px">'
|
|
2619
2749
|
+ '<div style="display:flex;align-items:center;gap:8px">'
|
|
2620
2750
|
+ '<span style="font-size:14px;font-weight:700">' + esc(site.label || site.name) + '</span>'
|
|
2621
2751
|
+ stateTxt
|
|
2622
2752
|
+ '</div>'
|
|
2623
|
-
+ '<div style="display:flex;align-items:center;gap:6px">' + btn + pasteBtn + clearBtn + '</div>'
|
|
2753
|
+
+ '<div style="display:flex;align-items:center;gap:6px">' + btn + pasteBtn + clearBtn + eyeBtn + '</div>'
|
|
2624
2754
|
+ '</div>'
|
|
2625
2755
|
+ hint
|
|
2626
2756
|
+ '</div>'
|
|
2627
2757
|
+ accRows
|
|
2628
2758
|
+ '</div>';
|
|
2629
2759
|
}).join('');
|
|
2760
|
+
bindCardDrag(body, {
|
|
2761
|
+
kind: 'free-site',
|
|
2762
|
+
onReorder: (drag, targetId) => {
|
|
2763
|
+
if (!drag.id) return;
|
|
2764
|
+
const ids = state.freeOrder.slice();
|
|
2765
|
+
const from = ids.indexOf(drag.id);
|
|
2766
|
+
if (from >= 0) ids.splice(from, 1);
|
|
2767
|
+
const ti = ids.indexOf(targetId);
|
|
2768
|
+
ids.splice(ti < 0 ? ids.length : ti, 0, drag.id);
|
|
2769
|
+
state.freeOrder = ids;
|
|
2770
|
+
saveFreeOrder();
|
|
2771
|
+
renderFreePool();
|
|
2772
|
+
},
|
|
2773
|
+
});
|
|
2630
2774
|
}
|
|
2631
2775
|
|
|
2632
2776
|
function renderCookieSites() {
|
|
@@ -2642,8 +2786,8 @@ function renderCookieSites() {
|
|
|
2642
2786
|
? T('<span class="dim">已捕获 ') + rows.length + T(' 个会话</span>')
|
|
2643
2787
|
: T('<span class="dim">未捕获</span>');
|
|
2644
2788
|
const btn = canCapture
|
|
2645
|
-
? '<button class="btn" data-capture-site="' + esc(site.id) + '">' + T('
|
|
2646
|
-
: T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)"
|
|
2789
|
+
? '<button class="btn" data-capture-site="' + esc(site.id) + '">' + T('网页版') + '</button>'
|
|
2790
|
+
: T('<button class="btn" disabled title="需要桌面版 CreditDaddy(无痕登录窗)">网页版</button>');
|
|
2647
2791
|
const hint = site.hint ? T('<div class="hint">') + esc(site.hint) + '</div>' : '';
|
|
2648
2792
|
const accRows = rows.map((acc) =>
|
|
2649
2793
|
'<div style="display:flex;align-items:center;justify-content:space-between;gap:10px;font-size:12px">'
|
|
@@ -2655,13 +2799,115 @@ function renderCookieSites() {
|
|
|
2655
2799
|
+ hint + accRows + '</div>';
|
|
2656
2800
|
}).join('');
|
|
2657
2801
|
}
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2802
|
+
/**
|
|
2803
|
+
* 「账号管理」弹窗:把两件同性质的事收在一处——
|
|
2804
|
+
* 1. 账号同步:本机 Qoder / WorkBuddy 账号推为 10Router 上游连接(原藏在「设置」弹窗底部)
|
|
2805
|
+
* 2. Cookie 会话:网页会话型供应商的捕获入口(原名「Cookie 会话」按钮)
|
|
2806
|
+
* 面板密码栏同步展示:账号同步走 OAuth 通道时要它(带 refreshToken,自动续期)。
|
|
2807
|
+
*
|
|
2808
|
+
* 渠道开关(每渠道一行勾选):取消勾选 = 两边都不再推——
|
|
2809
|
+
* 账号连接(sync-accounts)跳过该渠道的账号;额度卡推送跳过该渠道账号的免费卡。
|
|
2810
|
+
* 场景:10Router 已单独配了同源渠道(如 minimax-code 连接),CD 侧维护同名免费卡
|
|
2811
|
+
* (minimax-free-*)纯属重复——两边卡片并存且数字各刷各的。
|
|
2812
|
+
*/
|
|
2813
|
+
const AM_CHANNELS = [
|
|
2814
|
+
{ provider: 'minimax', label: 'MiniMax', syncable: false },
|
|
2815
|
+
{ provider: 'tokenharbor', label: 'Token Harbor', syncable: true },
|
|
2816
|
+
{ provider: 'xiaomi-mimo', label: '小米 MiMo', syncable: false },
|
|
2817
|
+
{ provider: 'river', label: 'River AI', syncable: false },
|
|
2818
|
+
{ provider: 'modelscope', label: 'ModelScope 魔搭', syncable: false },
|
|
2819
|
+
{ provider: 'longcat', label: 'LongCat', syncable: false },
|
|
2820
|
+
{ provider: 'qoder', label: 'Qoder 国际版', syncable: true },
|
|
2821
|
+
{ provider: 'qoder-cn', label: 'Qoder 国内版', syncable: true },
|
|
2822
|
+
{ provider: 'workbuddy', label: 'WorkBuddy 国内版', syncable: true },
|
|
2823
|
+
{ provider: 'workbuddy-intl', label: 'WorkBuddy 国际版', syncable: true },
|
|
2824
|
+
];
|
|
2825
|
+
|
|
2826
|
+
function openTrAccountManage() {
|
|
2827
|
+
if (!(tr.config && tr.config.configured)) return toast(T('请先保存 10Router 设置'), false);
|
|
2828
|
+
$('am-sync-msg').textContent = '';
|
|
2829
|
+
$('am-pw').value = '';
|
|
2830
|
+
$('am-pw').placeholder = T('填 10Router 面板密码,走 OAuth 通道(带 refreshToken 自动续期);不填则按已保存的走');
|
|
2831
|
+
const btn = $('am-sync');
|
|
2832
|
+
btn.disabled = false;
|
|
2833
|
+
btn.querySelector('.dot').className = 'dot' + (tr.config.configured ? ' on' : '');
|
|
2834
|
+
btn.querySelector('.lbl').textContent = T('同步账号到 10R');
|
|
2835
|
+
renderAmChannels();
|
|
2836
|
+
loadCookieSites(true).then(() => renderCookieSites()).catch(() => {});
|
|
2837
|
+
openModal('m-am');
|
|
2838
|
+
}
|
|
2839
|
+
|
|
2840
|
+
function renderAmChannels() {
|
|
2841
|
+
const c = tr.config || {};
|
|
2842
|
+
const enabled = c.accountSync?.enabled !== false;
|
|
2843
|
+
const excluded = (c.quotaPushDisabledProviders || []);
|
|
2844
|
+
const have = new Set((state.accounts || []).map((a) => a.provider));
|
|
2845
|
+
const box = $('am-sync-channels');
|
|
2846
|
+
if (!box) return;
|
|
2847
|
+
const rows = AM_CHANNELS.filter((ch) => have.has(ch.provider)).map((ch) => {
|
|
2848
|
+
// 勾选语义 = 推送该渠道:账号同步看 accountSync(总开关关了整组禁用),
|
|
2849
|
+
// 额度卡看 quotaPushDisabledProviders 反集
|
|
2850
|
+
const syncOn = enabled && ((c.accountSync?.providers || []).length
|
|
2851
|
+
? (c.accountSync.providers.includes(ch.provider)) : ch.syncable);
|
|
2852
|
+
const pushOn = !excluded.includes(ch.provider);
|
|
2853
|
+
return '<label class="check" style="display:flex;gap:6px;align-items:center">'
|
|
2854
|
+
+ '<input type="checkbox" data-am-push="' + ch.provider + '"' + (pushOn ? ' checked' : '') + '> ' + esc(T(ch.label))
|
|
2855
|
+
+ '<label class="check" style="margin-left:auto;gap:4px"><input type="checkbox" data-am-sync="' + ch.provider + '"' + (syncOn ? ' checked' : '') + (!ch.syncable || !enabled ? ' disabled' : '') + '> <span class="dim">连接</span></label>'
|
|
2856
|
+
+ '</label>';
|
|
2857
|
+
});
|
|
2858
|
+
box.innerHTML = rows.length
|
|
2859
|
+
? '<div class="hint" style="margin-bottom:4px">' + T('勾选 = 同步该渠道(「连接」= 账号连接;主勾 = 额度卡推送)。10Router 已单独配了同源渠道时,取消对应渠道即可去掉重复卡片。') + '</div>'
|
|
2860
|
+
+ '<div style="display:grid;gap:4px">' + rows.join('') + '</div>'
|
|
2861
|
+
+ '<div style="margin-top:6px"><button class="btn" id="am-channels-save">' + T('保存渠道设置') + '</button></div>'
|
|
2862
|
+
: '';
|
|
2863
|
+
$('am-channels-save')?.addEventListener('click', saveAmChannels);
|
|
2864
|
+
}
|
|
2865
|
+
|
|
2866
|
+
async function saveAmChannels() {
|
|
2867
|
+
const push = [...document.querySelectorAll('#am-sync-channels input[data-am-push]:checked')].map((x) => x.dataset.amPush);
|
|
2868
|
+
const sync = [...document.querySelectorAll('#am-sync-channels input[data-am-sync]:checked')].map((x) => x.dataset.amSync);
|
|
2869
|
+
try {
|
|
2870
|
+
tr.config = await api('/api/tenrouter', {
|
|
2871
|
+
method: 'PUT',
|
|
2872
|
+
json: {
|
|
2873
|
+
accountSyncEnabled: true,
|
|
2874
|
+
accountSyncProviders: sync,
|
|
2875
|
+
// 主勾没勾的 = 排除推送
|
|
2876
|
+
quotaPushDisabledProviders: AM_CHANNELS.map((c) => c.provider).filter((p) => !push.includes(p)),
|
|
2877
|
+
},
|
|
2878
|
+
});
|
|
2879
|
+
toast(T('渠道同步设置已保存'), true);
|
|
2880
|
+
renderAmChannels();
|
|
2881
|
+
} catch (e) { toast(e.message, false); }
|
|
2882
|
+
}
|
|
2883
|
+
async function trSyncAccountsFromModal() {
|
|
2884
|
+
if (!(tr.config && tr.config.configured)) return toast(T('请先保存 10Router 设置'), false);
|
|
2885
|
+
const btn = $('am-sync');
|
|
2886
|
+
const msg = $('am-sync-msg');
|
|
2887
|
+
const pw = $('am-pw').value;
|
|
2888
|
+
btn.disabled = true;
|
|
2889
|
+
btn.querySelector('.dot').className = 'dot on pulse';
|
|
2890
|
+
btn.querySelector('.lbl').textContent = T('同步账号中…');
|
|
2891
|
+
tr.accountsSyncing = true;
|
|
2892
|
+
renderTenrouter();
|
|
2893
|
+
try {
|
|
2894
|
+
const r = await api('/api/tenrouter/sync-accounts', { method: 'POST', json: pw ? { adminPassword: pw } : {} });
|
|
2895
|
+
msg.textContent = r.summary || '';
|
|
2896
|
+
toast(r.summary, !/失败/.test(r.summary || ''));
|
|
2897
|
+
if (pw) { await loadTrConfig(); $('am-pw').value = ''; }
|
|
2898
|
+
loadLogs();
|
|
2899
|
+
} catch (e) {
|
|
2900
|
+
msg.textContent = e.message;
|
|
2901
|
+
toast(e.message, false);
|
|
2902
|
+
} finally {
|
|
2903
|
+
tr.accountsSyncing = false;
|
|
2904
|
+
btn.disabled = false;
|
|
2905
|
+
btn.querySelector('.dot').className = 'dot on';
|
|
2906
|
+
btn.querySelector('.lbl').textContent = T('同步账号到 10R');
|
|
2907
|
+
renderTenrouter();
|
|
2908
|
+
}
|
|
2663
2909
|
}
|
|
2664
|
-
document.getElementById('m-
|
|
2910
|
+
document.getElementById('m-am')?.addEventListener('click', (e) => {
|
|
2665
2911
|
const b = e.target.closest && e.target.closest('[data-capture-site]');
|
|
2666
2912
|
if (b) cookieSiteCapture(b.dataset.captureSite);
|
|
2667
2913
|
});
|
|
@@ -2697,11 +2943,13 @@ $('btn-free-refresh')?.addEventListener('click', () => {
|
|
|
2697
2943
|
$('btn-free-settings')?.addEventListener('click', openFreeSettings);
|
|
2698
2944
|
// 「同步到 10Router」= 推连接(会话凭据)+ 推额度卡(issue #24 数字快照),一键两路
|
|
2699
2945
|
$('btn-free-sync-10r')?.addEventListener('click', async () => {
|
|
2700
|
-
await trSyncAccounts();
|
|
2701
2946
|
const btn = $('btn-free-sync-10r');
|
|
2702
|
-
if (!(tr.config && tr.config.configured)) return;
|
|
2947
|
+
if (!(tr.config && tr.config.configured)) return toast(T('请先保存 10Router 设置'), false);
|
|
2703
2948
|
btn.disabled = true;
|
|
2704
2949
|
try {
|
|
2950
|
+
// 先推账号连接(原 trSyncAccounts 的职责),失败不阻断额度卡推送
|
|
2951
|
+
const sa = await api('/api/tenrouter/sync-accounts', { method: 'POST', json: {} }).catch((e) => ({ summary: e.message, error: true }));
|
|
2952
|
+
if (sa && sa.summary) toast(sa.summary, !sa.error && !/失败/.test(sa.summary));
|
|
2705
2953
|
const r = await api('/api/tenrouter/push-quota', { method: 'POST' });
|
|
2706
2954
|
const failed = (r.results || []).filter((x) => x.status === 'failed');
|
|
2707
2955
|
if (failed.length) toast(T('额度卡部分推送失败:') + failed.map((x) => x.source + '(' + x.error + ')').join(';'), false);
|
|
@@ -2736,7 +2984,7 @@ $('free-settings-save')?.addEventListener('click', async () => {
|
|
|
2736
2984
|
},
|
|
2737
2985
|
});
|
|
2738
2986
|
closeModal('m-free-settings');
|
|
2739
|
-
toast(T('
|
|
2987
|
+
toast(T('额度卡片推送设置已保存'));
|
|
2740
2988
|
} catch (e) { toast(e.message, false); }
|
|
2741
2989
|
});
|
|
2742
2990
|
$('free-settings-push-now')?.addEventListener('click', async () => {
|
|
@@ -2780,7 +3028,10 @@ async function cookieSitePasteCapture(siteId) {
|
|
|
2780
3028
|
try {
|
|
2781
3029
|
const r = await api('/api/cookie-sites/' + encodeURIComponent(siteId) + '/capture', { json: { cookie: cookie.trim() } });
|
|
2782
3030
|
if (r && r.ok) {
|
|
2783
|
-
|
|
3031
|
+
// 豆包不推 10R(无外部配额卡),文案不带「同步 10Router」以免误导
|
|
3032
|
+
toast(site.no10rSync
|
|
3033
|
+
? T('{name} cookie 捕获成功,额度卡片已就绪', { name: site.name })
|
|
3034
|
+
: T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: site.name }), true);
|
|
2784
3035
|
state.cookieSites = null;
|
|
2785
3036
|
loadCookieSites(true).then(() => { if (state.view === 'free') renderFreePool(); });
|
|
2786
3037
|
} else toast(T('捕获失败'), false);
|
|
@@ -2796,7 +3047,10 @@ if (window.creditdaddy && typeof window.creditdaddy.onCookieCaptured === 'functi
|
|
|
2796
3047
|
window.creditdaddy.onCookieCaptured((payload) => {
|
|
2797
3048
|
if (payload && payload.ok === false) { toast(payload.error || T('cookie 捕获失败'), false); return; }
|
|
2798
3049
|
const name = payload && payload.name;
|
|
2799
|
-
|
|
3050
|
+
const site2 = (state.cookieSites || []).find((s) => s.name === name) || null;
|
|
3051
|
+
toast(site2?.no10rSync
|
|
3052
|
+
? T('{name} cookie 捕获成功,额度卡片已就绪', { name: name || '' })
|
|
3053
|
+
: T('{name} cookie 捕获成功,同步到 10Router 后卡片点亮', { name: name || '' }), true);
|
|
2800
3054
|
state.cookieSites = null;
|
|
2801
3055
|
Promise.allSettled([loadCookieSites(true), refreshAccountList(), loadTrQuotas(true)])
|
|
2802
3056
|
.then(() => {
|
|
@@ -2804,6 +3058,11 @@ if (window.creditdaddy && typeof window.creditdaddy.onCookieCaptured === 'functi
|
|
|
2804
3058
|
if (state.view === 'free') renderFreePool();
|
|
2805
3059
|
renderCookieSites();
|
|
2806
3060
|
renderDash();
|
|
3061
|
+
// 收割时余额快照已随账号落库——账号卡上的余额读 state.quotas 缓存,
|
|
3062
|
+
// 不重拉就显示旧数字(实测:小米收割成功后卡面 ¥0.00 挂到手动刷新)。
|
|
3063
|
+
// 非 live 读快照零网络,成本可忽略;只刷 cookie-site 型账号。
|
|
3064
|
+
const ids = (state.accounts || []).filter((a) => a.meta?.cookieSiteId).map((a) => a.id);
|
|
3065
|
+
if (ids.length) loadQuotas(ids).then(renderDash);
|
|
2807
3066
|
});
|
|
2808
3067
|
});
|
|
2809
3068
|
}
|
|
@@ -2871,8 +3130,8 @@ function renderTenrouter() {
|
|
|
2871
3130
|
+ '<button class="btn" data-tr="refresh"' + (tr.loading ? ' disabled' : '') + '>' + (tr.loading ? T('刷新中…') : T('刷新额度')) + '</button>'
|
|
2872
3131
|
+ T('<button class="btn" data-tr="sync" title="用量同步:自动开关、同步来源与手动同步(点我打开)"') + (tr.syncing || c.syncing ? ' disabled' : '') + '>'
|
|
2873
3132
|
+ '<span class="dot' + (c.sync.enabled ? ' on' : '') + '"></span>' + (tr.syncing || c.syncing ? T('同步中…') : T('用量同步')) + '</button>'
|
|
3133
|
+
+ '<button class="btn" data-tr="account-manage" title="' + T('账号同步与网页会话 Cookie 捕获') + '">' + T('账号管理') + '</button>'
|
|
2874
3134
|
+ T('<button class="btn" data-tr="settings">设置</button>')
|
|
2875
|
-
+ T('<button class="btn" data-tr="cookie-sites" title="Token Harbor 等网页会话型供应商">Cookie 会话</button>')
|
|
2876
3135
|
+ '</div></div>'
|
|
2877
3136
|
+ tabStrip
|
|
2878
3137
|
+ '<div class="tr-grid">' + grid + '</div>';
|
|
@@ -2993,19 +3252,6 @@ async function trDisconnect() {
|
|
|
2993
3252
|
try { tr.config = await api('/api/tenrouter', { method: 'DELETE' }); tr.quotas = null; tr.error = null; closeModal('m-10r'); render(); }
|
|
2994
3253
|
catch (e) { toast(e.message, false); }
|
|
2995
3254
|
}
|
|
2996
|
-
async function trSyncAccounts() {
|
|
2997
|
-
if (!(tr.config && tr.config.configured)) return toast(T('请先保存 10Router 设置'), false);
|
|
2998
|
-
const btn = $('tr-sync-accounts');
|
|
2999
|
-
const pw = $('tr-admin-pw').value;
|
|
3000
|
-
btn.disabled = true;
|
|
3001
|
-
try {
|
|
3002
|
-
const r = await api('/api/tenrouter/sync-accounts', { method: 'POST', json: pw ? { adminPassword: pw } : {} });
|
|
3003
|
-
toast(r.summary, !/失败/.test(r.summary));
|
|
3004
|
-
if (pw) await loadTrConfig();
|
|
3005
|
-
loadLogs();
|
|
3006
|
-
} catch (e) { toast(e.message, false); }
|
|
3007
|
-
btn.disabled = false;
|
|
3008
|
-
}
|
|
3009
3255
|
$('tr-panel').addEventListener('click', (e) => {
|
|
3010
3256
|
const b = e.target.closest('[data-tr]');
|
|
3011
3257
|
if (!b) return;
|
|
@@ -3013,7 +3259,7 @@ $('tr-panel').addEventListener('click', (e) => {
|
|
|
3013
3259
|
if (b.dataset.tr === 'settings') openTrSettings();
|
|
3014
3260
|
else if (b.dataset.tr === 'refresh') loadTrQuotas(true);
|
|
3015
3261
|
else if (b.dataset.tr === 'sync') openTrSync();
|
|
3016
|
-
else if (b.dataset.tr === '
|
|
3262
|
+
else if (b.dataset.tr === 'account-manage') openTrAccountManage();
|
|
3017
3263
|
else if (b.dataset.tr === 'vendor') {
|
|
3018
3264
|
const v = b.dataset.v || '';
|
|
3019
3265
|
state.trVendor = v;
|
|
@@ -3029,7 +3275,7 @@ $('tr-sync-now').onclick = async () => {
|
|
|
3029
3275
|
trSync();
|
|
3030
3276
|
api('/api/tenrouter/push-quota', { method: 'POST' }).catch(() => {});
|
|
3031
3277
|
};
|
|
3032
|
-
$('
|
|
3278
|
+
$('am-sync').onclick = trSyncAccountsFromModal;
|
|
3033
3279
|
$('tr-save').onclick = trSave;
|
|
3034
3280
|
$('tr-disconnect').onclick = trDisconnect;
|
|
3035
3281
|
|
|
@@ -3048,7 +3294,12 @@ async function syncAfterZcodeClaims() {
|
|
|
3048
3294
|
seenZcodeTickAt = t.at;
|
|
3049
3295
|
if (first || !(t.claimedIds && t.claimedIds.length)) return; // 首次加载已全量拉取
|
|
3050
3296
|
try { state.accounts = (await api('/api/accounts')).accounts; } catch (e) {}
|
|
3051
|
-
loadQuotas(t.claimedIds);
|
|
3297
|
+
await loadQuotas(t.claimedIds);
|
|
3298
|
+
// 额度拿到后必须重绘:ZCode 活动发的额度常带 effective_at(如 23:00 到账),
|
|
3299
|
+
// 卡片从「无有效套餐」变成「待生效 N Token · HH:MM 到账」,不重绘就一直停在旧样子,
|
|
3300
|
+
// 用户以为「领取成功了却不显示」——只能自己手动点刷新。
|
|
3301
|
+
render();
|
|
3302
|
+
renderStatus();
|
|
3052
3303
|
loadZGateway();
|
|
3053
3304
|
}
|
|
3054
3305
|
async function loadAccounts() {
|
|
@@ -3208,10 +3459,15 @@ async function onCardClick(e) {
|
|
|
3208
3459
|
if ([...sel.options].some((o) => o.value === a.provider && !o.hidden)) sel.value = a.provider;
|
|
3209
3460
|
return deviceLogin();
|
|
3210
3461
|
}
|
|
3211
|
-
if (act === 'quota') { loadQuotas([id], { live: true }); return; }
|
|
3462
|
+
if (act === 'quota') { loadQuotas([id], { live: true }).then(render); return; }
|
|
3212
3463
|
if (act === 'claim') return openClaim(id);
|
|
3213
3464
|
if (act === 'switch') {
|
|
3214
3465
|
const a = state.accounts.find((x) => x.id === id);
|
|
3466
|
+
// 本机没装客户端时先拦下(WorkBuddy 此前静默硬写凭据目录,面板显示已切换但客户端永远不应用)
|
|
3467
|
+
if (a && a.product === 'workbuddy' && state.status && state.status.workbuddyClient && state.status.workbuddyClient.installed === false) {
|
|
3468
|
+
toast(T('本机未安装 WorkBuddy / CodeBuddy 客户端,无法切换:切换是把登录写入客户端凭据,由客户端应用。请先安装客户端并登录,再「本机导入」。'), false);
|
|
3469
|
+
return;
|
|
3470
|
+
}
|
|
3215
3471
|
const client = (CLIENT_LABEL[a && a.product] ? T(CLIENT_LABEL[a && a.product]) : '') || (a && prodLabel(a.product)) || T('客户端');
|
|
3216
3472
|
const tip = a && a.product === 'zcode'
|
|
3217
3473
|
? T('\n需先退出 ZCode;切换后重新打开 ZCode 即为新账号。当前登录会先保存到 CreditDaddy,不会丢号。')
|
|
@@ -3263,6 +3519,16 @@ async function onCardClick(e) {
|
|
|
3263
3519
|
} catch (e) {}
|
|
3264
3520
|
}, 800);
|
|
3265
3521
|
}
|
|
3522
|
+
if (a && a.product === 'qoder' && r && !r.alreadyActive && window.creditdaddy && typeof window.creditdaddy.openQoderClient === 'function') {
|
|
3523
|
+
// 强制切换会先关掉 Qoder:写完登录自动拉起来,不然用户得自己找快捷方式
|
|
3524
|
+
setTimeout(async () => {
|
|
3525
|
+
try {
|
|
3526
|
+
const re = await window.creditdaddy.openQoderClient(a.provider);
|
|
3527
|
+
if (re && re.ok) toast(T('已重新启动 Qoder 客户端'));
|
|
3528
|
+
else toast((re && re.error) || T('无法打开 Qoder 客户端(桌面版才能自动拉起)'), false);
|
|
3529
|
+
} catch (e) {}
|
|
3530
|
+
}, 800);
|
|
3531
|
+
}
|
|
3266
3532
|
if (a && a.product === 'trae' && r && !r.alreadyActive && window.creditdaddy && typeof window.creditdaddy.openTraeClient === 'function') {
|
|
3267
3533
|
setTimeout(async () => {
|
|
3268
3534
|
try {
|
|
@@ -3458,7 +3724,8 @@ async function doClaim(i) {
|
|
|
3458
3724
|
});
|
|
3459
3725
|
toast(T('领取成功:') + (r.planName || pl.name || pl.planId));
|
|
3460
3726
|
closeModal('m-claim');
|
|
3461
|
-
|
|
3727
|
+
await loadStatus(); // 徽标(已领取)在账号的 lastResult 上,必须先重拉账号
|
|
3728
|
+
await loadAccounts(); // loadQuotas 之后必须重绘,否则卡片停在旧样子
|
|
3462
3729
|
loadLogs();
|
|
3463
3730
|
} catch (e) {
|
|
3464
3731
|
const errBox = $('claim-status');
|
|
@@ -3657,7 +3924,7 @@ function openChangelogModal() {
|
|
|
3657
3924
|
+ '<div style="font-weight:700;margin:0 0 4px">v' + esc(s.version)
|
|
3658
3925
|
+ (s.date ? ' <span class="dim" style="font-weight:400;font-size:11px">' + esc(s.date) + '</span>' : '') + '</div>'
|
|
3659
3926
|
+ (s.theme ? '<div class="hint">' + strip(s.theme) + '</div>' : '')
|
|
3660
|
-
+ (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map(strip).join(' · ') + '</div>' : '')
|
|
3927
|
+
+ (s.topics && s.topics.length ? '<div class="hint" style="margin-top:4px">' + s.topics.map((t) => strip(T(t.trim()))).join(' · ') + '</div>' : '')
|
|
3661
3928
|
+ '</div>').join('')
|
|
3662
3929
|
: T('<div class="hint">暂无本地更新日志,请到 GitHub Releases 查看。</div>');
|
|
3663
3930
|
}).catch(openReleases);
|
|
@@ -3767,7 +4034,7 @@ $('znet-ok').addEventListener('click', async () => {
|
|
|
3767
4034
|
state.zProxyFirst = r.proxyFirst === true;
|
|
3768
4035
|
await loadZNet();
|
|
3769
4036
|
$('znet-url').value = '';
|
|
3770
|
-
$('znet-url').placeholder = state.zProxyUrl
|
|
4037
|
+
$('znet-url').placeholder = state.zProxyUrl ? T('当前代理:{url}(输入新值可更换,留空清除)', { url: state.zProxyUrl }) : (state.zHasEnvProxy ? T('跟随环境变量 HTTPS_PROXY') : 'http://192.168.31.101:7890');
|
|
3771
4038
|
$('znet-state').textContent = T('已保存:') + (state.zProxyFirst === true ? T('代理优先') : T('直连优先'))
|
|
3772
4039
|
+ (state.zProxyUrl ? T(' · 代理 ') + state.zProxyUrl : '');
|
|
3773
4040
|
toast(T('网络出口:') + (r.proxyFirst ? T('代理优先(直连兜底)') : T('直连优先(代理兜底)')) + (body.proxyUrl === '' ? T(',代理已清除') : T(',代理已保存')));
|
|
@@ -3844,10 +4111,11 @@ async function localScan() {
|
|
|
3844
4111
|
const sub = c.kind === 'app'
|
|
3845
4112
|
? esc(c.source) + (c.email ? ' · ' + esc(mask(c.email)) : c.phone ? ' · ' + esc(c.phone) : '') + (c.expiresAt ? T(' · 有效期至 ') + esc(fmtTime(c.expiresAt)) : '')
|
|
3846
4113
|
: esc(c.source) + ' · ' + esc(String(c.file || '').split(/[\\/]/).pop()) + T('(') + c.hits + T(' 处)');
|
|
4114
|
+
const intl = c.intlWarning ? '<div class="hint" style="color:var(--warn,#d4b83a);margin-top:4px">⚠ ' + esc(c.intlWarning) + '</div>' : '';
|
|
3847
4115
|
const btns = c.imported ? T('<span class="pill ok">已导入</span>') + (c.provider ? '<button class="btn" data-cid="' + esc(c.id) + T('" title="用本机凭据更新 token / 有效期 / 邮箱">同步信息</button>') : '')
|
|
3848
4116
|
: c.provider ? '<button class="btn primary" data-cid="' + esc(c.id) + T('">导入</button>')
|
|
3849
4117
|
: '<button class="btn" data-cid="' + esc(c.id) + T('" data-prov="qoder">导入·国际</button><button class="btn" data-cid="') + esc(c.id) + T('" data-prov="qoder-cn">导入·国内</button>');
|
|
3850
|
-
html += '<div class="cand"><div class="cand-main"><div class="cand-name">' + title + prov + '</div><div class="cand-sub">' + sub + '</div
|
|
4118
|
+
html += '<div class="cand"><div class="cand-main"><div class="cand-name">' + title + prov + '</div><div class="cand-sub">' + sub + '</div>' + intl + '</div><div class="row">' + btns + '</div></div>';
|
|
3851
4119
|
}
|
|
3852
4120
|
for (const e of r.errors || []) {
|
|
3853
4121
|
html += T('<div class="hint" style="color:var(--err)">读取失败:') + esc(e.file) + T(':') + esc(e.error) + '</div>';
|
|
@@ -4308,7 +4576,6 @@ $('btn-comp').addEventListener('click', async () => {
|
|
|
4308
4576
|
renderCompMenu();
|
|
4309
4577
|
fillAutoCheckin();
|
|
4310
4578
|
$('cfg-logs-display').value = logsDisplayDefault();
|
|
4311
|
-
$('cfg-wb-activity').checked = wbActOn();
|
|
4312
4579
|
// 网络出口(全局):预填当前顺序与脱敏代理地址
|
|
4313
4580
|
await loadZNet();
|
|
4314
4581
|
(state.zProxyFirst === true ? $('znet-proxy') : $('znet-direct')).checked = true;
|