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 CHANGED
@@ -1,19 +1,19 @@
1
1
  # koishi-plugin-new-auth
2
2
 
3
- `koishi-plugin-new-auth` is a conservative role and scope based command permission layer for Koishi.
3
+ `koishi-plugin-new-auth` 是一个保守的 Koishi 指令权限层,按 `newauth.md` 中的设计实现“角色 + 作用域 + 指令策略”。
4
4
 
5
- It implements the first usable version described in `newauth.md`:
5
+ 当前版本已经覆盖:
6
6
 
7
- - commands are registered into a permission table;
8
- - newly discovered commands are `pending` by default;
9
- - pending commands are executable only by Bot administrators;
10
- - Koishi's legacy `authority` value is recorded as a suggestion, not used as the final grant;
11
- - policies are evaluated by `scope + role + command`;
12
- - guild owner/admin/member roles are separated from Bot administrator;
13
- - custom roles and role members can be managed from Koishi commands;
14
- - Koishi Console WebUI is available when `@koishijs/plugin-console` is installed.
7
+ - 指令注册后进入权限表;
8
+ - 新发现的指令默认是 `pending`;
9
+ - 待配置指令默认只有 Bot 管理员可执行;
10
+ - `authority` 只记录为建议,不直接放权;
11
+ - 策略按 `scope + role + command` 判断;
12
+ - 群主、群管理员、群成员与 Bot 管理员分离;
13
+ - 自定义角色、显式成员、角色权限复制;
14
+ - Koishi Console 中的 `⌗ 新权限` WebUI。
15
15
 
16
- ## Configuration
16
+ ## 配置
17
17
 
18
18
  ```ts
19
19
  export default {
@@ -25,13 +25,13 @@ export default {
25
25
  }
26
26
  ```
27
27
 
28
- `botAdmins` are explicit Koishi instance administrators. Platform guild owners are not treated as Bot administrators.
28
+ `botAdmins` 是显式 Bot 实例管理员,平台群主不会自动成为 Bot 管理员。
29
29
 
30
- The plugin also has a compatibility fallback for existing Koishi installations: users with `authority >= legacyAdminAuthority` are treated as Bot administrators when `trustLegacyAuthorityAsAdmin` is enabled.
30
+ 为了兼容旧实例,`trustLegacyAuthorityAsAdmin` 打开时,`authority >= legacyAdminAuthority` Koishi 用户会被视为 Bot 管理员。这个兼容只来自 Koishi 用户权限,不来自平台群主身份。
31
31
 
32
- ## Commands
32
+ ## 管理指令
33
33
 
34
- All management commands are under `newauth`.
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` uses `platform:userId`, for example `onebot:10000`.
54
+ `uid` 使用 `platform:userId`,例如 `onebot:10000`。
52
55
 
53
- `scope` can be:
56
+ `scope` 可以是:
54
57
 
55
58
  - `global`
56
59
  - `guild:<platform>:<guildId>`
57
60
 
58
- When `scope` is omitted, policy commands use `global`.
61
+ 省略 `scope` 时,策略指令默认写入 `global`。
59
62
 
60
- ## Built-In Roles
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
- Custom roles do not inherit from other roles. To give a custom role access to a command, add an explicit policy with `newauth.allow`.
75
+ 自定义角色不继承其他角色。要让自定义角色拥有某些指令,需要显式写策略,或在 WebUI / `newauth.role.copy` 中复制另一个角色当前的显式策略。
71
76
 
72
77
  ## WebUI
73
78
 
74
- Install Koishi Console and open the `⌗ 新权限` page.
79
+ 安装 Koishi Console 后打开 `⌗ 新权限` 页面。
75
80
 
76
- The page provides:
81
+ 页面提供:
77
82
 
78
- - role-first command policy editing;
79
- - global and guild scope switching;
80
- - pending command review;
81
- - legacy `authority` suggestions;
82
- - AI-style automatic assignment for pending commands;
83
- - command status and guild override toggles;
84
- - custom role creation and explicit member management;
85
- - copying command policies from one role to another;
86
- - batch assigning all commands from one plugin.
83
+ - 以角色为入口的指令权限编辑;
84
+ - 全局默认和单群作用域切换;
85
+ - 待配置指令审查和角色勾选分配;
86
+ - `authority` 建议与 AI 风格自动分配建议;
87
+ - 指令状态和群内自治开关;
88
+ - 自定义角色创建、显式成员管理;
89
+ - 从其他角色复制权限;
90
+ - 按插件批量采用建议、给群成员、给群管理员或仅给 Bot 管理员。
87
91
 
88
- The automatic assignment advisor is conservative: dangerous instance-level commands are kept for `bot-admin`, group moderation commands go to `guild-admin` and `guild-owner`, normal user commands go to `guild-member` and above, and unclear commands fall back to the legacy authority suggestion.
92
+ WebUI Koishi 实例管理员界面,Console 入口和所有事件都要求 `authority: 4`。群主自治只应影响允许自治的单群指令,不等于进入实例管理面板。
89
93
 
90
- Custom roles do not inherit from built-in roles. Use the WebUI copy action, or `newauth.role.copy`, to clone the current explicit policies from another role.
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-755ad7c8]{min-height:100vh;background:#f6f7f4;color:#1f2523;padding:20px}.topbar[data-v-755ad7c8],.detail-head[data-v-755ad7c8],.filters[data-v-755ad7c8],.row-actions[data-v-755ad7c8],.command-controls[data-v-755ad7c8],.top-actions[data-v-755ad7c8],.detail-tools[data-v-755ad7c8],.member-add[data-v-755ad7c8],.plugin-bulk[data-v-755ad7c8]{display:flex;align-items:center;gap:12px}.topbar[data-v-755ad7c8]{justify-content:space-between;border-bottom:1px solid #d9ded6;padding-bottom:16px}h1[data-v-755ad7c8],h2[data-v-755ad7c8],p[data-v-755ad7c8]{margin:0}h1[data-v-755ad7c8]{font-size:26px;line-height:1.2}h2[data-v-755ad7c8]{font-size:20px}p[data-v-755ad7c8],small[data-v-755ad7c8],span[data-v-755ad7c8],dd[data-v-755ad7c8],dt[data-v-755ad7c8]{color:#63706a}button[data-v-755ad7c8],select[data-v-755ad7c8],input[data-v-755ad7c8]{border:1px solid #c7cec8;border-radius:6px;background:#fff;color:#1f2523;min-height:34px;padding:0 10px;font:inherit}button[data-v-755ad7c8]{cursor:pointer}button[data-v-755ad7c8]:disabled{cursor:not-allowed;opacity:.55}.primary[data-v-755ad7c8]{background:#285e54;border-color:#285e54;color:#fff}.tabs[data-v-755ad7c8]{display:flex;gap:6px;margin:16px 0}.tabs button.active[data-v-755ad7c8],.state-buttons button.active[data-v-755ad7c8]{background:#25312d;border-color:#25312d;color:#fff}.role-layout[data-v-755ad7c8]{display:grid;grid-template-columns:minmax(220px,280px) 1fr;gap:16px}.role-list[data-v-755ad7c8]{display:grid;gap:8px;align-content:start}.role-create[data-v-755ad7c8]{display:grid;gap:8px;border:1px solid #d9ded6;border-radius:8px;background:#fff;padding:10px}.role-item[data-v-755ad7c8]{display:flex;justify-content:space-between;align-items:center;min-height:58px;text-align:left}.role-item span[data-v-755ad7c8],.command-main[data-v-755ad7c8]{display:grid;gap:3px;min-width:0}.role-item.active[data-v-755ad7c8]{border-color:#285e54;box-shadow:inset 3px 0 #285e54}.role-item em[data-v-755ad7c8]{font-style:normal;color:#8b4a2f}.detail[data-v-755ad7c8]{min-width:0}.detail-head[data-v-755ad7c8]{justify-content:space-between;margin-bottom:12px}.detail-tools[data-v-755ad7c8]{flex-wrap:wrap;justify-content:flex-end}.member-panel[data-v-755ad7c8]{display:grid;gap:10px;border:1px solid #d9ded6;border-radius:8px;background:#fff;padding:12px;margin-bottom:12px}.member-panel>div[data-v-755ad7c8]:first-child{display:flex;justify-content:space-between;gap:12px}.member-list[data-v-755ad7c8]{display:flex;flex-wrap:wrap;gap:6px;min-height:28px}.member-chip[data-v-755ad7c8]{display:inline-flex;align-items:center;gap:6px;border:1px solid #c7cec8;border-radius:999px;background:#f6f7f4;padding:3px 4px 3px 10px}.member-chip button[data-v-755ad7c8]{min-height:24px;padding:0 8px;border-radius:999px}.filters[data-v-755ad7c8]{margin-bottom:12px}.plugin-bulk[data-v-755ad7c8]{flex-wrap:wrap;border:1px solid #d9ded6;border-radius:8px;background:#fff;padding:10px;margin-bottom:12px}.filters input[data-v-755ad7c8]{flex:1;min-width:180px}.command-list[data-v-755ad7c8],.pending-grid[data-v-755ad7c8],.commands-table[data-v-755ad7c8]{display:grid;gap:8px}.command-row[data-v-755ad7c8],.pending-item[data-v-755ad7c8]{background:#fff;border:1px solid #d9ded6;border-radius:8px;padding:12px}.command-row[data-v-755ad7c8]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center}.command-main strong[data-v-755ad7c8],.command-main span[data-v-755ad7c8],.command-main small[data-v-755ad7c8]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.state-buttons[data-v-755ad7c8]{display:grid;grid-template-columns:repeat(3,54px);gap:6px}.pending-grid[data-v-755ad7c8]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.pending-item[data-v-755ad7c8]{display:grid;gap:10px}dl[data-v-755ad7c8]{display:grid;grid-template-columns:58px 1fr;gap:4px 8px;margin:0}dd[data-v-755ad7c8]{margin:0}.check[data-v-755ad7c8]{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.check input[data-v-755ad7c8]{min-height:0}.error[data-v-755ad7c8]{border:1px solid #d9a09a;background:#fff1ef;color:#8a2d22;border-radius:6px;padding:10px;margin-bottom:12px}.empty[data-v-755ad7c8]{padding:24px 0}@media (max-width: 760px){.new-auth-page[data-v-755ad7c8]{padding:12px}.topbar[data-v-755ad7c8],.detail-head[data-v-755ad7c8],.command-row[data-v-755ad7c8]{grid-template-columns:1fr;display:grid}.top-actions[data-v-755ad7c8],.filters[data-v-755ad7c8],.command-controls[data-v-755ad7c8],.detail-tools[data-v-755ad7c8],.member-add[data-v-755ad7c8],.plugin-bulk[data-v-755ad7c8]{flex-wrap:wrap}.role-layout[data-v-755ad7c8]{grid-template-columns:1fr}.state-buttons[data-v-755ad7c8]{grid-template-columns:repeat(3,minmax(0,1fr))}}
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
- private ctx;
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
- this.ctx = ctx;
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(config.ownerRoleNames.map(normalizeKey));
140
- this.adminRoleNames = new Set(config.adminRoleNames.map(normalizeKey));
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 (this.config.grantRuntimeCommandPermission) {
172
+ if (GRANT_RUNTIME_COMMAND_PERMISSION) {
187
173
  this.grantRuntimeCommandPermission(session, command, options || {});
188
174
  }
189
- if (this.config.raiseLegacyAuthority && session.user) {
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(a.builtin) - Number(b.builtin) || a.id.localeCompare(b.id));
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(`cannot overwrite builtin role: ${id}`);
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
- for (const roleId of roleIds) {
501
+ const selectedRoles = new Set(roleIds);
502
+ for (const roleId of selectedRoles) {
504
503
  if (!knownRoles.has(roleId))
505
- continue;
506
- await this.setPolicy(scope, roleId, commandId, 'allow');
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
- return {
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 <= this.config.allowGuildOverrideAuthorityMax,
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(`command not found: ${input}`);
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(`role not found: ${roleId}`);
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 === 'newauth' || command.name.startsWith('newauth/');
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('uid must be platform:userId');
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.3.1",
4
- "description": "Role and scope based command permission layer for Koishi.",
3
+ "version": "0.4.0",
4
+ "description": "Koishi 的角色与作用域指令权限系统。",
5
5
  "main": "lib/index.js",
6
6
  "types": "lib/index.d.ts",
7
7
  "files": [