Skip to content

Instantly share code, notes, and snippets.

@chemzqm
Last active August 8, 2026 17:35
Show Gist options
  • Select an option

  • Save chemzqm/481f1c5b3400abacb00874bae584eadb to your computer and use it in GitHub Desktop.

Select an option

Save chemzqm/481f1c5b3400abacb00874bae584eadb to your computer and use it in GitHub Desktop.
Deepseek balance
// 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