koishi-plugin-new-auth 0.2.0 → 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,19 +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 后打开 `⌗ 新权限` 页面。
80
+
81
+ 页面提供:
75
82
 
76
- The page provides:
83
+ - 以角色为入口的指令权限编辑;
84
+ - 全局默认和单群作用域切换;
85
+ - 待配置指令审查和角色勾选分配;
86
+ - 旧 `authority` 建议与 AI 风格自动分配建议;
87
+ - 指令状态和群内自治开关;
88
+ - 自定义角色创建、显式成员管理;
89
+ - 从其他角色复制权限;
90
+ - 按插件批量采用建议、给群成员、给群管理员或仅给 Bot 管理员。
77
91
 
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.
92
+ WebUI Koishi 实例管理员界面,Console 入口和所有事件都要求 `authority: 4`。群主自治只应影响允许自治的单群指令,不等于进入实例管理面板。
84
93
 
85
- 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.
94
+ 自动分配策略默认保守:实例级或高危操作只给 `bot-admin`,群管理操作给 `guild-admin` `guild-owner`,普通用户功能给 `guild-member` 及以上,无法判断时回退到旧 `authority` 建议。
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- import{defineComponent as H,ref as v,computed as c,watch as J,openBlock as o,createElementBlock as a,createElementVNode as t,toDisplayString as s,withDirectives as y,Fragment as m,renderList as x,vModelSelect as U,normalizeClass as g,createCommentVNode as G,vModelCheckbox as K,createTextVNode as T,vModelText as D}from"vue";import{store as E,send as k}from"@koishijs/client";const Q={class:"new-auth-page"},W={class:"topbar"},X={class:"top-actions"},Y=["value"],Z=["disabled"],ee=["disabled"],te={class:"tabs"},le={key:0,class:"error"},ne={key:1,class:"role-layout"},se={class:"role-list"},oe=["onClick"],ae={key:0,class:"detail"},ie={class:"detail-head"},ue={class:"check"},de={class:"filters"},re={class:"command-list"},ce={class:"command-main"},ve={class:"state-buttons"},pe=["onClick"],ge=["onClick"],he=["onClick"],_e={key:2,class:"pending-grid"},fe={class:"row-actions"},be=["disabled","onClick"],ye=["disabled","onClick"],ke={key:0,class:"empty"},Ce={key:3,class:"commands-table"},we={class:"filters"},me={class:"command-main"},xe={class:"command-controls"},Ae={class:"check"},Ie=["checked","onChange"],Ve=["value","onChange"],$e=H({__name:"new-auth",setup(C){const i=v("roles"),u=v("global"),d=v(""),p=v(""),h=v("all"),$=v(false),_=v(false),A=v(""),w=c(()=>E.newauth||{roles:[],commands:[],policies:[],scopes:[{id:"global",name:"全局默认"}],pendingCount:0}),I=c(()=>w.value.roles),P=c(()=>w.value.commands),L=c(()=>w.value.policies),B=c(()=>{var n;return(n=w.value.scopes)!=null&&n.length?w.value.scopes:[{id:"global",name:"全局默认"}]}),V=c(()=>P.value.filter(n=>n.status==="pending")),f=c(()=>I.value.find(n=>n.id===d.value));J(I,n=>{!d.value&&n.length&&(d.value=n[0].id)},{immediate:true});const M=c(()=>{const n=p.value.toLowerCase();return P.value.filter(l=>h.value!=="all"&&l.status!==h.value?false:n?[l.id,l.name,l.commandPath,l.plugin,l.description].some(e=>e==null?void 0:e.toLowerCase().includes(n)):true)}),F=c(()=>f.value?M.value.filter(n=>{var l,e;return $.value||((l=f.value)==null?void 0:l.id)==="bot-admin"||((e=f.value)==null?void 0:e.scopeType)==="global"?true:n.allowGuildOverride}):[]);function S(n){const l=L.value.find(e=>e.scope===u.value&&e.roleId===d.value&&e.commandId===n);return(l==null?void 0:l.state)||"inherit"}async function b(n){_.value=true,A.value="";try{await n}catch(l){A.value=l instanceof Error?l.message:String(l)}finally{_.value=false}}async function R(){return b((async()=>{const n=await k("newauth/getData");E.newauth=n})())}function O(n,l){return b(k("newauth/setPolicy",{scope:u.value,roleId:d.value,commandId:n,state:l}))}function N(n,l){return b(k("newauth/applySuggestion",{commandId:n,scope:u.value,mode:l}))}function q(){return b(k("newauth/autoAssignPending",{scope:u.value,mode:"advisor"}))}function z(n,l){const e=l.target;return b(k("newauth/setGuildOverride",{commandId:n.id,allowGuildOverride:e.checked}))}function j(n,l){const e=l.target;return b(k("newauth/setCommandStatus",{commandId:n.id,status:e.value}))}return(n,l)=>(o(),a("main",Q,[t("header",W,[t("div",null,[l[9]||(l[9]=t("h1",null,"新权限",-1)),t("p",null,s(I.value.length)+" 个角色 / "+s(P.value.length)+" 个指令 / "+s(V.value.length)+" 个待配置",1)]),t("div",X,[y(t("select",{"onUpdate:modelValue":l[0]||(l[0]=e=>u.value=e),class:"scope-select"},[(o(true),a(m,null,x(B.value,e=>(o(),a("option",{key:e.id,value:e.id},s(e.name),9,Y))),128))],512),[[U,u.value]]),t("button",{class:"primary",disabled:_.value||!V.value.length,onClick:q}," AI 自动分配待配置 ",8,Z),t("button",{disabled:_.value,onClick:R},"刷新",8,ee)])]),t("nav",te,[t("button",{class:g({active:i.value==="roles"}),onClick:l[1]||(l[1]=e=>i.value="roles")},"角色",2),t("button",{class:g({active:i.value==="pending"}),onClick:l[2]||(l[2]=e=>i.value="pending")},"待配置",2),t("button",{class:g({active:i.value==="commands"}),onClick:l[3]||(l[3]=e=>i.value="commands")},"指令",2)]),A.value?(o(),a("p",le,s(A.value),1)):G("v-if",true),i.value==="roles"?(o(),a("section",ne,[t("aside",se,[(o(true),a(m,null,x(I.value,e=>(o(),a("button",{key:e.id,class:g(["role-item",{active:e.id===d.value}]),onClick:r=>d.value=e.id},[t("span",null,[t("strong",null,s(e.name),1),t("small",null,s(e.builtin?"内置角色":"自定义角色")+" / "+s(e.scopeType),1)]),t("em",null,s(e.allowCount),1)],10,oe))),128))]),f.value?(o(),a("section",ae,[t("div",ie,[t("div",null,[t("h2",null,s(f.value.name),1),t("p",null,s(f.value.id),1)]),t("label",ue,[y(t("input",{type:"checkbox","onUpdate:modelValue":l[4]||(l[4]=e=>$.value=e)},null,512),[[K,$.value]]),l[10]||(l[10]=T(" 审计模式 ",-1))])]),t("div",de,[y(t("input",{"onUpdate:modelValue":l[5]||(l[5]=e=>p.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[D,p.value,void 0,{trim:true}]]),y(t("select",{"onUpdate:modelValue":l[6]||(l[6]=e=>h.value=e)},[...l[11]||(l[11]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[U,h.value]])]),t("div",re,[(o(true),a(m,null,x(F.value,e=>(o(),a("article",{key:e.id,class:"command-row"},[t("div",ce,[t("strong",null,s(e.name),1),t("span",null,s(e.commandPath),1),t("small",null,s(e.plugin)+" / legacy "+s(e.legacyAuthority)+" / "+s(e.suggestion.label),1)]),t("div",ve,[t("button",{class:g({active:S(e.id)==="inherit"}),onClick:r=>O(e.id,"inherit")}," 继承 ",10,pe),t("button",{class:g({active:S(e.id)==="allow"}),onClick:r=>O(e.id,"allow")}," 开 ",10,ge),t("button",{class:g({active:S(e.id)==="deny"}),onClick:r=>O(e.id,"deny")}," 关 ",10,he)])]))),128))])])):G("v-if",true)])):i.value==="pending"?(o(),a("section",_e,[(o(true),a(m,null,x(V.value,e=>(o(),a("article",{key:e.id,class:"pending-item"},[t("div",null,[t("strong",null,s(e.name),1),t("span",null,s(e.commandPath),1),t("small",null,s(e.plugin)+" / legacy "+s(e.legacyAuthority),1)]),t("dl",null,[l[12]||(l[12]=t("dt",null,"旧建议",-1)),t("dd",null,s(e.suggestion.label),1),l[13]||(l[13]=t("dt",null,"AI 建议",-1)),t("dd",null,s(e.autoAssign.label)+":"+s(e.autoAssign.reason),1)]),t("div",fe,[t("button",{disabled:_.value,onClick:r=>N(e.id,"legacy")},"采用旧建议",8,be),t("button",{class:"primary",disabled:_.value,onClick:r=>N(e.id,"advisor")},"采用 AI 建议",8,ye)])]))),128)),V.value.length?G("v-if",true):(o(),a("p",ke,"没有待配置指令。"))])):(o(),a("section",Ce,[t("div",we,[y(t("input",{"onUpdate:modelValue":l[7]||(l[7]=e=>p.value=e),placeholder:"搜索指令、插件、描述"},null,512),[[D,p.value,void 0,{trim:true}]]),y(t("select",{"onUpdate:modelValue":l[8]||(l[8]=e=>h.value=e)},[...l[14]||(l[14]=[t("option",{value:"all"},"全部状态",-1),t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],512),[[U,h.value]])]),(o(true),a(m,null,x(M.value,e=>(o(),a("article",{key:e.id,class:"command-row"},[t("div",me,[t("strong",null,s(e.name),1),t("span",null,s(e.id),1),t("small",null,s(e.plugin)+" / "+s(e.commandPath)+" / "+s(e.autoAssign.label),1)]),t("div",xe,[t("label",Ae,[t("input",{type:"checkbox",checked:e.allowGuildOverride,onChange:r=>z(e,r)},null,40,Ie),l[15]||(l[15]=T(" 群内自治 ",-1))]),t("select",{value:e.status,onChange:r=>j(e,r)},[...l[16]||(l[16]=[t("option",{value:"pending"},"待配置",-1),t("option",{value:"configured"},"已配置",-1),t("option",{value:"disabled"},"已禁用",-1)])],40,Ve)])]))),128))]))]))}}),Pe=(C,i)=>{const u=C.__vccOpts||C;for(const[d,p]of i)u[d]=p;return u},Se=Pe($e,[["__scopeId","data-v-b49c6f52"]]),Ge=C=>{C.page({name:"新权限",path:"/new-auth",icon:"⌗",fields:["newauth"],authority:4,component:Se})};export{Ge 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-b49c6f52]{min-height:100vh;background:#f6f7f4;color:#1f2523;padding:20px}.topbar[data-v-b49c6f52],.detail-head[data-v-b49c6f52],.filters[data-v-b49c6f52],.row-actions[data-v-b49c6f52],.command-controls[data-v-b49c6f52],.top-actions[data-v-b49c6f52]{display:flex;align-items:center;gap:12px}.topbar[data-v-b49c6f52]{justify-content:space-between;border-bottom:1px solid #d9ded6;padding-bottom:16px}h1[data-v-b49c6f52],h2[data-v-b49c6f52],p[data-v-b49c6f52]{margin:0}h1[data-v-b49c6f52]{font-size:26px;line-height:1.2}h2[data-v-b49c6f52]{font-size:20px}p[data-v-b49c6f52],small[data-v-b49c6f52],span[data-v-b49c6f52],dd[data-v-b49c6f52],dt[data-v-b49c6f52]{color:#63706a}button[data-v-b49c6f52],select[data-v-b49c6f52],input[data-v-b49c6f52]{border:1px solid #c7cec8;border-radius:6px;background:#fff;color:#1f2523;min-height:34px;padding:0 10px;font:inherit}button[data-v-b49c6f52]{cursor:pointer}button[data-v-b49c6f52]:disabled{cursor:not-allowed;opacity:.55}.primary[data-v-b49c6f52]{background:#285e54;border-color:#285e54;color:#fff}.tabs[data-v-b49c6f52]{display:flex;gap:6px;margin:16px 0}.tabs button.active[data-v-b49c6f52],.state-buttons button.active[data-v-b49c6f52]{background:#25312d;border-color:#25312d;color:#fff}.role-layout[data-v-b49c6f52]{display:grid;grid-template-columns:minmax(220px,280px) 1fr;gap:16px}.role-list[data-v-b49c6f52]{display:grid;gap:8px;align-content:start}.role-item[data-v-b49c6f52]{display:flex;justify-content:space-between;align-items:center;min-height:58px;text-align:left}.role-item span[data-v-b49c6f52],.command-main[data-v-b49c6f52]{display:grid;gap:3px;min-width:0}.role-item.active[data-v-b49c6f52]{border-color:#285e54;box-shadow:inset 3px 0 #285e54}.role-item em[data-v-b49c6f52]{font-style:normal;color:#8b4a2f}.detail[data-v-b49c6f52]{min-width:0}.detail-head[data-v-b49c6f52]{justify-content:space-between;margin-bottom:12px}.filters[data-v-b49c6f52]{margin-bottom:12px}.filters input[data-v-b49c6f52]{flex:1;min-width:180px}.command-list[data-v-b49c6f52],.pending-grid[data-v-b49c6f52],.commands-table[data-v-b49c6f52]{display:grid;gap:8px}.command-row[data-v-b49c6f52],.pending-item[data-v-b49c6f52]{background:#fff;border:1px solid #d9ded6;border-radius:8px;padding:12px}.command-row[data-v-b49c6f52]{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:12px;align-items:center}.command-main strong[data-v-b49c6f52],.command-main span[data-v-b49c6f52],.command-main small[data-v-b49c6f52]{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.state-buttons[data-v-b49c6f52]{display:grid;grid-template-columns:repeat(3,54px);gap:6px}.pending-grid[data-v-b49c6f52]{grid-template-columns:repeat(auto-fill,minmax(300px,1fr))}.pending-item[data-v-b49c6f52]{display:grid;gap:10px}dl[data-v-b49c6f52]{display:grid;grid-template-columns:58px 1fr;gap:4px 8px;margin:0}dd[data-v-b49c6f52]{margin:0}.check[data-v-b49c6f52]{display:inline-flex;align-items:center;gap:6px;white-space:nowrap}.check input[data-v-b49c6f52]{min-height:0}.error[data-v-b49c6f52]{border:1px solid #d9a09a;background:#fff1ef;color:#8a2d22;border-radius:6px;padding:10px;margin-bottom:12px}.empty[data-v-b49c6f52]{padding:24px 0}@media (max-width: 760px){.new-auth-page[data-v-b49c6f52]{padding:12px}.topbar[data-v-b49c6f52],.detail-head[data-v-b49c6f52],.command-row[data-v-b49c6f52]{grid-template-columns:1fr;display:grid}.top-actions[data-v-b49c6f52],.filters[data-v-b49c6f52],.command-controls[data-v-b49c6f52]{flex-wrap:wrap}.role-layout[data-v-b49c6f52]{grid-template-columns:1fr}.state-buttons[data-v-b49c6f52]{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: {
@@ -10,6 +10,7 @@ type ScopeType = 'global' | 'guild';
10
10
  type CommandStatus = 'pending' | 'configured' | 'disabled';
11
11
  type PolicyState = 'inherit' | 'allow' | 'deny';
12
12
  type AutoAssignKind = 'public' | 'moderation' | 'owner' | 'admin' | 'legacy';
13
+ type PluginAssignTarget = 'legacy' | 'bot-admin' | 'guild-owner' | 'guild-admin' | 'guild-member';
13
14
  export interface NewAuthCommandRecord {
14
15
  id: string;
15
16
  name: string;
@@ -81,6 +82,9 @@ declare module '@koishijs/plugin-console' {
81
82
  success: true;
82
83
  count: number;
83
84
  }>;
85
+ 'newauth/configureCommandRoles'(payload: ConfigureCommandRolesPayload): Promise<{
86
+ success: true;
87
+ }>;
84
88
  'newauth/createRole'(payload: CreateRolePayload): Promise<{
85
89
  success: true;
86
90
  }>;
@@ -94,6 +98,10 @@ declare module '@koishijs/plugin-console' {
94
98
  success: true;
95
99
  count: number;
96
100
  }>;
101
+ 'newauth/assignPluginCommands'(payload: AssignPluginCommandsPayload): Promise<{
102
+ success: true;
103
+ count: number;
104
+ }>;
97
105
  }
98
106
  }
99
107
  export interface NewAuthConsoleData {
@@ -148,6 +156,11 @@ interface AutoAssignPayload {
148
156
  scope?: string;
149
157
  mode?: 'legacy' | 'advisor';
150
158
  }
159
+ interface ConfigureCommandRolesPayload {
160
+ commandId: string;
161
+ scope: string;
162
+ roleIds: string[];
163
+ }
151
164
  interface CreateRolePayload {
152
165
  id: string;
153
166
  name: string;
@@ -163,22 +176,21 @@ interface CopyRolePoliciesPayload {
163
176
  targetRoleId: string;
164
177
  scope: string;
165
178
  }
179
+ interface AssignPluginCommandsPayload {
180
+ plugin: string;
181
+ scope: string;
182
+ target: PluginAssignTarget;
183
+ }
166
184
  export interface Config {
167
185
  botAdmins: string[];
168
186
  trustLegacyAuthorityAsAdmin: boolean;
169
187
  legacyAdminAuthority: number;
170
- ownerRoleNames: string[];
171
- adminRoleNames: string[];
172
- allowGuildOverrideAuthorityMax: number;
173
188
  deniedMessage: string;
174
- grantRuntimeCommandPermission: boolean;
175
- raiseLegacyAuthority: boolean;
176
189
  }
177
190
  export declare const Config: Schema<Config>;
178
191
  export declare function apply(ctx: Context, config: Config): void;
179
- export declare class NewAuthService {
180
- private ctx;
181
- private config;
192
+ export declare class NewAuthService extends Service {
193
+ config: Config;
182
194
  private commandCache;
183
195
  private adminSet;
184
196
  private ownerRoleNames;
@@ -252,7 +264,9 @@ export declare class NewAuthService {
252
264
  setCommandGuildOverride(input: string, allowGuildOverride: boolean): Promise<NewAuthCommandRecord>;
253
265
  setCommandPolicy(scope: string, roleId: string, input: string, state: PolicyState): Promise<NewAuthCommandRecord>;
254
266
  applySuggestedPolicy(input: string, scope?: string, mode?: 'legacy' | 'advisor'): Promise<NewAuthCommandRecord>;
267
+ configureCommandRoles(input: string, scope?: string, roleIds?: string[]): Promise<NewAuthCommandRecord>;
255
268
  autoAssignPending(scope?: string, mode?: 'legacy' | 'advisor'): Promise<number>;
269
+ assignPluginCommands(plugin: string, scope?: string, target?: PluginAssignTarget): Promise<number>;
256
270
  getCommand(input: string): Promise<NewAuthCommandRecord>;
257
271
  private ensureBuiltinRoles;
258
272
  private applyRolesToCommand;
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 });
@@ -379,7 +365,7 @@ class NewAuthService {
379
365
  updatedAt: now,
380
366
  };
381
367
  if (existing) {
382
- await this.ctx.database.set('new_auth_role', id, record);
368
+ await this.ctx.database.set('new_auth_role', id, omitPrimary(record, 'id'));
383
369
  }
384
370
  else {
385
371
  await this.ctx.database.create('new_auth_role', record);
@@ -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) {
@@ -454,6 +452,22 @@ class NewAuthService {
454
452
  }
455
453
  return commands.length;
456
454
  }
455
+ async assignPluginCommands(plugin, scope = 'global', target = 'legacy') {
456
+ const commands = (await this.listCommands({ all: true }))
457
+ .filter(command => command.plugin === plugin);
458
+ for (const command of commands) {
459
+ if (target === 'legacy') {
460
+ await this.applySuggestedPolicy(command.id, scope, 'legacy');
461
+ }
462
+ else {
463
+ await this.applyRolesToCommand(scope, command.id, getAssignTargetRoles(target));
464
+ if (command.status === 'pending') {
465
+ await this.setCommandStatus(command.id, 'configured');
466
+ }
467
+ }
468
+ }
469
+ return commands.length;
470
+ }
457
471
  async getCommand(input) {
458
472
  const cached = this.commandCache.get(input);
459
473
  if (cached)
@@ -474,7 +488,7 @@ class NewAuthService {
474
488
  updatedAt: now,
475
489
  };
476
490
  if (existing) {
477
- await this.ctx.database.set('new_auth_role', role.id, record);
491
+ await this.ctx.database.set('new_auth_role', role.id, omitPrimary(record, 'id'));
478
492
  }
479
493
  else {
480
494
  await this.ctx.database.create('new_auth_role', record);
@@ -484,10 +498,13 @@ class NewAuthService {
484
498
  async applyRolesToCommand(scope, commandId, roleIds) {
485
499
  const roles = await this.listRoles();
486
500
  const knownRoles = new Set(roles.map(role => role.id));
487
- for (const roleId of roleIds) {
501
+ const selectedRoles = new Set(roleIds);
502
+ for (const roleId of selectedRoles) {
488
503
  if (!knownRoles.has(roleId))
489
- continue;
490
- 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');
491
508
  }
492
509
  }
493
510
  createCommandRecord(command, now) {
@@ -496,7 +513,7 @@ class NewAuthService {
496
513
  const commandPath = command.name;
497
514
  const aliases = Object.keys(command._aliases || {});
498
515
  const id = makeCommandId(plugin, commandPath);
499
- return {
516
+ const record = {
500
517
  id,
501
518
  name: command.displayName || commandPath,
502
519
  commandPath,
@@ -505,10 +522,14 @@ class NewAuthService {
505
522
  description: this.getDescription(command),
506
523
  legacyAuthority,
507
524
  status: this.isSelfCommand(command) ? 'configured' : 'pending',
508
- allowGuildOverride: legacyAuthority <= this.config.allowGuildOverrideAuthorityMax,
525
+ allowGuildOverride: legacyAuthority <= DEFAULT_ALLOW_GUILD_OVERRIDE_AUTHORITY_MAX,
509
526
  createdAt: now,
510
527
  updatedAt: now,
511
528
  };
529
+ if (this.getAutoAssignSuggestion(record).kind === 'admin') {
530
+ record.allowGuildOverride = false;
531
+ }
532
+ return record;
512
533
  }
513
534
  getDescription(command) {
514
535
  const key = `commands.${command.name}.description`;
@@ -525,7 +546,7 @@ class NewAuthService {
525
546
  || record.name === normalized
526
547
  || record.aliases.includes(normalized));
527
548
  if (!match)
528
- throw new Error(`command not found: ${input}`);
549
+ throw new Error(`未找到指令:${input}`);
529
550
  return match;
530
551
  }
531
552
  async setPolicy(scope, roleId, commandId, state) {
@@ -627,7 +648,7 @@ class NewAuthService {
627
648
  async ensureRoleExists(roleId) {
628
649
  const [role] = await this.ctx.database.get('new_auth_role', { id: roleId });
629
650
  if (!role)
630
- throw new Error(`role not found: ${roleId}`);
651
+ throw new Error(`未找到角色:${roleId}`);
631
652
  return role;
632
653
  }
633
654
  hasPlatformRole(session, roleNames) {
@@ -658,11 +679,11 @@ class NewAuthService {
658
679
  return [...(this.ctx.$commander?._commandList || [])];
659
680
  }
660
681
  isSelfCommand(command) {
661
- return command.name === 'newauth' || command.name.startsWith('newauth/');
682
+ return isSelfCommandPath(command.name);
662
683
  }
663
684
  }
664
685
  exports.NewAuthService = NewAuthService;
665
- function createConsoleServiceClass() {
686
+ function createConsoleServiceClass(service) {
666
687
  const { DataService } = require('@koishijs/plugin-console');
667
688
  return class NewAuthConsoleDataService extends DataService {
668
689
  constructor(ctx) {
@@ -672,7 +693,7 @@ function createConsoleServiceClass() {
672
693
  });
673
694
  }
674
695
  async get(_forced, _client) {
675
- return this.ctx.newauth.getConsoleData();
696
+ return service.getConsoleData();
676
697
  }
677
698
  };
678
699
  }
@@ -681,7 +702,7 @@ function registerConsole(ctx, service) {
681
702
  dev: (0, path_1.resolve)(__dirname, '../client/index.ts'),
682
703
  prod: (0, path_1.resolve)(__dirname, '../dist'),
683
704
  });
684
- ctx.plugin(createConsoleServiceClass());
705
+ ctx.plugin(createConsoleServiceClass(service));
685
706
  const ok = async (task) => {
686
707
  await task;
687
708
  ctx.console.refresh('newauth');
@@ -707,6 +728,9 @@ function registerConsole(ctx, service) {
707
728
  ctx.console.refresh('newauth');
708
729
  return { success: true, count };
709
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 });
710
734
  ctx.console.addListener('newauth/createRole', async (payload) => {
711
735
  return ok(service.createCustomRole(payload.id, payload.name, payload.scopeType));
712
736
  }, { authority: 4 });
@@ -721,6 +745,11 @@ function registerConsole(ctx, service) {
721
745
  ctx.console.refresh('newauth');
722
746
  return { success: true, count };
723
747
  }, { authority: 4 });
748
+ ctx.console.addListener('newauth/assignPluginCommands', async (payload) => {
749
+ const count = await service.assignPluginCommands(payload.plugin, payload.scope, payload.target);
750
+ ctx.console.refresh('newauth');
751
+ return { success: true, count };
752
+ }, { authority: 4 });
724
753
  }
725
754
  function registerManagementCommands(ctx, config, service) {
726
755
  const authority = config.legacyAdminAuthority;
@@ -773,6 +802,15 @@ function registerManagementCommands(ctx, config, service) {
773
802
  const record = await service.setCommandPolicy(scope, roleId, command, 'inherit');
774
803
  return `已恢复继承:${scope} ${roleId} -> ${record.commandPath}`;
775
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
+ });
776
814
  ctx.command('newauth.disable <command>', '禁用指令', { authority })
777
815
  .action(async (_, command) => {
778
816
  const record = await service.setCommandStatus(command, 'disabled');
@@ -816,6 +854,14 @@ function registerManagementCommands(ctx, config, service) {
816
854
  const count = await service.copyRolePolicies(sourceRoleId, targetRoleId, scope);
817
855
  return `已复制 ${count} 条策略:${sourceRoleId} -> ${targetRoleId} (${scope})`;
818
856
  });
857
+ ctx.command('newauth.plugin.assign <plugin> <target> [scope]', '批量配置插件指令', { authority })
858
+ .action(async (_, plugin, target, scope = 'global') => {
859
+ if (!['legacy', 'bot-admin', 'guild-owner', 'guild-admin', 'guild-member'].includes(target)) {
860
+ return 'target 只能是 legacy、bot-admin、guild-owner、guild-admin 或 guild-member。';
861
+ }
862
+ const count = await service.assignPluginCommands(plugin, scope, target);
863
+ return `已批量配置 ${count} 个指令:${plugin} -> ${target} (${scope})`;
864
+ });
819
865
  }
820
866
  function inferLegacyAuthority(command) {
821
867
  if (typeof command.config.authority === 'number')
@@ -857,6 +903,22 @@ function getLegacySuggestion(authority) {
857
903
  label: '仅 Bot 管理员',
858
904
  };
859
905
  }
906
+ function getAssignTargetRoles(target) {
907
+ if (target === 'guild-member')
908
+ return ['guild-member', 'guild-admin', 'guild-owner'];
909
+ if (target === 'guild-admin')
910
+ return ['guild-admin', 'guild-owner'];
911
+ if (target === 'guild-owner')
912
+ return ['guild-owner'];
913
+ return ['bot-admin'];
914
+ }
915
+ function omitPrimary(record, key) {
916
+ const { [key]: _value, ...rest } = record;
917
+ return rest;
918
+ }
919
+ function isSelfCommandPath(commandPath) {
920
+ return commandPath === 'newauth' || commandPath.startsWith('newauth/');
921
+ }
860
922
  function inferPlugin(command) {
861
923
  const source = command.caller?.scope || command.ctx?.scope;
862
924
  const plugin = source?.plugin?.name || source?.uid || source?.id || 'unknown';
@@ -877,7 +939,7 @@ function getScope(session) {
877
939
  function parseUid(uid) {
878
940
  const index = uid.indexOf(':');
879
941
  if (index <= 0 || index === uid.length - 1) {
880
- throw new Error('uid must be platform:userId');
942
+ throw new Error('UID 必须为 platform:userId 格式');
881
943
  }
882
944
  return {
883
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.2.0",
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": [