koishi-plugin-bilibili-notify 4.1.2 → 4.2.0

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/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 7d01398: 修复账号失效时控制台仍显示「已登录」、整天无推送的 bug,并重构登录态管线:
8
+
9
+ - BilibiliAPI 在响应体识别到 code -101 时通过新的 `onAuthLost` 回调通知上层
10
+ (60 秒防抖),cookie 刷新返回 -101 时也走同一路径,不再静默重置 HTTP
11
+ 客户端。
12
+ - 新增 `LoginStatusController` 集中管理登录态:所有 14 处 emit 收敛到
13
+ reporter;启动期 `getMyselfInfo` 返回 -101 不再误报 LOGGED_IN;之前静默
14
+ swallow 的异常路径也会上报。控制器只在 `(status, msg, data)` 实际变化
15
+ 时 emit,避免心跳带来的 UI 抖动。
16
+ - 新增配置项 `loginHealthCheckMinutes`(默认 30 分钟,范围 5–180),在已
17
+ 登录态下定期 probe,运行中失效会立即翻转 UI、广播内部事件
18
+ `bilibili-notify/auth-lost`;恢复后广播 `bilibili-notify/auth-restored`,
19
+ 让 dynamic / live 自动重启检测,无需手动重启插件。
20
+ - live 删除手写的 3 次 retry(API 层已 retry 3 次),失败时改为 emit
21
+ `plugin-error` 而非静默 return。
22
+ - 新增调试命令 `bili status auth` 查看当前登录状态。
23
+ - 控制台 UI 删除一闪而过的「登录成功」中转视图(与「已登录」重复)及无
24
+ listener 的「重启插件」按钮。
25
+ - `BiliLoginStatus` 枚举删除 `LOGGING_IN`(从未 emit)与 `LOGIN_SUCCESS`
26
+ (已被 `LOGGED_IN` 取代),故 api 包按 minor 级别 bump。
27
+ - 工具函数 `withLock` 提升到 `@bilibili-notify/internal` 供后续复用。
28
+ - 修复 `auth-restored` 在"运行中失效 → 扫码恢复"路径下不会触发的回归:
29
+ 之前用"上一帧 status === NOT_LOGIN"作判据,但失效后用户扫码会经过
30
+ LOGIN_QR / LOGGING_QR 中间态,导致 dynamic / live 永远收不到恢复事件
31
+ 无法重启监测;改用 sticky 的 `needsRestore` 标志解决。
32
+ - 修复登录刚成功瞬间 controller 把 LOGIN_QR 留下的 base64 字符串作为
33
+ `data` fallback 传给前端,导致前端访问 `data.card.face` 抛错的小问题;
34
+ 现在仅当 `snapshot.data` 形态像 card 时才沿用,前端也加了 `data?.card`
35
+ 的安全访问。
36
+ - 整理 `UserCardInfoData` 类型:拆出 `UserCard` / `UserCardSpace` /
37
+ `UserCardInfo` 子类型并补齐控制台 UI 实际使用的 `attention` /
38
+ `vip.vipStatus` / `vip.label.img_label_uri_hans_static` / `space.l_img`
39
+ 字段,删除前端 settings.vue 内联的 80+ 行 workaround 类型定义。
40
+ - 收敛 `auth-lost` 事件来源:由 api response interceptor 的
41
+ `onAuthLost` 回调单点广播,dynamic 在 -101 分支不再重复 emit;同时
42
+ 删除 dynamic 自己的"账号未登录"私信,避免与 server-manager 节流私
43
+ 信内容重复。
44
+
45
+ ### Patch Changes
46
+
47
+ - Updated dependencies [7d01398]
48
+ - @bilibili-notify/api@0.1.0
49
+ - @bilibili-notify/internal@0.0.3
50
+ - @bilibili-notify/subscription@1.0.3
51
+
3
52
  ## 4.1.2
4
53
 
5
54
  ### Patch Changes
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{defineComponent as T,inject as D,ref as c,watch as E,resolveComponent as N,openBlock as n,createElementBlock as r,Fragment as V,createElementVNode as e,toDisplayString as a,createCommentVNode as u,createBlock as C,withCtx as o,createVNode as v,createTextVNode as m,withDirectives as M,vShow as j}from"vue";import{store as S,send as g}from"@koishijs/client";const A={key:0,class:"loading-wrapper"},K={class:"comment"},L={key:0,class:"comment"},O=["src"],z={key:1,class:"comment"},G={class:"comment"},H={class:"comment"},J={class:"comment"},P={style:{display:"flex",gap:"0.5rem"}},Q={key:0,class:"loading-wrapper"},R={key:1,class:"logged-in fade-in"},U={class:"user-bg-wrapper"},W=["src"],X={class:"user-info"},Y=["src"],Z={class:"name-sign"},h={class:"user-desc"},t1={class:"user-name"},e1=["src"],l1={class:"user-sign"},s1={class:"user-status"},i1=T({__name:"settings",setup(p){const x=D("manager.settings.local"),f=c(""),b=c(""),k=c(""),w=c(""),i=c({}),B=c(false),l=c(""),I=c(false);E(()=>{var s,t;return[(s=S["bilibili-notify"])==null?void 0:s.status,(t=S["bilibili-notify"])==null?void 0:t.msg]},async()=>{if(x.value.name!=="koishi-plugin-bilibili-notify")return;const s=S["bilibili-notify"];if(s)switch(i.value=s,s.status){case 1:l.value="loading";return;case 0:l.value="not_login";return;case 5:{l.value="logged_in";const t=s.data,d=setTimeout(()=>{I.value=true},6e4);try{f.value=await g("bilibili-notify/request-cors",t.card.face),b.value=await g("bilibili-notify/request-cors",t.space.l_img),k.value=await g("bilibili-notify/request-cors",t.card.vip.label.img_label_uri_hans_static),B.value=true}finally{clearTimeout(d)}return}case 2:w.value=i.value.data,l.value="logging_qr";return;case 3:l.value="logging_qr";return;case 4:l.value="logging_in";return;case 7:l.value="login_failed";return;case 6:l.value="login_success";return}},{immediate:true});const _=()=>{g("bilibili-notify/start-login")},$=()=>{g("bilibili-notify/restart-plugin")},F=()=>{g("bilibili-notify/reset-key")},q=s=>s>=1e8?`${(s/1e8).toFixed(1).replace(/\.0$/,"")}亿`:s>=1e4?`${(s/1e4).toFixed(1).replace(/\.0$/,"")}万`:s.toString();return(s,t)=>{const d=N("k-button"),y=N("k-comment");return n(),r(V,null,[l.value==="loading"?(n(),r("div",A,[t[0]||(t[0]=e("div",{class:"spinner"},null,-1)),e("p",null,a(i.value.msg),1)])):u("v-if",true),l.value==="not_login"?(n(),C(y,{key:1,type:"error"},{default:o(()=>[e("div",K,[e("p",null,a(i.value.msg),1),v(d,{onClick:_},{default:o(()=>[...t[1]||(t[1]=[m("登录",-1)])]),_:1})])]),_:1})):u("v-if",true),l.value==="logging_qr"?(n(),C(y,{key:2,type:"warning"},{default:o(()=>[w.value?(n(),r("div",L,[t[2]||(t[2]=e("p",null,"请使用Bilibili App扫码登录",-1)),e("img",{class:"qrcode",src:w.value,alt:"qrcode"},null,8,O),e("p",null,a(i.value.msg),1)])):u("v-if",true),w.value?u("v-if",true):(n(),r("div",z,[t[4]||(t[4]=e("p",null,"二维码显示失败,请重新登录",-1)),v(d,{onClick:_},{default:o(()=>[...t[3]||(t[3]=[m("重新登录",-1)])]),_:1})]))]),_:1})):u("v-if",true),l.value==="login_failed"?(n(),C(y,{key:3,type:"error"},{default:o(()=>[e("div",G,[e("p",null,a(i.value.msg),1),v(d,{onClick:_},{default:o(()=>[...t[5]||(t[5]=[m("重新登录",-1)])]),_:1})])]),_:1})):u("v-if",true),l.value==="login_success"?(n(),C(y,{key:4,type:"success"},{default:o(()=>[e("div",H,[e("p",null,a(i.value.msg),1),v(d,{onClick:$},{default:o(()=>[...t[6]||(t[6]=[m("重启插件",-1)])]),_:1})])]),_:1})):u("v-if",true),l.value==="logged_in"?(n(),r(V,{key:5},[v(y,{type:"warning",style:{"margin-bottom":"1rem"}},{default:o(()=>[e("div",J,[t[9]||(t[9]=e("p",null,"重新登录:重新触发扫码流程,不清除已有密钥。",-1)),t[10]||(t[10]=e("p",null,"重置密钥:清除已保存的 Cookie 和密钥,需要重新扫码登录。",-1)),e("div",P,[v(d,{onClick:_},{default:o(()=>[...t[7]||(t[7]=[m("重新登录",-1)])]),_:1}),v(d,{onClick:F},{default:o(()=>[...t[8]||(t[8]=[m("重置密钥",-1)])]),_:1})])])]),_:1}),B.value?(n(),r("div",R,[e("div",U,[e("img",{class:"user-bg",src:b.value,alt:"user-bg"},null,8,W)]),e("div",X,[e("img",{class:"avatar",src:f.value,alt:"avatar"},null,8,Y),e("div",Z,[e("div",h,[e("span",t1,a(i.value.data.card.name),1),i.value.data.card.vip.vipStatus===1?(n(),r("img",{key:0,class:"user-vip",src:k.value,alt:"vip"},null,8,e1)):u("v-if",true)]),e("span",l1,a(i.value.data.card.sign),1)])]),e("div",s1,[e("div",null,[t[14]||(t[14]=e("span",null,"关注数",-1)),e("span",null,a(q(i.value.data.card.attention)),1)]),e("div",null,[t[15]||(t[15]=e("span",null,"粉丝数",-1)),e("span",null,a(q(i.value.data.card.fans)),1)]),e("div",null,[t[16]||(t[16]=e("span",null,"获赞数",-1)),e("span",null,a(q(i.value.data.like_num)),1)])]),(n(),r("svg",{onClick:_,class:"logo",t:"1645466458357",viewBox:"0 0 2299 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"2663",width:"180",style:{fill:"var(--bew-theme-color)"}},[...t[17]||(t[17]=[e("path",{d:"M1775.840814 322.588002c6.0164 1.002733 53.144869-9.525967 55.150336-6.016401 3.0082 4.5123 24.065601 155.92504 18.550567 156.927774s-44.621635 10.027334-44.621635 10.027334c-3.0082-20.556034-28.577901-147.903173-29.079268-160.938707m75.205003-14.539634l20.556034 162.944174c10.5287-0.501367 53.144869-3.509567 57.155803-4.010934-6.0164-61.668103-16.545101-158.933241-16.545101-158.93324-20.054668-4.010934-41.112069-4.010934-61.166736 0m-40.610702 226.116376s92.752838-23.564234 126.344406-12.0328c17.046467 61.668103 48.131202 407.611118 51.139402 421.649386-21.057401 2.506833-90.246004 8.523234-95.761037 10.027333-4.5123-26.071068-81.72277-403.098818-81.722771-419.643919m343.436183-207.565809c5.515034 1.5041 54.648969-5.013667 55.150335-1.5041 1.002733 12.032801 6.0164 157.42914 0.501367 157.930507s-44.621635 4.010934-44.621635 4.010934c-1.002733-20.054668-12.032801-146.90044-11.030067-160.437341m75.70637-4.010933l4.010933 160.938707c10.5287 0 52.643502 2.506833 57.155803 2.005467-1.002733-61.668103 0-158.933241 0-158.933241-20.054668-3.509567-40.610702-5.013667-61.166736-4.010933m-64.676303 216.089043s94.758304-12.534167 126.845772 2.506833c7.019134 72.196803 6.0164 408.613852 7.019134 422.652119-21.558768 0-90.246004 1.002733-95.761038 2.005467-1.002733-26.071068-39.607968-410.619319-38.103868-427.164419m-220.099977-413.627519c54.648969 278.759879 96.262404 755.058234 97.766504 785.641602 0 0 43.117535 1.002733 91.750105 4.010934C2105.740095 614.383415 2070.644427 134.575493 2071.145794 119.033126c-12.032801-13.536901-126.344406 6.0164-126.344406 6.0164m-120.328005 659.297196c-10.5287-78.213204-290.291313-166.955108-447.720454-138.377206 0 0-19.553301-172.470141-27.073801-339.425248-6.517767-143.390873-1.002733-282.770813 0.501366-305.833681-10.5287-7.5205-123.837572 46.627102-185.004308 69.188603 0 0 73.199537 309.844614 126.344406 952.59671 0 0 84.730971 9.0246 230.12731-19.051934s317.365114-115.815705 302.825481-219.097244m-341.932083 140.88404l-24.566967-176.982441c6.0164-3.0082 156.927774 53.144869 172.971507 63.172203-2.506833 11.030067-148.40454 113.810238-148.40454 113.810238M610.664628 322.588002c6.0164 1.002733 53.144869-9.525967 55.150335-6.016401 3.0082 4.5123 24.065601 155.92504 18.550568 156.927774s-44.621635 10.027334-44.621635 10.027334c-3.0082-20.556034-28.577901-147.903173-29.079268-160.938707m75.205003-14.539634l20.556034 162.944174c10.5287-0.501367 53.144869-3.509567 57.155803-4.010934-6.517767-61.668103-16.545101-158.933241-16.545101-158.93324-20.054668-4.010934-41.112069-4.010934-61.166736 0m-40.610702 226.116376s92.752838-23.564234 126.344406-12.0328c17.046467 61.668103 48.131202 407.611118 51.139402 421.649386-21.057401 2.506833-90.246004 8.523234-95.761037 10.027333-4.5123-26.071068-81.72277-403.098818-81.722771-419.643919m343.436182-207.565809c5.515034 1.5041 54.648969-5.013667 55.150336-1.5041 1.002733 12.032801 6.0164 157.42914 0.501367 157.930507s-44.621635 4.010934-44.621635 4.010934c-1.002733-20.054668-11.531434-146.90044-11.030068-160.437341m75.706371-4.010933l4.010933 160.938707c10.5287 0 52.643502 2.506833 57.155803 2.005467-1.002733-61.668103 0-158.933241 0-158.933241-20.054668-3.509567-40.610702-4.5123-61.166736-4.010933m-64.676303 216.089043s94.758304-12.534167 126.845772 2.506833c7.019134 72.196803 6.0164 408.613852 7.019134 422.652119-21.558768 0-90.246004 1.002733-95.761038 2.005467-0.501367-26.071068-39.607968-410.619319-38.103868-427.164419m-220.099977-413.627519c54.648969 278.759879 96.262404 755.058234 97.766504 785.641602 0 0 43.117535 1.002733 91.750105 4.010934-28.577901-300.318647-63.67357-780.126569-63.172203-796.170303-12.032801-13.035534-126.344406 6.517767-126.344406 6.517767m-120.328005 659.297196c-10.5287-78.213204-290.291313-166.955108-447.720454-138.377206 0 0-19.553301-172.470141-27.073801-339.425248-6.517767-143.390873-1.002733-282.770813 0.501366-305.833681C174.475608-6.308547 61.166736 47.337689 0 69.89919c0 0 73.199537 309.844614 126.344406 952.59671 0 0 84.730971 9.0246 230.12731-19.051934s317.365114-115.815705 302.825481-219.097244m-341.932083 140.88404l-24.566967-176.982441c6.0164-3.0082 156.927774 53.144869 172.971507 63.172203-2.506833 11.030067-148.40454 113.810238-148.40454 113.810238","p-id":"2664"},null,-1)])]))])):(n(),r("div",Q,[t[12]||(t[12]=e("div",{class:"spinner"},null,-1)),t[13]||(t[13]=e("p",null,"正在加载登录账号信息中...",-1)),M(e("div",null,[...t[11]||(t[11]=[e("span",null,"加载太久?可能是网络错误,可以尝试切换到其他插件页再切回来;加载不出来也不影响使用哦~",-1)])],512),[[j,I.value]])]))],64)):u("v-if",true)],64)}}}),n1=(p,x)=>{const f=p.__vccOpts||p;for(const[b,k]of x)f[b]=k;return f},o1=n1(i1,[["__scopeId","data-v-af720fc8"]]),u1=p=>{p.slot({type:"plugin-details",component:o1,order:0})};export{u1 as default};
1
+ import{defineComponent as F,inject as T,ref as u,watch as D,resolveComponent as N,openBlock as i,createElementBlock as o,Fragment as V,createElementVNode as e,toDisplayString as a,createCommentVNode as d,createBlock as q,withCtx as r,createVNode as v,createTextVNode as f,withDirectives as E,vShow as M}from"vue";import{store as S,send as _}from"@koishijs/client";const j={key:0,class:"loading-wrapper"},A={class:"comment"},K={key:0,class:"comment"},L=["src"],O={key:1,class:"comment"},z={class:"comment"},G={class:"comment"},H={style:{display:"flex",gap:"0.5rem"}},J={key:0,class:"loading-wrapper"},P={key:1,class:"logged-in fade-in"},Q={class:"user-bg-wrapper"},R=["src"],U={class:"user-info"},W=["src"],X={class:"name-sign"},Y={class:"user-desc"},Z={class:"user-name"},h=["src"],t1={class:"user-sign"},e1={class:"user-status"},l1=F({__name:"settings",setup(m){const C=T("manager.settings.local"),p=u(""),y=u(""),b=u(""),k=u(""),s=u({}),B=u(false),n=u(""),I=u(false);D(()=>{var l,t;return[(l=S["bilibili-notify"])==null?void 0:l.status,(t=S["bilibili-notify"])==null?void 0:t.msg]},async()=>{if(C.value.name!=="koishi-plugin-bilibili-notify")return;const l=S["bilibili-notify"];if(l)switch(s.value=l,l.status){case 1:n.value="loading";return;case 0:n.value="not_login";return;case 5:{n.value="logged_in";const t=l.data;if(!(t!=null&&t.card))return;const c=setTimeout(()=>{I.value=true},6e4);try{p.value=await _("bilibili-notify/request-cors",t.card.face),y.value=await _("bilibili-notify/request-cors",t.space.l_img),b.value=await _("bilibili-notify/request-cors",t.card.vip.label.img_label_uri_hans_static),B.value=true}finally{clearTimeout(c)}return}case 2:k.value=s.value.data,n.value="logging_qr";return;case 3:n.value="logging_qr";return;case 7:n.value="login_failed";return}},{immediate:true});const g=()=>{_("bilibili-notify/start-login")},$=()=>{_("bilibili-notify/reset-key")},x=l=>l>=1e8?`${(l/1e8).toFixed(1).replace(/\.0$/,"")}亿`:l>=1e4?`${(l/1e4).toFixed(1).replace(/\.0$/,"")}万`:l.toString();return(l,t)=>{const c=N("k-button"),w=N("k-comment");return i(),o(V,null,[n.value==="loading"?(i(),o("div",j,[t[0]||(t[0]=e("div",{class:"spinner"},null,-1)),e("p",null,a(s.value.msg),1)])):d("v-if",true),n.value==="not_login"?(i(),q(w,{key:1,type:"error"},{default:r(()=>[e("div",A,[e("p",null,a(s.value.msg),1),v(c,{onClick:g},{default:r(()=>[...t[1]||(t[1]=[f("登录",-1)])]),_:1})])]),_:1})):d("v-if",true),n.value==="logging_qr"?(i(),q(w,{key:2,type:"warning"},{default:r(()=>[k.value?(i(),o("div",K,[t[2]||(t[2]=e("p",null,"请使用Bilibili App扫码登录",-1)),e("img",{class:"qrcode",src:k.value,alt:"qrcode"},null,8,L),e("p",null,a(s.value.msg),1)])):d("v-if",true),k.value?d("v-if",true):(i(),o("div",O,[t[4]||(t[4]=e("p",null,"二维码显示失败,请重新登录",-1)),v(c,{onClick:g},{default:r(()=>[...t[3]||(t[3]=[f("重新登录",-1)])]),_:1})]))]),_:1})):d("v-if",true),n.value==="login_failed"?(i(),q(w,{key:3,type:"error"},{default:r(()=>[e("div",z,[e("p",null,a(s.value.msg),1),v(c,{onClick:g},{default:r(()=>[...t[5]||(t[5]=[f("重新登录",-1)])]),_:1})])]),_:1})):d("v-if",true),n.value==="logged_in"?(i(),o(V,{key:4},[v(w,{type:"warning",style:{"margin-bottom":"1rem"}},{default:r(()=>[e("div",G,[t[8]||(t[8]=e("p",null,"重新登录:重新触发扫码流程,不清除已有密钥。",-1)),t[9]||(t[9]=e("p",null,"重置密钥:清除已保存的 Cookie 和密钥,需要重新扫码登录。",-1)),e("div",H,[v(c,{onClick:g},{default:r(()=>[...t[6]||(t[6]=[f("重新登录",-1)])]),_:1}),v(c,{onClick:$},{default:r(()=>[...t[7]||(t[7]=[f("重置密钥",-1)])]),_:1})])])]),_:1}),B.value?(i(),o("div",P,[e("div",Q,[e("img",{class:"user-bg",src:y.value,alt:"user-bg"},null,8,R)]),e("div",U,[e("img",{class:"avatar",src:p.value,alt:"avatar"},null,8,W),e("div",X,[e("div",Y,[e("span",Z,a(s.value.data.card.name),1),s.value.data.card.vip.vipStatus===1?(i(),o("img",{key:0,class:"user-vip",src:b.value,alt:"vip"},null,8,h)):d("v-if",true)]),e("span",t1,a(s.value.data.card.sign),1)])]),e("div",e1,[e("div",null,[t[13]||(t[13]=e("span",null,"关注数",-1)),e("span",null,a(x(s.value.data.card.attention)),1)]),e("div",null,[t[14]||(t[14]=e("span",null,"粉丝数",-1)),e("span",null,a(x(s.value.data.card.fans)),1)]),e("div",null,[t[15]||(t[15]=e("span",null,"获赞数",-1)),e("span",null,a(x(s.value.data.like_num)),1)])]),(i(),o("svg",{onClick:g,class:"logo",t:"1645466458357",viewBox:"0 0 2299 1024",version:"1.1",xmlns:"http://www.w3.org/2000/svg","p-id":"2663",width:"180",style:{fill:"var(--bew-theme-color)"}},[...t[16]||(t[16]=[e("path",{d:"M1775.840814 322.588002c6.0164 1.002733 53.144869-9.525967 55.150336-6.016401 3.0082 4.5123 24.065601 155.92504 18.550567 156.927774s-44.621635 10.027334-44.621635 10.027334c-3.0082-20.556034-28.577901-147.903173-29.079268-160.938707m75.205003-14.539634l20.556034 162.944174c10.5287-0.501367 53.144869-3.509567 57.155803-4.010934-6.0164-61.668103-16.545101-158.933241-16.545101-158.93324-20.054668-4.010934-41.112069-4.010934-61.166736 0m-40.610702 226.116376s92.752838-23.564234 126.344406-12.0328c17.046467 61.668103 48.131202 407.611118 51.139402 421.649386-21.057401 2.506833-90.246004 8.523234-95.761037 10.027333-4.5123-26.071068-81.72277-403.098818-81.722771-419.643919m343.436183-207.565809c5.515034 1.5041 54.648969-5.013667 55.150335-1.5041 1.002733 12.032801 6.0164 157.42914 0.501367 157.930507s-44.621635 4.010934-44.621635 4.010934c-1.002733-20.054668-12.032801-146.90044-11.030067-160.437341m75.70637-4.010933l4.010933 160.938707c10.5287 0 52.643502 2.506833 57.155803 2.005467-1.002733-61.668103 0-158.933241 0-158.933241-20.054668-3.509567-40.610702-5.013667-61.166736-4.010933m-64.676303 216.089043s94.758304-12.534167 126.845772 2.506833c7.019134 72.196803 6.0164 408.613852 7.019134 422.652119-21.558768 0-90.246004 1.002733-95.761038 2.005467-1.002733-26.071068-39.607968-410.619319-38.103868-427.164419m-220.099977-413.627519c54.648969 278.759879 96.262404 755.058234 97.766504 785.641602 0 0 43.117535 1.002733 91.750105 4.010934C2105.740095 614.383415 2070.644427 134.575493 2071.145794 119.033126c-12.032801-13.536901-126.344406 6.0164-126.344406 6.0164m-120.328005 659.297196c-10.5287-78.213204-290.291313-166.955108-447.720454-138.377206 0 0-19.553301-172.470141-27.073801-339.425248-6.517767-143.390873-1.002733-282.770813 0.501366-305.833681-10.5287-7.5205-123.837572 46.627102-185.004308 69.188603 0 0 73.199537 309.844614 126.344406 952.59671 0 0 84.730971 9.0246 230.12731-19.051934s317.365114-115.815705 302.825481-219.097244m-341.932083 140.88404l-24.566967-176.982441c6.0164-3.0082 156.927774 53.144869 172.971507 63.172203-2.506833 11.030067-148.40454 113.810238-148.40454 113.810238M610.664628 322.588002c6.0164 1.002733 53.144869-9.525967 55.150335-6.016401 3.0082 4.5123 24.065601 155.92504 18.550568 156.927774s-44.621635 10.027334-44.621635 10.027334c-3.0082-20.556034-28.577901-147.903173-29.079268-160.938707m75.205003-14.539634l20.556034 162.944174c10.5287-0.501367 53.144869-3.509567 57.155803-4.010934-6.517767-61.668103-16.545101-158.933241-16.545101-158.93324-20.054668-4.010934-41.112069-4.010934-61.166736 0m-40.610702 226.116376s92.752838-23.564234 126.344406-12.0328c17.046467 61.668103 48.131202 407.611118 51.139402 421.649386-21.057401 2.506833-90.246004 8.523234-95.761037 10.027333-4.5123-26.071068-81.72277-403.098818-81.722771-419.643919m343.436182-207.565809c5.515034 1.5041 54.648969-5.013667 55.150336-1.5041 1.002733 12.032801 6.0164 157.42914 0.501367 157.930507s-44.621635 4.010934-44.621635 4.010934c-1.002733-20.054668-11.531434-146.90044-11.030068-160.437341m75.706371-4.010933l4.010933 160.938707c10.5287 0 52.643502 2.506833 57.155803 2.005467-1.002733-61.668103 0-158.933241 0-158.933241-20.054668-3.509567-40.610702-4.5123-61.166736-4.010933m-64.676303 216.089043s94.758304-12.534167 126.845772 2.506833c7.019134 72.196803 6.0164 408.613852 7.019134 422.652119-21.558768 0-90.246004 1.002733-95.761038 2.005467-0.501367-26.071068-39.607968-410.619319-38.103868-427.164419m-220.099977-413.627519c54.648969 278.759879 96.262404 755.058234 97.766504 785.641602 0 0 43.117535 1.002733 91.750105 4.010934-28.577901-300.318647-63.67357-780.126569-63.172203-796.170303-12.032801-13.035534-126.344406 6.517767-126.344406 6.517767m-120.328005 659.297196c-10.5287-78.213204-290.291313-166.955108-447.720454-138.377206 0 0-19.553301-172.470141-27.073801-339.425248-6.517767-143.390873-1.002733-282.770813 0.501366-305.833681C174.475608-6.308547 61.166736 47.337689 0 69.89919c0 0 73.199537 309.844614 126.344406 952.59671 0 0 84.730971 9.0246 230.12731-19.051934s317.365114-115.815705 302.825481-219.097244m-341.932083 140.88404l-24.566967-176.982441c6.0164-3.0082 156.927774 53.144869 172.971507 63.172203-2.506833 11.030067-148.40454 113.810238-148.40454 113.810238","p-id":"2664"},null,-1)])]))])):(i(),o("div",J,[t[11]||(t[11]=e("div",{class:"spinner"},null,-1)),t[12]||(t[12]=e("p",null,"正在加载登录账号信息中...",-1)),E(e("div",null,[...t[10]||(t[10]=[e("span",null,"加载太久?可能是网络错误,可以尝试切换到其他插件页再切回来;加载不出来也不影响使用哦~",-1)])],512),[[M,I.value]])]))],64)):d("v-if",true)],64)}}}),s1=(m,C)=>{const p=m.__vccOpts||m;for(const[y,b]of C)p[y]=b;return p},i1=s1(l1,[["__scopeId","data-v-d2414804"]]),a1=m=>{m.slot({type:"plugin-details",component:i1,order:0})};export{a1 as default};
package/dist/style.css CHANGED
@@ -1 +1 @@
1
- :root{--bew-theme-color: #FB7299}.comment[data-v-af720fc8]{margin-bottom:1rem}.qrcode[data-v-af720fc8]{width:10rem;height:10rem}.loading-wrapper[data-v-af720fc8]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:200px;color:#888}.loading-wrapper .spinner[data-v-af720fc8]{width:40px;height:40px;border:4px solid #ccc;border-top-color:var(--bew-theme-color);border-radius:50%;animation:spin-af720fc8 1s linear infinite;margin-bottom:10px}.fade-in[data-v-af720fc8]{opacity:0;transform:translateY(10px);animation:fadeIn-af720fc8 .5s forwards}.logged-in[data-v-af720fc8]{position:relative;display:flex;flex-direction:column;justify-content:space-between;width:30rem;height:8rem;border-radius:1rem;padding:1rem;margin-top:1rem;margin-bottom:1rem;overflow:hidden;box-shadow:0 4px 8px #00000026;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.logged-in .user-bg-wrapper[data-v-af720fc8]{position:absolute;left:0;top:0;width:100%;height:10rem;overflow:hidden;z-index:-1}.logged-in .user-bg[data-v-af720fc8]{width:100%;height:100%;object-fit:cover}.logged-in .user-bg-wrapper[data-v-af720fc8]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to bottom,#0003,#0000);pointer-events:none}.logged-in .user-bg[data-v-af720fc8]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to top,#00000080,#0000);pointer-events:none;z-index:1}.logged-in .user-info[data-v-af720fc8]{display:flex;gap:1rem}.logged-in .user-info .avatar[data-v-af720fc8]{width:5rem;height:5rem;border-radius:50%;border:2px solid white}.logged-in .user-info .name-sign[data-v-af720fc8]{display:flex;flex-direction:column;margin-top:.3rem;gap:.2rem;color:#fff;text-shadow:3px 3px 5px rgba(0,0,0,.7)}.logged-in .user-info .name-sign .user-desc[data-v-af720fc8]{display:flex;align-items:center;gap:.5rem}.logged-in .user-info .name-sign .user-desc .user-name[data-v-af720fc8]{font-weight:700;font-size:1.7rem}.logged-in .user-info .name-sign .user-desc .user-vip[data-v-af720fc8]{width:90px}.logged-in .user-info .name-sign .user-sign[data-v-af720fc8]{font-weight:700;font-size:.7rem}.logged-in .user-status[data-v-af720fc8]{display:flex;gap:1rem;margin-top:1rem;color:#fff;font-size:12px;font-weight:700;text-shadow:3px 3px 5px rgba(0,0,0,.7)}.logged-in .user-status div[data-v-af720fc8]{display:flex;flex-direction:column;align-items:center;gap:2px}.logged-in .logo[data-v-af720fc8]{position:absolute;right:1rem;bottom:.7rem;width:5rem;box-shadow:0 4px 8px #00000026}@keyframes spin-af720fc8{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes fadeIn-af720fc8{to{opacity:1;transform:translateY(0)}}
1
+ :root{--bew-theme-color: #FB7299}.comment[data-v-d2414804]{margin-bottom:1rem}.qrcode[data-v-d2414804]{width:10rem;height:10rem}.loading-wrapper[data-v-d2414804]{display:flex;flex-direction:column;align-items:center;justify-content:center;height:200px;color:#888}.loading-wrapper .spinner[data-v-d2414804]{width:40px;height:40px;border:4px solid #ccc;border-top-color:var(--bew-theme-color);border-radius:50%;animation:spin-d2414804 1s linear infinite;margin-bottom:10px}.fade-in[data-v-d2414804]{opacity:0;transform:translateY(10px);animation:fadeIn-d2414804 .5s forwards}.logged-in[data-v-d2414804]{position:relative;display:flex;flex-direction:column;justify-content:space-between;width:30rem;height:8rem;border-radius:1rem;padding:1rem;margin-top:1rem;margin-bottom:1rem;overflow:hidden;box-shadow:0 4px 8px #00000026;-webkit-backdrop-filter:blur(10px);backdrop-filter:blur(10px)}.logged-in .user-bg-wrapper[data-v-d2414804]{position:absolute;left:0;top:0;width:100%;height:10rem;overflow:hidden;z-index:-1}.logged-in .user-bg[data-v-d2414804]{width:100%;height:100%;object-fit:cover}.logged-in .user-bg-wrapper[data-v-d2414804]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to bottom,#0003,#0000);pointer-events:none}.logged-in .user-bg[data-v-d2414804]:after{content:"";position:absolute;top:0;right:0;bottom:0;left:0;background:linear-gradient(to top,#00000080,#0000);pointer-events:none;z-index:1}.logged-in .user-info[data-v-d2414804]{display:flex;gap:1rem}.logged-in .user-info .avatar[data-v-d2414804]{width:5rem;height:5rem;border-radius:50%;border:2px solid white}.logged-in .user-info .name-sign[data-v-d2414804]{display:flex;flex-direction:column;margin-top:.3rem;gap:.2rem;color:#fff;text-shadow:3px 3px 5px rgba(0,0,0,.7)}.logged-in .user-info .name-sign .user-desc[data-v-d2414804]{display:flex;align-items:center;gap:.5rem}.logged-in .user-info .name-sign .user-desc .user-name[data-v-d2414804]{font-weight:700;font-size:1.7rem}.logged-in .user-info .name-sign .user-desc .user-vip[data-v-d2414804]{width:90px}.logged-in .user-info .name-sign .user-sign[data-v-d2414804]{font-weight:700;font-size:.7rem}.logged-in .user-status[data-v-d2414804]{display:flex;gap:1rem;margin-top:1rem;color:#fff;font-size:12px;font-weight:700;text-shadow:3px 3px 5px rgba(0,0,0,.7)}.logged-in .user-status div[data-v-d2414804]{display:flex;flex-direction:column;align-items:center;gap:2px}.logged-in .logo[data-v-d2414804]{position:absolute;right:1rem;bottom:.7rem;width:5rem;box-shadow:0 4px 8px #00000026}@keyframes spin-d2414804{0%{transform:rotate(0)}to{transform:rotate(360deg)}}@keyframes fadeIn-d2414804{to{opacity:1;transform:translateY(0)}}
package/lib/index.cjs CHANGED
@@ -54,6 +54,7 @@ const BilibiliNotifyConfigSchema = koishi.Schema.object({
54
54
  })).role("table").description("在这里填写主人的订阅信息~UP 昵称、UID、roomid、平台、群号都要填正确,不然女仆会迷路哒 (;>_<)如果多个群聊/频道,请用英文逗号分隔哦~女仆会努力送到每一个地方的!"),
55
55
  logLevel: koishi.Schema.number().min(1).max(3).step(1).default(1).description("这里可以设置日志等级喔~3 是最详细的调试信息,1 是只显示错误信息。主人可以根据需要选择合适的等级,让女仆更好地为您服务 (๑•̀ㅂ•́)و✧"),
56
56
  userAgent: koishi.Schema.string().description("这里可以设置请求头的 User-Agent 哦~如果请求出现了 -352 的奇怪错误,主人可以试着在这里换一个看看 (;>_<)"),
57
+ loginHealthCheckMinutes: koishi.Schema.number().min(5).max(180).step(1).default(30).description("登录状态周期检测的间隔(分钟)。女仆会按这个频率悄悄帮主人确认账号还在线哦~如果发现失效会立刻汇报呢 (๑•̀ㅂ•́)و✧"),
57
58
  master: koishi.Schema.intersect([koishi.Schema.object({ enable: koishi.Schema.boolean().default(false).description("要不要让笨笨女仆开启主人账号功能呢?(>﹏<)如果机器人遭遇了奇怪的小错误,女仆会立刻跑来向主人报告的!不、不过……如果没有私聊权限的话,女仆就联系不到主人了……请不要打开这个开关喔 (;´д`)ゞ") }).description("主人的特别区域……女仆会乖乖侍奉的!(>///<)"), koishi.Schema.union([koishi.Schema.object({
58
59
  enable: koishi.Schema.const(true).required(),
59
60
  platform: koishi.Schema.union([
@@ -407,6 +408,10 @@ function biliCommands() {
407
408
  //#region src/commands/status.ts
408
409
  function statusCommands() {
409
410
  const statusCom = this.ctx.command("status", "插件状态相关指令", { permissions: ["authority:5"] });
411
+ statusCom.subcommand(".auth", "查看登录状态").usage("查看登录状态").example("status auth").action(() => {
412
+ const snap = this.getAuthSnapshot();
413
+ return `登录状态:${_bilibili_notify_api.BiliLoginStatus[snap.status] ?? `unknown(${snap.status})`}\n信息:${snap.msg || "(无)"}`;
414
+ });
410
415
  statusCom.subcommand(".dyn", "查看动态监测运行状态").usage("查看动态监测运行状态").example("status dyn").action(() => {
411
416
  if (this.ctx.get("bilibili-notify-dynamic")) return "动态监测正在运行";
412
417
  return "动态插件未运行(请检查是否已安装并启用 koishi-plugin-bilibili-notify-dynamic)";
@@ -451,6 +456,136 @@ function sysCommands() {
451
456
  });
452
457
  }
453
458
  //#endregion
459
+ //#region src/login-status.ts
460
+ const MESSAGES = {
461
+ loading: "正在加载登录信息...",
462
+ notLogin: "账号未登录,请点击「扫码登录」",
463
+ keyReset: "密钥已重置,cookie 已清除,请重新扫码登录",
464
+ authLost: "账号登录已失效,请在控制台重新扫码登录",
465
+ loggedIn: "已登录",
466
+ loginJustSucceeded: "登录成功,正在加载订阅...",
467
+ fetchAccountFailed: "账号已登录,但获取个人信息失败,请检查",
468
+ waitScan: "尚未扫码,请扫码",
469
+ waitConfirm: "已扫码,但尚未确认,请确认",
470
+ qrFetchFailed: "获取二维码失败,请重试",
471
+ qrRenderFailed: "生成二维码失败",
472
+ qrExpired: "二维码已超时(3分钟),请重新登录",
473
+ qrInvalidated: "二维码已失效,请重新登录",
474
+ noCookieAfterLogin: "登录成功但未获取到 cookie,请重试",
475
+ genericLoginFail: "登录失败,请重试"
476
+ };
477
+ /** 类型守卫:snapshot.data 是否长得像 UserCardInfo。 */
478
+ function looksLikeCardData(data) {
479
+ return typeof data === "object" && data !== null && "card" in data;
480
+ }
481
+ /**
482
+ * 集中管理登录态:所有变更都经过这里,再以 `bilibili-notify/login-status-report`
483
+ * 推到前端。心跳定时器在登录态下定期 probe,发现失效会同时广播
484
+ * `bilibili-notify/auth-lost`,恢复时广播 `bilibili-notify/auth-restored`。
485
+ */
486
+ var LoginStatusController = class {
487
+ snapshot = {
488
+ status: _bilibili_notify_api.BiliLoginStatus.LOADING_LOGIN_INFO,
489
+ msg: MESSAGES.loading
490
+ };
491
+ healthTimer;
492
+ /**
493
+ * 标记"曾从已登录态掉线",下一次成功登录时翻转为已登录后才发 auth-restored。
494
+ * 之所以不再用"上一帧 status === NOT_LOGIN"判断:失效后用户走扫码流程,会经过
495
+ * LOGIN_QR / LOGGING_QR 等中间态,直接基于上一帧会漏掉这条恢复路径。
496
+ */
497
+ needsRestore = false;
498
+ constructor(ctx, options) {
499
+ this.ctx = ctx;
500
+ this.options = options;
501
+ }
502
+ current() {
503
+ return { ...this.snapshot };
504
+ }
505
+ attachHealthCheck() {
506
+ this.detachHealthCheck();
507
+ if (this.options.healthCheckMs <= 0) return;
508
+ this.healthTimer = this.ctx.setInterval(() => void this.runHealthCheck(), this.options.healthCheckMs);
509
+ }
510
+ detachHealthCheck() {
511
+ this.healthTimer?.();
512
+ this.healthTimer = void 0;
513
+ }
514
+ reportLoggedIn(card, reasonKey = "loggedIn") {
515
+ const wasLoggedIn = this.snapshot.status === _bilibili_notify_api.BiliLoginStatus.LOGGED_IN;
516
+ const fallback = looksLikeCardData(this.snapshot.data) ? this.snapshot.data : void 0;
517
+ this.transition({
518
+ status: _bilibili_notify_api.BiliLoginStatus.LOGGED_IN,
519
+ msg: MESSAGES[reasonKey],
520
+ data: card ?? fallback
521
+ });
522
+ if (!wasLoggedIn && this.needsRestore) {
523
+ this.needsRestore = false;
524
+ this.ctx.emit("bilibili-notify/auth-restored");
525
+ }
526
+ }
527
+ reportLoggedOut(reasonKey = "notLogin") {
528
+ const wasLoggedIn = this.snapshot.status === _bilibili_notify_api.BiliLoginStatus.LOGGED_IN;
529
+ this.transition({
530
+ status: _bilibili_notify_api.BiliLoginStatus.NOT_LOGIN,
531
+ msg: MESSAGES[reasonKey]
532
+ });
533
+ if (wasLoggedIn) {
534
+ this.needsRestore = true;
535
+ this.ctx.emit("bilibili-notify/auth-lost");
536
+ }
537
+ }
538
+ /** Dispatch on `getMyselfInfo` result code. */
539
+ reportLoginCheck(code, card) {
540
+ if (code === 0) this.reportLoggedIn(card);
541
+ else if (code === -101) this.reportLoggedOut("authLost");
542
+ else this.reportTransientFailure(`code=${code}`);
543
+ }
544
+ /** Keep current status, only refresh msg. Logs at warn level. */
545
+ reportTransientFailure(detail) {
546
+ this.options.logger.warn(`[auth] 瞬时失败:${detail}`);
547
+ if (this.snapshot.status !== _bilibili_notify_api.BiliLoginStatus.LOGGED_IN) return;
548
+ this.transition({
549
+ ...this.snapshot,
550
+ msg: MESSAGES.fetchAccountFailed
551
+ });
552
+ }
553
+ reportQrReady(base64) {
554
+ this.transition({
555
+ status: _bilibili_notify_api.BiliLoginStatus.LOGIN_QR,
556
+ msg: "",
557
+ data: base64
558
+ });
559
+ }
560
+ reportQrPending(reasonKey) {
561
+ this.transition({
562
+ status: _bilibili_notify_api.BiliLoginStatus.LOGGING_QR,
563
+ msg: MESSAGES[reasonKey]
564
+ });
565
+ }
566
+ reportQrFailure(reasonKey) {
567
+ this.transition({
568
+ status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
569
+ msg: MESSAGES[reasonKey]
570
+ });
571
+ }
572
+ /** Emit only when (status, msg, data) changes. */
573
+ transition(next) {
574
+ if (this.snapshot.status === next.status && this.snapshot.msg === next.msg && this.snapshot.data === next.data) return;
575
+ this.snapshot = next;
576
+ this.ctx.emit("bilibili-notify/login-status-report", next);
577
+ }
578
+ async runHealthCheck() {
579
+ if (this.snapshot.status === _bilibili_notify_api.BiliLoginStatus.LOGIN_QR || this.snapshot.status === _bilibili_notify_api.BiliLoginStatus.LOGGING_QR || this.snapshot.status === _bilibili_notify_api.BiliLoginStatus.NOT_LOGIN) return;
580
+ try {
581
+ const res = await this.options.probe();
582
+ this.reportLoginCheck(res.code);
583
+ } catch (e) {
584
+ this.options.logger.warn(`[auth] 心跳异常(保持当前状态):${e}`);
585
+ }
586
+ }
587
+ };
588
+ //#endregion
454
589
  //#region src/server-manager.ts
455
590
  const SERVICE_NAME = "bilibili-notify";
456
591
  const LIVE_MASTER_KEYS = [
@@ -502,6 +637,8 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
502
637
  running = false;
503
638
  storageMgr;
504
639
  currentSubs = null;
640
+ auth;
641
+ authLostNotifiedAt = 0;
505
642
  constructor(ctx, config) {
506
643
  super(ctx, SERVICE_NAME);
507
644
  this.selfCtx = ctx;
@@ -512,6 +649,10 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
512
649
  get subManager() {
513
650
  return this.subMgr?.subManager ?? /* @__PURE__ */ new Map();
514
651
  }
652
+ /** For commands: read the current login snapshot. */
653
+ getAuthSnapshot() {
654
+ return this.auth.current();
655
+ }
515
656
  subList() {
516
657
  const map = this.subManager;
517
658
  if (!map.size) return "没有订阅任何UP";
@@ -526,6 +667,14 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
526
667
  this.serverLogger.info("[start] 正在启动中...");
527
668
  this.storageMgr = new _bilibili_notify_storage.StorageManager(this.ctx.baseDir, this.ctx);
528
669
  await this.storageMgr.init();
670
+ this.auth = new LoginStatusController(this.selfCtx, {
671
+ healthCheckMs: this.config.loginHealthCheckMinutes * 6e4,
672
+ logger: this.serverLogger,
673
+ probe: () => {
674
+ if (!this.api) throw new Error("api not initialized");
675
+ return this.api.getMyselfInfo();
676
+ }
677
+ });
529
678
  this.ctx.on("bilibili-notify/cookies-refreshed", async (data) => {
530
679
  try {
531
680
  await this.storageMgr.cookieStore.save(data);
@@ -701,8 +850,13 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
701
850
  this.api = new _bilibili_notify_api.BilibiliAPI(this.selfCtx, {
702
851
  logLevel: this.config.logLevel,
703
852
  userAgent: this.config.userAgent
704
- }, (data) => {
705
- this.selfCtx.emit("bilibili-notify/cookies-refreshed", data);
853
+ }, {
854
+ onCookiesRefreshed: (data) => {
855
+ this.selfCtx.emit("bilibili-notify/cookies-refreshed", data);
856
+ },
857
+ onAuthLost: () => {
858
+ this.handleAuthLost();
859
+ }
706
860
  });
707
861
  this.push = new _bilibili_notify_push.BilibiliPush(this.selfCtx, {
708
862
  logLevel: this.config.logLevel,
@@ -721,10 +875,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
721
875
  this.serverLogger.debug(`[cookie] Cookie 加载完成,登录状态:${this.isLoggedIn() ? "已登录" : "未登录"}`);
722
876
  if (!this.isLoggedIn()) {
723
877
  this.serverLogger.info("[login] 账号未登录,请在控制台扫码登录");
724
- this.selfCtx.emit("bilibili-notify/login-status-report", {
725
- status: _bilibili_notify_api.BiliLoginStatus.NOT_LOGIN,
726
- msg: "账号未登录,请点击「扫码登录」"
727
- });
878
+ this.auth.reportLoggedOut("notLogin");
728
879
  return true;
729
880
  }
730
881
  await this.reportAccountInfo();
@@ -740,6 +891,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
740
891
  this.serverLogger.debug("[stop] 正在清理插件资源...");
741
892
  this.running = false;
742
893
  this.clearLoginTimer();
894
+ this.auth?.detachHealthCheck();
743
895
  if (this.subNotifier) {
744
896
  this.subNotifier.dispose();
745
897
  this.subNotifier = void 0;
@@ -802,23 +954,38 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
802
954
  }
803
955
  async reportAccountInfo() {
804
956
  if (!this.api) return;
957
+ let personalInfo;
805
958
  try {
806
- const personalInfo = await this.api.getMyselfInfo();
807
- if (personalInfo.code !== 0) {
808
- this.selfCtx.emit("bilibili-notify/login-status-report", {
809
- status: _bilibili_notify_api.BiliLoginStatus.LOGGED_IN,
810
- msg: "账号已登录,但获取个人信息失败,请检查"
811
- });
812
- return;
813
- }
814
- const myCardInfo = await this.api.getUserCardInfo(personalInfo.data.mid.toString(), true);
815
- this.selfCtx.emit("bilibili-notify/login-status-report", {
816
- status: _bilibili_notify_api.BiliLoginStatus.LOGGED_IN,
817
- msg: "已登录",
818
- data: myCardInfo.data
819
- });
959
+ personalInfo = await this.api.getMyselfInfo();
960
+ } catch (e) {
961
+ this.serverLogger.warn(`[account] 获取个人信息异常: ${e}`);
962
+ this.auth.reportTransientFailure(e);
963
+ this.auth.attachHealthCheck();
964
+ return;
965
+ }
966
+ if (personalInfo.code !== 0) {
967
+ this.auth.reportLoginCheck(personalInfo.code);
968
+ if (personalInfo.code !== -101) this.auth.attachHealthCheck();
969
+ return;
970
+ }
971
+ let card;
972
+ try {
973
+ card = (await this.api.getUserCardInfo(personalInfo.data.mid.toString(), true)).data;
820
974
  } catch (e) {
821
- this.serverLogger.warn(`[account] 获取账号信息失败: ${e}`);
975
+ this.serverLogger.warn(`[account] 获取用户卡片失败: ${e}`);
976
+ }
977
+ this.auth.reportLoggedIn(card);
978
+ this.auth.attachHealthCheck();
979
+ }
980
+ async handleAuthLost() {
981
+ this.auth.reportLoggedOut("authLost");
982
+ const now = Date.now();
983
+ if (now - this.authLostNotifiedAt < 6e4) return;
984
+ this.authLostNotifiedAt = now;
985
+ try {
986
+ await this.push?.sendPrivateMsg("账号登录已失效,请在控制台重新扫码登录");
987
+ } catch (e) {
988
+ this.serverLogger.warn(`[auth] 失效通知私信失败:${e}`);
822
989
  }
823
990
  }
824
991
  async loadInitialSubscriptions() {
@@ -863,10 +1030,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
863
1030
  this.serverLogger.info("[login] 触发重置密钥事件");
864
1031
  try {
865
1032
  await this.storageMgr.cookieStore.resetKey();
866
- this.selfCtx.emit("bilibili-notify/login-status-report", {
867
- status: _bilibili_notify_api.BiliLoginStatus.NOT_LOGIN,
868
- msg: "密钥已重置,cookie 已清除,请重新扫码登录"
869
- });
1033
+ this.auth.reportLoggedOut("keyReset");
870
1034
  } catch (e) {
871
1035
  this.serverLogger.error(`[login] 重置密钥失败:${e}`);
872
1036
  }
@@ -908,10 +1072,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
908
1072
  return;
909
1073
  }
910
1074
  if (qrContent.code !== 0) {
911
- this.selfCtx.emit("bilibili-notify/login-status-report", {
912
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
913
- msg: "获取二维码失败,请重试"
914
- });
1075
+ this.auth.reportQrFailure("qrFetchFailed");
915
1076
  return;
916
1077
  }
917
1078
  qrcode.default.toBuffer(qrContent.data.url, {
@@ -925,17 +1086,10 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
925
1086
  }, (err, buffer) => {
926
1087
  if (err) {
927
1088
  this.serverLogger.error(`[login] 生成二维码失败:${err}`);
928
- this.selfCtx.emit("bilibili-notify/login-status-report", {
929
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
930
- msg: "生成二维码失败"
931
- });
1089
+ this.auth.reportQrFailure("qrRenderFailed");
932
1090
  return;
933
1091
  }
934
- this.selfCtx.emit("bilibili-notify/login-status-report", {
935
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_QR,
936
- msg: "",
937
- data: `data:image/png;base64,${Buffer.from(buffer).toString("base64")}`
938
- });
1092
+ this.auth.reportQrReady(`data:image/png;base64,${Buffer.from(buffer).toString("base64")}`);
939
1093
  });
940
1094
  this.clearLoginTimer();
941
1095
  let polling = true;
@@ -951,10 +1105,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
951
1105
  this.selfCtx.setTimeout(() => {
952
1106
  if (!this.loginTimer) return;
953
1107
  this.clearLoginTimer();
954
- this.selfCtx.emit("bilibili-notify/login-status-report", {
955
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
956
- msg: "二维码已超时(3分钟),请重新登录"
957
- });
1108
+ this.auth.reportQrFailure("qrExpired");
958
1109
  }, 180 * 1e3);
959
1110
  }
960
1111
  async pollLoginStatus(qrcodeKey) {
@@ -968,25 +1119,16 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
968
1119
  }
969
1120
  const code = loginContent?.data?.code;
970
1121
  if (code === 86101) {
971
- this.selfCtx.emit("bilibili-notify/login-status-report", {
972
- status: _bilibili_notify_api.BiliLoginStatus.LOGGING_QR,
973
- msg: "尚未扫码,请扫码"
974
- });
1122
+ this.auth.reportQrPending("waitScan");
975
1123
  return;
976
1124
  }
977
1125
  if (code === 86090) {
978
- this.selfCtx.emit("bilibili-notify/login-status-report", {
979
- status: _bilibili_notify_api.BiliLoginStatus.LOGGING_QR,
980
- msg: "已扫码,但尚未确认,请确认"
981
- });
1126
+ this.auth.reportQrPending("waitConfirm");
982
1127
  return;
983
1128
  }
984
1129
  if (code === 86038) {
985
1130
  this.clearLoginTimer();
986
- this.selfCtx.emit("bilibili-notify/login-status-report", {
987
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
988
- msg: "二维码已失效,请重新登录"
989
- });
1131
+ this.auth.reportQrFailure("qrInvalidated");
990
1132
  return;
991
1133
  }
992
1134
  if (code === 0) {
@@ -994,10 +1136,7 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
994
1136
  const cookiesJson = this.api.getCookiesJson();
995
1137
  if (!cookiesJson || cookiesJson === "[]") {
996
1138
  this.serverLogger.error("[login] 登录成功但未获取到任何 cookie,放弃保存");
997
- this.selfCtx.emit("bilibili-notify/login-status-report", {
998
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
999
- msg: "登录成功但未获取到 cookie,请重试"
1000
- });
1139
+ this.auth.reportQrFailure("noCookieAfterLogin");
1001
1140
  return;
1002
1141
  }
1003
1142
  try {
@@ -1009,20 +1148,14 @@ var BilibiliNotifyServerManager = class extends koishi.Service {
1009
1148
  } catch (e) {
1010
1149
  this.serverLogger.error(`[login] 保存 cookie 失败:${e}`);
1011
1150
  }
1012
- this.selfCtx.emit("bilibili-notify/login-status-report", {
1013
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_SUCCESS,
1014
- msg: "登录成功,正在加载订阅..."
1015
- });
1151
+ this.auth.reportLoggedIn(void 0, "loginJustSucceeded");
1016
1152
  await this.reportAccountInfo();
1017
1153
  await this.loadInitialSubscriptions();
1018
1154
  return;
1019
1155
  }
1020
1156
  if (loginContent?.code !== 0) {
1021
1157
  this.clearLoginTimer();
1022
- this.selfCtx.emit("bilibili-notify/login-status-report", {
1023
- status: _bilibili_notify_api.BiliLoginStatus.LOGIN_FAILED,
1024
- msg: "登录失败,请重试"
1025
- });
1158
+ this.auth.reportQrFailure("genericLoginFail");
1026
1159
  }
1027
1160
  }
1028
1161
  async warnMissingPlugins(subs) {
package/lib/index.d.cts CHANGED
@@ -1,4 +1,4 @@
1
- import { BiliDataServer, BilibiliAPI } from "@bilibili-notify/api";
1
+ import { BiliDataServer as BiliDataServer$1, BilibiliAPI } from "@bilibili-notify/api";
2
2
  import { BilibiliPush, SubItem, Subscriptions } from "@bilibili-notify/push";
3
3
  import { CookieData, StorageManager } from "@bilibili-notify/storage";
4
4
  import { Awaitable, Context, Schema, Service } from "koishi";
@@ -11,6 +11,7 @@ interface BilibiliNotifyConfig {
11
11
  subs: FlatSubConfigItem[];
12
12
  logLevel: number;
13
13
  userAgent?: string;
14
+ loginHealthCheckMinutes: number;
14
15
  master: {
15
16
  enable: boolean;
16
17
  platform?: string;
@@ -21,10 +22,10 @@ interface BilibiliNotifyConfig {
21
22
  declare const BilibiliNotifyConfigSchema: Schema<BilibiliNotifyConfig>;
22
23
  //#endregion
23
24
  //#region src/data-server.d.ts
24
- declare class BilibiliNotifyDataServer extends DataService<BiliDataServer> {
25
+ declare class BilibiliNotifyDataServer extends DataService<BiliDataServer$1> {
25
26
  private biliData;
26
27
  constructor(ctx: Context);
27
- get(): Promise<BiliDataServer>;
28
+ get(): Promise<BiliDataServer$1>;
28
29
  }
29
30
  //#endregion
30
31
  //#region src/server-manager.d.ts
@@ -40,9 +41,13 @@ declare class BilibiliNotifyServerManager extends Service<BilibiliNotifyConfig>
40
41
  private running;
41
42
  storageMgr: StorageManager;
42
43
  private currentSubs;
44
+ private auth;
45
+ private authLostNotifiedAt;
43
46
  constructor(ctx: Context, config: BilibiliNotifyConfig);
44
47
  /** For commands */
45
48
  get subManager(): any;
49
+ /** For commands: read the current login snapshot. */
50
+ getAuthSnapshot(): BiliDataServer;
46
51
  subList(): string;
47
52
  protected start(): Promise<void>;
48
53
  protected stop(): Awaitable<void>;
@@ -97,6 +102,7 @@ declare class BilibiliNotifyServerManager extends Service<BilibiliNotifyConfig>
97
102
  private isLoggedIn;
98
103
  private clearLoginTimer;
99
104
  private reportAccountInfo;
105
+ private handleAuthLost;
100
106
  private loadInitialSubscriptions;
101
107
  private updateSubNotifier;
102
108
  private registerConsoleEvents;
@@ -135,7 +141,9 @@ declare module "koishi" {
135
141
  "bilibili-notify": BilibiliNotifyServerManager;
136
142
  }
137
143
  interface Events {
138
- "bilibili-notify/login-status-report"(data: BiliDataServer): void;
144
+ "bilibili-notify/login-status-report"(data: BiliDataServer$1): void;
145
+ "bilibili-notify/auth-lost"(): void;
146
+ "bilibili-notify/auth-restored"(): void;
139
147
  "bilibili-notify/advanced-sub"(subs: Subscriptions): void;
140
148
  "bilibili-notify/ready-to-receive"(): void;
141
149
  "bilibili-notify/cookies-refreshed"(data: CookieData): void;
package/lib/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { DataService } from "@koishijs/plugin-console";
2
2
  import { Awaitable, Context, Schema, Service } from "koishi";
3
- import { BiliDataServer, BilibiliAPI } from "@bilibili-notify/api";
3
+ import { BiliDataServer as BiliDataServer$1, BilibiliAPI } from "@bilibili-notify/api";
4
4
  import { BilibiliPush, SubItem, Subscriptions } from "@bilibili-notify/push";
5
5
  import { CookieData, StorageManager } from "@bilibili-notify/storage";
6
6
  import { FlatSubConfigItem } from "@bilibili-notify/subscription";
@@ -11,6 +11,7 @@ interface BilibiliNotifyConfig {
11
11
  subs: FlatSubConfigItem[];
12
12
  logLevel: number;
13
13
  userAgent?: string;
14
+ loginHealthCheckMinutes: number;
14
15
  master: {
15
16
  enable: boolean;
16
17
  platform?: string;
@@ -21,10 +22,10 @@ interface BilibiliNotifyConfig {
21
22
  declare const BilibiliNotifyConfigSchema: Schema<BilibiliNotifyConfig>;
22
23
  //#endregion
23
24
  //#region src/data-server.d.ts
24
- declare class BilibiliNotifyDataServer extends DataService<BiliDataServer> {
25
+ declare class BilibiliNotifyDataServer extends DataService<BiliDataServer$1> {
25
26
  private biliData;
26
27
  constructor(ctx: Context);
27
- get(): Promise<BiliDataServer>;
28
+ get(): Promise<BiliDataServer$1>;
28
29
  }
29
30
  //#endregion
30
31
  //#region src/server-manager.d.ts
@@ -40,9 +41,13 @@ declare class BilibiliNotifyServerManager extends Service<BilibiliNotifyConfig>
40
41
  private running;
41
42
  storageMgr: StorageManager;
42
43
  private currentSubs;
44
+ private auth;
45
+ private authLostNotifiedAt;
43
46
  constructor(ctx: Context, config: BilibiliNotifyConfig);
44
47
  /** For commands */
45
48
  get subManager(): any;
49
+ /** For commands: read the current login snapshot. */
50
+ getAuthSnapshot(): BiliDataServer;
46
51
  subList(): string;
47
52
  protected start(): Promise<void>;
48
53
  protected stop(): Awaitable<void>;
@@ -97,6 +102,7 @@ declare class BilibiliNotifyServerManager extends Service<BilibiliNotifyConfig>
97
102
  private isLoggedIn;
98
103
  private clearLoginTimer;
99
104
  private reportAccountInfo;
105
+ private handleAuthLost;
100
106
  private loadInitialSubscriptions;
101
107
  private updateSubNotifier;
102
108
  private registerConsoleEvents;
@@ -135,7 +141,9 @@ declare module "koishi" {
135
141
  "bilibili-notify": BilibiliNotifyServerManager;
136
142
  }
137
143
  interface Events {
138
- "bilibili-notify/login-status-report"(data: BiliDataServer): void;
144
+ "bilibili-notify/login-status-report"(data: BiliDataServer$1): void;
145
+ "bilibili-notify/auth-lost"(): void;
146
+ "bilibili-notify/auth-restored"(): void;
139
147
  "bilibili-notify/advanced-sub"(subs: Subscriptions): void;
140
148
  "bilibili-notify/ready-to-receive"(): void;
141
149
  "bilibili-notify/cookies-refreshed"(data: CookieData): void;
package/lib/index.mjs CHANGED
@@ -34,6 +34,7 @@ const BilibiliNotifyConfigSchema = Schema.object({
34
34
  })).role("table").description("在这里填写主人的订阅信息~UP 昵称、UID、roomid、平台、群号都要填正确,不然女仆会迷路哒 (;>_<)如果多个群聊/频道,请用英文逗号分隔哦~女仆会努力送到每一个地方的!"),
35
35
  logLevel: Schema.number().min(1).max(3).step(1).default(1).description("这里可以设置日志等级喔~3 是最详细的调试信息,1 是只显示错误信息。主人可以根据需要选择合适的等级,让女仆更好地为您服务 (๑•̀ㅂ•́)و✧"),
36
36
  userAgent: Schema.string().description("这里可以设置请求头的 User-Agent 哦~如果请求出现了 -352 的奇怪错误,主人可以试着在这里换一个看看 (;>_<)"),
37
+ loginHealthCheckMinutes: Schema.number().min(5).max(180).step(1).default(30).description("登录状态周期检测的间隔(分钟)。女仆会按这个频率悄悄帮主人确认账号还在线哦~如果发现失效会立刻汇报呢 (๑•̀ㅂ•́)و✧"),
37
38
  master: Schema.intersect([Schema.object({ enable: Schema.boolean().default(false).description("要不要让笨笨女仆开启主人账号功能呢?(>﹏<)如果机器人遭遇了奇怪的小错误,女仆会立刻跑来向主人报告的!不、不过……如果没有私聊权限的话,女仆就联系不到主人了……请不要打开这个开关喔 (;´д`)ゞ") }).description("主人的特别区域……女仆会乖乖侍奉的!(>///<)"), Schema.union([Schema.object({
38
39
  enable: Schema.const(true).required(),
39
40
  platform: Schema.union([
@@ -387,6 +388,10 @@ function biliCommands() {
387
388
  //#region src/commands/status.ts
388
389
  function statusCommands() {
389
390
  const statusCom = this.ctx.command("status", "插件状态相关指令", { permissions: ["authority:5"] });
391
+ statusCom.subcommand(".auth", "查看登录状态").usage("查看登录状态").example("status auth").action(() => {
392
+ const snap = this.getAuthSnapshot();
393
+ return `登录状态:${BiliLoginStatus[snap.status] ?? `unknown(${snap.status})`}\n信息:${snap.msg || "(无)"}`;
394
+ });
390
395
  statusCom.subcommand(".dyn", "查看动态监测运行状态").usage("查看动态监测运行状态").example("status dyn").action(() => {
391
396
  if (this.ctx.get("bilibili-notify-dynamic")) return "动态监测正在运行";
392
397
  return "动态插件未运行(请检查是否已安装并启用 koishi-plugin-bilibili-notify-dynamic)";
@@ -431,6 +436,136 @@ function sysCommands() {
431
436
  });
432
437
  }
433
438
  //#endregion
439
+ //#region src/login-status.ts
440
+ const MESSAGES = {
441
+ loading: "正在加载登录信息...",
442
+ notLogin: "账号未登录,请点击「扫码登录」",
443
+ keyReset: "密钥已重置,cookie 已清除,请重新扫码登录",
444
+ authLost: "账号登录已失效,请在控制台重新扫码登录",
445
+ loggedIn: "已登录",
446
+ loginJustSucceeded: "登录成功,正在加载订阅...",
447
+ fetchAccountFailed: "账号已登录,但获取个人信息失败,请检查",
448
+ waitScan: "尚未扫码,请扫码",
449
+ waitConfirm: "已扫码,但尚未确认,请确认",
450
+ qrFetchFailed: "获取二维码失败,请重试",
451
+ qrRenderFailed: "生成二维码失败",
452
+ qrExpired: "二维码已超时(3分钟),请重新登录",
453
+ qrInvalidated: "二维码已失效,请重新登录",
454
+ noCookieAfterLogin: "登录成功但未获取到 cookie,请重试",
455
+ genericLoginFail: "登录失败,请重试"
456
+ };
457
+ /** 类型守卫:snapshot.data 是否长得像 UserCardInfo。 */
458
+ function looksLikeCardData(data) {
459
+ return typeof data === "object" && data !== null && "card" in data;
460
+ }
461
+ /**
462
+ * 集中管理登录态:所有变更都经过这里,再以 `bilibili-notify/login-status-report`
463
+ * 推到前端。心跳定时器在登录态下定期 probe,发现失效会同时广播
464
+ * `bilibili-notify/auth-lost`,恢复时广播 `bilibili-notify/auth-restored`。
465
+ */
466
+ var LoginStatusController = class {
467
+ snapshot = {
468
+ status: BiliLoginStatus.LOADING_LOGIN_INFO,
469
+ msg: MESSAGES.loading
470
+ };
471
+ healthTimer;
472
+ /**
473
+ * 标记"曾从已登录态掉线",下一次成功登录时翻转为已登录后才发 auth-restored。
474
+ * 之所以不再用"上一帧 status === NOT_LOGIN"判断:失效后用户走扫码流程,会经过
475
+ * LOGIN_QR / LOGGING_QR 等中间态,直接基于上一帧会漏掉这条恢复路径。
476
+ */
477
+ needsRestore = false;
478
+ constructor(ctx, options) {
479
+ this.ctx = ctx;
480
+ this.options = options;
481
+ }
482
+ current() {
483
+ return { ...this.snapshot };
484
+ }
485
+ attachHealthCheck() {
486
+ this.detachHealthCheck();
487
+ if (this.options.healthCheckMs <= 0) return;
488
+ this.healthTimer = this.ctx.setInterval(() => void this.runHealthCheck(), this.options.healthCheckMs);
489
+ }
490
+ detachHealthCheck() {
491
+ this.healthTimer?.();
492
+ this.healthTimer = void 0;
493
+ }
494
+ reportLoggedIn(card, reasonKey = "loggedIn") {
495
+ const wasLoggedIn = this.snapshot.status === BiliLoginStatus.LOGGED_IN;
496
+ const fallback = looksLikeCardData(this.snapshot.data) ? this.snapshot.data : void 0;
497
+ this.transition({
498
+ status: BiliLoginStatus.LOGGED_IN,
499
+ msg: MESSAGES[reasonKey],
500
+ data: card ?? fallback
501
+ });
502
+ if (!wasLoggedIn && this.needsRestore) {
503
+ this.needsRestore = false;
504
+ this.ctx.emit("bilibili-notify/auth-restored");
505
+ }
506
+ }
507
+ reportLoggedOut(reasonKey = "notLogin") {
508
+ const wasLoggedIn = this.snapshot.status === BiliLoginStatus.LOGGED_IN;
509
+ this.transition({
510
+ status: BiliLoginStatus.NOT_LOGIN,
511
+ msg: MESSAGES[reasonKey]
512
+ });
513
+ if (wasLoggedIn) {
514
+ this.needsRestore = true;
515
+ this.ctx.emit("bilibili-notify/auth-lost");
516
+ }
517
+ }
518
+ /** Dispatch on `getMyselfInfo` result code. */
519
+ reportLoginCheck(code, card) {
520
+ if (code === 0) this.reportLoggedIn(card);
521
+ else if (code === -101) this.reportLoggedOut("authLost");
522
+ else this.reportTransientFailure(`code=${code}`);
523
+ }
524
+ /** Keep current status, only refresh msg. Logs at warn level. */
525
+ reportTransientFailure(detail) {
526
+ this.options.logger.warn(`[auth] 瞬时失败:${detail}`);
527
+ if (this.snapshot.status !== BiliLoginStatus.LOGGED_IN) return;
528
+ this.transition({
529
+ ...this.snapshot,
530
+ msg: MESSAGES.fetchAccountFailed
531
+ });
532
+ }
533
+ reportQrReady(base64) {
534
+ this.transition({
535
+ status: BiliLoginStatus.LOGIN_QR,
536
+ msg: "",
537
+ data: base64
538
+ });
539
+ }
540
+ reportQrPending(reasonKey) {
541
+ this.transition({
542
+ status: BiliLoginStatus.LOGGING_QR,
543
+ msg: MESSAGES[reasonKey]
544
+ });
545
+ }
546
+ reportQrFailure(reasonKey) {
547
+ this.transition({
548
+ status: BiliLoginStatus.LOGIN_FAILED,
549
+ msg: MESSAGES[reasonKey]
550
+ });
551
+ }
552
+ /** Emit only when (status, msg, data) changes. */
553
+ transition(next) {
554
+ if (this.snapshot.status === next.status && this.snapshot.msg === next.msg && this.snapshot.data === next.data) return;
555
+ this.snapshot = next;
556
+ this.ctx.emit("bilibili-notify/login-status-report", next);
557
+ }
558
+ async runHealthCheck() {
559
+ if (this.snapshot.status === BiliLoginStatus.LOGIN_QR || this.snapshot.status === BiliLoginStatus.LOGGING_QR || this.snapshot.status === BiliLoginStatus.NOT_LOGIN) return;
560
+ try {
561
+ const res = await this.options.probe();
562
+ this.reportLoginCheck(res.code);
563
+ } catch (e) {
564
+ this.options.logger.warn(`[auth] 心跳异常(保持当前状态):${e}`);
565
+ }
566
+ }
567
+ };
568
+ //#endregion
434
569
  //#region src/server-manager.ts
435
570
  const SERVICE_NAME = "bilibili-notify";
436
571
  const LIVE_MASTER_KEYS = [
@@ -482,6 +617,8 @@ var BilibiliNotifyServerManager = class extends Service {
482
617
  running = false;
483
618
  storageMgr;
484
619
  currentSubs = null;
620
+ auth;
621
+ authLostNotifiedAt = 0;
485
622
  constructor(ctx, config) {
486
623
  super(ctx, SERVICE_NAME);
487
624
  this.selfCtx = ctx;
@@ -492,6 +629,10 @@ var BilibiliNotifyServerManager = class extends Service {
492
629
  get subManager() {
493
630
  return this.subMgr?.subManager ?? /* @__PURE__ */ new Map();
494
631
  }
632
+ /** For commands: read the current login snapshot. */
633
+ getAuthSnapshot() {
634
+ return this.auth.current();
635
+ }
495
636
  subList() {
496
637
  const map = this.subManager;
497
638
  if (!map.size) return "没有订阅任何UP";
@@ -506,6 +647,14 @@ var BilibiliNotifyServerManager = class extends Service {
506
647
  this.serverLogger.info("[start] 正在启动中...");
507
648
  this.storageMgr = new StorageManager(this.ctx.baseDir, this.ctx);
508
649
  await this.storageMgr.init();
650
+ this.auth = new LoginStatusController(this.selfCtx, {
651
+ healthCheckMs: this.config.loginHealthCheckMinutes * 6e4,
652
+ logger: this.serverLogger,
653
+ probe: () => {
654
+ if (!this.api) throw new Error("api not initialized");
655
+ return this.api.getMyselfInfo();
656
+ }
657
+ });
509
658
  this.ctx.on("bilibili-notify/cookies-refreshed", async (data) => {
510
659
  try {
511
660
  await this.storageMgr.cookieStore.save(data);
@@ -681,8 +830,13 @@ var BilibiliNotifyServerManager = class extends Service {
681
830
  this.api = new BilibiliAPI(this.selfCtx, {
682
831
  logLevel: this.config.logLevel,
683
832
  userAgent: this.config.userAgent
684
- }, (data) => {
685
- this.selfCtx.emit("bilibili-notify/cookies-refreshed", data);
833
+ }, {
834
+ onCookiesRefreshed: (data) => {
835
+ this.selfCtx.emit("bilibili-notify/cookies-refreshed", data);
836
+ },
837
+ onAuthLost: () => {
838
+ this.handleAuthLost();
839
+ }
686
840
  });
687
841
  this.push = new BilibiliPush(this.selfCtx, {
688
842
  logLevel: this.config.logLevel,
@@ -701,10 +855,7 @@ var BilibiliNotifyServerManager = class extends Service {
701
855
  this.serverLogger.debug(`[cookie] Cookie 加载完成,登录状态:${this.isLoggedIn() ? "已登录" : "未登录"}`);
702
856
  if (!this.isLoggedIn()) {
703
857
  this.serverLogger.info("[login] 账号未登录,请在控制台扫码登录");
704
- this.selfCtx.emit("bilibili-notify/login-status-report", {
705
- status: BiliLoginStatus.NOT_LOGIN,
706
- msg: "账号未登录,请点击「扫码登录」"
707
- });
858
+ this.auth.reportLoggedOut("notLogin");
708
859
  return true;
709
860
  }
710
861
  await this.reportAccountInfo();
@@ -720,6 +871,7 @@ var BilibiliNotifyServerManager = class extends Service {
720
871
  this.serverLogger.debug("[stop] 正在清理插件资源...");
721
872
  this.running = false;
722
873
  this.clearLoginTimer();
874
+ this.auth?.detachHealthCheck();
723
875
  if (this.subNotifier) {
724
876
  this.subNotifier.dispose();
725
877
  this.subNotifier = void 0;
@@ -782,23 +934,38 @@ var BilibiliNotifyServerManager = class extends Service {
782
934
  }
783
935
  async reportAccountInfo() {
784
936
  if (!this.api) return;
937
+ let personalInfo;
785
938
  try {
786
- const personalInfo = await this.api.getMyselfInfo();
787
- if (personalInfo.code !== 0) {
788
- this.selfCtx.emit("bilibili-notify/login-status-report", {
789
- status: BiliLoginStatus.LOGGED_IN,
790
- msg: "账号已登录,但获取个人信息失败,请检查"
791
- });
792
- return;
793
- }
794
- const myCardInfo = await this.api.getUserCardInfo(personalInfo.data.mid.toString(), true);
795
- this.selfCtx.emit("bilibili-notify/login-status-report", {
796
- status: BiliLoginStatus.LOGGED_IN,
797
- msg: "已登录",
798
- data: myCardInfo.data
799
- });
939
+ personalInfo = await this.api.getMyselfInfo();
940
+ } catch (e) {
941
+ this.serverLogger.warn(`[account] 获取个人信息异常: ${e}`);
942
+ this.auth.reportTransientFailure(e);
943
+ this.auth.attachHealthCheck();
944
+ return;
945
+ }
946
+ if (personalInfo.code !== 0) {
947
+ this.auth.reportLoginCheck(personalInfo.code);
948
+ if (personalInfo.code !== -101) this.auth.attachHealthCheck();
949
+ return;
950
+ }
951
+ let card;
952
+ try {
953
+ card = (await this.api.getUserCardInfo(personalInfo.data.mid.toString(), true)).data;
800
954
  } catch (e) {
801
- this.serverLogger.warn(`[account] 获取账号信息失败: ${e}`);
955
+ this.serverLogger.warn(`[account] 获取用户卡片失败: ${e}`);
956
+ }
957
+ this.auth.reportLoggedIn(card);
958
+ this.auth.attachHealthCheck();
959
+ }
960
+ async handleAuthLost() {
961
+ this.auth.reportLoggedOut("authLost");
962
+ const now = Date.now();
963
+ if (now - this.authLostNotifiedAt < 6e4) return;
964
+ this.authLostNotifiedAt = now;
965
+ try {
966
+ await this.push?.sendPrivateMsg("账号登录已失效,请在控制台重新扫码登录");
967
+ } catch (e) {
968
+ this.serverLogger.warn(`[auth] 失效通知私信失败:${e}`);
802
969
  }
803
970
  }
804
971
  async loadInitialSubscriptions() {
@@ -843,10 +1010,7 @@ var BilibiliNotifyServerManager = class extends Service {
843
1010
  this.serverLogger.info("[login] 触发重置密钥事件");
844
1011
  try {
845
1012
  await this.storageMgr.cookieStore.resetKey();
846
- this.selfCtx.emit("bilibili-notify/login-status-report", {
847
- status: BiliLoginStatus.NOT_LOGIN,
848
- msg: "密钥已重置,cookie 已清除,请重新扫码登录"
849
- });
1013
+ this.auth.reportLoggedOut("keyReset");
850
1014
  } catch (e) {
851
1015
  this.serverLogger.error(`[login] 重置密钥失败:${e}`);
852
1016
  }
@@ -888,10 +1052,7 @@ var BilibiliNotifyServerManager = class extends Service {
888
1052
  return;
889
1053
  }
890
1054
  if (qrContent.code !== 0) {
891
- this.selfCtx.emit("bilibili-notify/login-status-report", {
892
- status: BiliLoginStatus.LOGIN_FAILED,
893
- msg: "获取二维码失败,请重试"
894
- });
1055
+ this.auth.reportQrFailure("qrFetchFailed");
895
1056
  return;
896
1057
  }
897
1058
  QRCode.toBuffer(qrContent.data.url, {
@@ -905,17 +1066,10 @@ var BilibiliNotifyServerManager = class extends Service {
905
1066
  }, (err, buffer) => {
906
1067
  if (err) {
907
1068
  this.serverLogger.error(`[login] 生成二维码失败:${err}`);
908
- this.selfCtx.emit("bilibili-notify/login-status-report", {
909
- status: BiliLoginStatus.LOGIN_FAILED,
910
- msg: "生成二维码失败"
911
- });
1069
+ this.auth.reportQrFailure("qrRenderFailed");
912
1070
  return;
913
1071
  }
914
- this.selfCtx.emit("bilibili-notify/login-status-report", {
915
- status: BiliLoginStatus.LOGIN_QR,
916
- msg: "",
917
- data: `data:image/png;base64,${Buffer.from(buffer).toString("base64")}`
918
- });
1072
+ this.auth.reportQrReady(`data:image/png;base64,${Buffer.from(buffer).toString("base64")}`);
919
1073
  });
920
1074
  this.clearLoginTimer();
921
1075
  let polling = true;
@@ -931,10 +1085,7 @@ var BilibiliNotifyServerManager = class extends Service {
931
1085
  this.selfCtx.setTimeout(() => {
932
1086
  if (!this.loginTimer) return;
933
1087
  this.clearLoginTimer();
934
- this.selfCtx.emit("bilibili-notify/login-status-report", {
935
- status: BiliLoginStatus.LOGIN_FAILED,
936
- msg: "二维码已超时(3分钟),请重新登录"
937
- });
1088
+ this.auth.reportQrFailure("qrExpired");
938
1089
  }, 180 * 1e3);
939
1090
  }
940
1091
  async pollLoginStatus(qrcodeKey) {
@@ -948,25 +1099,16 @@ var BilibiliNotifyServerManager = class extends Service {
948
1099
  }
949
1100
  const code = loginContent?.data?.code;
950
1101
  if (code === 86101) {
951
- this.selfCtx.emit("bilibili-notify/login-status-report", {
952
- status: BiliLoginStatus.LOGGING_QR,
953
- msg: "尚未扫码,请扫码"
954
- });
1102
+ this.auth.reportQrPending("waitScan");
955
1103
  return;
956
1104
  }
957
1105
  if (code === 86090) {
958
- this.selfCtx.emit("bilibili-notify/login-status-report", {
959
- status: BiliLoginStatus.LOGGING_QR,
960
- msg: "已扫码,但尚未确认,请确认"
961
- });
1106
+ this.auth.reportQrPending("waitConfirm");
962
1107
  return;
963
1108
  }
964
1109
  if (code === 86038) {
965
1110
  this.clearLoginTimer();
966
- this.selfCtx.emit("bilibili-notify/login-status-report", {
967
- status: BiliLoginStatus.LOGIN_FAILED,
968
- msg: "二维码已失效,请重新登录"
969
- });
1111
+ this.auth.reportQrFailure("qrInvalidated");
970
1112
  return;
971
1113
  }
972
1114
  if (code === 0) {
@@ -974,10 +1116,7 @@ var BilibiliNotifyServerManager = class extends Service {
974
1116
  const cookiesJson = this.api.getCookiesJson();
975
1117
  if (!cookiesJson || cookiesJson === "[]") {
976
1118
  this.serverLogger.error("[login] 登录成功但未获取到任何 cookie,放弃保存");
977
- this.selfCtx.emit("bilibili-notify/login-status-report", {
978
- status: BiliLoginStatus.LOGIN_FAILED,
979
- msg: "登录成功但未获取到 cookie,请重试"
980
- });
1119
+ this.auth.reportQrFailure("noCookieAfterLogin");
981
1120
  return;
982
1121
  }
983
1122
  try {
@@ -989,20 +1128,14 @@ var BilibiliNotifyServerManager = class extends Service {
989
1128
  } catch (e) {
990
1129
  this.serverLogger.error(`[login] 保存 cookie 失败:${e}`);
991
1130
  }
992
- this.selfCtx.emit("bilibili-notify/login-status-report", {
993
- status: BiliLoginStatus.LOGIN_SUCCESS,
994
- msg: "登录成功,正在加载订阅..."
995
- });
1131
+ this.auth.reportLoggedIn(void 0, "loginJustSucceeded");
996
1132
  await this.reportAccountInfo();
997
1133
  await this.loadInitialSubscriptions();
998
1134
  return;
999
1135
  }
1000
1136
  if (loginContent?.code !== 0) {
1001
1137
  this.clearLoginTimer();
1002
- this.selfCtx.emit("bilibili-notify/login-status-report", {
1003
- status: BiliLoginStatus.LOGIN_FAILED,
1004
- msg: "登录失败,请重试"
1005
- });
1138
+ this.auth.reportQrFailure("genericLoginFail");
1006
1139
  }
1007
1140
  }
1008
1141
  async warnMissingPlugins(subs) {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-bilibili-notify",
3
3
  "description": "Bilibili 动态推送、直播通知 Koishi 插件",
4
- "version": "4.1.2",
4
+ "version": "4.2.0",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "https://github.com/Akokk0/bilibili-notify"
@@ -39,11 +39,11 @@
39
39
  "typecheck": "tsc --noEmit"
40
40
  },
41
41
  "dependencies": {
42
- "@bilibili-notify/api": "^0.0.2",
43
- "@bilibili-notify/internal": "^0.0.2",
42
+ "@bilibili-notify/api": "^0.1.0",
43
+ "@bilibili-notify/internal": "^0.0.3",
44
44
  "@bilibili-notify/push": "^1.0.2",
45
45
  "@bilibili-notify/storage": "^0.0.2",
46
- "@bilibili-notify/subscription": "^1.0.2",
46
+ "@bilibili-notify/subscription": "^1.0.3",
47
47
  "qrcode": "^1.5.4"
48
48
  },
49
49
  "devDependencies": {