Last active
August 8, 2026 17:35
-
-
Save chemzqm/481f1c5b3400abacb00874bae584eadb to your computer and use it in GitHub Desktop.
Deepseek balance
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // Variables used by Scriptable. | |
| // These must be at the very top of the file. Do not edit. | |
| // icon-color: deep-blue; icon-glyph: chart-line; | |
| // ============================================================ | |
| // DeepSeek 余额查询(Scriptable) | |
| // | |
| // 数据来源(DeepSeek 官方 API): | |
| // 余额 GET /user/balance (官方文档接口,API key 认证,可靠) | |
| // (官方 API 不提供用量/消费查询,脚本只展示余额) | |
| // | |
| // 用法: | |
| // 1. 在 Scriptable App 内新建脚本,粘贴本代码,命名如"DeepSeek 余额" | |
| // 2. 在 App 内运行一次,按提示粘贴 API Key(sk- 开头, | |
| // 来自 platform.deepseek.com → API keys),会安全存入钥匙串 | |
| // 3. 之后可直接运行查看,或添加到主屏幕小组件 | |
| // 也可用 URL scheme 快速设 key: | |
| // scriptable://run/DeepSeek%20余额?key=sk-xxxx | |
| // ============================================================ | |
| // ---------- 配置 ---------- | |
| const KEYCHAIN_NAME = "deepseek_api_key"; // Keychain 中存储 API Key 的名字 | |
| const CACHE_MINUTES = 5; // 小组件结果缓存分钟数(0 = 不缓存) | |
| const CURRENCY = "¥"; // 显示货币符号(CNY 时用 ¥) | |
| // ---------- API Key 管理 ---------- | |
| function getAPIKey() { | |
| try { return Keychain.get(KEYCHAIN_NAME) || ""; } catch (e) { return ""; } | |
| } | |
| async function promptForAPIKey() { | |
| const alert = new Alert(); | |
| alert.title = "DeepSeek API Key"; | |
| alert.message = "请粘贴你的 API Key(sk- 开头,在 platform.deepseek.com 获取)。将安全保存在钥匙串 Keychain 中。"; | |
| alert.addTextField("sk-..."); | |
| alert.addAction("保存"); | |
| alert.addCancelAction("取消"); | |
| const idx = await alert.present(); | |
| if (idx === -1) return ""; | |
| const key = (alert.textFieldValue(0) || "").trim(); | |
| if (key) Keychain.set(KEYCHAIN_NAME, key); | |
| return key; | |
| } | |
| // ---------- API 请求 ---------- | |
| async function apiGet(path) { | |
| const req = new Request("https://api.deepseek.com" + path); | |
| req.headers = { | |
| "Authorization": "Bearer " + getAPIKey(), | |
| "Accept": "application/json" | |
| }; | |
| const text = await req.loadString(); | |
| const status = (req.response && req.response.statusCode) || 0; | |
| const ctype = (req.response && req.response.headers && req.response.headers["Content-Type"]) || ""; | |
| let json; | |
| try { | |
| json = JSON.parse(text); | |
| } catch (e) { | |
| const preview = text.slice(0, 160).replace(/\s+/g, " "); | |
| throw new Error("HTTP " + status + " 返回非 JSON" + (ctype ? " (" + ctype + ")" : "") + ": " + preview); | |
| } | |
| if (json.error) { | |
| const msg = (json.error.message || json.error.type || "未知错误"); | |
| throw new Error("HTTP " + status + ": " + msg); | |
| } | |
| return json; | |
| } | |
| async function fetchBalance() { | |
| const json = await apiGet("/user/balance"); | |
| const info = (json.balance_infos || [])[0] || {}; | |
| return { | |
| available: !!json.is_available, | |
| currency: info.currency || "CNY", | |
| total: Number(info.total_balance || 0), | |
| granted: Number(info.granted_balance || 0), | |
| toppedUp: Number(info.topped_up_balance || 0) | |
| }; | |
| } | |
| // ---------- 格式化 ---------- | |
| function fmtMoney(v) { | |
| const s = CURRENCY; | |
| if (v >= 100) return s + v.toFixed(2); | |
| if (v >= 1) return s + v.toFixed(3); | |
| if (v >= 0.001) return s + v.toFixed(4); | |
| return s + v.toFixed(6); | |
| } | |
| // ---------- 缓存(仅小组件使用) ---------- | |
| function cachePath() { | |
| const fm = FileManager.local(); | |
| return fm.joinPath(fm.documentsDirectory(), "deepseek_cache.json"); | |
| } | |
| function loadCache() { | |
| try { | |
| return JSON.parse(FileManager.local().readString(cachePath())); | |
| } catch (e) { return null; } | |
| } | |
| function saveCache(data) { | |
| try { FileManager.local().writeString(cachePath(), JSON.stringify(data)); } catch (e) {} | |
| } | |
| // ---------- 渲染:小组件 ---------- | |
| function buildWidget(data) { | |
| const w = new ListWidget(); | |
| w.backgroundColor = Color.dynamic(new Color("#0d1117"), new Color("#0d1117")); | |
| w.refreshAfterDate = new Date(Date.now() + CACHE_MINUTES * 60 * 1000); | |
| const title = w.addText("DeepSeek"); | |
| title.font = Font.mediumSystemFont(14); | |
| title.textColor = new Color("#7ee0a3"); | |
| const balance = w.addText(fmtMoney(data.balance.total)); | |
| balance.font = Font.boldSystemFont(30); | |
| balance.textColor = new Color("#ffffff"); | |
| if (data.balance.available === false) { | |
| const warn = w.addText("⚠️ 余额不足,API 调用将受限"); | |
| warn.font = Font.systemFont(11); | |
| warn.textColor = new Color("#ff6b6b"); | |
| } | |
| w.addSpacer(6); | |
| if (data.errors && data.errors.length) { | |
| w.addSpacer(4); | |
| const err = w.addText(data.errors[0]); | |
| err.font = Font.systemFont(10); | |
| err.textColor = new Color("#ffab70"); | |
| } | |
| w.addSpacer(2); | |
| const t = w.addText("更新于 " + new Date().toLocaleTimeString("zh-CN", { hour: "2-digit", minute: "2-digit" })); | |
| t.font = Font.systemFont(9); | |
| t.textColor = new Color("#8b949e"); | |
| return w; | |
| } | |
| // ---------- 渲染:App 内表格 ---------- | |
| // UITableRow.dummy() 在新版 Scriptable 中已移除,用空行 + 行高实现分隔效果 | |
| function spacerRow() { | |
| const row = new UITableRow(); | |
| row.height = 12; | |
| return row; | |
| } | |
| function showTable(data) { | |
| const table = new UITable(); | |
| table.showSeparators = true; | |
| const title = new UITableRow(); | |
| title.addText("DeepSeek 账户概览", "更新于 " + new Date().toLocaleString("zh-CN")).titleFont = Font.boldSystemFont(18); | |
| table.addRow(title); | |
| const head = new UITableRow(); | |
| head.addText("余额", "金额"); | |
| head.dismissOnSelect = false; | |
| table.addRow(head); | |
| const rowTotal = new UITableRow(); | |
| rowTotal.addText("可用余额", fmtMoney(data.balance.total) + (data.balance.available ? "" : "(不可用)")); | |
| table.addRow(rowTotal); | |
| const rowTop = new UITableRow(); | |
| rowTop.addText("其中充值余额", fmtMoney(data.balance.toppedUp)); | |
| table.addRow(rowTop); | |
| const rowGrant = new UITableRow(); | |
| rowGrant.addText("其中赠金余额", fmtMoney(data.balance.granted)); | |
| table.addRow(rowGrant); | |
| if (data.errors && data.errors.length) { | |
| table.addRow(spacerRow()); | |
| const eHead = new UITableRow(); | |
| eHead.addText("错误", data.errors.length + " 项"); | |
| table.addRow(eHead); | |
| data.errors.forEach(msg => { | |
| const er = new UITableRow(); | |
| er.addText(msg); | |
| table.addRow(er); | |
| }); | |
| } | |
| table.present(); | |
| } | |
| // ---------- 主流程 ---------- | |
| async function run() { | |
| // URL scheme 传入 key 时直接保存 | |
| if (args.queryParameters && args.queryParameters.key) { | |
| Keychain.set(KEYCHAIN_NAME, String(args.queryParameters.key).trim()); | |
| } | |
| let key = getAPIKey(); | |
| if (!key && config.runsInApp) { | |
| key = await promptForAPIKey(); | |
| } | |
| if (!key) { | |
| if (config.runsInWidget) { | |
| const w = new ListWidget(); | |
| w.backgroundColor = new Color("#0d1117"); | |
| const t = w.addText("未设置 API Key"); | |
| t.font = Font.systemFont(12); | |
| t.textColor = new Color("#ff6b6b"); | |
| const s = w.addText("请在 Scriptable App 中运行一次以配置"); | |
| s.font = Font.systemFont(10); | |
| s.textColor = new Color("#8b949e"); | |
| Script.setWidget(w); | |
| } else { | |
| const a = new Alert(); | |
| a.title = "未设置 API Key"; | |
| a.message = "请先在 App 内运行一次并粘贴 DeepSeek API Key,或用 URL scheme 传入:\nscriptable://run/脚本名?key=sk-xxx"; | |
| a.addAction("好"); | |
| await a.present(); | |
| } | |
| Script.complete(); | |
| return; | |
| } | |
| const data = { balance: null, errors: [] }; | |
| // 小组件模式优先用缓存,避免频繁请求 | |
| const cached = loadCache(); | |
| if (config.runsInWidget && cached && Date.now() - cached.time < CACHE_MINUTES * 60000) { | |
| data.balance = cached.balance; | |
| } else { | |
| try { data.balance = await fetchBalance(); } | |
| catch (e) { data.errors.push("余额查询失败: " + e.message); } | |
| if (data.balance) { | |
| saveCache({ time: Date.now(), balance: data.balance }); | |
| } | |
| } | |
| if (config.runsInWidget) { | |
| const w = buildWidget(data); | |
| Script.setWidget(w); | |
| } else { | |
| if (data.balance) { | |
| await showTable(data); | |
| } else { | |
| const a = new Alert(); | |
| a.title = "查询失败"; | |
| a.message = data.errors.join("\n") || "未知错误,请检查网络或 API Key"; | |
| a.addAction("好"); | |
| await a.present(); | |
| } | |
| } | |
| Script.complete(); | |
| } | |
| await run(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment