koishi-plugin-new-auth 0.3.1 → 0.4.4
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/README.md +43 -33
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/index.d.ts +37 -10
- package/lib/index.js +327 -97
- package/newauth.md +5 -5
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
# koishi-plugin-new-auth
|
|
2
2
|
|
|
3
|
-
`koishi-plugin-new-auth`
|
|
3
|
+
`koishi-plugin-new-auth` 是一个保守的 Koishi 指令权限层,按 `newauth.md` 中的设计实现“角色 + 作用域 + 指令策略”。
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
当前版本已经覆盖:
|
|
6
6
|
|
|
7
|
-
-
|
|
8
|
-
-
|
|
9
|
-
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
- Koishi Console
|
|
7
|
+
- 指令注册后进入权限表;
|
|
8
|
+
- 新发现的指令默认是 `pending`;
|
|
9
|
+
- 待配置指令默认只有 Bot 管理员可执行;
|
|
10
|
+
- 旧 `authority` 只记录为建议,不直接放权;
|
|
11
|
+
- 策略按 `scope + role + command` 判断;
|
|
12
|
+
- 群主、群管理员、群成员与 Bot 管理员分离;
|
|
13
|
+
- 自定义角色、显式成员、角色权限复制;
|
|
14
|
+
- Koishi Console 中的 `⌗ 新权限` WebUI。
|
|
15
15
|
|
|
16
|
-
##
|
|
16
|
+
## 配置
|
|
17
17
|
|
|
18
18
|
```ts
|
|
19
19
|
export default {
|
|
@@ -25,13 +25,13 @@ export default {
|
|
|
25
25
|
}
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
`botAdmins`
|
|
28
|
+
`botAdmins` 是显式 Bot 实例管理员,平台群主不会自动成为 Bot 管理员。
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
为了兼容旧实例,`trustLegacyAuthorityAsAdmin` 打开时,`authority >= legacyAdminAuthority` 的 Koishi 用户会被视为 Bot 管理员。这个兼容只来自 Koishi 用户权限,不来自平台群主身份。
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## 管理指令
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
所有管理指令都在 `newauth` 下。
|
|
35
35
|
|
|
36
36
|
```txt
|
|
37
37
|
newauth.commands --pending
|
|
@@ -39,6 +39,7 @@ newauth.roles
|
|
|
39
39
|
newauth.allow <roleId> <command> [scope]
|
|
40
40
|
newauth.deny <roleId> <command> [scope]
|
|
41
41
|
newauth.inherit <roleId> <command> [scope]
|
|
42
|
+
newauth.assign <command> <roleIds> [scope]
|
|
42
43
|
newauth.disable <command>
|
|
43
44
|
newauth.enable <command>
|
|
44
45
|
newauth.admin.add <uid>
|
|
@@ -46,18 +47,28 @@ newauth.admin.remove <uid>
|
|
|
46
47
|
newauth.role.create <id> <name> [scopeType]
|
|
47
48
|
newauth.member.add <roleId> <uid> [scope]
|
|
48
49
|
newauth.member.remove <roleId> <uid> [scope]
|
|
50
|
+
newauth.role.copy <sourceRoleId> <targetRoleId> [scope]
|
|
51
|
+
newauth.guild.commands [query]
|
|
52
|
+
newauth.guild.allow <roleId> <command>
|
|
53
|
+
newauth.guild.deny <roleId> <command>
|
|
54
|
+
newauth.guild.inherit <roleId> <command>
|
|
55
|
+
newauth.plugin.assign <plugin> <target> [scope]
|
|
49
56
|
```
|
|
50
57
|
|
|
51
|
-
`uid`
|
|
58
|
+
`uid` 使用 `platform:userId`,例如 `onebot:10000`。
|
|
52
59
|
|
|
53
|
-
`scope`
|
|
60
|
+
`scope` 可以是:
|
|
54
61
|
|
|
55
62
|
- `global`
|
|
56
63
|
- `guild:<platform>:<guildId>`
|
|
57
64
|
|
|
58
|
-
|
|
65
|
+
省略 `scope` 时,策略指令默认写入 `global`。
|
|
59
66
|
|
|
60
|
-
|
|
67
|
+
`newauth.assign` 与待配置 WebUI 的角色勾选行为一致:列出的角色写为允许,其他角色在该作用域下写为关闭,并把待配置指令变为已配置。
|
|
68
|
+
|
|
69
|
+
`newauth.guild.*` 是当前群的自治入口,只能由 Bot 管理员或当前群群主使用,只能修改当前群、已配置且允许群内自治的指令,不能修改全局权限或 Bot 管理员角色。
|
|
70
|
+
|
|
71
|
+
## 内置角色
|
|
61
72
|
|
|
62
73
|
```txt
|
|
63
74
|
bot-admin
|
|
@@ -67,24 +78,23 @@ guild-member
|
|
|
67
78
|
guest
|
|
68
79
|
```
|
|
69
80
|
|
|
70
|
-
|
|
81
|
+
自定义角色不继承其他角色。要让自定义角色拥有某些指令,需要显式写策略,或在 WebUI / `newauth.role.copy` 中复制另一个角色当前的显式策略。
|
|
71
82
|
|
|
72
83
|
## WebUI
|
|
73
84
|
|
|
74
|
-
|
|
85
|
+
安装 Koishi Console 后打开 `⌗ 新权限` 页面。
|
|
75
86
|
|
|
76
|
-
|
|
87
|
+
页面提供:
|
|
77
88
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- batch assigning all commands from one plugin.
|
|
89
|
+
- 以角色为入口的指令权限编辑;
|
|
90
|
+
- 全局默认和单群作用域切换;
|
|
91
|
+
- 待配置指令审查和角色勾选分配;
|
|
92
|
+
- 旧 `authority` 建议与基于 ChatLuna 默认模型的 AI 分配审查表;
|
|
93
|
+
- 指令状态和群内自治开关;
|
|
94
|
+
- 自定义角色创建、显式成员管理;
|
|
95
|
+
- 从其他角色复制权限;
|
|
96
|
+
- 按插件批量采用建议、给群成员、给群管理员或仅给 Bot 管理员。
|
|
87
97
|
|
|
88
|
-
|
|
98
|
+
WebUI 是 Koishi 实例管理员界面,Console 入口和所有事件都要求 `authority: 4`。群主自治只应影响允许自治的单群指令,不等于进入实例管理面板。
|
|
89
99
|
|
|
90
|
-
|
|
100
|
+
AI 自动分配不会按关键词硬编码推断。安装并启用 ChatLuna 且设置默认模型后,newauth 会把指令元数据和角色列表交给 ChatLuna 默认模型,让模型返回允许的角色;WebUI 会先显示审查表并填入勾选项,只有管理员点击保存后才写入策略。模型不可用或返回无效时回退到旧 `authority` 建议。
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import{defineComponent as ve,ref as i,computed as v,watch as H,openBlock as s,createElementBlock as u,createElementVNode as l,toDisplayString as o,withDirectives as c,Fragment as f,renderList as y,vModelSelect as _,normalizeClass as w,createCommentVNode as B,withModifiers as J,vModelText as x,vModelCheckbox as ce,createTextVNode as q}from"vue";import{store as Q,send as g}from"@koishijs/client";const pe={class:"new-auth-page"},ge={class:"topbar"},be={class:"top-actions"},me=["value"],fe=["disabled"],ye=["disabled"],ke={class:"tabs"},he={key:0,class:"error"},_e={key:1,class:"role-layout"},we={class:"role-list"},Ce=["onClick"],Ie=["disabled"],$e={key:0,class:"detail"},Ve={class:"detail-head"},Se={class:"detail-tools"},Ue=["value"],Pe=["disabled"],Re={class:"check"},xe={class:"member-panel"},Ae={class:"member-list"},Me=["disabled","onClick"],Oe=["value"],Te=["disabled"],Ge={class:"filters"},Ne={class:"command-list"},Be={class:"command-main"},De={class:"state-buttons"},Ee=["onClick"],Le=["onClick"],Fe=["onClick"],qe={key:2,class:"pending-grid"},ze={class:"row-actions"},Ke=["disabled","onClick"],je=["disabled","onClick"],He={key:0,class:"empty"},Je={key:3,class:"commands-table"},Qe={class:"filters"},We={class:"plugin-bulk"},Xe=["value"],Ye=["disabled"],Ze=["disabled"],el=["disabled"],ll=["disabled"],tl={class:"command-main"},nl={class:"command-controls"},ol={class:"check"},sl=["checked","onChange"],ul=["value","onChange"],al=ve({__name:"new-auth",setup(S){const b=i("roles"),a=i("global"),d=i(""),k=i(""),C=i("all"),D=i(false),r=i(false),A=i(""),I=i(""),U=i(""),E=i("guild"),P=i(""),M=i("global"),O=i(""),h=i(""),$=v(()=>Q.newauth||{roles:[],commands:[],policies:[],members:[],scopes:[{id:"global",name:"全局默认"}],pendingCount:0}),R=v(()=>$.value.roles),T=v(()=>$.value.commands),W=v(()=>$.value.policies),X=v(()=>$.value.members),Y=v(()=>{var n;return(n=$.value.scopes)!=null&&n.length?$.value.scopes:[{id:"global",name:"全局默认"}]}),G=v(()=>T.value.filter(n=>n.status==="pending")),V=v(()=>R.value.find(n=>n.id===d.value)),z=v(()=>X.value.filter(n=>n.roleId===d.value)),Z=v(()=>R.value.filter(n=>n.id!==d.value)),ee=v(()=>[...new Set(T.value.map(n=>n.plugin))].sort());H(R,n=>{!d.value&&n.length&&(d.value=n[0].id)},{immediate:true}),H(a,n=>{M.value=n==="global"?"global":n});const K=v(()=>{const n=k.value.toLowerCase();return T.value.filter(t=>C.value!=="all"&&t.status!==C.value?false:n?[t.id,t.name,t.commandPath,t.plugin,t.description].some(e=>e==null?void 0:e.toLowerCase().includes(n)):true)}),le=v(()=>V.value?K.value.filter(n=>{var t,e;return D.value||((t=V.value)==null?void 0:t.id)==="bot-admin"||((e=V.value)==null?void 0:e.scopeType)==="global"?true:n.allowGuildOverride}):[]);function L(n){const t=W.value.find(e=>e.scope===a.value&&e.roleId===d.value&&e.commandId===n);return(t==null?void 0:t.state)||"inherit"}async function p(n){r.value=true,A.value="";try{await n}catch(t){A.value=t instanceof Error?t.message:String(t)}finally{r.value=false}}async function te(){return p((async()=>{const n=await g("newauth/getData");Q.newauth=n})())}function F(n,t){return p(g("newauth/setPolicy",{scope:a.value,roleId:d.value,commandId:n,state:t}))}function j(n,t){return p(g("newauth/applySuggestion",{commandId:n,scope:a.value,mode:t}))}function ne(){return p(g("newauth/autoAssignPending",{scope:a.value,mode:"advisor"}))}function oe(n,t){const e=t.target;return p(g("newauth/setGuildOverride",{commandId:n.id,allowGuildOverride:e.checked}))}function se(n,t){const e=t.target;return p(g("newauth/setCommandStatus",{commandId:n.id,status:e.value}))}function ue(){return p((async()=>{await g("newauth/createRole",{id:I.value,name:U.value,scopeType:E.value}),d.value=I.value,I.value="",U.value=""})())}function ae(){return p((async()=>{await g("newauth/addMember",{roleId:d.value,uid:P.value,scope:M.value}),P.value=""})())}function ie(n){return p(g("newauth/removeMember",{roleId:n.roleId,uid:`${n.platform}:${n.userId}`,scope:n.scope}))}function de(){return p(g("newauth/copyRolePolicies",{sourceRoleId:O.value,targetRoleId:d.value,scope:a.value}))}function re(n){return`${n.roleId}:${n.platform}:${n.userId}:${n.scope}`}function N(n){return p(g("newauth/assignPluginCommands",{plugin:h.value,scope:a.value,target:n}))}return(n,t)=>(s(),u("main",pe,[l("header",ge,[l("div",null,[t[20]||(t[20]=l("h1",null,"新权限",-1)),l("p",null,o(R.value.length)+" 个角色 / "+o(T.value.length)+" 个指令 / "+o(G.value.length)+" 个待配置",1)]),l("div",be,[c(l("select",{"onUpdate:modelValue":t[0]||(t[0]=e=>a.value=e),class:"scope-select"},[(s(true),u(f,null,y(Y.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,me))),128))],512),[[_,a.value]]),l("button",{class:"primary",disabled:r.value||!G.value.length,onClick:ne}," AI 自动分配待配置 ",8,fe),l("button",{disabled:r.value,onClick:te},"刷新",8,ye)])]),l("nav",ke,[l("button",{class:w({active:b.value==="roles"}),onClick:t[1]||(t[1]=e=>b.value="roles")},"角色",2),l("button",{class:w({active:b.value==="pending"}),onClick:t[2]||(t[2]=e=>b.value="pending")},"待配置",2),l("button",{class:w({active:b.value==="commands"}),onClick:t[3]||(t[3]=e=>b.value="commands")},"指令",2)]),A.value?(s(),u("p",he,o(A.value),1)):B("v-if",true),b.value==="roles"?(s(),u("section",_e,[l("aside",we,[(s(true),u(f,null,y(R.value,e=>(s(),u("button",{key:e.id,class:w(["role-item",{active:e.id===d.value}]),onClick:m=>d.value=e.id},[l("span",null,[l("strong",null,o(e.name),1),l("small",null,o(e.builtin?"内置角色":"自定义角色")+" / "+o(e.scopeType),1)]),l("em",null,o(e.allowCount),1)],10,Ce))),128)),l("form",{class:"role-create",onSubmit:J(ue,["prevent"])},[c(l("input",{"onUpdate:modelValue":t[4]||(t[4]=e=>I.value=e),placeholder:"custom:music-admin"},null,512),[[x,I.value,void 0,{trim:true}]]),c(l("input",{"onUpdate:modelValue":t[5]||(t[5]=e=>U.value=e),placeholder:"角色名称"},null,512),[[x,U.value,void 0,{trim:true}]]),c(l("select",{"onUpdate:modelValue":t[6]||(t[6]=e=>E.value=e)},[...t[21]||(t[21]=[l("option",{value:"guild"},"群内角色",-1),l("option",{value:"global"},"全局角色",-1)])],512),[[_,E.value]]),l("button",{class:"primary",disabled:r.value||!I.value||!U.value},"创建角色",8,Ie)],32)]),V.value?(s(),u("section",$e,[l("div",Ve,[l("div",null,[l("h2",null,o(V.value.name),1),l("p",null,o(V.value.id),1)]),l("div",Se,[c(l("select",{"onUpdate:modelValue":t[7]||(t[7]=e=>O.value=e)},[t[22]||(t[22]=l("option",{disabled:"",value:""},"复制来源",-1)),(s(true),u(f,null,y(Z.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,Ue))),128))],512),[[_,O.value]]),l("button",{disabled:r.value||!O.value,onClick:de},"复制权限",8,Pe),l("label",Re,[c(l("input",{type:"checkbox","onUpdate:modelValue":t[8]||(t[8]=e=>D.value=e)},null,512),[[ce,D.value]]),t[23]||(t[23]=q(" 审计模式 ",-1))])])]),l("section",xe,[l("div",null,[t[24]||(t[24]=l("strong",null,"成员",-1)),l("small",null,o(z.value.length)+" 个显式成员",1)]),l("div",Ae,[(s(true),u(f,null,y(z.value,e=>(s(),u("span",{key:re(e),class:"member-chip"},[q(o(e.platform)+":"+o(e.userId)+" / "+o(e.scope)+" ",1),l("button",{disabled:r.value,onClick:m=>ie(e)},"移除",8,Me)]))),128))]),l("form",{class:"member-add",onSubmit:J(ae,["prevent"])},[c(l("input",{"onUpdate:modelValue":t[9]||(t[9]=e=>P.value=e),placeholder:"platform:userId"},null,512),[[x,P.value,void 0,{trim:true}]]),c(l("select",{"onUpdate:modelValue":t[10]||(t[10]=e=>M.value=e)},[t[25]||(t[25]=l("option",{value:"global"},"global",-1)),a.value!=="global"?(s(),u("option",{key:0,value:a.value},o(a.value),9,Oe)):B("v-if",true)],512),[[_,M.value]]),l("button",{class:"primary",disabled:r.value||!P.value},"添加成员",8,Te)],32)]),l("div",Ge,[c(l("input",{"onUpdate:modelValue":t[11]||(t[11]=e=>k.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[x,k.value,void 0,{trim:true}]]),c(l("select",{"onUpdate:modelValue":t[12]||(t[12]=e=>C.value=e)},[...t[26]||(t[26]=[l("option",{value:"all"},"全部状态",-1),l("option",{value:"pending"},"待配置",-1),l("option",{value:"configured"},"已配置",-1),l("option",{value:"disabled"},"已禁用",-1)])],512),[[_,C.value]])]),l("div",Ne,[(s(true),u(f,null,y(le.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[l("div",Be,[l("strong",null,o(e.name),1),l("span",null,o(e.commandPath),1),l("small",null,o(e.plugin)+" / legacy "+o(e.legacyAuthority)+" / "+o(e.suggestion.label),1)]),l("div",De,[l("button",{class:w({active:L(e.id)==="inherit"}),onClick:m=>F(e.id,"inherit")}," 继承 ",10,Ee),l("button",{class:w({active:L(e.id)==="allow"}),onClick:m=>F(e.id,"allow")}," 开 ",10,Le),l("button",{class:w({active:L(e.id)==="deny"}),onClick:m=>F(e.id,"deny")}," 关 ",10,Fe)])]))),128))])])):B("v-if",true)])):b.value==="pending"?(s(),u("section",qe,[(s(true),u(f,null,y(G.value,e=>(s(),u("article",{key:e.id,class:"pending-item"},[l("div",null,[l("strong",null,o(e.name),1),l("span",null,o(e.commandPath),1),l("small",null,o(e.plugin)+" / legacy "+o(e.legacyAuthority),1)]),l("dl",null,[t[27]||(t[27]=l("dt",null,"旧建议",-1)),l("dd",null,o(e.suggestion.label),1),t[28]||(t[28]=l("dt",null,"AI 建议",-1)),l("dd",null,o(e.autoAssign.label)+":"+o(e.autoAssign.reason),1)]),l("div",ze,[l("button",{disabled:r.value,onClick:m=>j(e.id,"legacy")},"采用旧建议",8,Ke),l("button",{class:"primary",disabled:r.value,onClick:m=>j(e.id,"advisor")},"采用 AI 建议",8,je)])]))),128)),G.value.length?B("v-if",true):(s(),u("p",He,"没有待配置指令。"))])):(s(),u("section",Je,[l("div",Qe,[c(l("input",{"onUpdate:modelValue":t[13]||(t[13]=e=>k.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[x,k.value,void 0,{trim:true}]]),c(l("select",{"onUpdate:modelValue":t[14]||(t[14]=e=>C.value=e)},[...t[29]||(t[29]=[l("option",{value:"all"},"全部状态",-1),l("option",{value:"pending"},"待配置",-1),l("option",{value:"configured"},"已配置",-1),l("option",{value:"disabled"},"已禁用",-1)])],512),[[_,C.value]])]),l("div",We,[c(l("select",{"onUpdate:modelValue":t[15]||(t[15]=e=>h.value=e)},[t[30]||(t[30]=l("option",{disabled:"",value:""},"选择插件",-1)),(s(true),u(f,null,y(ee.value,e=>(s(),u("option",{key:e,value:e},o(e),9,Xe))),128))],512),[[_,h.value]]),l("button",{disabled:r.value||!h.value,onClick:t[16]||(t[16]=e=>N("legacy"))},"采用插件建议",8,Ye),l("button",{disabled:r.value||!h.value,onClick:t[17]||(t[17]=e=>N("guild-member"))},"给群成员",8,Ze),l("button",{disabled:r.value||!h.value,onClick:t[18]||(t[18]=e=>N("guild-admin"))},"给群管理员",8,el),l("button",{disabled:r.value||!h.value,onClick:t[19]||(t[19]=e=>N("bot-admin"))},"仅 Bot 管理员",8,ll)]),(s(true),u(f,null,y(K.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[l("div",tl,[l("strong",null,o(e.name),1),l("span",null,o(e.id),1),l("small",null,o(e.plugin)+" / "+o(e.commandPath)+" / "+o(e.autoAssign.label),1)]),l("div",nl,[l("label",ol,[l("input",{type:"checkbox",checked:e.allowGuildOverride,onChange:m=>oe(e,m)},null,40,sl),t[31]||(t[31]=q(" 群内自治 ",-1))]),l("select",{value:e.status,onChange:m=>se(e,m)},[...t[32]||(t[32]=[l("option",{value:"pending"},"待配置",-1),l("option",{value:"configured"},"已配置",-1),l("option",{value:"disabled"},"已禁用",-1)])],40,ul)])]))),128))]))]))}}),il=(S,b)=>{const a=S.__vccOpts||S;for(const[d,k]of b)a[d]=k;return a},dl=il(al,[["__scopeId","data-v-755ad7c8"]]),cl=S=>{S.page({name:"新权限",path:"/new-auth",icon:"⌗",fields:["newauth"],authority:4,component:dl})};export{cl as default};
|
|
1
|
+
import{defineComponent as Ae,ref as d,computed as c,watch as Y,openBlock as s,createElementBlock as u,createElementVNode as t,toDisplayString as o,withDirectives as p,Fragment as h,renderList as k,vModelSelect as I,normalizeClass as $,createCommentVNode as T,withModifiers as ne,vModelText as N,vModelCheckbox as Re,createTextVNode as K}from"vue";import{store as oe,send as b}from"@koishijs/client";const Pe={class:"new-auth-page"},Ve={class:"topbar"},Ue={class:"top-actions"},xe=["value"],Me=["disabled"],Te=["disabled"],Ne={class:"tabs"},Oe={key:0,class:"error"},Ge={key:1,class:"role-layout"},Be={class:"role-list"},De=["onClick"],Ee=["disabled"],Le={key:0,class:"detail"},Fe={class:"detail-head"},Ke={class:"detail-tools"},je=["value"],qe=["disabled"],ze={class:"check"},He={class:"member-panel"},Je={class:"member-list"},Qe=["disabled","onClick"],We=["value"],Xe=["disabled"],Ye={class:"filters"},Ze={class:"command-list"},el={class:"command-main"},ll={class:"state-buttons"},tl=["onClick"],nl=["onClick"],ol=["onClick"],sl={key:2,class:"pending-panel"},ul={key:0,class:"review-table"},il={class:"role-picker"},al=["checked","onChange"],dl={class:"row-actions"},rl=["disabled","onClick"],vl=["disabled","onClick"],cl=["disabled","onClick"],pl={key:1,class:"empty"},gl={key:3,class:"commands-table"},bl={class:"filters"},fl={class:"plugin-bulk"},hl=["value"],kl=["disabled"],yl=["disabled"],ml=["disabled"],wl=["disabled"],_l={class:"command-main"},Cl={class:"command-controls"},Sl={class:"check"},Il=["checked","onChange"],$l=["value","onChange"],Al=Ae({__name:"new-auth",setup(U){const f=d("roles"),a=d("global"),v=d(""),_=d(""),A=d("all"),j=d(false),r=d(false),O=d(""),R=d(""),x=d(""),q=d("guild"),M=d(""),G=d("global"),B=d(""),C=d(""),y=d({}),z=d({}),m=c(()=>oe.newauth||{roles:[],commands:[],policies:[],members:[],scopes:[{id:"global",name:"全局默认"}],pendingCount:0,autoAssignAvailable:false}),S=c(()=>m.value.roles),D=c(()=>m.value.commands),H=c(()=>m.value.policies),se=c(()=>m.value.members),ue=c(()=>{var n;return(n=m.value.scopes)!=null&&n.length?m.value.scopes:[{id:"global",name:"全局默认"}]}),w=c(()=>D.value.filter(n=>n.status==="pending")),Z=c(()=>w.value.filter(n=>z.value[n.id])),P=c(()=>S.value.find(n=>n.id===v.value)),ee=c(()=>se.value.filter(n=>n.roleId===v.value)),ie=c(()=>S.value.filter(n=>n.id!==v.value)),ae=c(()=>[...new Set(D.value.map(n=>n.plugin))].sort());Y(S,n=>{!v.value&&n.length&&(v.value=n[0].id)},{immediate:true}),Y(a,n=>{G.value=n==="global"?"global":n}),Y([w,H,a],()=>{for(const n of w.value)E(n)},{immediate:true});const le=c(()=>{const n=_.value.toLowerCase();return D.value.filter(l=>A.value!=="all"&&l.status!==A.value?false:n?[l.id,l.name,l.commandPath,l.plugin,l.description].some(e=>e==null?void 0:e.toLowerCase().includes(n)):true)}),de=c(()=>P.value?le.value.filter(n=>{var l,e;return j.value||((l=P.value)==null?void 0:l.id)==="bot-admin"||((e=P.value)==null?void 0:e.scopeType)==="global"?true:n.allowGuildOverride}):[]);function J(n){const l=H.value.find(e=>e.scope===a.value&&e.roleId===v.value&&e.commandId===n);return(l==null?void 0:l.state)||"inherit"}async function g(n){r.value=true,O.value="";try{await n}catch(l){O.value=l instanceof Error?l.message:String(l)}finally{r.value=false}}async function re(){return g((async()=>{const n=await b("newauth/getData");oe.newauth=n})())}function Q(n,l){return g(b("newauth/setPolicy",{scope:a.value,roleId:v.value,commandId:n,state:l}))}function V(n){return`${a.value}:${n}`}function ve(n){const l=H.value.filter(e=>e.scope===a.value&&e.commandId===n.id&&e.state==="allow").map(e=>e.roleId);return l.length?l:[...n.suggestion.roles]}function E(n){const l=V(n.id);y.value[l]||(y.value[l]=ve(n))}function ce(n,l){return E(n),y.value[V(n.id)].includes(l)}function pe(n,l,e){E(n);const i=V(n.id),W=e.target.checked,X=new Set(y.value[i]);W?X.add(l):X.delete(l),y.value[i]=[...X]}function ge(n,l){y.value[V(n.id)]=[...n.suggestion.roles]}function L(n){return z.value[n.id]||n.autoAssign}function be(n){return L(n).roles.map(l=>{var e;return((e=S.value.find(i=>i.id===l))==null?void 0:e.name)||l}).join("、")||"无"}function te(n,l){z.value[n.id]=l,y.value[V(n.id)]=[...l.roles]}function fe(n){return g((async()=>{const l=await b("newauth/generateAiSuggestion",{commandId:n.id});te(n,l)})())}function he(){return g((async()=>{const n=await b("newauth/generateAiSuggestions");for(const l of w.value){const e=n.suggestions[l.id];e&&te(l,e)}})())}function ke(n){return E(n),g(b("newauth/configureCommandRoles",{commandId:n.id,scope:a.value,roleIds:y.value[V(n.id)]}))}function ye(n,l){const e=l.target;return g(b("newauth/setGuildOverride",{commandId:n.id,allowGuildOverride:e.checked}))}function me(n,l){const e=l.target;return g(b("newauth/setCommandStatus",{commandId:n.id,status:e.value}))}function we(){return g((async()=>{await b("newauth/createRole",{id:R.value,name:x.value,scopeType:q.value}),v.value=R.value,R.value="",x.value=""})())}function _e(){return g((async()=>{await b("newauth/addMember",{roleId:v.value,uid:M.value,scope:G.value}),M.value=""})())}function Ce(n){return g(b("newauth/removeMember",{roleId:n.roleId,uid:`${n.platform}:${n.userId}`,scope:n.scope}))}function Se(){return g(b("newauth/copyRolePolicies",{sourceRoleId:B.value,targetRoleId:v.value,scope:a.value}))}function Ie(n){return`${n.roleId}:${n.platform}:${n.userId}:${n.scope}`}function F(n){return g(b("newauth/assignPluginCommands",{plugin:C.value,scope:a.value,target:n}))}function $e(n){return n==="global"?"全局":"群内"}return(n,l)=>(s(),u("main",Pe,[t("header",Ve,[t("div",null,[l[20]||(l[20]=t("h1",null,"新权限",-1)),t("p",null,o(S.value.length)+" 个角色 / "+o(D.value.length)+" 个指令 / "+o(w.value.length)+" 个待配置",1)]),t("div",Ue,[p(t("select",{"onUpdate:modelValue":l[0]||(l[0]=e=>a.value=e),class:"scope-select"},[(s(true),u(h,null,k(ue.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,xe))),128))],512),[[I,a.value]]),t("button",{class:"primary",disabled:r.value||!w.value.length||!m.value.autoAssignAvailable,onClick:he}," AI 生成审查表 ",8,Me),t("button",{disabled:r.value,onClick:re},"刷新",8,Te)])]),t("nav",Ne,[t("button",{class:$({active:f.value==="roles"}),onClick:l[1]||(l[1]=e=>f.value="roles")},"角色",2),t("button",{class:$({active:f.value==="pending"}),onClick:l[2]||(l[2]=e=>f.value="pending")},"待配置",2),t("button",{class:$({active:f.value==="commands"}),onClick:l[3]||(l[3]=e=>f.value="commands")},"指令",2)]),O.value?(s(),u("p",Oe,o(O.value),1)):T("v-if",true),f.value==="roles"?(s(),u("section",Ge,[t("aside",Be,[(s(true),u(h,null,k(S.value,e=>(s(),u("button",{key:e.id,class:$(["role-item",{active:e.id===v.value}]),onClick:i=>v.value=e.id},[t("span",null,[t("strong",null,o(e.name),1),t("small",null,o(e.builtin?"内置角色":"自定义角色")+" / "+o($e(e.scopeType)),1)]),t("em",null,o(e.allowCount),1)],10,De))),128)),t("form",{class:"role-create",onSubmit:ne(we,["prevent"])},[p(t("input",{"onUpdate:modelValue":l[4]||(l[4]=e=>R.value=e),placeholder:"custom:music-admin"},null,512),[[N,R.value,void 0,{trim:true}]]),p(t("input",{"onUpdate:modelValue":l[5]||(l[5]=e=>x.value=e),placeholder:"角色名称"},null,512),[[N,x.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[6]||(l[6]=e=>q.value=e)},[...l[21]||(l[21]=[t("option",{value:"guild"},"群内角色",-1),t("option",{value:"global"},"全局角色",-1)])],512),[[I,q.value]]),t("button",{class:"primary",disabled:r.value||!R.value||!x.value},"创建角色",8,Ee)],32)]),P.value?(s(),u("section",Le,[t("div",Fe,[t("div",null,[t("h2",null,o(P.value.name),1),t("p",null,o(P.value.id),1)]),t("div",Ke,[p(t("select",{"onUpdate:modelValue":l[7]||(l[7]=e=>B.value=e)},[l[22]||(l[22]=t("option",{disabled:"",value:""},"复制来源",-1)),(s(true),u(h,null,k(ie.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,je))),128))],512),[[I,B.value]]),t("button",{disabled:r.value||!B.value,onClick:Se},"复制权限",8,qe),t("label",ze,[p(t("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=e=>j.value=e)},null,512),[[Re,j.value]]),l[23]||(l[23]=K(" 审计模式 ",-1))])])]),t("section",He,[t("div",null,[l[24]||(l[24]=t("strong",null,"成员",-1)),t("small",null,o(ee.value.length)+" 个显式成员",1)]),t("div",Je,[(s(true),u(h,null,k(ee.value,e=>(s(),u("span",{key:Ie(e),class:"member-chip"},[K(o(e.platform)+":"+o(e.userId)+" / "+o(e.scope)+" ",1),t("button",{disabled:r.value,onClick:i=>Ce(e)},"移除",8,Qe)]))),128))]),t("form",{class:"member-add",onSubmit:ne(_e,["prevent"])},[p(t("input",{"onUpdate:modelValue":l[9]||(l[9]=e=>M.value=e),placeholder:"platform:userId"},null,512),[[N,M.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[10]||(l[10]=e=>G.value=e)},[l[25]||(l[25]=t("option",{value:"global"},"global",-1)),a.value!=="global"?(s(),u("option",{key:0,value:a.value},o(a.value),9,We)):T("v-if",true)],512),[[I,G.value]]),t("button",{class:"primary",disabled:r.value||!M.value},"添加成员",8,Xe)],32)]),t("div",Ye,[p(t("input",{"onUpdate:modelValue":l[11]||(l[11]=e=>_.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[N,_.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[12]||(l[12]=e=>A.value=e)},[...l[26]||(l[26]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[I,A.value]])]),t("div",Ze,[(s(true),u(h,null,k(de.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[t("div",el,[t("strong",null,o(e.name),1),t("span",null,o(e.commandPath),1),t("small",null,o(e.plugin)+" / legacy "+o(e.legacyAuthority)+" / "+o(e.suggestion.label),1)]),t("div",ll,[t("button",{class:$({active:J(e.id)==="inherit"}),onClick:i=>Q(e.id,"inherit")}," 继承 ",10,tl),t("button",{class:$({active:J(e.id)==="allow"}),onClick:i=>Q(e.id,"allow")}," 开 ",10,nl),t("button",{class:$({active:J(e.id)==="deny"}),onClick:i=>Q(e.id,"deny")}," 关 ",10,ol)])]))),128))])])):T("v-if",true)])):f.value==="pending"?(s(),u("section",sl,[Z.value.length?(s(),u("table",ul,[l[27]||(l[27]=t("thead",null,[t("tr",null,[t("th",null,"指令"),t("th",null,"建议角色"),t("th",null,"理由")])],-1)),t("tbody",null,[(s(true),u(h,null,k(Z.value,e=>(s(),u("tr",{key:`review:${e.id}`},[t("td",null,o(e.commandPath),1),t("td",null,o(be(e)),1),t("td",null,o(L(e).reason),1)]))),128))])])):T("v-if",true),(s(true),u(h,null,k(w.value,e=>(s(),u("article",{key:e.id,class:"pending-item"},[t("div",null,[t("strong",null,o(e.name),1),t("span",null,o(e.commandPath),1),t("small",null,o(e.plugin)+" / legacy "+o(e.legacyAuthority),1)]),t("dl",null,[l[28]||(l[28]=t("dt",null,"旧建议",-1)),t("dd",null,o(e.suggestion.label),1),l[29]||(l[29]=t("dt",null,"AI 状态",-1)),t("dd",null,o(L(e).label)+":"+o(L(e).reason),1)]),t("div",il,[(s(true),u(h,null,k(S.value,i=>(s(),u("label",{key:`${e.id}:${i.id}`,class:"check"},[t("input",{type:"checkbox",checked:ce(e,i.id),onChange:W=>pe(e,i.id,W)},null,40,al),K(" "+o(i.name),1)]))),128))]),t("div",dl,[t("button",{disabled:r.value,onClick:i=>ge(e)},"填入旧建议",8,rl),t("button",{disabled:r.value||!m.value.autoAssignAvailable,onClick:i=>fe(e)},"AI 生成建议",8,vl),t("button",{class:"primary",disabled:r.value,onClick:i=>ke(e)},"保存分配",8,cl)])]))),128)),w.value.length?T("v-if",true):(s(),u("p",pl,"没有待配置指令。"))])):(s(),u("section",gl,[t("div",bl,[p(t("input",{"onUpdate:modelValue":l[13]||(l[13]=e=>_.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[N,_.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[14]||(l[14]=e=>A.value=e)},[...l[30]||(l[30]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[I,A.value]])]),t("div",fl,[p(t("select",{"onUpdate:modelValue":l[15]||(l[15]=e=>C.value=e)},[l[31]||(l[31]=t("option",{disabled:"",value:""},"选择插件",-1)),(s(true),u(h,null,k(ae.value,e=>(s(),u("option",{key:e,value:e},o(e),9,hl))),128))],512),[[I,C.value]]),t("button",{disabled:r.value||!C.value,onClick:l[16]||(l[16]=e=>F("legacy"))},"采用插件建议",8,kl),t("button",{disabled:r.value||!C.value,onClick:l[17]||(l[17]=e=>F("guild-member"))},"给群成员",8,yl),t("button",{disabled:r.value||!C.value,onClick:l[18]||(l[18]=e=>F("guild-admin"))},"给群管理员",8,ml),t("button",{disabled:r.value||!C.value,onClick:l[19]||(l[19]=e=>F("bot-admin"))},"仅 Bot 管理员",8,wl)]),(s(true),u(h,null,k(le.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[t("div",_l,[t("strong",null,o(e.name),1),t("span",null,o(e.id),1),t("small",null,o(e.plugin)+" / "+o(e.commandPath)+" / "+o(e.autoAssign.label),1)]),t("div",Cl,[t("label",Sl,[t("input",{type:"checkbox",checked:e.allowGuildOverride,onChange:i=>ye(e,i)},null,40,Il),l[32]||(l[32]=K(" 群内自治 ",-1))]),t("select",{value:e.status,onChange:i=>me(e,i)},[...l[33]||(l[33]=[t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],40,$l)])]))),128))]))]))}}),Rl=(U,f)=>{const a=U.__vccOpts||U;for(const[v,_]of f)a[v]=_;return a},Pl=Rl(Al,[["__scopeId","data-v-b9691780"]]),xl=U=>{U.page({name:"新权限",path:"/new-auth",icon:"⌗",fields:["newauth"],authority:4,component:Pl})};export{xl as default};
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.new-auth-page[data-v-
|
|
1
|
+
.new-auth-page[data-v-b9691780]{min-height:100vh;background:#eef2f6;color:#18222d;padding:20px}.topbar[data-v-b9691780],.detail-head[data-v-b9691780],.filters[data-v-b9691780],.row-actions[data-v-b9691780],.command-controls[data-v-b9691780],.top-actions[data-v-b9691780],.detail-tools[data-v-b9691780],.member-add[data-v-b9691780],.plugin-bulk[data-v-b9691780]{display:flex;align-items:center;gap:12px}.topbar[data-v-b9691780]{justify-content:space-between;border:1px solid #d7dee8;border-radius:8px;background:#fff;box-shadow:0 8px 24px #20304814;padding:16px}h1[data-v-b9691780],h2[data-v-b9691780],p[data-v-b9691780]{margin:0}h1[data-v-b9691780]{font-size:26px;line-height:1.2}h2[data-v-b9691780]{font-size:20px}p[data-v-b9691780],small[data-v-b9691780],span[data-v-b9691780],dd[data-v-b9691780],dt[data-v-b9691780]{color:#607083}button[data-v-b9691780],select[data-v-b9691780],input[data-v-b9691780]{border:1px solid #c9d3df;border-radius:6px;background:#fff;color:#18222d;min-height:34px;padding:0 10px;font:inherit}button[data-v-b9691780]{cursor:pointer;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease}button[data-v-b9691780]:not(:disabled):hover,select[data-v-b9691780]:focus,input[data-v-b9691780]:focus{border-color:#2d6cdf;box-shadow:0 0 0 3px #2d6cdf1f;outline:none}button[data-v-b9691780]:disabled{cursor:not-allowed;opacity:.55}.primary[data-v-b9691780]{background:#1d6f68;border-color:#1d6f68;color:#fff}.tabs[data-v-b9691780]{display:flex;gap:6px;margin:16px 0}.tabs button[data-v-b9691780]{background:#f8fafc}.tabs button.active[data-v-b9691780],.state-buttons button.active[data-v-b9691780]{background:#17202b;border-color:#17202b;color:#fff}.role-layout[data-v-b9691780]{display:grid;grid-template-columns:minmax(220px,280px) 1fr;gap:16px}.role-list[data-v-b9691780]{display:grid;gap:8px;align-content:start}.role-create[data-v-b9691780]{display:grid;gap:8px;border:1px solid #d7dee8;border-radius:8px;background:#fff;padding:10px;box-shadow:0 4px 14px #2030480d}.role-item[data-v-b9691780]{display:flex;justify-content:space-between;align-items:center;min-height:58px;text-align:left}.role-item span[data-v-b9691780],.command-main[data-v-b9691780]{display:grid;gap:3px;min-width:0}.role-item.active[data-v-b9691780]{border-color:#2d6cdf;box-shadow:inset 3px 0 #2d6cdf,0 4px 14px #2d6cdf1f}.role-item em[data-v-b9691780]{font-style:normal;color:#9a5a00}.detail[data-v-b9691780]{min-width:0}.detail-head[data-v-b9691780]{justify-content:space-between;margin-bottom:12px}.detail-tools[data-v-b9691780]{flex-wrap:wrap;justify-content:flex-end}.member-panel[data-v-b9691780]{display:grid;gap:10px;border:1px solid #d7dee8;border-radius:8px;background:#fff;padding:12px;margin-bottom:12px;box-shadow:0 4px 14px #2030480d}.member-panel>div[data-v-b9691780]:first-child{display:flex;justify-content:space-between;gap:12px}.member-list[data-v-b9691780]{display:flex;flex-wrap:wrap;gap:6px;min-height:28px}.member-chip[data-v-b9691780]{display:inline-flex;align-items:center;gap:6px;border:1px solid #c9d3df;border-radius:999px;background:#f8fafc;padding:3px 4px 3px 10px}.member-chip button[data-v-b9691780]{min-height:24px;padding:0 8px;border-radius:999px}.filters[data-v-b9691780]{margin-bottom:12px}.plugin-bulk[data-v-b9691780]{flex-wrap:wrap;border:1px solid #d7dee8;border-radius:8px;background:#fff;padding:10px;margin-bottom:12px;box-shadow:0 4px 14px #2030480d}.filters input[data-v-b9691780]{flex:1;min-width:180px}.command-list[data-v-b9691780],.pending-panel[data-v-b9691780],.commands-table[data-v-b9691780]{display:grid;gap:8px}.command-row[data-v-b9691780],.pending-item[data-v-b9691780]{background:#fff;border:1px solid #d7dee8;border-radius:8px;padding:12px;box-shadow:0 4px 14px #2030480d}.command-row[data-v-b9691780]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center}.command-main strong[data-v-b9691780],.command-main span[data-v-b9691780],.command-main small[data-v-b9691780]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.state-buttons[data-v-b9691780]{display:grid;grid-template-columns:repeat(3,54px);gap:6px}.pending-panel[data-v-b9691780]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.review-table[data-v-b9691780]{grid-column:1 / -1;width:100%;border-collapse:collapse;overflow:hidden;border:1px solid #d7dee8;border-radius:8px;background:#fff;box-shadow:0 4px 14px #2030480d}.review-table th[data-v-b9691780],.review-table td[data-v-b9691780]{border-bottom:1px solid #e5ebf1;padding:10px;text-align:left;vertical-align:top}.review-table th[data-v-b9691780]{background:#f8fafc;color:#314155;font-weight:600}.review-table tr:last-child td[data-v-b9691780]{border-bottom:0}.pending-item[data-v-b9691780]{display:grid;gap:10px}.role-picker[data-v-b9691780]{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:8px}.role-picker .check[data-v-b9691780]{border:1px solid #d7dee8;border-radius:6px;background:#f8fafc;min-height:32px;padding:0 8px}dl[data-v-b9691780]{display:grid;grid-template-columns:58px 1fr;gap:4px 8px;margin:0}dd[data-v-b9691780]{margin:0}.check[data-v-b9691780]{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.check input[data-v-b9691780]{min-height:0}.error[data-v-b9691780]{border:1px solid #e2a15d;background:#fff6e9;color:#85510d;border-radius:6px;padding:10px;margin-bottom:12px}.empty[data-v-b9691780]{padding:24px 0}@media (max-width: 760px){.new-auth-page[data-v-b9691780]{padding:12px}.topbar[data-v-b9691780],.detail-head[data-v-b9691780],.command-row[data-v-b9691780]{grid-template-columns:1fr;display:grid}.top-actions[data-v-b9691780],.filters[data-v-b9691780],.command-controls[data-v-b9691780],.detail-tools[data-v-b9691780],.member-add[data-v-b9691780],.plugin-bulk[data-v-b9691780]{flex-wrap:wrap}.role-layout[data-v-b9691780]{grid-template-columns:1fr}.state-buttons[data-v-b9691780]{grid-template-columns:repeat(3,minmax(0,1fr))}}
|
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Command, Context, Schema, Session } from 'koishi';
|
|
1
|
+
import { Command, Context, Schema, Service, Session } from 'koishi';
|
|
2
2
|
import type { Argv } from 'koishi';
|
|
3
3
|
export declare const name = "new-auth";
|
|
4
4
|
export declare const inject: {
|
|
@@ -9,7 +9,7 @@ type RoleType = 'builtin' | 'custom';
|
|
|
9
9
|
type ScopeType = 'global' | 'guild';
|
|
10
10
|
type CommandStatus = 'pending' | 'configured' | 'disabled';
|
|
11
11
|
type PolicyState = 'inherit' | 'allow' | 'deny';
|
|
12
|
-
type AutoAssignKind = '
|
|
12
|
+
type AutoAssignKind = 'ai' | 'legacy';
|
|
13
13
|
type PluginAssignTarget = 'legacy' | 'bot-admin' | 'guild-owner' | 'guild-admin' | 'guild-member';
|
|
14
14
|
export interface NewAuthCommandRecord {
|
|
15
15
|
id: string;
|
|
@@ -82,6 +82,13 @@ declare module '@koishijs/plugin-console' {
|
|
|
82
82
|
success: true;
|
|
83
83
|
count: number;
|
|
84
84
|
}>;
|
|
85
|
+
'newauth/generateAiSuggestion'(payload: AiSuggestionPayload): Promise<AutoAssignSuggestion>;
|
|
86
|
+
'newauth/generateAiSuggestions'(payload?: AutoAssignPayload): Promise<{
|
|
87
|
+
suggestions: Record<string, AutoAssignSuggestion>;
|
|
88
|
+
}>;
|
|
89
|
+
'newauth/configureCommandRoles'(payload: ConfigureCommandRolesPayload): Promise<{
|
|
90
|
+
success: true;
|
|
91
|
+
}>;
|
|
85
92
|
'newauth/createRole'(payload: CreateRolePayload): Promise<{
|
|
86
93
|
success: true;
|
|
87
94
|
}>;
|
|
@@ -153,6 +160,14 @@ interface AutoAssignPayload {
|
|
|
153
160
|
scope?: string;
|
|
154
161
|
mode?: 'legacy' | 'advisor';
|
|
155
162
|
}
|
|
163
|
+
interface AiSuggestionPayload {
|
|
164
|
+
commandId: string;
|
|
165
|
+
}
|
|
166
|
+
interface ConfigureCommandRolesPayload {
|
|
167
|
+
commandId: string;
|
|
168
|
+
scope: string;
|
|
169
|
+
roleIds: string[];
|
|
170
|
+
}
|
|
156
171
|
interface CreateRolePayload {
|
|
157
172
|
id: string;
|
|
158
173
|
name: string;
|
|
@@ -177,25 +192,23 @@ export interface Config {
|
|
|
177
192
|
botAdmins: string[];
|
|
178
193
|
trustLegacyAuthorityAsAdmin: boolean;
|
|
179
194
|
legacyAdminAuthority: number;
|
|
180
|
-
ownerRoleNames: string[];
|
|
181
|
-
adminRoleNames: string[];
|
|
182
|
-
allowGuildOverrideAuthorityMax: number;
|
|
183
195
|
deniedMessage: string;
|
|
184
|
-
grantRuntimeCommandPermission: boolean;
|
|
185
|
-
raiseLegacyAuthority: boolean;
|
|
186
196
|
}
|
|
187
197
|
export declare const Config: Schema<Config>;
|
|
188
198
|
export declare function apply(ctx: Context, config: Config): void;
|
|
189
|
-
export declare class NewAuthService {
|
|
190
|
-
|
|
191
|
-
private config;
|
|
199
|
+
export declare class NewAuthService extends Service {
|
|
200
|
+
config: Config;
|
|
192
201
|
private commandCache;
|
|
202
|
+
private commandLocks;
|
|
193
203
|
private adminSet;
|
|
194
204
|
private ownerRoleNames;
|
|
195
205
|
private adminRoleNames;
|
|
196
206
|
constructor(ctx: Context, config: Config);
|
|
197
207
|
start(): Promise<void>;
|
|
198
208
|
registerCommand(command: Command): Promise<string | undefined>;
|
|
209
|
+
private writeCommandRecord;
|
|
210
|
+
private ensureSelfCommandPolicies;
|
|
211
|
+
private cleanupSelfCommandDuplicates;
|
|
199
212
|
intercept(argv: Argv): Promise<string | undefined>;
|
|
200
213
|
canExecute(session: Session, commandId: string): Promise<{
|
|
201
214
|
allowed: boolean;
|
|
@@ -262,6 +275,17 @@ export declare class NewAuthService {
|
|
|
262
275
|
setCommandGuildOverride(input: string, allowGuildOverride: boolean): Promise<NewAuthCommandRecord>;
|
|
263
276
|
setCommandPolicy(scope: string, roleId: string, input: string, state: PolicyState): Promise<NewAuthCommandRecord>;
|
|
264
277
|
applySuggestedPolicy(input: string, scope?: string, mode?: 'legacy' | 'advisor'): Promise<NewAuthCommandRecord>;
|
|
278
|
+
configureCommandRoles(input: string, scope?: string, roleIds?: string[]): Promise<NewAuthCommandRecord>;
|
|
279
|
+
generateAiSuggestion(input: string): Promise<AutoAssignSuggestion>;
|
|
280
|
+
generateAiSuggestionsForPending(): Promise<Record<string, AutoAssignSuggestion>>;
|
|
281
|
+
listGuildManageableCommands(session: Session, query?: string): Promise<{
|
|
282
|
+
command: NewAuthCommandRecord;
|
|
283
|
+
states: string[];
|
|
284
|
+
}[]>;
|
|
285
|
+
setGuildManagedPolicy(session: Session, roleId: string, input: string, state: PolicyState): Promise<{
|
|
286
|
+
command: NewAuthCommandRecord;
|
|
287
|
+
scope: string;
|
|
288
|
+
}>;
|
|
265
289
|
autoAssignPending(scope?: string, mode?: 'legacy' | 'advisor'): Promise<number>;
|
|
266
290
|
assignPluginCommands(plugin: string, scope?: string, target?: PluginAssignTarget): Promise<number>;
|
|
267
291
|
getCommand(input: string): Promise<NewAuthCommandRecord>;
|
|
@@ -272,7 +296,10 @@ export declare class NewAuthService {
|
|
|
272
296
|
private resolveCommandInput;
|
|
273
297
|
private setPolicy;
|
|
274
298
|
private getEffectivePolicy;
|
|
299
|
+
private ensureGuildManager;
|
|
275
300
|
private getAutoAssignSuggestion;
|
|
301
|
+
private hasAiAdvisor;
|
|
302
|
+
private getAiAssignSuggestion;
|
|
276
303
|
private ensureRoleMember;
|
|
277
304
|
private ensureRoleExists;
|
|
278
305
|
private hasPlatformRole;
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,7 @@ exports.apply = apply;
|
|
|
5
5
|
const koishi_1 = require("koishi");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
exports.name = 'new-auth';
|
|
8
|
-
exports.inject = { required: ['database'], optional: ['console'] };
|
|
8
|
+
exports.inject = { required: ['database'], optional: ['console', 'chatluna'] };
|
|
9
9
|
const BUILTIN_ROLES = [
|
|
10
10
|
{
|
|
11
11
|
id: 'bot-admin',
|
|
@@ -38,6 +38,11 @@ const BUILTIN_ROLES = [
|
|
|
38
38
|
scopeType: 'global',
|
|
39
39
|
},
|
|
40
40
|
];
|
|
41
|
+
const OWNER_ROLE_NAMES = ['owner', 'guild-owner'];
|
|
42
|
+
const ADMIN_ROLE_NAMES = ['admin', 'administrator', 'guild-admin', 'moderator'];
|
|
43
|
+
const DEFAULT_ALLOW_GUILD_OVERRIDE_AUTHORITY_MAX = 2;
|
|
44
|
+
const GRANT_RUNTIME_COMMAND_PERMISSION = true;
|
|
45
|
+
const RAISE_LEGACY_AUTHORITY = false;
|
|
41
46
|
exports.Config = koishi_1.Schema.object({
|
|
42
47
|
botAdmins: koishi_1.Schema.array(String)
|
|
43
48
|
.role('table')
|
|
@@ -51,26 +56,9 @@ exports.Config = koishi_1.Schema.object({
|
|
|
51
56
|
.max(10)
|
|
52
57
|
.description('旧 authority 管理员阈值。')
|
|
53
58
|
.default(4),
|
|
54
|
-
ownerRoleNames: koishi_1.Schema.array(String)
|
|
55
|
-
.description('平台角色名/ID 命中这些值时识别为群主。')
|
|
56
|
-
.default(['owner', 'guild-owner', 'administrator']),
|
|
57
|
-
adminRoleNames: koishi_1.Schema.array(String)
|
|
58
|
-
.description('平台角色名/ID 命中这些值时识别为群管理员。')
|
|
59
|
-
.default(['admin', 'administrator', 'guild-admin', 'moderator']),
|
|
60
|
-
allowGuildOverrideAuthorityMax: koishi_1.Schema.number()
|
|
61
|
-
.min(0)
|
|
62
|
-
.max(10)
|
|
63
|
-
.description('旧 authority 建议值不高于该值时,默认认为允许群内自治。')
|
|
64
|
-
.default(3),
|
|
65
59
|
deniedMessage: koishi_1.Schema.string()
|
|
66
60
|
.description('权限不足时返回的提示。')
|
|
67
61
|
.default('你没有权限使用该指令。'),
|
|
68
|
-
grantRuntimeCommandPermission: koishi_1.Schema.boolean()
|
|
69
|
-
.description('新权限系统放行后,临时授予本次 Koishi command 权限,绕过旧 command authority 校验。')
|
|
70
|
-
.default(true),
|
|
71
|
-
raiseLegacyAuthority: koishi_1.Schema.boolean()
|
|
72
|
-
.description('新权限系统放行后,临时提升 session.user.authority 以兼容插件内部硬编码判断。可能影响依赖旧 authority 的插件行为。')
|
|
73
|
-
.default(false),
|
|
74
62
|
});
|
|
75
63
|
function apply(ctx, config) {
|
|
76
64
|
ctx.model.extend('new_auth_command', {
|
|
@@ -118,8 +106,6 @@ function apply(ctx, config) {
|
|
|
118
106
|
primary: ['scope', 'roleId', 'commandId'],
|
|
119
107
|
});
|
|
120
108
|
const service = new NewAuthService(ctx, config);
|
|
121
|
-
ctx.provide('newauth', service);
|
|
122
|
-
ctx.on('ready', () => service.start());
|
|
123
109
|
ctx.on('command-added', command => service.registerCommand(command));
|
|
124
110
|
ctx.on('command-updated', command => service.registerCommand(command));
|
|
125
111
|
ctx.before('command/execute', async (argv) => {
|
|
@@ -130,14 +116,15 @@ function apply(ctx, config) {
|
|
|
130
116
|
registerConsole(ctx, service);
|
|
131
117
|
});
|
|
132
118
|
}
|
|
133
|
-
class NewAuthService {
|
|
119
|
+
class NewAuthService extends koishi_1.Service {
|
|
134
120
|
constructor(ctx, config) {
|
|
135
|
-
|
|
136
|
-
this.config = config;
|
|
121
|
+
super(ctx, 'newauth', true);
|
|
137
122
|
this.commandCache = new Map();
|
|
123
|
+
this.commandLocks = new Map();
|
|
124
|
+
this.config = config;
|
|
138
125
|
this.adminSet = new Set(config.botAdmins.map(normalizeKey));
|
|
139
|
-
this.ownerRoleNames = new Set(
|
|
140
|
-
this.adminRoleNames = new Set(
|
|
126
|
+
this.ownerRoleNames = new Set(OWNER_ROLE_NAMES.map(normalizeKey));
|
|
127
|
+
this.adminRoleNames = new Set(ADMIN_ROLE_NAMES.map(normalizeKey));
|
|
141
128
|
}
|
|
142
129
|
async start() {
|
|
143
130
|
await this.ensureBuiltinRoles();
|
|
@@ -150,6 +137,17 @@ class NewAuthService {
|
|
|
150
137
|
return;
|
|
151
138
|
const now = new Date();
|
|
152
139
|
const record = this.createCommandRecord(command, now);
|
|
140
|
+
const previous = this.commandLocks.get(record.id) || Promise.resolve(undefined);
|
|
141
|
+
const next = previous.then(() => this.writeCommandRecord(command, record, now));
|
|
142
|
+
this.commandLocks.set(record.id, next);
|
|
143
|
+
next.finally(() => {
|
|
144
|
+
if (this.commandLocks.get(record.id) === next)
|
|
145
|
+
this.commandLocks.delete(record.id);
|
|
146
|
+
});
|
|
147
|
+
return next;
|
|
148
|
+
}
|
|
149
|
+
async writeCommandRecord(command, record, now) {
|
|
150
|
+
await this.cleanupSelfCommandDuplicates(record);
|
|
153
151
|
const [existing] = await this.ctx.database.get('new_auth_command', { id: record.id });
|
|
154
152
|
if (existing) {
|
|
155
153
|
const next = {
|
|
@@ -161,17 +159,41 @@ class NewAuthService {
|
|
|
161
159
|
legacyAuthority: record.legacyAuthority,
|
|
162
160
|
updatedAt: now,
|
|
163
161
|
};
|
|
162
|
+
if (this.isSelfCommand(command)) {
|
|
163
|
+
next.status = 'configured';
|
|
164
|
+
}
|
|
164
165
|
await this.ctx.database.set('new_auth_command', record.id, next);
|
|
165
166
|
this.commandCache.set(record.id, { ...existing, ...next });
|
|
167
|
+
await this.ensureSelfCommandPolicies(record);
|
|
166
168
|
return record.id;
|
|
167
169
|
}
|
|
168
170
|
await this.ctx.database.create('new_auth_command', record);
|
|
169
171
|
this.commandCache.set(record.id, record);
|
|
170
|
-
|
|
171
|
-
await this.setPolicy('global', 'bot-admin', record.id, 'allow');
|
|
172
|
-
}
|
|
172
|
+
await this.ensureSelfCommandPolicies(record);
|
|
173
173
|
return record.id;
|
|
174
174
|
}
|
|
175
|
+
async ensureSelfCommandPolicies(record) {
|
|
176
|
+
if (!isSelfCommandPath(record.commandPath))
|
|
177
|
+
return;
|
|
178
|
+
await this.setPolicy('global', 'bot-admin', record.id, 'allow');
|
|
179
|
+
if (isGuildAutonomyCommandPath(record.commandPath)) {
|
|
180
|
+
await this.setPolicy('global', 'guild-owner', record.id, 'allow');
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
async cleanupSelfCommandDuplicates(record) {
|
|
184
|
+
if (!isSelfCommandPath(record.commandPath))
|
|
185
|
+
return;
|
|
186
|
+
const staleRecords = await this.ctx.database.get('new_auth_command', {
|
|
187
|
+
commandPath: record.commandPath,
|
|
188
|
+
});
|
|
189
|
+
for (const stale of staleRecords) {
|
|
190
|
+
if (stale.id === record.id)
|
|
191
|
+
continue;
|
|
192
|
+
await this.ctx.database.remove('new_auth_policy', { commandId: stale.id });
|
|
193
|
+
await this.ctx.database.remove('new_auth_command', { id: stale.id });
|
|
194
|
+
this.commandCache.delete(stale.id);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
175
197
|
async intercept(argv) {
|
|
176
198
|
const { command, session, options } = argv;
|
|
177
199
|
if (!command || !session)
|
|
@@ -183,10 +205,10 @@ class NewAuthService {
|
|
|
183
205
|
if (!result.allowed) {
|
|
184
206
|
return command.config.showWarning ? this.config.deniedMessage : '';
|
|
185
207
|
}
|
|
186
|
-
if (
|
|
208
|
+
if (GRANT_RUNTIME_COMMAND_PERMISSION) {
|
|
187
209
|
this.grantRuntimeCommandPermission(session, command, options || {});
|
|
188
210
|
}
|
|
189
|
-
if (
|
|
211
|
+
if (RAISE_LEGACY_AUTHORITY && session.user) {
|
|
190
212
|
const user = session.user;
|
|
191
213
|
const legacyAuthority = result.command?.legacyAuthority ?? command.config.authority ?? 0;
|
|
192
214
|
if ((user.authority ?? 0) < legacyAuthority) {
|
|
@@ -292,7 +314,7 @@ class NewAuthService {
|
|
|
292
314
|
}
|
|
293
315
|
async listRoles() {
|
|
294
316
|
const records = await this.ctx.database.get('new_auth_role', {});
|
|
295
|
-
return records.sort((a, b) => Number(
|
|
317
|
+
return records.sort((a, b) => Number(b.builtin) - Number(a.builtin) || a.id.localeCompare(b.id));
|
|
296
318
|
}
|
|
297
319
|
async listPolicies() {
|
|
298
320
|
return this.ctx.database.get('new_auth_policy', {});
|
|
@@ -347,7 +369,7 @@ class NewAuthService {
|
|
|
347
369
|
members,
|
|
348
370
|
scopes,
|
|
349
371
|
pendingCount: commands.filter(command => command.status === 'pending').length,
|
|
350
|
-
autoAssignAvailable:
|
|
372
|
+
autoAssignAvailable: this.hasAiAdvisor(),
|
|
351
373
|
};
|
|
352
374
|
}
|
|
353
375
|
async addBotAdmin(uid) {
|
|
@@ -365,7 +387,7 @@ class NewAuthService {
|
|
|
365
387
|
}
|
|
366
388
|
async createCustomRole(id, name, scopeType = 'guild') {
|
|
367
389
|
if (BUILTIN_ROLES.some(role => role.id === id)) {
|
|
368
|
-
throw new Error(
|
|
390
|
+
throw new Error(`不能覆盖内置角色:${id}`);
|
|
369
391
|
}
|
|
370
392
|
const now = new Date();
|
|
371
393
|
const [existing] = await this.ctx.database.get('new_auth_role', { id });
|
|
@@ -412,6 +434,9 @@ class NewAuthService {
|
|
|
412
434
|
}
|
|
413
435
|
async setCommandStatus(input, status) {
|
|
414
436
|
const command = await this.resolveCommandInput(input);
|
|
437
|
+
if (status === 'disabled' && isSelfCommandPath(command.commandPath)) {
|
|
438
|
+
throw new Error('不能禁用 new-auth 管理指令');
|
|
439
|
+
}
|
|
415
440
|
await this.ctx.database.set('new_auth_command', command.id, {
|
|
416
441
|
status,
|
|
417
442
|
updatedAt: new Date(),
|
|
@@ -429,6 +454,7 @@ class NewAuthService {
|
|
|
429
454
|
return command;
|
|
430
455
|
}
|
|
431
456
|
async setCommandPolicy(scope, roleId, input, state) {
|
|
457
|
+
await this.ensureRoleExists(roleId);
|
|
432
458
|
const command = await this.resolveCommandInput(input);
|
|
433
459
|
await this.setPolicy(scope, roleId, command.id, state);
|
|
434
460
|
if (state !== 'inherit' && command.status === 'pending') {
|
|
@@ -439,7 +465,7 @@ class NewAuthService {
|
|
|
439
465
|
async applySuggestedPolicy(input, scope = 'global', mode = 'legacy') {
|
|
440
466
|
const command = await this.resolveCommandInput(input);
|
|
441
467
|
const suggestion = mode === 'advisor'
|
|
442
|
-
? this.
|
|
468
|
+
? await this.getAiAssignSuggestion(command)
|
|
443
469
|
: getLegacySuggestion(command.legacyAuthority);
|
|
444
470
|
await this.applyRolesToCommand(scope, command.id, suggestion.roles);
|
|
445
471
|
if (command.status === 'pending') {
|
|
@@ -447,6 +473,60 @@ class NewAuthService {
|
|
|
447
473
|
}
|
|
448
474
|
return command;
|
|
449
475
|
}
|
|
476
|
+
async configureCommandRoles(input, scope = 'global', roleIds = []) {
|
|
477
|
+
const command = await this.resolveCommandInput(input);
|
|
478
|
+
await this.applyRolesToCommand(scope, command.id, roleIds);
|
|
479
|
+
if (command.status === 'pending') {
|
|
480
|
+
await this.setCommandStatus(command.id, 'configured');
|
|
481
|
+
}
|
|
482
|
+
return command;
|
|
483
|
+
}
|
|
484
|
+
async generateAiSuggestion(input) {
|
|
485
|
+
const command = await this.resolveCommandInput(input);
|
|
486
|
+
return this.getAiAssignSuggestion(command);
|
|
487
|
+
}
|
|
488
|
+
async generateAiSuggestionsForPending() {
|
|
489
|
+
const commands = await this.listCommands({ pending: true });
|
|
490
|
+
const suggestions = {};
|
|
491
|
+
for (const command of commands) {
|
|
492
|
+
suggestions[command.id] = await this.getAiAssignSuggestion(command);
|
|
493
|
+
}
|
|
494
|
+
return suggestions;
|
|
495
|
+
}
|
|
496
|
+
async listGuildManageableCommands(session, query) {
|
|
497
|
+
const scope = await this.ensureGuildManager(session);
|
|
498
|
+
const commands = (await this.listCommands({ all: true, query }))
|
|
499
|
+
.filter(command => command.status === 'configured' && command.allowGuildOverride);
|
|
500
|
+
const roles = ['guild-member', 'guild-admin', 'guild-owner'];
|
|
501
|
+
const rows = [];
|
|
502
|
+
for (const command of commands) {
|
|
503
|
+
const states = [];
|
|
504
|
+
for (const roleId of roles) {
|
|
505
|
+
states.push(`${roleId}:${await this.getEffectivePolicy(scope, roleId, command.id)}`);
|
|
506
|
+
}
|
|
507
|
+
rows.push({ command, states });
|
|
508
|
+
}
|
|
509
|
+
return rows;
|
|
510
|
+
}
|
|
511
|
+
async setGuildManagedPolicy(session, roleId, input, state) {
|
|
512
|
+
const scope = await this.ensureGuildManager(session);
|
|
513
|
+
const role = await this.ensureRoleExists(roleId);
|
|
514
|
+
if (role.scopeType !== 'guild') {
|
|
515
|
+
throw new Error('群内自治只能配置群内角色');
|
|
516
|
+
}
|
|
517
|
+
if (role.id === 'bot-admin') {
|
|
518
|
+
throw new Error('群内自治不能配置 Bot 管理员');
|
|
519
|
+
}
|
|
520
|
+
const command = await this.resolveCommandInput(input);
|
|
521
|
+
if (command.status !== 'configured') {
|
|
522
|
+
throw new Error('该指令尚未由 Koishi 管理员配置');
|
|
523
|
+
}
|
|
524
|
+
if (!command.allowGuildOverride) {
|
|
525
|
+
throw new Error('该指令不允许群内自治');
|
|
526
|
+
}
|
|
527
|
+
await this.setPolicy(scope, role.id, command.id, state);
|
|
528
|
+
return { command, scope };
|
|
529
|
+
}
|
|
450
530
|
async autoAssignPending(scope = 'global', mode = 'advisor') {
|
|
451
531
|
const commands = await this.listCommands({ pending: true });
|
|
452
532
|
for (const command of commands) {
|
|
@@ -500,10 +580,13 @@ class NewAuthService {
|
|
|
500
580
|
async applyRolesToCommand(scope, commandId, roleIds) {
|
|
501
581
|
const roles = await this.listRoles();
|
|
502
582
|
const knownRoles = new Set(roles.map(role => role.id));
|
|
503
|
-
|
|
583
|
+
const selectedRoles = new Set(roleIds);
|
|
584
|
+
for (const roleId of selectedRoles) {
|
|
504
585
|
if (!knownRoles.has(roleId))
|
|
505
|
-
|
|
506
|
-
|
|
586
|
+
throw new Error(`未找到角色:${roleId}`);
|
|
587
|
+
}
|
|
588
|
+
for (const role of roles) {
|
|
589
|
+
await this.setPolicy(scope, role.id, commandId, selectedRoles.has(role.id) ? 'allow' : 'deny');
|
|
507
590
|
}
|
|
508
591
|
}
|
|
509
592
|
createCommandRecord(command, now) {
|
|
@@ -512,7 +595,7 @@ class NewAuthService {
|
|
|
512
595
|
const commandPath = command.name;
|
|
513
596
|
const aliases = Object.keys(command._aliases || {});
|
|
514
597
|
const id = makeCommandId(plugin, commandPath);
|
|
515
|
-
|
|
598
|
+
const record = {
|
|
516
599
|
id,
|
|
517
600
|
name: command.displayName || commandPath,
|
|
518
601
|
commandPath,
|
|
@@ -521,15 +604,20 @@ class NewAuthService {
|
|
|
521
604
|
description: this.getDescription(command),
|
|
522
605
|
legacyAuthority,
|
|
523
606
|
status: this.isSelfCommand(command) ? 'configured' : 'pending',
|
|
524
|
-
allowGuildOverride: legacyAuthority <=
|
|
607
|
+
allowGuildOverride: legacyAuthority <= DEFAULT_ALLOW_GUILD_OVERRIDE_AUTHORITY_MAX,
|
|
525
608
|
createdAt: now,
|
|
526
609
|
updatedAt: now,
|
|
527
610
|
};
|
|
611
|
+
return record;
|
|
528
612
|
}
|
|
529
613
|
getDescription(command) {
|
|
530
614
|
const key = `commands.${command.name}.description`;
|
|
531
615
|
const value = this.ctx.i18n.get(key);
|
|
532
|
-
|
|
616
|
+
if (typeof value === 'string')
|
|
617
|
+
return value;
|
|
618
|
+
if (Array.isArray(value))
|
|
619
|
+
return stringifyI18nValue(value[0]);
|
|
620
|
+
return stringifyI18nValue(value);
|
|
533
621
|
}
|
|
534
622
|
async resolveCommandInput(input) {
|
|
535
623
|
const normalized = input.trim();
|
|
@@ -541,7 +629,7 @@ class NewAuthService {
|
|
|
541
629
|
|| record.name === normalized
|
|
542
630
|
|| record.aliases.includes(normalized));
|
|
543
631
|
if (!match)
|
|
544
|
-
throw new Error(
|
|
632
|
+
throw new Error(`未找到指令:${input}`);
|
|
545
633
|
return match;
|
|
546
634
|
}
|
|
547
635
|
async setPolicy(scope, roleId, commandId, state) {
|
|
@@ -568,69 +656,79 @@ class NewAuthService {
|
|
|
568
656
|
});
|
|
569
657
|
return global?.state ?? 'inherit';
|
|
570
658
|
}
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
].join(' '));
|
|
579
|
-
const dangerous = [
|
|
580
|
-
'plugin', 'market', 'install', 'uninstall', 'reload', 'restart',
|
|
581
|
-
'config', 'database', 'db', 'file', 'delete', 'remove', 'exec',
|
|
582
|
-
'eval', 'shell', 'token', 'env', 'broadcast',
|
|
583
|
-
'插件', '市场', '安装', '卸载', '重载', '重启', '配置', '数据库',
|
|
584
|
-
'文件', '删除', '执行', '脚本', '令牌', '环境变量', '广播',
|
|
585
|
-
];
|
|
586
|
-
if (dangerous.some(keyword => text.includes(keyword))) {
|
|
587
|
-
return {
|
|
588
|
-
kind: 'admin',
|
|
589
|
-
roles: ['bot-admin'],
|
|
590
|
-
label: '仅 Bot 管理员',
|
|
591
|
-
reason: '命中实例级或高危操作关键词。',
|
|
592
|
-
};
|
|
659
|
+
async ensureGuildManager(session) {
|
|
660
|
+
if (!session?.platform || !session.guildId || session.isDirect) {
|
|
661
|
+
throw new Error('只能在群聊中管理群内自治');
|
|
662
|
+
}
|
|
663
|
+
const roles = await this.resolveRoles(session);
|
|
664
|
+
if (!roles.includes('bot-admin') && !roles.includes('guild-owner')) {
|
|
665
|
+
throw new Error('只有 Bot 管理员或当前群群主可以管理群内自治');
|
|
593
666
|
}
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
667
|
+
return getScope(session);
|
|
668
|
+
}
|
|
669
|
+
getAutoAssignSuggestion(command) {
|
|
670
|
+
const fallback = getLegacySuggestion(command.legacyAuthority);
|
|
671
|
+
return {
|
|
672
|
+
kind: 'legacy',
|
|
673
|
+
roles: fallback.roles,
|
|
674
|
+
label: '等待 AI 分配',
|
|
675
|
+
reason: '未调用 AI 时只显示旧 authority 建议,不做关键词推断。',
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
hasAiAdvisor() {
|
|
679
|
+
const chatluna = this.ctx.chatluna;
|
|
680
|
+
const defaultModel = chatluna?.currentConfig?.defaultModel ?? chatluna?.config?.defaultModel;
|
|
681
|
+
return typeof chatluna?.createChatModel === 'function'
|
|
682
|
+
&& typeof defaultModel === 'string'
|
|
683
|
+
&& !!defaultModel
|
|
684
|
+
&& defaultModel !== '无';
|
|
685
|
+
}
|
|
686
|
+
async getAiAssignSuggestion(command) {
|
|
687
|
+
const fallback = getLegacySuggestion(command.legacyAuthority);
|
|
688
|
+
if (!this.hasAiAdvisor()) {
|
|
599
689
|
return {
|
|
600
|
-
kind: '
|
|
601
|
-
roles:
|
|
602
|
-
label:
|
|
603
|
-
reason: '
|
|
690
|
+
kind: 'legacy',
|
|
691
|
+
roles: fallback.roles,
|
|
692
|
+
label: fallback.label,
|
|
693
|
+
reason: 'ChatLuna 默认模型不可用,已回退到旧 authority 建议。',
|
|
604
694
|
};
|
|
605
695
|
}
|
|
606
|
-
|
|
607
|
-
|
|
696
|
+
try {
|
|
697
|
+
const roles = await this.listRoles();
|
|
698
|
+
const chatluna = this.ctx.chatluna;
|
|
699
|
+
const defaultModel = chatluna.currentConfig?.defaultModel ?? chatluna.config?.defaultModel;
|
|
700
|
+
const modelRef = await chatluna.createChatModel(defaultModel);
|
|
701
|
+
const model = modelRef?.value;
|
|
702
|
+
if (typeof model?.invoke !== 'function')
|
|
703
|
+
throw new Error('ChatLuna 模型未就绪');
|
|
704
|
+
const output = await model.invoke(buildAiAdvisorPrompt(command, roles), {
|
|
705
|
+
maxTokens: 512,
|
|
706
|
+
signal: AbortSignal.timeout(60000),
|
|
707
|
+
configurable: {
|
|
708
|
+
conversationId: 'newauth-permission-advisor',
|
|
709
|
+
userId: 'newauth',
|
|
710
|
+
},
|
|
711
|
+
});
|
|
712
|
+
const parsed = parseAiAdvisorOutput(output);
|
|
713
|
+
const knownRoles = new Set(roles.map(role => role.id));
|
|
714
|
+
const selected = [...new Set(parsed.roles)].filter(roleId => knownRoles.has(roleId));
|
|
715
|
+
if (!selected.length)
|
|
716
|
+
throw new Error('AI 没有返回有效角色');
|
|
608
717
|
return {
|
|
609
|
-
kind: '
|
|
610
|
-
roles:
|
|
611
|
-
label: '
|
|
612
|
-
reason: '
|
|
718
|
+
kind: 'ai',
|
|
719
|
+
roles: selected,
|
|
720
|
+
label: selected.map(roleId => roles.find(role => role.id === roleId)?.name ?? roleId).join('、'),
|
|
721
|
+
reason: parsed.reason || '由 ChatLuna 默认模型生成。',
|
|
613
722
|
};
|
|
614
723
|
}
|
|
615
|
-
|
|
616
|
-
'help', 'sign', 'rank', 'weather', 'search', 'music', 'play', 'query',
|
|
617
|
-
'帮助', '签到', '排行', '天气', '搜索', '点歌', '播放', '查询',
|
|
618
|
-
];
|
|
619
|
-
if (publicUse.some(keyword => text.includes(keyword))) {
|
|
724
|
+
catch (error) {
|
|
620
725
|
return {
|
|
621
|
-
kind: '
|
|
622
|
-
roles:
|
|
623
|
-
label:
|
|
624
|
-
reason:
|
|
726
|
+
kind: 'legacy',
|
|
727
|
+
roles: fallback.roles,
|
|
728
|
+
label: fallback.label,
|
|
729
|
+
reason: `AI 分配失败,已回退到旧 authority 建议:${error instanceof Error ? error.message : String(error)}`,
|
|
625
730
|
};
|
|
626
731
|
}
|
|
627
|
-
const fallback = getLegacySuggestion(command.legacyAuthority);
|
|
628
|
-
return {
|
|
629
|
-
kind: 'legacy',
|
|
630
|
-
roles: fallback.roles,
|
|
631
|
-
label: fallback.label,
|
|
632
|
-
reason: '未命中明确语义,回退到旧 authority 建议。',
|
|
633
|
-
};
|
|
634
732
|
}
|
|
635
733
|
async ensureRoleMember(roleId, platform, userId, scope) {
|
|
636
734
|
await this.ensureRoleExists(roleId);
|
|
@@ -643,7 +741,7 @@ class NewAuthService {
|
|
|
643
741
|
async ensureRoleExists(roleId) {
|
|
644
742
|
const [role] = await this.ctx.database.get('new_auth_role', { id: roleId });
|
|
645
743
|
if (!role)
|
|
646
|
-
throw new Error(
|
|
744
|
+
throw new Error(`未找到角色:${roleId}`);
|
|
647
745
|
return role;
|
|
648
746
|
}
|
|
649
747
|
hasPlatformRole(session, roleNames) {
|
|
@@ -674,7 +772,7 @@ class NewAuthService {
|
|
|
674
772
|
return [...(this.ctx.$commander?._commandList || [])];
|
|
675
773
|
}
|
|
676
774
|
isSelfCommand(command) {
|
|
677
|
-
return command.name
|
|
775
|
+
return isSelfCommandPath(command.name);
|
|
678
776
|
}
|
|
679
777
|
}
|
|
680
778
|
exports.NewAuthService = NewAuthService;
|
|
@@ -723,6 +821,16 @@ function registerConsole(ctx, service) {
|
|
|
723
821
|
ctx.console.refresh('newauth');
|
|
724
822
|
return { success: true, count };
|
|
725
823
|
}, { authority: 4 });
|
|
824
|
+
ctx.console.addListener('newauth/generateAiSuggestion', async (payload) => {
|
|
825
|
+
return service.generateAiSuggestion(payload.commandId);
|
|
826
|
+
}, { authority: 4 });
|
|
827
|
+
ctx.console.addListener('newauth/generateAiSuggestions', async () => {
|
|
828
|
+
const suggestions = await service.generateAiSuggestionsForPending();
|
|
829
|
+
return { suggestions };
|
|
830
|
+
}, { authority: 4 });
|
|
831
|
+
ctx.console.addListener('newauth/configureCommandRoles', async (payload) => {
|
|
832
|
+
return ok(service.configureCommandRoles(payload.commandId, payload.scope, payload.roleIds));
|
|
833
|
+
}, { authority: 4 });
|
|
726
834
|
ctx.console.addListener('newauth/createRole', async (payload) => {
|
|
727
835
|
return ok(service.createCustomRole(payload.id, payload.name, payload.scopeType));
|
|
728
836
|
}, { authority: 4 });
|
|
@@ -794,6 +902,15 @@ function registerManagementCommands(ctx, config, service) {
|
|
|
794
902
|
const record = await service.setCommandPolicy(scope, roleId, command, 'inherit');
|
|
795
903
|
return `已恢复继承:${scope} ${roleId} -> ${record.commandPath}`;
|
|
796
904
|
});
|
|
905
|
+
ctx.command('newauth.assign <command> <roleIds> [scope]', '按角色配置单个指令,roleIds 用逗号分隔', { authority })
|
|
906
|
+
.action(async (_, command, roleIds, scope = 'global') => {
|
|
907
|
+
const roles = String(roleIds || '')
|
|
908
|
+
.split(',')
|
|
909
|
+
.map(role => role.trim())
|
|
910
|
+
.filter(Boolean);
|
|
911
|
+
const record = await service.configureCommandRoles(command, scope, roles);
|
|
912
|
+
return `已配置:${record.commandPath} -> ${roles.join(', ') || '无角色'} (${scope})`;
|
|
913
|
+
});
|
|
797
914
|
ctx.command('newauth.disable <command>', '禁用指令', { authority })
|
|
798
915
|
.action(async (_, command) => {
|
|
799
916
|
const record = await service.setCommandStatus(command, 'disabled');
|
|
@@ -837,6 +954,38 @@ function registerManagementCommands(ctx, config, service) {
|
|
|
837
954
|
const count = await service.copyRolePolicies(sourceRoleId, targetRoleId, scope);
|
|
838
955
|
return `已复制 ${count} 条策略:${sourceRoleId} -> ${targetRoleId} (${scope})`;
|
|
839
956
|
});
|
|
957
|
+
ctx.command('newauth.guild', '管理当前群的新权限自治', { authority: 0 })
|
|
958
|
+
.action(async ({ session }) => {
|
|
959
|
+
const rows = await service.listGuildManageableCommands(session);
|
|
960
|
+
return [
|
|
961
|
+
`当前群可自治指令:${rows.length}`,
|
|
962
|
+
'使用 newauth.guild.commands 查看列表。',
|
|
963
|
+
].join('\n');
|
|
964
|
+
});
|
|
965
|
+
ctx.command('newauth.guild.commands [query:text]', '列出当前群可自治指令', { authority: 0 })
|
|
966
|
+
.action(async ({ session }, query) => {
|
|
967
|
+
const rows = await service.listGuildManageableCommands(session, query);
|
|
968
|
+
if (!rows.length)
|
|
969
|
+
return '当前群没有可自治指令。';
|
|
970
|
+
return rows.map(({ command, states }) => {
|
|
971
|
+
return `${command.commandPath} (${command.id}) ${states.join(' ')}`;
|
|
972
|
+
}).join('\n');
|
|
973
|
+
});
|
|
974
|
+
ctx.command('newauth.guild.allow <roleId> <command>', '允许当前群角色使用可自治指令', { authority: 0 })
|
|
975
|
+
.action(async ({ session }, roleId, command) => {
|
|
976
|
+
const result = await service.setGuildManagedPolicy(session, roleId, command, 'allow');
|
|
977
|
+
return `已允许:${result.scope} ${roleId} -> ${result.command.commandPath}`;
|
|
978
|
+
});
|
|
979
|
+
ctx.command('newauth.guild.deny <roleId> <command>', '关闭当前群角色使用可自治指令', { authority: 0 })
|
|
980
|
+
.action(async ({ session }, roleId, command) => {
|
|
981
|
+
const result = await service.setGuildManagedPolicy(session, roleId, command, 'deny');
|
|
982
|
+
return `已关闭:${result.scope} ${roleId} -> ${result.command.commandPath}`;
|
|
983
|
+
});
|
|
984
|
+
ctx.command('newauth.guild.inherit <roleId> <command>', '恢复当前群角色的继承策略', { authority: 0 })
|
|
985
|
+
.action(async ({ session }, roleId, command) => {
|
|
986
|
+
const result = await service.setGuildManagedPolicy(session, roleId, command, 'inherit');
|
|
987
|
+
return `已恢复继承:${result.scope} ${roleId} -> ${result.command.commandPath}`;
|
|
988
|
+
});
|
|
840
989
|
ctx.command('newauth.plugin.assign <plugin> <target> [scope]', '批量配置插件指令', { authority })
|
|
841
990
|
.action(async (_, plugin, target, scope = 'global') => {
|
|
842
991
|
if (!['legacy', 'bot-admin', 'guild-owner', 'guild-admin', 'guild-member'].includes(target)) {
|
|
@@ -895,15 +1044,96 @@ function getAssignTargetRoles(target) {
|
|
|
895
1044
|
return ['guild-owner'];
|
|
896
1045
|
return ['bot-admin'];
|
|
897
1046
|
}
|
|
1047
|
+
function buildAiAdvisorPrompt(command, roles) {
|
|
1048
|
+
const roleLines = roles.map(role => {
|
|
1049
|
+
return `- ${role.id}: ${role.name},${role.scopeType === 'global' ? '全局' : '群内'},${role.builtin ? '内置' : '自定义'}`;
|
|
1050
|
+
}).join('\n');
|
|
1051
|
+
return [
|
|
1052
|
+
'你是 Koishi Bot 的指令权限分配助手。请根据指令元数据判断此指令应该分配给哪些角色。',
|
|
1053
|
+
'必须遵守:群主不是 Bot 管理员;实例级、配置级、文件/数据库/插件/执行类能力应保守;不确定时选择 bot-admin。',
|
|
1054
|
+
'只允许返回 JSON,不要 Markdown,不要解释 JSON 之外的内容。',
|
|
1055
|
+
'JSON 格式:{"roles":["role-id"],"reason":"一句中文理由"}',
|
|
1056
|
+
'',
|
|
1057
|
+
'可选角色:',
|
|
1058
|
+
roleLines,
|
|
1059
|
+
'',
|
|
1060
|
+
'指令:',
|
|
1061
|
+
JSON.stringify({
|
|
1062
|
+
id: command.id,
|
|
1063
|
+
name: command.name,
|
|
1064
|
+
commandPath: command.commandPath,
|
|
1065
|
+
aliases: command.aliases,
|
|
1066
|
+
plugin: command.plugin,
|
|
1067
|
+
description: command.description,
|
|
1068
|
+
legacyAuthority: command.legacyAuthority,
|
|
1069
|
+
}, null, 2),
|
|
1070
|
+
].join('\n');
|
|
1071
|
+
}
|
|
1072
|
+
function parseAiAdvisorOutput(output) {
|
|
1073
|
+
const text = extractMessageText(output);
|
|
1074
|
+
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text);
|
|
1075
|
+
const raw = fenced?.[1] ?? /\{[\s\S]*\}/.exec(text)?.[0] ?? text;
|
|
1076
|
+
const parsed = JSON.parse(raw);
|
|
1077
|
+
if (!Array.isArray(parsed.roles))
|
|
1078
|
+
throw new Error('AI 返回格式缺少 roles 数组');
|
|
1079
|
+
return {
|
|
1080
|
+
roles: parsed.roles.filter(role => typeof role === 'string'),
|
|
1081
|
+
reason: typeof parsed.reason === 'string' ? parsed.reason : '',
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
function extractMessageText(value) {
|
|
1085
|
+
if (value == null)
|
|
1086
|
+
return '';
|
|
1087
|
+
if (typeof value === 'string')
|
|
1088
|
+
return value;
|
|
1089
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
1090
|
+
return String(value);
|
|
1091
|
+
if (Array.isArray(value))
|
|
1092
|
+
return value.map(extractMessageText).join('');
|
|
1093
|
+
if (typeof value === 'object') {
|
|
1094
|
+
const record = value;
|
|
1095
|
+
if ('content' in record)
|
|
1096
|
+
return extractMessageText(record.content);
|
|
1097
|
+
if ('text' in record)
|
|
1098
|
+
return extractMessageText(record.text);
|
|
1099
|
+
}
|
|
1100
|
+
return String(value);
|
|
1101
|
+
}
|
|
898
1102
|
function omitPrimary(record, key) {
|
|
899
1103
|
const { [key]: _value, ...rest } = record;
|
|
900
1104
|
return rest;
|
|
901
1105
|
}
|
|
1106
|
+
function isSelfCommandPath(commandPath) {
|
|
1107
|
+
return commandPath === 'newauth'
|
|
1108
|
+
|| commandPath.startsWith('newauth/')
|
|
1109
|
+
|| commandPath.startsWith('newauth.');
|
|
1110
|
+
}
|
|
1111
|
+
function isGuildAutonomyCommandPath(commandPath) {
|
|
1112
|
+
return commandPath === 'newauth.guild'
|
|
1113
|
+
|| commandPath.startsWith('newauth.guild.');
|
|
1114
|
+
}
|
|
902
1115
|
function inferPlugin(command) {
|
|
1116
|
+
if (isSelfCommandPath(command.name))
|
|
1117
|
+
return 'new-auth';
|
|
903
1118
|
const source = command.caller?.scope || command.ctx?.scope;
|
|
904
1119
|
const plugin = source?.plugin?.name || source?.uid || source?.id || 'unknown';
|
|
905
1120
|
return String(plugin).replace(/^koishi-plugin-/, '') || 'unknown';
|
|
906
1121
|
}
|
|
1122
|
+
function stringifyI18nValue(value) {
|
|
1123
|
+
if (value == null)
|
|
1124
|
+
return '';
|
|
1125
|
+
if (typeof value === 'string')
|
|
1126
|
+
return value;
|
|
1127
|
+
if (typeof value === 'number' || typeof value === 'boolean')
|
|
1128
|
+
return String(value);
|
|
1129
|
+
if (Array.isArray(value))
|
|
1130
|
+
return value.map(stringifyI18nValue).filter(Boolean).join('\n');
|
|
1131
|
+
if (typeof value === 'object') {
|
|
1132
|
+
const record = value;
|
|
1133
|
+
return stringifyI18nValue(record['zh-CN'] ?? record.zh ?? record[''] ?? Object.values(record)[0]);
|
|
1134
|
+
}
|
|
1135
|
+
return '';
|
|
1136
|
+
}
|
|
907
1137
|
function makeCommandId(plugin, commandPath) {
|
|
908
1138
|
return `command:${normalizeSegment(plugin)}:${normalizeSegment(commandPath)}`;
|
|
909
1139
|
}
|
|
@@ -919,7 +1149,7 @@ function getScope(session) {
|
|
|
919
1149
|
function parseUid(uid) {
|
|
920
1150
|
const index = uid.indexOf(':');
|
|
921
1151
|
if (index <= 0 || index === uid.length - 1) {
|
|
922
|
-
throw new Error('
|
|
1152
|
+
throw new Error('UID 必须为 platform:userId 格式');
|
|
923
1153
|
}
|
|
924
1154
|
return {
|
|
925
1155
|
platform: uid.slice(0, index),
|
package/newauth.md
CHANGED
|
@@ -488,7 +488,7 @@ Bot 管理
|
|
|
488
488
|
|
|
489
489
|
## 9.3 当前范围切换
|
|
490
490
|
|
|
491
|
-
|
|
491
|
+
页面内有范围选择:
|
|
492
492
|
|
|
493
493
|
```txt
|
|
494
494
|
配置范围:
|
|
@@ -586,7 +586,7 @@ Bot 管理
|
|
|
586
586
|
数据库管理:不允许群内自治
|
|
587
587
|
```
|
|
588
588
|
|
|
589
|
-
|
|
589
|
+
群主打开自己群的配置页时(通过指令列出 而没有webui),只能看到:
|
|
590
590
|
|
|
591
591
|
```txt
|
|
592
592
|
允许群内自治的指令
|
|
@@ -1214,7 +1214,7 @@ deny:
|
|
|
1214
1214
|
|
|
1215
1215
|
## 20.5 Platform Role Provider
|
|
1216
1216
|
|
|
1217
|
-
|
|
1217
|
+
负责把平台身份转为角色。(Onebot平台会自动按QQ号来解析用户平台名称)
|
|
1218
1218
|
|
|
1219
1219
|
例如:
|
|
1220
1220
|
|
|
@@ -1454,5 +1454,5 @@ authority 只做建议,不直接授权;
|
|
|
1454
1454
|
|
|
1455
1455
|
---
|
|
1456
1456
|
|
|
1457
|
-
KOISHI侧边栏的图标不使用默认的
|
|
1458
|
-
同时根据F:\chatluna-1.4.0-alpha.17 为指令权限限制制作一个AI
|
|
1457
|
+
KOISHI侧边栏的图标不使用默认的 使用“⌗”
|
|
1458
|
+
同时根据F:\chatluna-1.4.0-alpha.17 为指令权限限制制作一个AI自动分配(即:让AI来配置节点应该给哪个组)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-new-auth",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.4.4",
|
|
4
|
+
"description": "Koishi 的角色与作用域指令权限系统。",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -49,6 +49,10 @@
|
|
|
49
49
|
"service": {
|
|
50
50
|
"required": [
|
|
51
51
|
"database"
|
|
52
|
+
],
|
|
53
|
+
"optional": [
|
|
54
|
+
"console",
|
|
55
|
+
"chatluna"
|
|
52
56
|
]
|
|
53
57
|
}
|
|
54
58
|
}
|