Skip to content

Commit 42cfa8a

Browse files
committed
feat: add req count 0.3.0
1 parent b8c64fe commit 42cfa8a

File tree

2 files changed

+82
-1
lines changed

2 files changed

+82
-1
lines changed

main.py

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ def __init__(self):
152152
self._last_data: Dict[str, Any] = {}
153153
self._last_error: Optional[str] = None
154154
self._last_usage: Optional[Dict[str, Any]] = None
155+
self._jwt_expired_notified: bool = False
155156

156157
# 信息区(只读)
157158
self.info_title = rumps.MenuItem("状态:未初始化")
@@ -177,6 +178,10 @@ def __init__(self):
177178
self.info_last = rumps.MenuItem("上次更新:-")
178179
self.info_last.set_callback(None)
179180

181+
# Token 到期信息
182+
self.info_token_exp = rumps.MenuItem("Token:-")
183+
self.info_token_exp.set_callback(None)
184+
180185
# 账号类型子菜单
181186
self.menu_account = {
182187
"共享(公交车)": rumps.MenuItem("共享(公交车)", callback=self._set_shared),
@@ -199,6 +204,7 @@ def __init__(self):
199204
self.info_usage_span,
200205
self.info_monthly,
201206
self.info_balance,
207+
self.info_token_exp,
202208
self.info_last,
203209
None,
204210
rumps.MenuItem("刷新", callback=self.refresh_now),
@@ -255,6 +261,8 @@ def set_token(self, _: Optional[rumps.MenuItem] = None):
255261
with self._lock:
256262
self._cfg["token"] = token
257263
save_config(self._cfg)
264+
# 重置过期提醒
265+
self._jwt_expired_notified = False
258266
self._refresh(force=True)
259267

260268
def _set_shared(self, _: Optional[rumps.MenuItem] = None):
@@ -343,6 +351,42 @@ def _get_base_and_dashboard(self) -> Tuple[str, str]:
343351
env = ACCOUNT_ENV.get(account, ACCOUNT_ENV["shared"]) # type: ignore
344352
return env["base"], env["dashboard"]
345353

354+
def _update_token_status(self) -> None:
355+
"""更新菜单中的 Token 到期信息,并在过期后提醒一次。"""
356+
try:
357+
token = (self._cfg.get("token") or "").strip()
358+
if not token or not _is_probable_jwt(token):
359+
self.info_token_exp.title = "Token:-"
360+
return
361+
exp = _extract_exp_from_jwt(token)
362+
if not exp:
363+
self.info_token_exp.title = "Token:-"
364+
return
365+
# 本地时间展示
366+
dt_local = datetime.datetime.fromtimestamp(exp)
367+
remaining = int(exp - time.time())
368+
remain_text = _fmt_remaining(remaining)
369+
if remaining <= 0:
370+
self.info_token_exp.title = f"Token:已过期({dt_local.strftime('%Y-%m-%d %H:%M')})"
371+
if not self._jwt_expired_notified:
372+
try:
373+
rumps.notification(
374+
title="PackyCode",
375+
subtitle="Token 已过期",
376+
message="请在“设置 Token...”中更换 JWT",
377+
)
378+
except Exception:
379+
pass
380+
self._jwt_expired_notified = True
381+
else:
382+
self.info_token_exp.title = (
383+
f"Token:{dt_local.strftime('%Y-%m-%d %H:%M')}{remain_text})"
384+
)
385+
# 未过期时允许再次提醒(比如用户换新 Token 后)
386+
self._jwt_expired_notified = False
387+
except Exception:
388+
self.info_token_exp.title = "Token:-"
389+
346390
def _refresh(self, force: bool = False):
347391
# 避免过于频繁的刷新
348392
if not force:
@@ -432,6 +476,7 @@ def _update_ui_from_info(self, info: Optional[Dict[str, Any]], usage: Optional[D
432476
self.info_last.title = f"上次更新:{now_str()}"
433477
self.info_requests.title = "请求次数:-"
434478
self.info_usage_span.title = "近30日:-"
479+
self._update_token_status()
435480
return
436481

437482
# 解析字段(参考 packycode-cost UserApiResponse 与转换逻辑)
@@ -495,6 +540,8 @@ def _update_ui_from_info(self, info: Optional[Dict[str, Any]], usage: Optional[D
495540
self.info_balance.title = "余额:-"
496541

497542
self.info_last.title = f"上次更新:{now_str()}"
543+
# 更新 Token 到期信息与提醒
544+
self._update_token_status()
498545

499546
# 状态栏标题(根据设置)
500547
if self._cfg.get("hidden"):
@@ -508,6 +555,7 @@ def _update_ui_error(self, err: str):
508555
self.info_last.title = f"上次更新:{now_str()}"
509556
self.info_requests.title = "请求次数:-"
510557
self.info_usage_span.title = "近30日:-"
558+
self._update_token_status()
511559
if not self._cfg.get("hidden"):
512560
self.title = "错误"
513561

@@ -608,5 +656,38 @@ def _extract_user_id_from_jwt(token: str) -> Optional[str]:
608656
return None
609657

610658

659+
def _extract_exp_from_jwt(token: str) -> Optional[int]:
660+
try:
661+
parts = token.split(".")
662+
if len(parts) != 3:
663+
return None
664+
payload_b64 = parts[1]
665+
pad = '=' * ((4 - len(payload_b64) % 4) % 4)
666+
payload_json = base64.urlsafe_b64decode((payload_b64 + pad).encode("utf-8")).decode("utf-8")
667+
payload = json.loads(payload_json)
668+
exp = payload.get("exp")
669+
return int(exp) if exp is not None else None
670+
except Exception:
671+
return None
672+
673+
674+
def _fmt_remaining(sec: int) -> str:
675+
try:
676+
if sec <= 0:
677+
return "已过期"
678+
days = sec // 86400
679+
sec %= 86400
680+
hours = sec // 3600
681+
sec %= 3600
682+
minutes = sec // 60
683+
if days > 0:
684+
return f"剩余{days}{hours}小时"
685+
if hours > 0:
686+
return f"剩余{hours}小时{minutes}分钟"
687+
return f"剩余{minutes}分钟"
688+
except Exception:
689+
return "-"
690+
691+
611692
if __name__ == "__main__":
612693
PackycodeStatusApp().run()

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
app=APP,
5151
name=APP_NAME,
5252
author="packy",
53-
version="0.2.2",
53+
version="0.3.0",
5454
options={"py2app": OPTIONS},
5555
setup_requires=["py2app"],
5656
install_requires=["rumps", "requests"],

0 commit comments

Comments
 (0)