koishi-plugin-new-auth 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -33
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/lib/index.d.ts +12 -9
- package/lib/index.js +57 -37
- package/newauth.md +4 -4
- package/package.json +2 -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,22 @@ 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.plugin.assign <plugin> <target> [scope]
|
|
49
52
|
```
|
|
50
53
|
|
|
51
|
-
`uid`
|
|
54
|
+
`uid` 使用 `platform:userId`,例如 `onebot:10000`。
|
|
52
55
|
|
|
53
|
-
`scope`
|
|
56
|
+
`scope` 可以是:
|
|
54
57
|
|
|
55
58
|
- `global`
|
|
56
59
|
- `guild:<platform>:<guildId>`
|
|
57
60
|
|
|
58
|
-
|
|
61
|
+
省略 `scope` 时,策略指令默认写入 `global`。
|
|
59
62
|
|
|
60
|
-
|
|
63
|
+
`newauth.assign` 与待配置 WebUI 的角色勾选行为一致:列出的角色写为允许,其他角色在该作用域下写为关闭,并把待配置指令变为已配置。
|
|
64
|
+
|
|
65
|
+
## 内置角色
|
|
61
66
|
|
|
62
67
|
```txt
|
|
63
68
|
bot-admin
|
|
@@ -67,24 +72,23 @@ guild-member
|
|
|
67
72
|
guest
|
|
68
73
|
```
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
自定义角色不继承其他角色。要让自定义角色拥有某些指令,需要显式写策略,或在 WebUI / `newauth.role.copy` 中复制另一个角色当前的显式策略。
|
|
71
76
|
|
|
72
77
|
## WebUI
|
|
73
78
|
|
|
74
|
-
|
|
79
|
+
安装 Koishi Console 后打开 `⌗ 新权限` 页面。
|
|
75
80
|
|
|
76
|
-
|
|
81
|
+
页面提供:
|
|
77
82
|
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
86
|
-
- batch assigning all commands from one plugin.
|
|
83
|
+
- 以角色为入口的指令权限编辑;
|
|
84
|
+
- 全局默认和单群作用域切换;
|
|
85
|
+
- 待配置指令审查和角色勾选分配;
|
|
86
|
+
- 旧 `authority` 建议与 AI 风格自动分配建议;
|
|
87
|
+
- 指令状态和群内自治开关;
|
|
88
|
+
- 自定义角色创建、显式成员管理;
|
|
89
|
+
- 从其他角色复制权限;
|
|
90
|
+
- 按插件批量采用建议、给群成员、给群管理员或仅给 Bot 管理员。
|
|
87
91
|
|
|
88
|
-
|
|
92
|
+
WebUI 是 Koishi 实例管理员界面,Console 入口和所有事件都要求 `authority: 4`。群主自治只应影响允许自治的单群指令,不等于进入实例管理面板。
|
|
89
93
|
|
|
90
|
-
|
|
94
|
+
自动分配策略默认保守:实例级或高危操作只给 `bot-admin`,群管理操作给 `guild-admin` 和 `guild-owner`,普通用户功能给 `guild-member` 及以上,无法判断时回退到旧 `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 _e,ref as d,computed as c,watch as W,openBlock as s,createElementBlock as u,createElementVNode as t,toDisplayString as o,withDirectives as p,Fragment as k,renderList as y,vModelSelect as C,normalizeClass as w,createCommentVNode as L,withModifiers as ee,vModelText as T,vModelCheckbox as Ce,createTextVNode as F}from"vue";import{store as le,send as b}from"@koishijs/client";const we={class:"new-auth-page"},Ie={class:"topbar"},$e={class:"top-actions"},Se=["value"],Pe=["disabled"],Re=["disabled"],Ve={class:"tabs"},Ae={key:0,class:"error"},Ue={key:1,class:"role-layout"},xe={class:"role-list"},Me=["onClick"],Te=["disabled"],Oe={key:0,class:"detail"},Ge={class:"detail-head"},Ne={class:"detail-tools"},Be=["value"],De=["disabled"],Ee={class:"check"},Le={class:"member-panel"},Fe={class:"member-list"},Ke=["disabled","onClick"],qe=["value"],ze=["disabled"],je={class:"filters"},He={class:"command-list"},Je={class:"command-main"},Qe={class:"state-buttons"},We=["onClick"],Xe=["onClick"],Ye=["onClick"],Ze={key:2,class:"pending-grid"},el={class:"role-picker"},ll=["checked","onChange"],tl={class:"row-actions"},nl=["disabled","onClick"],ol=["disabled","onClick"],sl=["disabled","onClick"],ul={key:0,class:"empty"},il={key:3,class:"commands-table"},al={class:"filters"},dl={class:"plugin-bulk"},rl=["value"],vl=["disabled"],cl=["disabled"],pl=["disabled"],gl=["disabled"],bl={class:"command-main"},fl={class:"command-controls"},kl={class:"check"},yl=["checked","onChange"],hl=["value","onChange"],ml=_e({__name:"new-auth",setup(A){const f=d("roles"),a=d("global"),v=d(""),h=d(""),I=d("all"),K=d(false),r=d(false),O=d(""),$=d(""),U=d(""),q=d("guild"),x=d(""),G=d("global"),N=d(""),m=d(""),_=d({}),S=c(()=>le.newauth||{roles:[],commands:[],policies:[],members:[],scopes:[{id:"global",name:"全局默认"}],pendingCount:0}),P=c(()=>S.value.roles),B=c(()=>S.value.commands),z=c(()=>S.value.policies),te=c(()=>S.value.members),ne=c(()=>{var n;return(n=S.value.scopes)!=null&&n.length?S.value.scopes:[{id:"global",name:"全局默认"}]}),R=c(()=>B.value.filter(n=>n.status==="pending")),V=c(()=>P.value.find(n=>n.id===v.value)),X=c(()=>te.value.filter(n=>n.roleId===v.value)),oe=c(()=>P.value.filter(n=>n.id!==v.value)),se=c(()=>[...new Set(B.value.map(n=>n.plugin))].sort());W(P,n=>{!v.value&&n.length&&(v.value=n[0].id)},{immediate:true}),W(a,n=>{G.value=n==="global"?"global":n}),W([R,z,a],()=>{for(const n of R.value)D(n)},{immediate:true});const Y=c(()=>{const n=h.value.toLowerCase();return B.value.filter(l=>I.value!=="all"&&l.status!==I.value?false:n?[l.id,l.name,l.commandPath,l.plugin,l.description].some(e=>e==null?void 0:e.toLowerCase().includes(n)):true)}),ue=c(()=>V.value?Y.value.filter(n=>{var l,e;return K.value||((l=V.value)==null?void 0:l.id)==="bot-admin"||((e=V.value)==null?void 0:e.scopeType)==="global"?true:n.allowGuildOverride}):[]);function j(n){const l=z.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 ie(){return g((async()=>{const n=await b("newauth/getData");le.newauth=n})())}function H(n,l){return g(b("newauth/setPolicy",{scope:a.value,roleId:v.value,commandId:n,state:l}))}function M(n){return`${a.value}:${n}`}function ae(n){const l=z.value.filter(e=>e.scope===a.value&&e.commandId===n.id&&e.state==="allow").map(e=>e.roleId);return l.length?l:[...n.autoAssign.roles]}function D(n){const l=M(n.id);_.value[l]||(_.value[l]=ae(n))}function de(n,l){return D(n),_.value[M(n.id)].includes(l)}function re(n,l,e){D(n);const i=M(n.id),J=e.target.checked,Q=new Set(_.value[i]);J?Q.add(l):Q.delete(l),_.value[i]=[...Q]}function Z(n,l){_.value[M(n.id)]=[...l==="legacy"?n.suggestion.roles:n.autoAssign.roles]}function ve(n){return D(n),g(b("newauth/configureCommandRoles",{commandId:n.id,scope:a.value,roleIds:_.value[M(n.id)]}))}function ce(){return g(b("newauth/autoAssignPending",{scope:a.value,mode:"advisor"}))}function pe(n,l){const e=l.target;return g(b("newauth/setGuildOverride",{commandId:n.id,allowGuildOverride:e.checked}))}function ge(n,l){const e=l.target;return g(b("newauth/setCommandStatus",{commandId:n.id,status:e.value}))}function be(){return g((async()=>{await b("newauth/createRole",{id:$.value,name:U.value,scopeType:q.value}),v.value=$.value,$.value="",U.value=""})())}function fe(){return g((async()=>{await b("newauth/addMember",{roleId:v.value,uid:x.value,scope:G.value}),x.value=""})())}function ke(n){return g(b("newauth/removeMember",{roleId:n.roleId,uid:`${n.platform}:${n.userId}`,scope:n.scope}))}function ye(){return g(b("newauth/copyRolePolicies",{sourceRoleId:N.value,targetRoleId:v.value,scope:a.value}))}function he(n){return`${n.roleId}:${n.platform}:${n.userId}:${n.scope}`}function E(n){return g(b("newauth/assignPluginCommands",{plugin:m.value,scope:a.value,target:n}))}function me(n){return n==="global"?"全局":"群内"}return(n,l)=>(s(),u("main",we,[t("header",Ie,[t("div",null,[l[20]||(l[20]=t("h1",null,"新权限",-1)),t("p",null,o(P.value.length)+" 个角色 / "+o(B.value.length)+" 个指令 / "+o(R.value.length)+" 个待配置",1)]),t("div",$e,[p(t("select",{"onUpdate:modelValue":l[0]||(l[0]=e=>a.value=e),class:"scope-select"},[(s(true),u(k,null,y(ne.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,Se))),128))],512),[[C,a.value]]),t("button",{class:"primary",disabled:r.value||!R.value.length,onClick:ce}," AI 自动分配待配置 ",8,Pe),t("button",{disabled:r.value,onClick:ie},"刷新",8,Re)])]),t("nav",Ve,[t("button",{class:w({active:f.value==="roles"}),onClick:l[1]||(l[1]=e=>f.value="roles")},"角色",2),t("button",{class:w({active:f.value==="pending"}),onClick:l[2]||(l[2]=e=>f.value="pending")},"待配置",2),t("button",{class:w({active:f.value==="commands"}),onClick:l[3]||(l[3]=e=>f.value="commands")},"指令",2)]),O.value?(s(),u("p",Ae,o(O.value),1)):L("v-if",true),f.value==="roles"?(s(),u("section",Ue,[t("aside",xe,[(s(true),u(k,null,y(P.value,e=>(s(),u("button",{key:e.id,class:w(["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(me(e.scopeType)),1)]),t("em",null,o(e.allowCount),1)],10,Me))),128)),t("form",{class:"role-create",onSubmit:ee(be,["prevent"])},[p(t("input",{"onUpdate:modelValue":l[4]||(l[4]=e=>$.value=e),placeholder:"custom:music-admin"},null,512),[[T,$.value,void 0,{trim:true}]]),p(t("input",{"onUpdate:modelValue":l[5]||(l[5]=e=>U.value=e),placeholder:"角色名称"},null,512),[[T,U.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),[[C,q.value]]),t("button",{class:"primary",disabled:r.value||!$.value||!U.value},"创建角色",8,Te)],32)]),V.value?(s(),u("section",Oe,[t("div",Ge,[t("div",null,[t("h2",null,o(V.value.name),1),t("p",null,o(V.value.id),1)]),t("div",Ne,[p(t("select",{"onUpdate:modelValue":l[7]||(l[7]=e=>N.value=e)},[l[22]||(l[22]=t("option",{disabled:"",value:""},"复制来源",-1)),(s(true),u(k,null,y(oe.value,e=>(s(),u("option",{key:e.id,value:e.id},o(e.name),9,Be))),128))],512),[[C,N.value]]),t("button",{disabled:r.value||!N.value,onClick:ye},"复制权限",8,De),t("label",Ee,[p(t("input",{type:"checkbox","onUpdate:modelValue":l[8]||(l[8]=e=>K.value=e)},null,512),[[Ce,K.value]]),l[23]||(l[23]=F(" 审计模式 ",-1))])])]),t("section",Le,[t("div",null,[l[24]||(l[24]=t("strong",null,"成员",-1)),t("small",null,o(X.value.length)+" 个显式成员",1)]),t("div",Fe,[(s(true),u(k,null,y(X.value,e=>(s(),u("span",{key:he(e),class:"member-chip"},[F(o(e.platform)+":"+o(e.userId)+" / "+o(e.scope)+" ",1),t("button",{disabled:r.value,onClick:i=>ke(e)},"移除",8,Ke)]))),128))]),t("form",{class:"member-add",onSubmit:ee(fe,["prevent"])},[p(t("input",{"onUpdate:modelValue":l[9]||(l[9]=e=>x.value=e),placeholder:"platform:userId"},null,512),[[T,x.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,qe)):L("v-if",true)],512),[[C,G.value]]),t("button",{class:"primary",disabled:r.value||!x.value},"添加成员",8,ze)],32)]),t("div",je,[p(t("input",{"onUpdate:modelValue":l[11]||(l[11]=e=>h.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[T,h.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[12]||(l[12]=e=>I.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),[[C,I.value]])]),t("div",He,[(s(true),u(k,null,y(ue.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[t("div",Je,[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",Qe,[t("button",{class:w({active:j(e.id)==="inherit"}),onClick:i=>H(e.id,"inherit")}," 继承 ",10,We),t("button",{class:w({active:j(e.id)==="allow"}),onClick:i=>H(e.id,"allow")}," 开 ",10,Xe),t("button",{class:w({active:j(e.id)==="deny"}),onClick:i=>H(e.id,"deny")}," 关 ",10,Ye)])]))),128))])])):L("v-if",true)])):f.value==="pending"?(s(),u("section",Ze,[(s(true),u(k,null,y(R.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[27]||(l[27]=t("dt",null,"旧建议",-1)),t("dd",null,o(e.suggestion.label),1),l[28]||(l[28]=t("dt",null,"AI 建议",-1)),t("dd",null,o(e.autoAssign.label)+":"+o(e.autoAssign.reason),1)]),t("div",el,[(s(true),u(k,null,y(P.value,i=>(s(),u("label",{key:`${e.id}:${i.id}`,class:"check"},[t("input",{type:"checkbox",checked:de(e,i.id),onChange:J=>re(e,i.id,J)},null,40,ll),F(" "+o(i.name),1)]))),128))]),t("div",tl,[t("button",{disabled:r.value,onClick:i=>Z(e,"legacy")},"填入旧建议",8,nl),t("button",{disabled:r.value,onClick:i=>Z(e,"advisor")},"填入 AI 建议",8,ol),t("button",{class:"primary",disabled:r.value,onClick:i=>ve(e)},"保存分配",8,sl)])]))),128)),R.value.length?L("v-if",true):(s(),u("p",ul,"没有待配置指令。"))])):(s(),u("section",il,[t("div",al,[p(t("input",{"onUpdate:modelValue":l[13]||(l[13]=e=>h.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[T,h.value,void 0,{trim:true}]]),p(t("select",{"onUpdate:modelValue":l[14]||(l[14]=e=>I.value=e)},[...l[29]||(l[29]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[C,I.value]])]),t("div",dl,[p(t("select",{"onUpdate:modelValue":l[15]||(l[15]=e=>m.value=e)},[l[30]||(l[30]=t("option",{disabled:"",value:""},"选择插件",-1)),(s(true),u(k,null,y(se.value,e=>(s(),u("option",{key:e,value:e},o(e),9,rl))),128))],512),[[C,m.value]]),t("button",{disabled:r.value||!m.value,onClick:l[16]||(l[16]=e=>E("legacy"))},"采用插件建议",8,vl),t("button",{disabled:r.value||!m.value,onClick:l[17]||(l[17]=e=>E("guild-member"))},"给群成员",8,cl),t("button",{disabled:r.value||!m.value,onClick:l[18]||(l[18]=e=>E("guild-admin"))},"给群管理员",8,pl),t("button",{disabled:r.value||!m.value,onClick:l[19]||(l[19]=e=>E("bot-admin"))},"仅 Bot 管理员",8,gl)]),(s(true),u(k,null,y(Y.value,e=>(s(),u("article",{key:e.id,class:"command-row"},[t("div",bl,[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",fl,[t("label",kl,[t("input",{type:"checkbox",checked:e.allowGuildOverride,onChange:i=>pe(e,i)},null,40,yl),l[31]||(l[31]=F(" 群内自治 ",-1))]),t("select",{value:e.status,onChange:i=>ge(e,i)},[...l[32]||(l[32]=[t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],40,hl)])]))),128))]))]))}}),_l=(A,f)=>{const a=A.__vccOpts||A;for(const[v,h]of f)a[v]=h;return a},Cl=_l(ml,[["__scopeId","data-v-6014eba6"]]),$l=A=>{A.page({name:"新权限",path:"/new-auth",icon:"⌗",fields:["newauth"],authority:4,component:Cl})};export{$l as default};
|
package/dist/style.css
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
.new-auth-page[data-v-
|
|
1
|
+
.new-auth-page[data-v-6014eba6]{min-height:100vh;background:#eef2f6;color:#18222d;padding:20px}.topbar[data-v-6014eba6],.detail-head[data-v-6014eba6],.filters[data-v-6014eba6],.row-actions[data-v-6014eba6],.command-controls[data-v-6014eba6],.top-actions[data-v-6014eba6],.detail-tools[data-v-6014eba6],.member-add[data-v-6014eba6],.plugin-bulk[data-v-6014eba6]{display:flex;align-items:center;gap:12px}.topbar[data-v-6014eba6]{justify-content:space-between;border:1px solid #d7dee8;border-radius:8px;background:#fff;box-shadow:0 8px 24px #20304814;padding:16px}h1[data-v-6014eba6],h2[data-v-6014eba6],p[data-v-6014eba6]{margin:0}h1[data-v-6014eba6]{font-size:26px;line-height:1.2}h2[data-v-6014eba6]{font-size:20px}p[data-v-6014eba6],small[data-v-6014eba6],span[data-v-6014eba6],dd[data-v-6014eba6],dt[data-v-6014eba6]{color:#607083}button[data-v-6014eba6],select[data-v-6014eba6],input[data-v-6014eba6]{border:1px solid #c9d3df;border-radius:6px;background:#fff;color:#18222d;min-height:34px;padding:0 10px;font:inherit}button[data-v-6014eba6]{cursor:pointer;transition:border-color .16s ease,box-shadow .16s ease,background .16s ease}button[data-v-6014eba6]:not(:disabled):hover,select[data-v-6014eba6]:focus,input[data-v-6014eba6]:focus{border-color:#2d6cdf;box-shadow:0 0 0 3px #2d6cdf1f;outline:none}button[data-v-6014eba6]:disabled{cursor:not-allowed;opacity:.55}.primary[data-v-6014eba6]{background:#1d6f68;border-color:#1d6f68;color:#fff}.tabs[data-v-6014eba6]{display:flex;gap:6px;margin:16px 0}.tabs button[data-v-6014eba6]{background:#f8fafc}.tabs button.active[data-v-6014eba6],.state-buttons button.active[data-v-6014eba6]{background:#17202b;border-color:#17202b;color:#fff}.role-layout[data-v-6014eba6]{display:grid;grid-template-columns:minmax(220px,280px) 1fr;gap:16px}.role-list[data-v-6014eba6]{display:grid;gap:8px;align-content:start}.role-create[data-v-6014eba6]{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-6014eba6]{display:flex;justify-content:space-between;align-items:center;min-height:58px;text-align:left}.role-item span[data-v-6014eba6],.command-main[data-v-6014eba6]{display:grid;gap:3px;min-width:0}.role-item.active[data-v-6014eba6]{border-color:#2d6cdf;box-shadow:inset 3px 0 #2d6cdf,0 4px 14px #2d6cdf1f}.role-item em[data-v-6014eba6]{font-style:normal;color:#9a5a00}.detail[data-v-6014eba6]{min-width:0}.detail-head[data-v-6014eba6]{justify-content:space-between;margin-bottom:12px}.detail-tools[data-v-6014eba6]{flex-wrap:wrap;justify-content:flex-end}.member-panel[data-v-6014eba6]{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-6014eba6]:first-child{display:flex;justify-content:space-between;gap:12px}.member-list[data-v-6014eba6]{display:flex;flex-wrap:wrap;gap:6px;min-height:28px}.member-chip[data-v-6014eba6]{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-6014eba6]{min-height:24px;padding:0 8px;border-radius:999px}.filters[data-v-6014eba6]{margin-bottom:12px}.plugin-bulk[data-v-6014eba6]{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-6014eba6]{flex:1;min-width:180px}.command-list[data-v-6014eba6],.pending-grid[data-v-6014eba6],.commands-table[data-v-6014eba6]{display:grid;gap:8px}.command-row[data-v-6014eba6],.pending-item[data-v-6014eba6]{background:#fff;border:1px solid #d7dee8;border-radius:8px;padding:12px;box-shadow:0 4px 14px #2030480d}.command-row[data-v-6014eba6]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center}.command-main strong[data-v-6014eba6],.command-main span[data-v-6014eba6],.command-main small[data-v-6014eba6]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.state-buttons[data-v-6014eba6]{display:grid;grid-template-columns:repeat(3,54px);gap:6px}.pending-grid[data-v-6014eba6]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.pending-item[data-v-6014eba6]{display:grid;gap:10px}.role-picker[data-v-6014eba6]{display:grid;grid-template-columns:repeat(auto-fill,minmax(128px,1fr));gap:8px}.role-picker .check[data-v-6014eba6]{border:1px solid #d7dee8;border-radius:6px;background:#f8fafc;min-height:32px;padding:0 8px}dl[data-v-6014eba6]{display:grid;grid-template-columns:58px 1fr;gap:4px 8px;margin:0}dd[data-v-6014eba6]{margin:0}.check[data-v-6014eba6]{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.check input[data-v-6014eba6]{min-height:0}.error[data-v-6014eba6]{border:1px solid #e2a15d;background:#fff6e9;color:#85510d;border-radius:6px;padding:10px;margin-bottom:12px}.empty[data-v-6014eba6]{padding:24px 0}@media (max-width: 760px){.new-auth-page[data-v-6014eba6]{padding:12px}.topbar[data-v-6014eba6],.detail-head[data-v-6014eba6],.command-row[data-v-6014eba6]{grid-template-columns:1fr;display:grid}.top-actions[data-v-6014eba6],.filters[data-v-6014eba6],.command-controls[data-v-6014eba6],.detail-tools[data-v-6014eba6],.member-add[data-v-6014eba6],.plugin-bulk[data-v-6014eba6]{flex-wrap:wrap}.role-layout[data-v-6014eba6]{grid-template-columns:1fr}.state-buttons[data-v-6014eba6]{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: {
|
|
@@ -82,6 +82,9 @@ declare module '@koishijs/plugin-console' {
|
|
|
82
82
|
success: true;
|
|
83
83
|
count: number;
|
|
84
84
|
}>;
|
|
85
|
+
'newauth/configureCommandRoles'(payload: ConfigureCommandRolesPayload): Promise<{
|
|
86
|
+
success: true;
|
|
87
|
+
}>;
|
|
85
88
|
'newauth/createRole'(payload: CreateRolePayload): Promise<{
|
|
86
89
|
success: true;
|
|
87
90
|
}>;
|
|
@@ -153,6 +156,11 @@ interface AutoAssignPayload {
|
|
|
153
156
|
scope?: string;
|
|
154
157
|
mode?: 'legacy' | 'advisor';
|
|
155
158
|
}
|
|
159
|
+
interface ConfigureCommandRolesPayload {
|
|
160
|
+
commandId: string;
|
|
161
|
+
scope: string;
|
|
162
|
+
roleIds: string[];
|
|
163
|
+
}
|
|
156
164
|
interface CreateRolePayload {
|
|
157
165
|
id: string;
|
|
158
166
|
name: string;
|
|
@@ -177,18 +185,12 @@ export interface Config {
|
|
|
177
185
|
botAdmins: string[];
|
|
178
186
|
trustLegacyAuthorityAsAdmin: boolean;
|
|
179
187
|
legacyAdminAuthority: number;
|
|
180
|
-
ownerRoleNames: string[];
|
|
181
|
-
adminRoleNames: string[];
|
|
182
|
-
allowGuildOverrideAuthorityMax: number;
|
|
183
188
|
deniedMessage: string;
|
|
184
|
-
grantRuntimeCommandPermission: boolean;
|
|
185
|
-
raiseLegacyAuthority: boolean;
|
|
186
189
|
}
|
|
187
190
|
export declare const Config: Schema<Config>;
|
|
188
191
|
export declare function apply(ctx: Context, config: Config): void;
|
|
189
|
-
export declare class NewAuthService {
|
|
190
|
-
|
|
191
|
-
private config;
|
|
192
|
+
export declare class NewAuthService extends Service {
|
|
193
|
+
config: Config;
|
|
192
194
|
private commandCache;
|
|
193
195
|
private adminSet;
|
|
194
196
|
private ownerRoleNames;
|
|
@@ -262,6 +264,7 @@ export declare class NewAuthService {
|
|
|
262
264
|
setCommandGuildOverride(input: string, allowGuildOverride: boolean): Promise<NewAuthCommandRecord>;
|
|
263
265
|
setCommandPolicy(scope: string, roleId: string, input: string, state: PolicyState): Promise<NewAuthCommandRecord>;
|
|
264
266
|
applySuggestedPolicy(input: string, scope?: string, mode?: 'legacy' | 'advisor'): Promise<NewAuthCommandRecord>;
|
|
267
|
+
configureCommandRoles(input: string, scope?: string, roleIds?: string[]): Promise<NewAuthCommandRecord>;
|
|
265
268
|
autoAssignPending(scope?: string, mode?: 'legacy' | 'advisor'): Promise<number>;
|
|
266
269
|
assignPluginCommands(plugin: string, scope?: string, target?: PluginAssignTarget): Promise<number>;
|
|
267
270
|
getCommand(input: string): Promise<NewAuthCommandRecord>;
|
package/lib/index.js
CHANGED
|
@@ -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 = 3;
|
|
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,14 @@ 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.config = config;
|
|
138
124
|
this.adminSet = new Set(config.botAdmins.map(normalizeKey));
|
|
139
|
-
this.ownerRoleNames = new Set(
|
|
140
|
-
this.adminRoleNames = new Set(
|
|
125
|
+
this.ownerRoleNames = new Set(OWNER_ROLE_NAMES.map(normalizeKey));
|
|
126
|
+
this.adminRoleNames = new Set(ADMIN_ROLE_NAMES.map(normalizeKey));
|
|
141
127
|
}
|
|
142
128
|
async start() {
|
|
143
129
|
await this.ensureBuiltinRoles();
|
|
@@ -183,10 +169,10 @@ class NewAuthService {
|
|
|
183
169
|
if (!result.allowed) {
|
|
184
170
|
return command.config.showWarning ? this.config.deniedMessage : '';
|
|
185
171
|
}
|
|
186
|
-
if (
|
|
172
|
+
if (GRANT_RUNTIME_COMMAND_PERMISSION) {
|
|
187
173
|
this.grantRuntimeCommandPermission(session, command, options || {});
|
|
188
174
|
}
|
|
189
|
-
if (
|
|
175
|
+
if (RAISE_LEGACY_AUTHORITY && session.user) {
|
|
190
176
|
const user = session.user;
|
|
191
177
|
const legacyAuthority = result.command?.legacyAuthority ?? command.config.authority ?? 0;
|
|
192
178
|
if ((user.authority ?? 0) < legacyAuthority) {
|
|
@@ -292,7 +278,7 @@ class NewAuthService {
|
|
|
292
278
|
}
|
|
293
279
|
async listRoles() {
|
|
294
280
|
const records = await this.ctx.database.get('new_auth_role', {});
|
|
295
|
-
return records.sort((a, b) => Number(
|
|
281
|
+
return records.sort((a, b) => Number(b.builtin) - Number(a.builtin) || a.id.localeCompare(b.id));
|
|
296
282
|
}
|
|
297
283
|
async listPolicies() {
|
|
298
284
|
return this.ctx.database.get('new_auth_policy', {});
|
|
@@ -365,7 +351,7 @@ class NewAuthService {
|
|
|
365
351
|
}
|
|
366
352
|
async createCustomRole(id, name, scopeType = 'guild') {
|
|
367
353
|
if (BUILTIN_ROLES.some(role => role.id === id)) {
|
|
368
|
-
throw new Error(
|
|
354
|
+
throw new Error(`不能覆盖内置角色:${id}`);
|
|
369
355
|
}
|
|
370
356
|
const now = new Date();
|
|
371
357
|
const [existing] = await this.ctx.database.get('new_auth_role', { id });
|
|
@@ -412,6 +398,9 @@ class NewAuthService {
|
|
|
412
398
|
}
|
|
413
399
|
async setCommandStatus(input, status) {
|
|
414
400
|
const command = await this.resolveCommandInput(input);
|
|
401
|
+
if (status === 'disabled' && isSelfCommandPath(command.commandPath)) {
|
|
402
|
+
throw new Error('不能禁用 new-auth 管理指令');
|
|
403
|
+
}
|
|
415
404
|
await this.ctx.database.set('new_auth_command', command.id, {
|
|
416
405
|
status,
|
|
417
406
|
updatedAt: new Date(),
|
|
@@ -429,6 +418,7 @@ class NewAuthService {
|
|
|
429
418
|
return command;
|
|
430
419
|
}
|
|
431
420
|
async setCommandPolicy(scope, roleId, input, state) {
|
|
421
|
+
await this.ensureRoleExists(roleId);
|
|
432
422
|
const command = await this.resolveCommandInput(input);
|
|
433
423
|
await this.setPolicy(scope, roleId, command.id, state);
|
|
434
424
|
if (state !== 'inherit' && command.status === 'pending') {
|
|
@@ -447,6 +437,14 @@ class NewAuthService {
|
|
|
447
437
|
}
|
|
448
438
|
return command;
|
|
449
439
|
}
|
|
440
|
+
async configureCommandRoles(input, scope = 'global', roleIds = []) {
|
|
441
|
+
const command = await this.resolveCommandInput(input);
|
|
442
|
+
await this.applyRolesToCommand(scope, command.id, roleIds);
|
|
443
|
+
if (command.status === 'pending') {
|
|
444
|
+
await this.setCommandStatus(command.id, 'configured');
|
|
445
|
+
}
|
|
446
|
+
return command;
|
|
447
|
+
}
|
|
450
448
|
async autoAssignPending(scope = 'global', mode = 'advisor') {
|
|
451
449
|
const commands = await this.listCommands({ pending: true });
|
|
452
450
|
for (const command of commands) {
|
|
@@ -500,10 +498,13 @@ class NewAuthService {
|
|
|
500
498
|
async applyRolesToCommand(scope, commandId, roleIds) {
|
|
501
499
|
const roles = await this.listRoles();
|
|
502
500
|
const knownRoles = new Set(roles.map(role => role.id));
|
|
503
|
-
|
|
501
|
+
const selectedRoles = new Set(roleIds);
|
|
502
|
+
for (const roleId of selectedRoles) {
|
|
504
503
|
if (!knownRoles.has(roleId))
|
|
505
|
-
|
|
506
|
-
|
|
504
|
+
throw new Error(`未找到角色:${roleId}`);
|
|
505
|
+
}
|
|
506
|
+
for (const role of roles) {
|
|
507
|
+
await this.setPolicy(scope, role.id, commandId, selectedRoles.has(role.id) ? 'allow' : 'deny');
|
|
507
508
|
}
|
|
508
509
|
}
|
|
509
510
|
createCommandRecord(command, now) {
|
|
@@ -512,7 +513,7 @@ class NewAuthService {
|
|
|
512
513
|
const commandPath = command.name;
|
|
513
514
|
const aliases = Object.keys(command._aliases || {});
|
|
514
515
|
const id = makeCommandId(plugin, commandPath);
|
|
515
|
-
|
|
516
|
+
const record = {
|
|
516
517
|
id,
|
|
517
518
|
name: command.displayName || commandPath,
|
|
518
519
|
commandPath,
|
|
@@ -521,10 +522,14 @@ class NewAuthService {
|
|
|
521
522
|
description: this.getDescription(command),
|
|
522
523
|
legacyAuthority,
|
|
523
524
|
status: this.isSelfCommand(command) ? 'configured' : 'pending',
|
|
524
|
-
allowGuildOverride: legacyAuthority <=
|
|
525
|
+
allowGuildOverride: legacyAuthority <= DEFAULT_ALLOW_GUILD_OVERRIDE_AUTHORITY_MAX,
|
|
525
526
|
createdAt: now,
|
|
526
527
|
updatedAt: now,
|
|
527
528
|
};
|
|
529
|
+
if (this.getAutoAssignSuggestion(record).kind === 'admin') {
|
|
530
|
+
record.allowGuildOverride = false;
|
|
531
|
+
}
|
|
532
|
+
return record;
|
|
528
533
|
}
|
|
529
534
|
getDescription(command) {
|
|
530
535
|
const key = `commands.${command.name}.description`;
|
|
@@ -541,7 +546,7 @@ class NewAuthService {
|
|
|
541
546
|
|| record.name === normalized
|
|
542
547
|
|| record.aliases.includes(normalized));
|
|
543
548
|
if (!match)
|
|
544
|
-
throw new Error(
|
|
549
|
+
throw new Error(`未找到指令:${input}`);
|
|
545
550
|
return match;
|
|
546
551
|
}
|
|
547
552
|
async setPolicy(scope, roleId, commandId, state) {
|
|
@@ -643,7 +648,7 @@ class NewAuthService {
|
|
|
643
648
|
async ensureRoleExists(roleId) {
|
|
644
649
|
const [role] = await this.ctx.database.get('new_auth_role', { id: roleId });
|
|
645
650
|
if (!role)
|
|
646
|
-
throw new Error(
|
|
651
|
+
throw new Error(`未找到角色:${roleId}`);
|
|
647
652
|
return role;
|
|
648
653
|
}
|
|
649
654
|
hasPlatformRole(session, roleNames) {
|
|
@@ -674,7 +679,7 @@ class NewAuthService {
|
|
|
674
679
|
return [...(this.ctx.$commander?._commandList || [])];
|
|
675
680
|
}
|
|
676
681
|
isSelfCommand(command) {
|
|
677
|
-
return command.name
|
|
682
|
+
return isSelfCommandPath(command.name);
|
|
678
683
|
}
|
|
679
684
|
}
|
|
680
685
|
exports.NewAuthService = NewAuthService;
|
|
@@ -723,6 +728,9 @@ function registerConsole(ctx, service) {
|
|
|
723
728
|
ctx.console.refresh('newauth');
|
|
724
729
|
return { success: true, count };
|
|
725
730
|
}, { authority: 4 });
|
|
731
|
+
ctx.console.addListener('newauth/configureCommandRoles', async (payload) => {
|
|
732
|
+
return ok(service.configureCommandRoles(payload.commandId, payload.scope, payload.roleIds));
|
|
733
|
+
}, { authority: 4 });
|
|
726
734
|
ctx.console.addListener('newauth/createRole', async (payload) => {
|
|
727
735
|
return ok(service.createCustomRole(payload.id, payload.name, payload.scopeType));
|
|
728
736
|
}, { authority: 4 });
|
|
@@ -794,6 +802,15 @@ function registerManagementCommands(ctx, config, service) {
|
|
|
794
802
|
const record = await service.setCommandPolicy(scope, roleId, command, 'inherit');
|
|
795
803
|
return `已恢复继承:${scope} ${roleId} -> ${record.commandPath}`;
|
|
796
804
|
});
|
|
805
|
+
ctx.command('newauth.assign <command> <roleIds> [scope]', '按角色配置单个指令,roleIds 用逗号分隔', { authority })
|
|
806
|
+
.action(async (_, command, roleIds, scope = 'global') => {
|
|
807
|
+
const roles = String(roleIds || '')
|
|
808
|
+
.split(',')
|
|
809
|
+
.map(role => role.trim())
|
|
810
|
+
.filter(Boolean);
|
|
811
|
+
const record = await service.configureCommandRoles(command, scope, roles);
|
|
812
|
+
return `已配置:${record.commandPath} -> ${roles.join(', ') || '无角色'} (${scope})`;
|
|
813
|
+
});
|
|
797
814
|
ctx.command('newauth.disable <command>', '禁用指令', { authority })
|
|
798
815
|
.action(async (_, command) => {
|
|
799
816
|
const record = await service.setCommandStatus(command, 'disabled');
|
|
@@ -899,6 +916,9 @@ function omitPrimary(record, key) {
|
|
|
899
916
|
const { [key]: _value, ...rest } = record;
|
|
900
917
|
return rest;
|
|
901
918
|
}
|
|
919
|
+
function isSelfCommandPath(commandPath) {
|
|
920
|
+
return commandPath === 'newauth' || commandPath.startsWith('newauth/');
|
|
921
|
+
}
|
|
902
922
|
function inferPlugin(command) {
|
|
903
923
|
const source = command.caller?.scope || command.ctx?.scope;
|
|
904
924
|
const plugin = source?.plugin?.name || source?.uid || source?.id || 'unknown';
|
|
@@ -919,7 +939,7 @@ function getScope(session) {
|
|
|
919
939
|
function parseUid(uid) {
|
|
920
940
|
const index = uid.indexOf(':');
|
|
921
941
|
if (index <= 0 || index === uid.length - 1) {
|
|
922
|
-
throw new Error('
|
|
942
|
+
throw new Error('UID 必须为 platform:userId 格式');
|
|
923
943
|
}
|
|
924
944
|
return {
|
|
925
945
|
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侧边栏的图标不使用默认的
|
|
1457
|
+
KOISHI侧边栏的图标不使用默认的 使用“⌗”
|
|
1458
1458
|
同时根据F:\chatluna-1.4.0-alpha.17 为指令权限限制制作一个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.0",
|
|
4
|
+
"description": "Koishi 的角色与作用域指令权限系统。",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
7
7
|
"files": [
|