@koishi-ce/plugin-analytics 1.0.2 → 1.1.1

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/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{icons as e,store as t}from"@koishi-ce/client";import{Fragment as n,computed as r,createBlock as i,createCommentVNode as a,createElementBlock as o,createElementVNode as s,createSlots as ee,createTextVNode as c,createVNode as l,defineAsyncComponent as u,defineComponent as d,h as f,openBlock as p,provide as m,ref as h,renderSlot as g,resolveComponent as _,toDisplayString as v,unref as y,withCtx as b}from"vue";var x=u(()=>import(`./echarts-BMfAiWrm.js`)),S=h(`send`);function C({title:e,fields:n=[],showTab:r,options:i}){return d({render:()=>{if(!n.every(e=>t[e]))return null;let a=i(t,S.value);if(a)return f(_(`k-card`),{class:`frameless analytic-chart`},{header:()=>[f(`span`,{class:`left`},[e]),...r?[f(`span`,{class:`right`},[f(`span`,{class:`tab-item`+(S.value===`send`?` active`:``),onClick:()=>S.value=`send`},[`发送`]),f(`span`,{class:`tab-item`+(S.value===`receive`?` active`:``),onClick:()=>S.value=`receive`},[`接收`])])]:[]],default:()=>f(x,{option:a,autoresize:!0})})}})}var w={item(e){return{trigger:`item`,formatter:e}},axis(e){return{trigger:`axis`,axisPointer:{type:`cross`},formatter:e}}},T=e=>{e.slot({type:`analytic-chart`,component:C({title:`各平台消息占比`,fields:[`analytics`],showTab:!0,options({analytics:e},t){if(!e)return;let n=Object.entries(e.messageByBot).map(([e,n])=>({name:e,children:Object.entries(n).map(([e,n])=>({name:n.name||e,value:n[t]}))}));if(n.reduce((e,{children:t})=>e+t.reduce((e,{value:t})=>e+t,0),0))return{tooltip:w.item(({data:e})=>`${e.children?`平台`:`昵称`}:${e.name}<br>日均消息数量:${+e.value.toFixed(1)}`),series:[{type:`sunburst`,data:n,radius:[`0`,`65%`],nodeClick:!1,emphasis:{focus:`ancestor`},levels:[{},{label:{rotate:`tangential`}},{label:{position:`outside`,padding:3,silent:!1}}]}]}}})})},E=e=>{e.slot({type:`analytic-chart`,component:C({title:`指令调用频率`,fields:[`analytics`],options({analytics:e}){if(!e)return;let t=Object.entries(e.commandRate).sort((e,t)=>t[1]-e[1]).map(([e,t])=>({name:e,value:t}));if(t.length)return{tooltip:w.item(({data:e})=>{let t=[e.name];return t.push(`日均调用:${+e.value.toFixed(1)}`),t.join(`<br>`)}),series:[{type:`pie`,data:t,radius:[`35%`,`65%`],minShowLabelAngle:3}]}}})})},D=`日一二三四五六`,O=e=>{e.slot({type:`analytic-chart`,component:C({title:`历史消息数量`,fields:[`analytics`],showTab:!0,options({analytics:e},t){if(!e||!e.messageByDate.length)return;let n=e.messageByDate.slice(1);return{tooltip:w.axis(([e])=>{if(!e)return``;let{name:t,value:n}=e;return`${t} 星期${D[new Date(t).getDay()]}<br>消息数量:${n}`}),xAxis:{type:`category`,data:n.map((e,t)=>new Date(Date.now()-(t+1)*864e5).toLocaleDateString(`zh-CN`)).reverse()},yAxis:{type:`value`},series:{type:`line`,smooth:!0,data:n.map(e=>e[t]).reverse()}}}})})},k=e=>`${(e-.5).toFixed()}:00-${(e+.5).toFixed()}:00`,A=e=>{e.slot({type:`analytic-chart`,component:C({title:`每小时消息数量`,fields:[`analytics`],showTab:!0,options({analytics:e},t){if(e&&!e.messageByHour.every(e=>!e[t]))return{tooltip:w.axis(([n])=>{if(!n)return``;let[r=0]=n.data,{dataIndex:i}=n,a=e.messageByHour[i];return[`${k(r)}`,`日均消息数量:${+(a?.[t]||0).toFixed(1)}`].join(`<br>`)}),xAxis:{type:`value`,min:0,max:24,minInterval:1,maxInterval:4,axisLabel:{formatter:e=>`${e}:00`},axisPointer:{label:{formatter:({value:e})=>k(e)}}},yAxis:{type:`value`},series:[{data:e.messageByHour.map((e,n)=>[n+.5,e[t]||0]),type:`bar`,stack:`1`}]}}})})},j=e=>{e.plugin(O),e.plugin(A),e.plugin(T),e.plugin(E)},M={class:`content`},N={class:`title`},P={class:`value`},F={class:`left`},I={class:`right`},L=d({__name:`numeric`,props:{title:{},icon:{}},setup(e){return(t,n)=>{let r=_(`k-icon`),a=_(`k-card`);return p(),i(a,{class:`k-analytic-number`},ee({default:b(()=>[l(r,{name:e.icon},null,8,[`name`]),s(`div`,M,[s(`p`,N,v(e.title),1),s(`p`,P,[g(t.$slots,`default`,{},()=>[n[0]||=c(`-`,-1)])])])]),_:2},[t.$slots[`footer-left`]?{name:`footer`,fn:b(()=>[s(`span`,F,[g(t.$slots,`footer-left`)]),s(`span`,I,[g(t.$slots,`footer-right`,{},()=>[n[1]||=c(`-`,-1)])])]),key:`0`}:void 0]),1024)}}}),R={key:0,class:`card-grid numeric-grid`},z=d({__name:`index`,setup(e){m(`component:analytic-number`,L);let n=r(()=>{let e=t.analytics.dauHistory.slice(1),n=t.analytics.dauHistory.length-1;return n?e.reduce((e,t)=>e+t,0)/Math.min(e.length,n):0});return(e,r)=>{let i=_(`k-slot-item`),s=_(`k-slot`);return y(t).analytics?(p(),o(`div`,R,[l(s,{name:`analytic-number`},{default:b(()=>[l(i,null,{default:b(()=>[l(L,{icon:`analytic:user`,title:`用户数量`},{default:b(()=>[c(v(y(t).analytics.userCount),1)]),"footer-left":b(()=>[...r[0]||=[c(`昨日新增用户`,-1)]]),"footer-right":b(()=>[c(v(y(t).analytics.userIncrement),1)]),_:1}),l(L,{icon:`analytic:guild`,title:`群组数量`},{default:b(()=>[c(v(y(t).analytics.guildCount),1)]),"footer-left":b(()=>[...r[1]||=[c(`昨日新增群组`,-1)]]),"footer-right":b(()=>[c(v(y(t).analytics.guildIncrement),1)]),_:1}),l(L,{icon:`analytic:heart`,title:`今日 DAU`},{default:b(()=>[c(v(y(t).analytics.dauHistory[0]),1)]),"footer-left":b(()=>[...r[2]||=[c(`近期 DAU`,-1)]]),"footer-right":b(()=>[c(v(+n.value.toFixed(1)),1)]),_:1})]),_:1})]),_:1})])):a(`v-if`,!0)}}}),B=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},V=B(z,[[`__scopeId`,`data-v-41af15ac`]]),H={class:`card-grid chart-grid`},U=d({__name:`home`,setup(e){return(e,t)=>{let r=_(`k-slot`);return p(),o(n,null,[l(V),s(`div`,H,[l(r,{name:`analytic-chart`})])],64)}}}),W={},G={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 640 512`};function K(e,t){return p(),o(`svg`,G,[...t[0]||=[s(`path`,{fill:`currentColor`,d:`M274.7 304H173.3c-95.73 0-173.3 77.6-173.3 173.3C0 496.5 15.52 512 34.66 512H413.3C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304zM413.3 480H34.66C33.2 480 32 478.8 32 477.3C32 399.4 95.4 336 173.3 336H274.7C352.6 336 416 399.4 416 477.3C416 478.8 414.8 480 413.3 480zM224 256c70.7 0 128-57.31 128-128S294.7 0 224 0C153.3 0 96 57.31 96 128S153.3 256 224 256zM224 32c52.94 0 96 43.06 96 96c0 52.93-43.06 96-96 96S128 180.9 128 128C128 75.06 171.1 32 224 32zM375.1 241C392.9 250.8 412.3 256 432 256C493.8 256 544 205.8 544 144S493.8 32 432 32c-12.83 0-25.39 2.156-37.34 6.391c-8.328 2.953-12.69 12.09-9.734 20.42c2.953 8.344 12.12 12.66 20.42 9.734C413.9 65.53 422.8 64 432 64C476.1 64 512 99.89 512 144S476.1 224 432 224c-14.08 0-27.91-3.703-39.98-10.69c-7.656-4.453-17.44-1.828-21.86 5.828C365.7 226.8 368.3 236.6 375.1 241zM490.7 320H448c-8.844 0-16 7.156-16 16S439.2 352 448 352h42.67C555.4 352 608 404.6 608 469.3C608 475.2 603.2 480 597.3 480H496c-8.844 0-16 7.156-16 16s7.156 16 16 16h101.3C620.9 512 640 492.9 640 469.3C640 386.1 573 320 490.7 320z`},null,-1)]])}var q=B(W,[[`render`,K]]),J={},Y={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function X(e,t){return p(),o(`svg`,Y,[...t[0]||=[s(`path`,{fill:`currentColor`,d:`M255.1 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L255.1 96zM255.1 141.3L221.4 106.6C196.1 81.31 160 69.77 124.7 75.66C71.21 84.58 31.1 130.9 31.1 185.1V190.9C31.1 223.6 45.55 254.7 69.42 277L250.1 445.7C251.7 447.2 253.8 448 255.1 448C258.2 448 260.3 447.2 261.9 445.7L442.6 277C466.4 254.7 480 223.6 480 190.9V185.1C480 130.9 440.8 84.58 387.3 75.66C351.1 69.77 315.9 81.31 290.6 106.6L255.1 141.3z`},null,-1)]])}var Z=B(J,[[`render`,X]]),Q={},$={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function te(e,t){return p(),o(`svg`,$,[...t[0]||=[s(`path`,{fill:`currentColor`,d:`M256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C172.2 512 97.87 471.8 51.19 409.6C45.88 402.5 47.31 392.5 54.37 387.2C61.44 381.9 71.47 383.3 76.78 390.4C117.7 444.8 182.7 480 256 480C379.7 480 480 379.7 480 256C480 132.3 379.7 32 256 32C166.7 32 89.51 84.3 53.55 160H144C152.8 160 160 167.2 160 176C160 184.8 152.8 192 144 192H16C7.164 192 0 184.8 0 176V48C0 39.16 7.164 32 16 32C24.84 32 32 39.16 32 48V131.1C75.66 53.29 159.6 0 256 0zM256 128C264.8 128 272 135.2 272 144V249.4L347.3 324.7C353.6 330.9 353.6 341.1 347.3 347.3C341.1 353.6 330.9 353.6 324.7 347.3L244.7 267.3C241.7 264.3 239.1 260.2 239.1 256V144C239.1 135.2 247.2 128 255.1 128H256z`},null,-1)]])}var ne=B(Q,[[`render`,te]]),re={},ie={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 448 512`};function ae(e,t){return p(),o(`svg`,ie,[...t[0]||=[s(`path`,{fill:`currentColor`,d:`M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM224 32c52.94 0 96 43.06 96 96c0 52.93-43.06 96-96 96S128 180.9 128 128C128 75.06 171.1 32 224 32zM274.7 304H173.3C77.61 304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304zM413.3 480H34.66C33.2 480 32 478.8 32 477.3C32 399.4 95.4 336 173.3 336h101.3C352.6 336 416 399.4 416 477.3C416 478.8 414.8 480 413.3 480z`},null,-1)]])}var oe=B(re,[[`render`,ae]]);e.register(`analytic:guild`,q),e.register(`analytic:heart`,Z),e.register(`analytic:history`,ne),e.register(`analytic:user`,oe);var se=e=>{e.plugin(j),e.slot({type:`home`,component:U,order:0})};export{se as default};
1
+ import{icons as e,root as t,store as n,useI18n as r}from"@koishi-ce/client";import{Fragment as i,computed as a,createBlock as o,createCommentVNode as s,createElementBlock as c,createElementVNode as l,createSlots as u,createTextVNode as d,createVNode as f,defineAsyncComponent as p,defineComponent as m,h,openBlock as g,provide as ee,ref as _,renderSlot as v,resolveComponent as y,toDisplayString as b,unref as x,withCtx as S}from"vue";var C=p(()=>import(`./echarts-B6DtxUFq.js`)),w=_(`send`);function T({title:e,fields:r=[],showTab:i,options:a}){return m({render:()=>{if(!r.every(e=>n[e]))return null;let o=a(n,w.value);if(!o)return;let s=e=>t.$i18n.t(e);return h(y(`k-card`),{class:`frameless analytic-chart`},{header:()=>[h(`span`,{class:`left`},[typeof e==`function`?e():e]),...i?[h(`span`,{class:`right`},[h(`span`,{class:`tab-item`+(w.value===`send`?` active`:``),onClick:()=>w.value=`send`},[s(`analytics.stats.send`)]),h(`span`,{class:`tab-item`+(w.value===`receive`?` active`:``),onClick:()=>w.value=`receive`},[s(`analytics.stats.receive`)])])]:[]],default:()=>h(C,{option:o,autoresize:!0})})}})}var E={item(e){return{trigger:`item`,formatter:e}},axis(e){return{trigger:`axis`,axisPointer:{type:`cross`},formatter:e}}},D=e=>{e.slot({type:`analytic-chart`,component:T({title:()=>e.$i18n.t(`analytics.bot.title`),fields:[`analytics`],showTab:!0,options({analytics:t},n){if(!t)return;let r=Object.entries(t.messageByBot).map(([e,t])=>({name:e,children:Object.entries(t).map(([e,t])=>({name:t.name||e,value:t[n]}))}));if(r.reduce((e,{children:t})=>e+t.reduce((e,{value:t})=>e+t,0),0))return{tooltip:E.item(({data:t})=>e.$i18n.t(t.children?`analytics.bot.tipPlatform`:`analytics.bot.tipAccount`,[t.name,+t.value.toFixed(1)])),series:[{type:`sunburst`,data:r,radius:[`0`,`65%`],nodeClick:!1,emphasis:{focus:`ancestor`},levels:[{},{label:{rotate:`tangential`}},{label:{position:`outside`,padding:3,silent:!1}}]}]}}})})},O=e=>{e.slot({type:`analytic-chart`,component:T({title:()=>e.$i18n.t(`analytics.command.title`),fields:[`analytics`],options({analytics:t}){if(!t)return;let n=Object.entries(t.commandRate).sort((e,t)=>t[1]-e[1]).map(([e,t])=>({name:e,value:t}));if(n.length)return{tooltip:E.item(({data:t})=>{let n=[t.name];return n.push(e.$i18n.t(`analytics.command.tip`,[+t.value.toFixed(1)])),n.join(`<br>`)}),series:[{type:`pie`,data:n,radius:[`35%`,`65%`],minShowLabelAngle:3}]}}})})},k=e=>{e.slot({type:`analytic-chart`,component:T({title:()=>e.$i18n.t(`analytics.history.title`),fields:[`analytics`],showTab:!0,options({analytics:t},n){if(!t||!t.messageByDate.length)return;let r=t.messageByDate.slice(1),i=e.$i18n.i18n.global.locale.value;return{tooltip:E.axis(([t])=>{if(!t)return``;let{name:n,value:r}=t,i=new Date(n).getDay(),a=e.$i18n.t(`analytics.history.weekdays`).split(` `)[i];return e.$i18n.t(`analytics.history.tip`,[n,a,r])}),xAxis:{type:`category`,data:r.map((e,t)=>new Date(Date.now()-(t+1)*864e5).toLocaleDateString(i)).reverse()},yAxis:{type:`value`},series:{type:`line`,smooth:!0,data:r.map(e=>e[n]).reverse()}}}})})},A=e=>`${(e-.5).toFixed()}:00-${(e+.5).toFixed()}:00`,j=e=>{e.slot({type:`analytic-chart`,component:T({title:()=>e.$i18n.t(`analytics.hour.title`),fields:[`analytics`],showTab:!0,options({analytics:t},n){if(t&&!t.messageByHour.every(e=>!e[n]))return{tooltip:E.axis(([r])=>{if(!r)return``;let[i=0]=r.data,{dataIndex:a}=r,o=t.messageByHour[a];return[`${A(i)}`,e.$i18n.t(`analytics.hour.tip`,[+(o?.[n]||0).toFixed(1)])].join(`<br>`)}),xAxis:{type:`value`,min:0,max:24,minInterval:1,maxInterval:4,axisLabel:{formatter:e=>`${e}:00`},axisPointer:{label:{formatter:({value:e})=>A(e)}}},yAxis:{type:`value`},series:[{data:t.messageByHour.map((e,t)=>[t+.5,e[n]||0]),type:`bar`,stack:`1`}]}}})})},M=e=>{e.plugin(k),e.plugin(j),e.plugin(D),e.plugin(O)},N={class:`content`},P={class:`title`},F={class:`value`},I={class:`left`},L={class:`right`},R=m({__name:`numeric`,props:{title:{},icon:{}},setup(e){return(t,n)=>{let r=y(`k-icon`),i=y(`k-card`);return g(),o(i,{class:`k-analytic-number`},u({default:S(()=>[f(r,{name:e.icon},null,8,[`name`]),l(`div`,N,[l(`p`,P,b(e.title),1),l(`p`,F,[v(t.$slots,`default`,{},()=>[n[0]||=d(`-`,-1)])])])]),_:2},[t.$slots[`footer-left`]?{name:`footer`,fn:S(()=>[l(`span`,I,[v(t.$slots,`footer-left`)]),l(`span`,L,[v(t.$slots,`footer-right`,{},()=>[n[1]||=d(`-`,-1)])])]),key:`0`}:void 0]),1024)}}}),z={key:0,class:`card-grid numeric-grid`},B=m({__name:`index`,setup(e){let{t}=r();ee(`component:analytic-number`,R);let i=a(()=>{let e=n.analytics.dauHistory.slice(1),t=n.analytics.dauHistory.length-1;return t?e.reduce((e,t)=>e+t,0)/Math.min(e.length,t):0});return(e,r)=>{let a=y(`k-slot-item`),o=y(`k-slot`);return x(n).analytics?(g(),c(`div`,z,[f(o,{name:`analytic-number`},{default:S(()=>[f(a,null,{default:S(()=>[f(R,{icon:`analytic:user`,title:x(t)(`analytics.numbers.users`)},{default:S(()=>[d(b(x(n).analytics.userCount),1)]),"footer-left":S(()=>[d(b(x(t)(`analytics.numbers.newUsers`)),1)]),"footer-right":S(()=>[d(b(x(n).analytics.userIncrement),1)]),_:1},8,[`title`]),f(R,{icon:`analytic:guild`,title:x(t)(`analytics.numbers.guilds`)},{default:S(()=>[d(b(x(n).analytics.guildCount),1)]),"footer-left":S(()=>[d(b(x(t)(`analytics.numbers.newGuilds`)),1)]),"footer-right":S(()=>[d(b(x(n).analytics.guildIncrement),1)]),_:1},8,[`title`]),f(R,{icon:`analytic:heart`,title:x(t)(`analytics.numbers.dauToday`)},{default:S(()=>[d(b(x(n).analytics.dauHistory[0]),1)]),"footer-left":S(()=>[d(b(x(t)(`analytics.numbers.dauRecent`)),1)]),"footer-right":S(()=>[d(b(+i.value.toFixed(1)),1)]),_:1},8,[`title`])]),_:1})]),_:1})])):s(`v-if`,!0)}}}),V=(e,t)=>{let n=e.__vccOpts||e;for(let[e,r]of t)n[e]=r;return n},H=V(B,[[`__scopeId`,`data-v-bc1c77bd`]]),U={class:`card-grid chart-grid`},W=m({__name:`home`,setup(e){return(e,t)=>{let n=y(`k-slot`);return g(),c(i,null,[f(H),l(`div`,U,[f(n,{name:`analytic-chart`})])],64)}}}),G={analytics:{hour:{title:`Nachrichten pro Stunde`,tip:`Tagesdurchschnitt Nachrichten: {0}`},bot:{title:`Nachrichtenanteil pro Plattform`,tipPlatform:`Plattform: {0}<br>Tagesdurchschnitt: {1}`,tipAccount:`Spitzname: {0}<br>Tagesdurchschnitt: {1}`},command:{title:`Befehlsaufruf-Häufigkeit`,tip:`Tagesdurchschnitt Aufrufe: {0}`},history:{title:`Nachrichtenverlauf`,weekdays:`So Mo Di Mi Do Fr Sa`,tip:`{0} {1}<br>Nachrichten: {2}`},stats:{send:`Gesendet`,receive:`Empfangen`},numbers:{users:`Benutzer`,newUsers:`Neue Benutzer gestern`,guilds:`Gruppen`,newGuilds:`Neue Gruppen gestern`,dauToday:`DAU heute`,dauRecent:`DAU zuletzt`}}},te={analytics:{hour:{title:`Messages per Hour`,tip:`Daily average messages: {0}`},bot:{title:`Message Share by Platform`,tipPlatform:`Platform: {0}<br>Daily average messages: {1}`,tipAccount:`Nickname: {0}<br>Daily average messages: {1}`},command:{title:`Command Invocation Frequency`,tip:`Daily average invocations: {0}`},history:{title:`Message History`,weekdays:`Sun Mon Tue Wed Thu Fri Sat`,tip:`{0} {1}<br>Messages: {2}`},stats:{send:`Sent`,receive:`Received`},numbers:{users:`Users`,newUsers:`New users yesterday`,guilds:`Groups`,newGuilds:`New groups yesterday`,dauToday:`DAU today`,dauRecent:`Recent DAU`}}},K={analytics:{hour:{title:`Messages par heure`,tip:`Moyenne quotidienne de messages : {0}`},bot:{title:`Répartition des messages par plateforme`,tipPlatform:`Plateforme : {0}<br>Moyenne quotidienne : {1}`,tipAccount:`Pseudo : {0}<br>Moyenne quotidienne : {1}`},command:{title:`Fréquence d'appel des commandes`,tip:`Appels quotidiens moyens : {0}`},history:{title:`Historique des messages`,weekdays:`dim. lun. mar. mer. jeu. ven. sam.`,tip:`{0} {1}<br>Messages : {2}`},stats:{send:`Envoyés`,receive:`Reçus`},numbers:{users:`Utilisateurs`,newUsers:`Nouveaux utilisateurs hier`,guilds:`Groupes`,newGuilds:`Nouveaux groupes hier`,dauToday:`DAU du jour`,dauRecent:`DAU récent`}}},q={analytics:{hour:{title:`毎時間のメッセージ数`,tip:`日平均メッセージ数:{0}`},bot:{title:`プラットフォーム別メッセージ割合`,tipPlatform:`プラットフォーム:{0}<br>日平均メッセージ数:{1}`,tipAccount:`ニックネーム:{0}<br>日平均メッセージ数:{1}`},command:{title:`コマンド実行頻度`,tip:`日平均実行回数:{0}`},history:{title:`履歴メッセージ数`,weekdays:`日 月 火 水 木 金 土`,tip:`{0} {1}曜日<br>メッセージ数:{2}`},stats:{send:`送信`,receive:`受信`},numbers:{users:`ユーザー数`,newUsers:`昨日の新規ユーザー`,guilds:`グループ数`,newGuilds:`昨日の新規グループ`,dauToday:`本日の DAU`,dauRecent:`最近の DAU`}}},J={analytics:{hour:{title:`Сообщений по часам`,tip:`В среднем сообщений в день: {0}`},bot:{title:`Доля сообщений по платформам`,tipPlatform:`Платформа: {0}<br>В среднем сообщений в день: {1}`,tipAccount:`Ник: {0}<br>В среднем сообщений в день: {1}`},command:{title:`Частота вызова команд`,tip:`В среднем вызовов в день: {0}`},history:{title:`История сообщений`,weekdays:`Вс Пн Вт Ср Чт Пт Сб`,tip:`{0} {1}<br>Сообщений: {2}`},stats:{send:`Отправлено`,receive:`Получено`},numbers:{users:`Пользователи`,newUsers:`Новые пользователи за вчера`,guilds:`Группы`,newGuilds:`Новые группы за вчера`,dauToday:`DAU сегодня`,dauRecent:`DAU за последнее время`}}},Y={analytics:{hour:{title:`每小时消息数量`,tip:`日均消息数量:{0}`},bot:{title:`各平台消息占比`,tipPlatform:`平台:{0}<br>日均消息数量:{1}`,tipAccount:`昵称:{0}<br>日均消息数量:{1}`},command:{title:`指令调用频率`,tip:`日均调用:{0}`},history:{title:`历史消息数量`,weekdays:`日 一 二 三 四 五 六`,tip:`{0} 星期{1}<br>消息数量:{2}`},stats:{send:`发送`,receive:`接收`},numbers:{users:`用户数量`,newUsers:`昨日新增用户`,guilds:`群组数量`,newGuilds:`昨日新增群组`,dauToday:`今日 DAU`,dauRecent:`近期 DAU`}}},X={analytics:{hour:{title:`每小時訊息數量`,tip:`日均訊息數量:{0}`},bot:{title:`各平台訊息佔比`,tipPlatform:`平台:{0}<br>日均訊息數量:{1}`,tipAccount:`暱稱:{0}<br>日均訊息數量:{1}`},command:{title:`指令呼叫頻率`,tip:`日均呼叫:{0}`},history:{title:`歷史訊息數量`,weekdays:`日 一 二 三 四 五 六`,tip:`{0} 星期{1}<br>訊息數量:{2}`},stats:{send:`傳送`,receive:`接收`},numbers:{users:`使用者數量`,newUsers:`昨日新增使用者`,guilds:`群組數量`,newGuilds:`昨日新增群組`,dauToday:`今日 DAU`,dauRecent:`近期 DAU`}}},Z={},Q={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 640 512`};function ne(e,t){return g(),c(`svg`,Q,[...t[0]||=[l(`path`,{fill:`currentColor`,d:`M274.7 304H173.3c-95.73 0-173.3 77.6-173.3 173.3C0 496.5 15.52 512 34.66 512H413.3C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304zM413.3 480H34.66C33.2 480 32 478.8 32 477.3C32 399.4 95.4 336 173.3 336H274.7C352.6 336 416 399.4 416 477.3C416 478.8 414.8 480 413.3 480zM224 256c70.7 0 128-57.31 128-128S294.7 0 224 0C153.3 0 96 57.31 96 128S153.3 256 224 256zM224 32c52.94 0 96 43.06 96 96c0 52.93-43.06 96-96 96S128 180.9 128 128C128 75.06 171.1 32 224 32zM375.1 241C392.9 250.8 412.3 256 432 256C493.8 256 544 205.8 544 144S493.8 32 432 32c-12.83 0-25.39 2.156-37.34 6.391c-8.328 2.953-12.69 12.09-9.734 20.42c2.953 8.344 12.12 12.66 20.42 9.734C413.9 65.53 422.8 64 432 64C476.1 64 512 99.89 512 144S476.1 224 432 224c-14.08 0-27.91-3.703-39.98-10.69c-7.656-4.453-17.44-1.828-21.86 5.828C365.7 226.8 368.3 236.6 375.1 241zM490.7 320H448c-8.844 0-16 7.156-16 16S439.2 352 448 352h42.67C555.4 352 608 404.6 608 469.3C608 475.2 603.2 480 597.3 480H496c-8.844 0-16 7.156-16 16s7.156 16 16 16h101.3C620.9 512 640 492.9 640 469.3C640 386.1 573 320 490.7 320z`},null,-1)]])}var re=V(Z,[[`render`,ne]]),ie={},ae={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function oe(e,t){return g(),c(`svg`,ae,[...t[0]||=[l(`path`,{fill:`currentColor`,d:`M255.1 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 0 232.4 0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L255.1 96zM255.1 141.3L221.4 106.6C196.1 81.31 160 69.77 124.7 75.66C71.21 84.58 31.1 130.9 31.1 185.1V190.9C31.1 223.6 45.55 254.7 69.42 277L250.1 445.7C251.7 447.2 253.8 448 255.1 448C258.2 448 260.3 447.2 261.9 445.7L442.6 277C466.4 254.7 480 223.6 480 190.9V185.1C480 130.9 440.8 84.58 387.3 75.66C351.1 69.77 315.9 81.31 290.6 106.6L255.1 141.3z`},null,-1)]])}var $=V(ie,[[`render`,oe]]),se={},ce={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 512 512`};function le(e,t){return g(),c(`svg`,ce,[...t[0]||=[l(`path`,{fill:`currentColor`,d:`M256 0C397.4 0 512 114.6 512 256C512 397.4 397.4 512 256 512C172.2 512 97.87 471.8 51.19 409.6C45.88 402.5 47.31 392.5 54.37 387.2C61.44 381.9 71.47 383.3 76.78 390.4C117.7 444.8 182.7 480 256 480C379.7 480 480 379.7 480 256C480 132.3 379.7 32 256 32C166.7 32 89.51 84.3 53.55 160H144C152.8 160 160 167.2 160 176C160 184.8 152.8 192 144 192H16C7.164 192 0 184.8 0 176V48C0 39.16 7.164 32 16 32C24.84 32 32 39.16 32 48V131.1C75.66 53.29 159.6 0 256 0zM256 128C264.8 128 272 135.2 272 144V249.4L347.3 324.7C353.6 330.9 353.6 341.1 347.3 347.3C341.1 353.6 330.9 353.6 324.7 347.3L244.7 267.3C241.7 264.3 239.1 260.2 239.1 256V144C239.1 135.2 247.2 128 255.1 128H256z`},null,-1)]])}var ue=V(se,[[`render`,le]]),de={},fe={class:`k-icon`,xmlns:`http://www.w3.org/2000/svg`,viewBox:`0 0 448 512`};function pe(e,t){return g(),c(`svg`,fe,[...t[0]||=[l(`path`,{fill:`currentColor`,d:`M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM224 32c52.94 0 96 43.06 96 96c0 52.93-43.06 96-96 96S128 180.9 128 128C128 75.06 171.1 32 224 32zM274.7 304H173.3C77.61 304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304zM413.3 480H34.66C33.2 480 32 478.8 32 477.3C32 399.4 95.4 336 173.3 336h101.3C352.6 336 416 399.4 416 477.3C416 478.8 414.8 480 413.3 480z`},null,-1)]])}var me=V(de,[[`render`,pe]]);e.register(`analytic:guild`,re),e.register(`analytic:heart`,$),e.register(`analytic:history`,ue),e.register(`analytic:user`,me);var he=e=>{e.$i18n.extend(`de-DE`,G),e.$i18n.extend(`en-US`,te),e.$i18n.extend(`fr-FR`,K),e.$i18n.extend(`ja-JP`,q),e.$i18n.extend(`ru-RU`,J),e.$i18n.extend(`zh-CN`,Y),e.$i18n.extend(`zh-TW`,X),e.plugin(M),e.slot({type:`home`,component:W,order:0})};export{he as default};
package/dist/style.css CHANGED
@@ -1,2 +1,2 @@
1
- .analytic-chart header{justify-content:space-between;display:flex}.analytic-chart header .right{gap:1rem;font-size:1rem;font-weight:400;display:flex}.analytic-chart header .tab-item{cursor:pointer;padding:0 4px}.analytic-chart header .tab-item.active{color:var(--primary);border-bottom:2px solid var(--primary)}.k-analytic-number .k-card-body{justify-content:space-between;align-items:center;margin:1rem 0;display:flex}.k-analytic-number .k-card-body .k-icon{width:3rem;height:2.5rem;color:var(--k-text-normal);transition:color .3s}@media screen and (width<=768px){.k-analytic-number .k-card-body .k-icon{height:2.25rem}}.k-analytic-number .k-card-body .content{float:right;text-align:right;flex-direction:column;justify-content:space-around;height:3rem;display:flex}.k-analytic-number .k-card-body p{margin:0;font-size:.9375rem}.k-analytic-number .k-card-body .value{font-size:1rem}@media (width<=768px){.k-analytic-number .k-card-body{margin:.5rem 0;padding:0 1rem}}.k-analytic-number footer{border-top:1px solid var(--k-card-border);justify-content:space-between;margin-bottom:.75rem;padding-top:.75rem;font-size:.9em;display:flex}@media (width<=768px){.k-analytic-number footer{margin:.5rem 0;padding:.5rem 1rem 0}}.numeric-grid[data-v-41af15ac]{grid-template-columns:repeat(4,1fr)}@media screen and (width<=1280px){.numeric-grid[data-v-41af15ac]{grid-template-columns:repeat(2,1fr)}}.card-grid{margin:var(--card-margin);grid-gap:var(--card-margin);display:grid}.chart-grid .echarts{max-width:100%;margin:0 auto}@media (width>=1280px){.chart-grid{grid-template-columns:repeat(2,1fr)}}@media (width>=1280px) and (width>=1600px){.chart-grid .echarts{width:600px;max-width:100%;height:400px;margin:0 auto}}@media (width>=1280px) and (width<=1600px){.chart-grid .echarts{width:480px;height:360px}}@media (width<=1280px){.chart-grid{grid-template-columns:1fr}}@media (width<=1280px) and (width>=768px){.chart-grid .echarts{width:800px;height:400px}}@media (width<=1280px) and (width<=768px) and (width>=480px){.chart-grid .echarts{width:600px;height:400px}}@media (width<=1280px) and (width<=480px){.chart-grid .echarts{width:100%;height:300px}}.grid{display:grid}.flex{display:flex}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease{transition-timing-function:cubic-bezier(.4,0,.2,1)}
1
+ .analytic-chart header{justify-content:space-between;display:flex}.analytic-chart header .right{gap:1rem;font-size:1rem;font-weight:400;display:flex}.analytic-chart header .tab-item{cursor:pointer;padding:0 4px}.analytic-chart header .tab-item.active{color:var(--primary);border-bottom:2px solid var(--primary)}.k-analytic-number .k-card-body{justify-content:space-between;align-items:center;margin:1rem 0;display:flex}.k-analytic-number .k-card-body .k-icon{width:3rem;height:2.5rem;color:var(--k-text-normal);transition:color .3s}@media screen and (width<=768px){.k-analytic-number .k-card-body .k-icon{height:2.25rem}}.k-analytic-number .k-card-body .content{float:right;text-align:right;flex-direction:column;justify-content:space-around;height:3rem;display:flex}.k-analytic-number .k-card-body p{margin:0;font-size:.9375rem}.k-analytic-number .k-card-body .value{font-size:1rem}@media (width<=768px){.k-analytic-number .k-card-body{margin:.5rem 0;padding:0 1rem}}.k-analytic-number footer{border-top:1px solid var(--k-card-border);justify-content:space-between;margin-bottom:.75rem;padding-top:.75rem;font-size:.9em;display:flex}@media (width<=768px){.k-analytic-number footer{margin:.5rem 0;padding:.5rem 1rem 0}}.numeric-grid[data-v-bc1c77bd]{grid-template-columns:repeat(4,1fr)}@media screen and (width<=1280px){.numeric-grid[data-v-bc1c77bd]{grid-template-columns:repeat(2,1fr)}}.card-grid{margin:var(--card-margin);grid-gap:var(--card-margin);display:grid}.chart-grid .echarts{max-width:100%;margin:0 auto}@media (width>=1280px){.chart-grid{grid-template-columns:repeat(2,1fr)}}@media (width>=1280px) and (width>=1600px){.chart-grid .echarts{width:600px;max-width:100%;height:400px;margin:0 auto}}@media (width>=1280px) and (width<=1600px){.chart-grid .echarts{width:480px;height:360px}}@media (width<=1280px){.chart-grid{grid-template-columns:1fr}}@media (width<=1280px) and (width>=768px){.chart-grid .echarts{width:800px;height:400px}}@media (width<=1280px) and (width<=768px) and (width>=480px){.chart-grid .echarts{width:600px;height:400px}}@media (width<=1280px) and (width<=480px){.chart-grid .echarts{width:100%;height:300px}}.grid{display:grid}.flex{display:flex}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter,backdrop-filter;transition-duration:.15s;transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease{transition-timing-function:cubic-bezier(.4,0,.2,1)}
2
2
  /*$vite$:1*/
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: Intervall für das Senden der Statistiken.
6
+ recentDayCount: Anzahl der letzten Tage, die in die Statistiken einfließen.
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: Interval for pushing statistics.
6
+ recentDayCount: Number of recent days to include in statistics.
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: Intervalle d'envoi des statistiques.
6
+ recentDayCount: Nombre de jours récents à inclure dans les statistiques.
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: 統計データを送信する間隔。
6
+ recentDayCount: 統計に含める直近の日数。
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: Интервал отправки статистики.
6
+ recentDayCount: Количество последних дней для статистики.
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: 统计数据推送的时间间隔。
6
+ recentDayCount: 统计最近几天的数据。
@@ -0,0 +1,6 @@
1
+ # SPDX-License-Identifier: AGPL-3.0-only
2
+ # Copyright (c) 2019-present Shigma and Koishijs contributors.
3
+ # Copyright (c) 2026-present Koishi-CE contributors.
4
+
5
+ statsInternal: 統計資料推送的時間間隔。
6
+ recentDayCount: 統計最近幾天的資料。
package/lib/index.d.ts CHANGED
@@ -15,7 +15,7 @@ declare module "@koishi-ce/console" {
15
15
  }
16
16
  }
17
17
  /** 收发消息计数对:send 为发出条数,receive 为收到条数。 */
18
- interface MessageStats {
18
+ export interface MessageStats {
19
19
  send: number;
20
20
  receive: number;
21
21
  }
@@ -149,4 +149,4 @@ declare namespace Analytics {
149
149
  }
150
150
  }
151
151
  //#endregion
152
- export { MessageStats, Analytics as default };
152
+ export { Analytics as default };
package/lib/index.mjs CHANGED
@@ -1,6 +1,13 @@
1
1
  import { resolve } from "node:path";
2
2
  import { DataService } from "@koishi-ce/console";
3
3
  import { $, Logger, Schema, Time, deepEqual, pick } from "@koishi-ce/koishi";
4
+ import deDE from "./assets/de-DE-C35VOgt9.yml";
5
+ import enUS from "./assets/en-US-C8mb89z7.yml";
6
+ import frFR from "./assets/fr-FR-WljZXOoA.yml";
7
+ import jaJP from "./assets/ja-JP-DP_l11TV.yml";
8
+ import ruRU from "./assets/ru-RU-DWF7MRwv.yml";
9
+ import zhCN from "./assets/zh-CN-Cof4p_74.yml";
10
+ import zhTW from "./assets/zh-TW-_jfudK1s.yml";
4
11
  //#region src/index.ts
5
12
  /**
6
13
  * analytics 插件(node 侧):消息与指令的统计分析服务。
@@ -18,6 +25,14 @@ var Analytics = class extends DataService {
18
25
  static Config = Schema.object({
19
26
  statsInternal: Schema.natural().role("ms").description("统计数据推送的时间间隔。").default(Time.minute * 10),
20
27
  recentDayCount: Schema.natural().description("统计最近几天的数据。").default(7)
28
+ }).i18n({
29
+ "de-DE": deDE,
30
+ "en-US": enUS,
31
+ "fr-FR": frFR,
32
+ "ja-JP": jaJP,
33
+ "ru-RU": ruRU,
34
+ "zh-CN": zhCN,
35
+ "zh-TW": zhTW
21
36
  });
22
37
  config;
23
38
  /** 上次落库时间,用于判断距下次上传是否超过 statsInternal。 */
@@ -106,7 +121,7 @@ var Analytics = class extends DataService {
106
121
  }
107
122
  createIndex(session) {
108
123
  return {
109
- selfId: session.selfId,
124
+ selfId: session.selfId ?? "",
110
125
  platform: session.platform,
111
126
  date: Time.getDateNumber(),
112
127
  hour: (/* @__PURE__ */ new Date()).getHours()
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@koishi-ce/plugin-analytics",
3
3
  "description": "Show statistics for Koishi application",
4
- "version": "1.0.2",
4
+ "version": "1.1.1",
5
5
  "type": "module",
6
6
  "main": "lib/index.mjs",
7
7
  "typings": "lib/index.d.ts",
@@ -54,9 +54,10 @@
54
54
  "@koishi-ce/koishi": "^1.0.0"
55
55
  },
56
56
  "devDependencies": {
57
- "@koishi-ce/client": "^1.0.3",
57
+ "@koishi-ce/plugin-database-memory": "^1.1.0",
58
+ "@koishi-ce/client": "^1.1.1",
58
59
  "echarts": "^6.1.0",
59
- "@koishi-ce/koishi": "^1.0.4",
60
+ "@koishi-ce/koishi": "^1.0.11",
60
61
  "vite": "^8.2.2",
61
62
  "vue-echarts": "^8.1.0"
62
63
  },
package/src/index.test.ts CHANGED
@@ -2,11 +2,17 @@
2
2
  // Copyright (c) 2019-present Shigma and Koishijs contributors.
3
3
  // Copyright (c) 2026-present Koishi-CE contributors.
4
4
 
5
- import { afterAll, beforeAll, describe, expect, it } from "bun:test";
5
+ import {
6
+ afterAll,
7
+ beforeAll,
8
+ describe,
9
+ expect,
10
+ it,
11
+ } from "bun:test";
6
12
  import { Console, type Entry } from "@koishi-ce/console";
7
13
  import { App, type Plugin, Time } from "@koishi-ce/koishi";
14
+ import memory from "@koishi-ce/plugin-database-memory";
8
15
  import mock from "@koishi-ce/plugin-mock";
9
- import memory from "@koishijs/plugin-database-memory";
10
16
  import analytics from "./index.ts";
11
17
 
12
18
  // Analytics 类仅默认导出,实例类型经构造器派生(namespace 侧的 Payload 等随实例可用)
@@ -14,7 +20,10 @@ type Analytics = InstanceType<typeof analytics>;
14
20
 
15
21
  /** 控制台服务桩:仅实现入口登记所需的最小面。 */
16
22
  class FakeConsole extends Console {
17
- protected resolveEntry(_files: Entry.Files, _key: string): string[] {
23
+ protected resolveEntry(
24
+ _files: Entry.Files,
25
+ _key: string,
26
+ ): string[] {
18
27
  return [];
19
28
  }
20
29
  }
@@ -22,17 +31,23 @@ class FakeConsole extends Console {
22
31
  const app = new App();
23
32
 
24
33
  // 同 admin:CJS 实现配 ESM 声明,nodenext 互操作视图多包一层 default,类型层穿透取真实类
25
- app.plugin(memory as unknown as typeof memory.default);
34
+ app.plugin(memory);
26
35
  // Console 基类的 static inject 是 cordis 3 旧形态,与 Plugin.Constructor 期待类型不兼容,仅做类型层转型
27
- app.plugin(FakeConsole as unknown as Plugin.Constructor<App>);
36
+ app.plugin(
37
+ FakeConsole as unknown as Plugin.Constructor<App>,
38
+ );
28
39
  // statsInternal 归零:每条消息事件后立即落库,便于断言
29
- app.plugin(analytics, { statsInternal: 0, recentDayCount: 7 });
40
+ app.plugin(analytics, {
41
+ statsInternal: 0,
42
+ recentDayCount: 7,
43
+ });
30
44
  app.plugin(mock);
31
45
 
32
46
  // 注册一个空指令,用于驱动 command/execute 计数;带回复以驱动 send 事件
33
47
  app.command("analytics-probe").action(() => "probe-done");
34
48
 
35
- const service = () => app.get("console.services.analytics") as Analytics;
49
+ const service = () =>
50
+ app.get("console.services.analytics") as Analytics;
36
51
 
37
52
  const client = app.mock.client("123", "321");
38
53
 
@@ -82,19 +97,27 @@ describe("analytics 统计服务", () => {
82
97
  await service().upload(true);
83
98
 
84
99
  const today = Time.getDateNumber();
85
- const rows = await app.database.get("analytics.message", {
86
- date: today,
87
- });
100
+ const rows = await app.database.get(
101
+ "analytics.message",
102
+ {
103
+ date: today,
104
+ },
105
+ );
88
106
  // mock 会话:用户消息计入 receive(mock 编码器不派发 send 会话,发出侧见下一用例)
89
107
  expect(rows.length).toBeGreaterThan(0);
90
- const receiveRow = rows.find((row) => row.type === "receive");
108
+ const receiveRow = rows.find(
109
+ (row) => row.type === "receive",
110
+ );
91
111
  expect(receiveRow?.count).toBe(1);
92
112
  expect(receiveRow?.platform).toBe("mock");
93
113
  expect(receiveRow?.selfId).toBe("514");
94
114
 
95
- const commands = await app.database.get("analytics.command", {
96
- date: today,
97
- });
115
+ const commands = await app.database.get(
116
+ "analytics.command",
117
+ {
118
+ date: today,
119
+ },
120
+ );
98
121
  expect(commands).toHaveLength(1);
99
122
  expect(commands[0]).toMatchObject({
100
123
  name: "analytics-probe",
@@ -105,30 +128,62 @@ describe("analytics 统计服务", () => {
105
128
 
106
129
  it("send 事件计入发出消息数", async () => {
107
130
  // mock 编码器不派发 send 会话,此处直接派发最小会话驱动计数
108
- app.emit("send", { selfId: "514", platform: "mock" } as never);
131
+ app.emit("send", {
132
+ selfId: "514",
133
+ platform: "mock",
134
+ } as never);
109
135
  await service().upload(true);
110
136
  const today = Time.getDateNumber();
111
- const rows = await app.database.get("analytics.message", {
112
- date: today,
113
- type: "send",
114
- });
137
+ const rows = await app.database.get(
138
+ "analytics.message",
139
+ {
140
+ date: today,
141
+ type: "send",
142
+ },
143
+ );
115
144
  expect(rows[0]?.count).toBe(1);
116
145
  });
117
146
 
147
+ it("无 selfId 的会话按空串落库不丢统计", async () => {
148
+ // upstream: koishijs/koishi#1501——selfId 是联合主键的非空列,
149
+ // 裸传 undefined 会令整批 upsert 因约束失败而丢失统计
150
+ app.emit("send", { platform: "sandbox:web" } as never);
151
+ await service().upload(true);
152
+ const today = Time.getDateNumber();
153
+ const rows = await app.database.get(
154
+ "analytics.message",
155
+ {
156
+ date: today,
157
+ platform: "sandbox:web",
158
+ },
159
+ );
160
+ // upsert 回调形式在同键行上不合并(既有行为,SUM 语义不受
161
+ // 影响),断言存在空串键的行即可
162
+ expect(
163
+ rows.some(
164
+ (row) => row.selfId === "" && row.count >= 1,
165
+ ),
166
+ ).toBe(true);
167
+ });
168
+
118
169
  it("非强制上传在间隔超时后触发(statsInternal=0)", async () => {
119
170
  // statsInternal 为 0:任意时间流逝都满足间隔条件,走 forced=false 路径
120
171
  await new Promise((resolve) => setTimeout(resolve, 5));
121
172
  const before = +service().lastUpdate;
122
173
  await service().upload();
123
174
  // 触发后 lastUpdate 刷新为更新的时间
124
- expect(+service().lastUpdate).toBeGreaterThanOrEqual(before);
175
+ expect(+service().lastUpdate).toBeGreaterThanOrEqual(
176
+ before,
177
+ );
125
178
  });
126
179
 
127
180
  it("非强制上传在跨小时边界触发", async () => {
128
181
  // 将记录的小时号置为不可能值:dateHour !== updateHour 分支命中
129
182
  service().updateHour = -1;
130
183
  await service().upload();
131
- expect(service().updateHour).toBe(new Date().getHours());
184
+ expect(service().updateHour).toBe(
185
+ new Date().getHours(),
186
+ );
132
187
  });
133
188
 
134
189
  it("download 聚合数值指标与各图表数据", async () => {
@@ -155,13 +210,25 @@ describe("analytics 统计服务", () => {
155
210
  expect(bot?.send).toBe(3);
156
211
 
157
212
  // 按日历史:昨天收 2 发 3,今天恒为 0 占位
158
- expect(payload.messageByDate[1]).toEqual({ receive: 2, send: 3 });
159
- expect(payload.messageByDate[0]).toEqual({ receive: 0, send: 0 });
213
+ expect(payload.messageByDate[1]).toEqual({
214
+ receive: 2,
215
+ send: 3,
216
+ });
217
+ expect(payload.messageByDate[0]).toEqual({
218
+ receive: 0,
219
+ send: 0,
220
+ });
160
221
 
161
222
  // 按小时分布:24 个时段,10 点的日均收发
162
223
  expect(payload.messageByHour).toHaveLength(24);
163
- expect(payload.messageByHour[10]).toEqual({ receive: 2, send: 3 });
164
- expect(payload.messageByHour[23]).toEqual({ receive: 0, send: 0 });
224
+ expect(payload.messageByHour[10]).toEqual({
225
+ receive: 2,
226
+ send: 3,
227
+ });
228
+ expect(payload.messageByHour[23]).toEqual({
229
+ receive: 0,
230
+ send: 0,
231
+ });
165
232
  });
166
233
 
167
234
  it("get 按自然日缓存聚合结果", async () => {