@zhin.js/adapter-sandbox 1.0.22

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.
@@ -0,0 +1,11 @@
1
+ import { addPage } from '@zhin.js/client'
2
+ import { Terminal } from 'lucide-react'
3
+ import Sandbox from './Sandbox'
4
+ addPage({
5
+ key: 'process-sandbox',
6
+ path: '/sandbox',
7
+ title: '沙盒',
8
+ icon: <Terminal className="w-5 h-5" />,
9
+ element: <Sandbox/>
10
+ })
11
+
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "outDir": "../dist",
4
+ "baseUrl": ".",
5
+ "declaration": true,
6
+ "module": "ESNext",
7
+ "moduleResolution": "bundler",
8
+ "target": "ES2022",
9
+ "jsx":"react-jsx",
10
+ "declarationMap": true,
11
+ "sourceMap": true,
12
+ "skipLibCheck": true,
13
+ "noEmit": false
14
+ },
15
+ "include": [
16
+ "./**/*"
17
+ ]
18
+ }
19
+
package/dist/index.js ADDED
@@ -0,0 +1,5 @@
1
+ import{jsx as t,jsxs as i}from"react/jsx-runtime";import{cn as he,addPage as Ae}from"@zhin.js/client";import{MessageSquare as Y,Wifi as Ie,WifiOff as $e,Trash2 as Le,Bot as De,User as se,Smile as Fe,Image as Me,X as Ue,Search as me,Check as fe,Send as Be,Info as Pe,Hash as je,Users as _e,Terminal as He}from"lucide-react";import ye,{forwardRef as Oe,useRef as B,useImperativeHandle as We,useState as f,useEffect as Q}from"react";import{Card as X,Box as v,Flex as p,Heading as xe,Badge as C,Button as k,Text as b,TextField as J,Grid as qe,Tabs as V}from"@radix-ui/themes";const ve=Oe(({placeholder:K="输入消息...",onSend:L,onChange:S,onAtTrigger:w,minHeight:G="44px",maxHeight:Z="200px"},m)=>{const u=B(null),x=B(null),E=()=>{if(!u.current)return{text:"",segments:[]};let s="";const a=[],o=Array.from(u.current.childNodes);for(const l of o)if(l.nodeType===Node.TEXT_NODE){const c=l.textContent||"";c&&(s+=c,a.push({type:"text",data:{text:c}}))}else if(l.nodeType===Node.ELEMENT_NODE){const c=l;if(c.classList.contains("editor-face")){const g=c.dataset.id;s+=`[face:${g}]`,a.push({type:"face",data:{id:Number(g)}})}else if(c.classList.contains("editor-image")){const g=c.dataset.url;s+=`[image:${g}]`,a.push({type:"image",data:{url:g}})}else if(c.classList.contains("editor-at")){const g=c.dataset.name,z=c.dataset.id;s+=`[@${g}]`,a.push({type:"at",data:{name:g,qq:z}})}else c.tagName==="BR"&&(s+=`
2
+ `)}return{text:s,segments:a}},D=s=>{if(!u.current)return;const a=document.createElement("img");a.src=`https://face.viki.moe/apng/${s}.png`,a.alt=`[face:${s}]`,a.dataset.type="face",a.dataset.id=String(s),a.className="editor-face",M(a),T()},ee=s=>{if(!u.current||!s.trim())return;const a=document.createElement("img");a.src=s.trim(),a.alt=`[image:${s.trim()}]`,a.dataset.type="image",a.dataset.url=s.trim(),a.className="editor-image",M(a),T()},F=(s,a)=>{if(!u.current||!s.trim())return;const o=document.createElement("span");o.dataset.type="at",o.dataset.name=s,a&&(o.dataset.id=a),o.className="editor-at",o.contentEditable="false";const l=document.createElement("span");l.textContent="@",l.className="editor-at-symbol";const c=document.createElement("span");c.textContent=s,c.className="editor-at-name",o.appendChild(l),o.appendChild(c),M(o),T()},M=s=>{if(!u.current)return;u.current.focus();const a=window.getSelection();if(a&&a.rangeCount>0){const o=a.getRangeAt(0);if(u.current.contains(o.commonAncestorContainer))o.deleteContents(),o.insertNode(s),o.collapse(false),a.removeAllRanges(),a.addRange(o);else{u.current.appendChild(s);const c=document.createRange();c.setStartAfter(s),c.collapse(true),a.removeAllRanges(),a.addRange(c)}}else{u.current.appendChild(s);const o=window.getSelection();if(o){const l=document.createRange();l.setStartAfter(s),l.collapse(true),o.removeAllRanges(),o.addRange(l)}}},P=()=>{u.current&&(u.current.innerHTML="",T())},j=()=>{u.current?.focus()},_=()=>E(),H=()=>{if(!u.current||!w)return;const s=window.getSelection();if(!s||s.rangeCount===0){w(false,""),x.current=null;return}const a=s.getRangeAt(0);if(!u.current.contains(a.commonAncestorContainer)){w(false,""),x.current=null;return}const o=a.startContainer;if(o.nodeType!==Node.TEXT_NODE){w(false,""),x.current=null;return}const l=o,c=l.textContent?.substring(0,a.startOffset)||"",g=c.lastIndexOf("@");if(g!==-1){const z=c.substring(g+1);if(z.includes(" ")||z.includes(`
3
+ `)){w(false,""),x.current=null;return}x.current=l;const R=document.createRange();R.setStart(l,g),R.setEnd(l,g+1);const A=R.getBoundingClientRect(),N=u.current.getBoundingClientRect();w(true,z,{top:A.bottom-N.top,left:A.left-N.left})}else w(false,""),x.current=null},T=()=>{if(H(),S){const{text:s,segments:a}=E();S(s,a)}},te=(s,a)=>{if(!x.current)return;const o=x.current,l=o.textContent||"",c=l.lastIndexOf("@");if(c!==-1){const g=l.substring(c+1),z=c+1+g.split(/[\s\n]/)[0].length,R=l.substring(0,c),A=l.substring(z);o.textContent=R+A;const N=window.getSelection();if(N){const W=document.createRange();W.setStart(o,c),W.collapse(true),N.removeAllRanges(),N.addRange(W)}}x.current=null,F(s,a)},O=s=>{if(s.key==="Enter"&&!s.shiftKey&&(s.preventDefault(),L)){const{text:a,segments:o}=E();L(a,o)}};return We(m,()=>({focus:j,clear:P,insertFace:D,insertImage:ee,insertAt:F,replaceAtTrigger:te,getContent:_})),t("div",{ref:u,contentEditable:true,suppressContentEditableWarning:true,onInput:T,onKeyDown:O,"data-placeholder":K,className:"rich-text-editor",style:{width:"100%",minHeight:G,maxHeight:Z,padding:"0.5rem 0.75rem",border:"1px solid var(--gray-6)",borderRadius:"6px",backgroundColor:"var(--gray-1)",fontSize:"var(--font-size-2)",outline:"none",overflowY:"auto",lineHeight:"1.5",wordWrap:"break-word",color:"var(--gray-12)"}})});ve.displayName="RichTextEditor";function Ke(){const[K,L]=f([]),[S,w]=f([{id:"user_1001",name:"测试用户",type:"private",unread:0},{id:"group_2001",name:"测试群组",type:"group",unread:0},{id:"channel_3001",name:"测试频道",type:"channel",unread:0}]),[G,Z]=f([]),[m,u]=f(S[0]),[x,E]=f(""),[D,ee]=f("ProcessBot"),[F,M]=f(false),[P,j]=f(false),[_,H]=f(false),[T,te]=f(false),[O,s]=f(null),[a,o]=f(""),[l,c]=f(""),[g,z]=f(""),[R,A]=f(""),[N]=f([{id:"10001",name:"张三"},{id:"10002",name:"李四"},{id:"10003",name:"王五"},{id:"10004",name:"赵六"},{id:"10005",name:"测试用户"},{id:"10086",name:"Admin"},{id:"10010",name:"Test User"}]),[W,q]=f([]),[ne,re]=f(false),oe=B(null),U=B(null);B(null);const I=B(null),be=async()=>{try{const r=await(await fetch("https://face.viki.moe/metadata.json")).json();Z(r)}catch(e){console.error("[ProcessSandbox] Failed to fetch face list:",e)}};Q(()=>{be()},[]),Q(()=>{const e=window.location.protocol==="https:"?"wss:":"ws:";return U.current=new WebSocket(`${e}//${window.location.host}/sandbox`),U.current.onopen=()=>{M(true)},U.current.onmessage=r=>{try{const n=JSON.parse(r.data);let h=[];typeof n.content=="string"?h=ae(n.content):Array.isArray(n.content)?h=n.content:h=ae(String(n.content));let d=S.find($=>$.id===n.id);if(!d){const $=n.type==="private"?`私聊-${n.bot||D}`:n.type==="group"?`群组-${n.id}`:`频道-${n.id}`;d={id:n.id,name:$,type:n.type,unread:0},w(Te=>[...Te,d]),u(d)}const y={id:`bot_${n.timestamp}`,type:"received",channelType:n.type,channelId:n.id,channelName:d.name,senderId:"bot",senderName:n.bot||D,content:h,timestamp:n.timestamp};L($=>[...$,y])}catch(n){console.error("[Sandbox] Failed to parse message:",n)}},U.current.onclose=()=>{M(false)},()=>{U.current?.close()}},[D,S]),Q(()=>{oe.current?.scrollIntoView({behavior:"smooth"})},[K]),Q(()=>{if(x.trim()){const e=ae(x);q(e)}else q([])},[x]);const ae=e=>{const r=[],n=/\[@([^\]]+)\]|\[face:(\d+)\]|\[image:([^\]]+)\]/g;let h=0,d;for(;(d=n.exec(e))!==null;){if(d.index>h){const y=e.substring(h,d.index);y&&r.push({type:"text",data:{text:y}})}d[1]?r.push({type:"at",data:{qq:d[1],name:d[1]}}):d[2]?r.push({type:"face",data:{id:parseInt(d[2])}}):d[3]&&r.push({type:"image",data:{url:d[3]}}),h=n.lastIndex}if(h<e.length){const y=e.substring(h);y&&r.push({type:"text",data:{text:y}})}return r.length>0?r:[{type:"text",data:{text:e}}]},we=e=>e.map((r,n)=>{if(typeof r=="string"){const h=r.split(`
4
+ `);return t("span",{children:h.map((d,y)=>i(ye.Fragment,{children:[d,y<h.length-1&&t("br",{})]},y))},n)}switch(r.type){case"text":const h=r.data.text.split(`
5
+ `);return t("span",{children:h.map((d,y)=>i(ye.Fragment,{children:[d,y<h.length-1&&t("br",{})]},y))},n);case"at":return i(C,{color:"blue",variant:"soft",style:{margin:"0 2px"},children:["@",r.data.name||r.data.qq]},n);case"face":return t("img",{src:`https://face.viki.moe/apng/${r.data.id}.png`,alt:`表情${r.data.id}`,style:{width:"24px",height:"24px",display:"inline-block",verticalAlign:"middle",margin:"0 2px"}},n);case"image":return t("img",{src:r.data.url,alt:"图片",style:{maxWidth:"300px",borderRadius:"8px",margin:"4px 0",display:"block"},onError:d=>{d.currentTarget.style.display="none"}},n);case"video":return t(C,{variant:"outline",style:{margin:"0 2px"},children:"📹 视频"},n);case"audio":return t(C,{variant:"outline",style:{margin:"0 2px"},children:"🎵 语音"},n);case"file":return i(C,{variant:"outline",style:{margin:"0 2px"},children:["📎 ",r.data.name||"文件"]},n);default:return t("span",{children:"[未知消息类型]"},n)}}),ce=(e,r)=>{if(!e.trim()||r.length===0)return;const n={id:`msg_${Date.now()}`,type:"sent",channelType:m.type,channelId:m.id,channelName:m.name,senderId:"test_user",senderName:"测试用户",content:r,timestamp:Date.now()};L(h=>[...h,n]),E(""),q([]),I.current?.clear(),U.current?.send(JSON.stringify({type:m.type,id:m.id,content:r,timestamp:Date.now()}))},Ce=()=>{confirm("确定清空所有消息记录?")&&L([])},ze=e=>{u(e),w(r=>r.map(n=>n.id===e.id?{...n,unread:0}:n)),window.innerWidth<768&&re(false)},Re=()=>{const e=["private","group","channel"],r={private:"私聊",group:"群聊",guild:"频道"},n=e[Math.floor(Math.random()*e.length)],h=`${n}_${Date.now()}`,d=prompt(`请输入${r[n]}名称:`);if(d){const y={id:h,name:d,type:n,unread:0};w($=>[...$,y]),u(y)}},le=e=>{switch(e){case"private":return t(se,{size:16});case"group":return t(_e,{size:16});case"channel":return t(je,{size:16});default:return t(Y,{size:16})}},Ne=e=>{I.current?.insertFace(e),j(false)},de=()=>{g.trim()&&(I.current?.insertImage(g.trim()),z(""),H(false))},pe=()=>{R.trim()&&(I.current?.insertAt(R.trim()),A(""),te(false))},ke=e=>{I.current?.replaceAtTrigger(e.name,e.id),s(null),o("")},Se=(e,r,n)=>{if(m.type==="private"){s(null),o("");return}e&&n?(s(n),o(r)):(s(null),o(""))},ue=N.filter(e=>{if(!a.trim())return true;const r=a.toLowerCase();return e.name.toLowerCase().includes(r)||e.id.toLowerCase().includes(r)}),Ee=(e,r)=>{E(e),q(r)},ge=G.filter(e=>e.name.toLowerCase().includes(l.toLowerCase())||e.describe.toLowerCase().includes(l.toLowerCase())),ie=K.filter(e=>e.channelId===m.id);return i("div",{className:"sandbox-container",children:[i("button",{className:"mobile-channel-toggle md:hidden",onClick:()=>re(!ne),children:[t(Y,{size:20}),"频道列表"]}),i(X,{className:he("channel-sidebar",ne&&"show"),children:[t(v,{p:"3",style:{borderBottom:"1px solid var(--gray-6)"},children:i(p,{justify:"between",align:"center",mb:"2",children:[i(p,{align:"center",gap:"2",children:[t(v,{p:"1",style:{borderRadius:"8px",backgroundColor:"var(--purple-3)"},children:t(Y,{size:16,color:"var(--purple-9)"})}),t(xe,{size:"4",children:"频道列表"})]}),t(C,{color:F?"green":"gray",children:i(p,{align:"center",gap:"1",children:[F?t(Ie,{size:12}):t($e,{size:12}),F?"已连接":"未连接"]})})]})}),t(v,{style:{flex:1,overflowY:"auto"},p:"3",children:t(p,{direction:"column",gap:"2",children:S.map(e=>{const r=m.id===e.id;return i("div",{className:he("channel-item",r&&"active"),onClick:()=>ze(e),children:[t("div",{className:"icon",children:le(e.type)}),i("div",{className:"text",children:[t("div",{className:"title",children:e.name}),i("div",{className:"subtitle",children:[e.type==="private"&&"私聊",e.type==="group"&&"群聊",e.type==="channel"&&"频道"]})]}),e.unread>0&&t(C,{color:"red",size:"1",className:"badge",children:e.unread}),r&&t("div",{className:"indicator"})]},e.id)})})}),t(v,{p:"2",style:{borderTop:"1px solid var(--gray-6)"},children:t(k,{variant:"outline",onClick:Re,style:{width:"100%"},children:"+ 添加频道"})})]}),ne&&t("div",{className:"channel-overlay md:hidden",onClick:()=>re(false)}),i("div",{className:"chat-area",children:[t(X,{children:i(p,{justify:"between",align:"center",p:"3",children:[i(p,{align:"center",gap:"3",children:[t(v,{p:"2",style:{borderRadius:"12px",backgroundColor:"var(--blue-3)"},children:le(m.type)}),i(v,{children:[t(xe,{size:"5",children:m.name}),i(p,{align:"center",gap:"2",children:[t(b,{size:"1",color:"gray",children:m.id}),t(C,{variant:"outline",size:"1",children:ie.length}),t(b,{size:"1",color:"gray",children:"条消息"})]})]}),i(C,{color:m.type==="private"?"blue":m.type==="group"?"green":"purple",children:[m.type==="private"&&"私聊",m.type==="group"&&"群聊",m.type==="channel"&&"频道"]})]}),i(p,{align:"center",gap:"2",children:[t(J.Root,{value:D,onChange:e=>ee(e.target.value),placeholder:"机器人名称",style:{width:"120px"}}),i(k,{variant:"soft",onClick:Ce,children:[t(Le,{size:16}),"清空"]})]})]})}),t(X,{style:{flex:1,display:"flex",flexDirection:"column"},children:t(v,{style:{flex:1,overflowY:"auto"},p:"4",children:ie.length===0?i(p,{direction:"column",align:"center",justify:"center",style:{height:"100%"},children:[t(Y,{size:64,color:"var(--gray-6)"}),t(b,{color:"gray",mt:"3",children:"暂无消息,开始对话吧!"})]}):i(p,{direction:"column",gap:"2",children:[ie.map(e=>t(p,{justify:e.type==="sent"?"end":"start",children:i(v,{style:{maxWidth:"70%",padding:"12px",borderRadius:"16px",backgroundColor:e.type==="sent"?"var(--blue-9)":"var(--gray-3)",color:e.type==="sent"?"white":"var(--gray-12)"},children:[i(p,{align:"center",gap:"2",mb:"1",children:[e.type==="received"&&t(De,{size:14}),e.type==="sent"&&t(se,{size:14}),t(b,{size:"1",weight:"medium",style:{opacity:.9},children:e.senderName}),t(b,{size:"1",style:{opacity:.7},children:new Date(e.timestamp).toLocaleTimeString()})]}),t(b,{size:"2",children:we(e.content)})]})},e.id)),t("div",{ref:oe})]})})}),t(X,{children:i(p,{direction:"column",gap:"3",p:"3",children:[i(p,{gap:"2",align:"center",children:[t(k,{variant:P?"solid":"outline",size:"2",onClick:()=>{j(!P),H(false)},title:"插入表情",children:t(Fe,{size:16})}),t(k,{variant:_?"solid":"outline",size:"2",onClick:()=>{H(!_),j(false)},title:"插入图片",children:t(Me,{size:16})}),t(v,{style:{flex:1}}),x&&t(k,{variant:"ghost",size:"2",onClick:()=>{E(""),q([])},title:"清空",children:t(Ue,{size:16})})]}),P&&i(v,{p:"3",style:{border:"1px solid var(--gray-6)",borderRadius:"8px",backgroundColor:"var(--gray-1)",maxHeight:"256px",overflowY:"auto"},children:[t(J.Root,{value:l,onChange:e=>c(e.target.value),placeholder:"搜索表情...",style:{marginBottom:"8px"}}),t(qe,{columns:"8",gap:"2",children:ge.slice(0,80).map(e=>t("button",{onClick:()=>Ne(e.id),style:{width:"40px",height:"40px",borderRadius:"8px",border:"1px solid var(--gray-6)",backgroundColor:"transparent",cursor:"pointer",display:"flex",alignItems:"center",justifyContent:"center"},title:e.name,children:t("img",{src:`https://face.viki.moe/apng/${e.id}.png`,alt:e.name,style:{width:"32px",height:"32px"}})},e.id))}),ge.length===0&&i(p,{direction:"column",align:"center",gap:"2",py:"4",children:[t(me,{size:32,color:"var(--gray-6)"}),t(b,{size:"2",color:"gray",children:"未找到匹配的表情"})]})]}),_&&t(v,{p:"3",style:{border:"1px solid var(--gray-6)",borderRadius:"8px",backgroundColor:"var(--gray-1)"},children:i(V.Root,{defaultValue:"url",children:[i(V.List,{children:[t(V.Trigger,{value:"url",children:"图片链接"}),t(V.Trigger,{value:"upload",children:"本地上传"})]}),t(v,{pt:"3",children:i(p,{direction:"column",gap:"2",children:[t(J.Root,{value:g,onChange:e=>z(e.target.value),placeholder:"输入图片 URL...",onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),de())}}),i(k,{onClick:de,disabled:!g.trim(),children:[t(fe,{size:16}),"插入"]})]})})]})}),T&&t(v,{p:"3",style:{border:"1px solid var(--gray-6)",borderRadius:"8px",backgroundColor:"var(--gray-1)"},children:i(p,{direction:"column",gap:"2",children:[t(J.Root,{value:R,onChange:e=>A(e.target.value),placeholder:"输入用户名...",onKeyDown:e=>{e.key==="Enter"&&(e.preventDefault(),pe())}}),i(k,{onClick:pe,disabled:!R.trim(),children:[t(fe,{size:16}),"插入"]})]})}),i(p,{gap:"2",align:"start",children:[i(v,{style:{flex:1,position:"relative"},children:[t(ve,{ref:I,placeholder:`向 ${m.name} 发送消息...`,onSend:ce,onChange:Ee,onAtTrigger:Se,minHeight:"44px",maxHeight:"200px"}),O&&t(v,{style:{position:"absolute",top:`${O.top}px`,left:`${O.left}px`,zIndex:1e3,backgroundColor:"var(--gray-1)",border:"1px solid var(--gray-6)",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",minWidth:"240px",maxHeight:"280px",overflowY:"auto",padding:"4px"},children:ue.length>0?ue.map(e=>i(p,{align:"center",gap:"2",p:"2",onClick:()=>ke(e),style:{cursor:"pointer",borderRadius:"6px",transition:"background-color 0.2s"},onMouseEnter:r=>{r.currentTarget.style.backgroundColor="var(--blue-a3)"},onMouseLeave:r=>{r.currentTarget.style.backgroundColor="transparent"},children:[t(se,{size:16,color:"var(--blue-9)"}),i(p,{direction:"column",gap:"0",style:{flex:1},children:[t(b,{size:"2",weight:"medium",children:e.name}),i(b,{size:"1",color:"gray",children:["ID: ",e.id]})]})]},e.id)):i(p,{align:"center",justify:"center",p:"4",direction:"column",gap:"2",children:[t(me,{size:20,color:"var(--gray-8)"}),t(b,{size:"1",color:"gray",children:"未找到匹配的用户"})]})})]}),i(k,{onClick:()=>{const e=I.current?.getContent();e&&ce(e.text,e.segments)},disabled:!x.trim()||W.length===0,size:"3",title:"发送消息 (Enter)",children:[t(Be,{size:16}),"发送"]})]}),i(p,{align:"center",gap:"2",wrap:"wrap",children:[t(Pe,{size:12,color:"var(--gray-9)"}),t(b,{size:"1",color:"gray",children:"快捷操作:"}),t(C,{variant:"outline",size:"1",children:"Enter"}),t(b,{size:"1",color:"gray",children:"发送"}),t(C,{variant:"outline",size:"1",children:"Shift+Enter"}),t(b,{size:"1",color:"gray",children:"换行"}),t(C,{variant:"outline",size:"1",children:"[@名称]"}),t(b,{size:"1",color:"gray",children:"@某人"})]})]})})]})]})}Ae({key:"process-sandbox",path:"/sandbox",title:"沙盒",icon:t(He,{className:"w-5 h-5"}),element:t(Ke,{})});
package/lib/index.d.ts ADDED
@@ -0,0 +1,54 @@
1
+ import { EventEmitter } from "events";
2
+ import { Bot, Adapter, Message, SendOptions, MessageType, MessageElement, Plugin } from "zhin.js";
3
+ import type { WebSocket } from "ws";
4
+ import { Router } from "@zhin.js/http";
5
+ export interface SandboxConfig {
6
+ context: "sandbox";
7
+ ws: WebSocket;
8
+ name: string;
9
+ }
10
+ declare module "zhin.js" {
11
+ namespace Plugin {
12
+ interface Contexts {
13
+ sandbox: SandboxAdapter;
14
+ router: Router;
15
+ web: any;
16
+ }
17
+ }
18
+ interface RegisteredAdapters {
19
+ sandbox: SandboxAdapter;
20
+ }
21
+ }
22
+ export declare class SandboxBot extends EventEmitter implements Bot<SandboxConfig, {
23
+ content: MessageElement[];
24
+ ts: number;
25
+ }> {
26
+ adapter: SandboxAdapter;
27
+ $config: SandboxConfig;
28
+ $connected: boolean;
29
+ get $id(): string;
30
+ private logger;
31
+ constructor(adapter: SandboxAdapter, $config: SandboxConfig);
32
+ $connect(): Promise<void>;
33
+ $disconnect(): Promise<void>;
34
+ $formatMessage({ content, type, id, ts }: {
35
+ content: MessageElement[];
36
+ id: string;
37
+ type: MessageType;
38
+ ts: number;
39
+ }): Message<{
40
+ content: MessageElement[];
41
+ ts: number;
42
+ }>;
43
+ $sendMessage(options: SendOptions): Promise<string>;
44
+ $recallMessage(id: string): Promise<void>;
45
+ }
46
+ declare class SandboxAdapter extends Adapter<SandboxBot> {
47
+ wss?: ReturnType<Router["ws"]>;
48
+ constructor(plugin: Plugin);
49
+ createBot(config: SandboxConfig): SandboxBot;
50
+ start(): Promise<void>;
51
+ setupWebSocket(router: Router): Promise<void>;
52
+ }
53
+ export {};
54
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AACtC,OAAO,EACL,GAAG,EACH,OAAO,EAEP,OAAO,EACP,WAAW,EAGX,WAAW,EACX,cAAc,EACd,MAAM,EACP,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AACpC,OAAO,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAGvC,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,SAAS,CAAC;IACnB,EAAE,EAAE,SAAS,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;CACd;AAED,OAAO,QAAQ,SAAS,CAAC;IACvB,UAAU,MAAM,CAAC;QACf,UAAU,QAAQ;YAChB,OAAO,EAAE,cAAc,CAAC;YACxB,MAAM,EAAE,MAAM,CAAC;YACf,GAAG,EAAE,GAAG,CAAC;SACV;KACF;IAED,UAAU,kBAAkB;QAC1B,OAAO,EAAE,cAAc,CAAC;KACzB;CACF;AAYD,qBAAa,UAAW,SAAQ,YAAa,YAAW,GAAG,CAAC,aAAa,EAAE;IAAE,OAAO,EAAE,cAAc,EAAE,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;IAShG,OAAO,EAAE,cAAc;IAAS,OAAO,EAAE,aAAa;IARzE,UAAU,EAAE,OAAO,CAAS;IAE5B,IAAI,GAAG,WAEN;IAED,OAAO,CAAC,MAAM,CAAU;gBAEL,OAAO,EAAE,cAAc,EAAS,OAAO,EAAE,aAAa;IAqBnE,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC;IAIzB,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC,cAAc,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE;QAAE,OAAO,EAAE,cAAc,EAAE,CAAC;QAAC,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,WAAW,CAAC;QAAC,EAAE,EAAE,MAAM,CAAA;KAAE;;;;IAoC5G,YAAY,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC;IAenD,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGhD;AAED,cAAM,cAAe,SAAQ,OAAO,CAAC,UAAU,CAAC;IAC9C,GAAG,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;gBAEnB,MAAM,EAAE,MAAM;IAI1B,SAAS,CAAC,MAAM,EAAE,aAAa,GAAG,UAAU;IAOtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAkCpD"}
package/lib/index.js ADDED
@@ -0,0 +1,173 @@
1
+ import { EventEmitter } from "events";
2
+ import { Adapter, usePlugin, Message, segment, } from "zhin.js";
3
+ import path from "path";
4
+ const plugin = usePlugin();
5
+ const logger = plugin.logger;
6
+ export class SandboxBot extends EventEmitter {
7
+ adapter;
8
+ $config;
9
+ $connected = false;
10
+ get $id() {
11
+ return this.$config.name;
12
+ }
13
+ logger = logger;
14
+ constructor(adapter, $config) {
15
+ super();
16
+ this.adapter = adapter;
17
+ this.$config = $config;
18
+ this.$config.ws.on("message", (data) => {
19
+ const message = JSON.parse(data.toString());
20
+ // 确保 content 是 MessageElement[] 格式
21
+ const content = typeof message.content === 'string'
22
+ ? [{ type: 'text', data: { text: message.content } }]
23
+ : message.content;
24
+ this.logger.debug(`${this.$config.name} recv ${message.type}(${message.id}):${segment.raw(content)}`);
25
+ const formattedMessage = this.$formatMessage({ content: content, type: message.type, id: message.id, ts: message.timestamp });
26
+ this.adapter.emit("message.receive", formattedMessage);
27
+ });
28
+ this.$config.ws.on("close", () => {
29
+ this.logger.debug(`Sandbox bot ${this.$config.name} disconnected`);
30
+ this.$connected = false;
31
+ // 从 adapter 中移除 bot
32
+ this.adapter.bots.delete(this.$id);
33
+ });
34
+ }
35
+ async $connect() {
36
+ this.$connected = true;
37
+ }
38
+ async $disconnect() {
39
+ this.$config.ws.close();
40
+ this.$connected = false;
41
+ }
42
+ $formatMessage({ content, type, id, ts }) {
43
+ const message = Message.from({ content, ts }, {
44
+ $id: `${ts}`,
45
+ $adapter: "sandbox",
46
+ $bot: `${this.$config.name}`,
47
+ $sender: {
48
+ id: `${id}`,
49
+ name: `mock`,
50
+ },
51
+ $channel: {
52
+ id: `${id}`,
53
+ type: type,
54
+ },
55
+ $content: content,
56
+ $raw: segment.raw(content),
57
+ $timestamp: ts,
58
+ $recall: async () => {
59
+ await this.$recallMessage(message.$id);
60
+ },
61
+ $reply: async (content, quote) => {
62
+ if (!Array.isArray(content))
63
+ content = [content];
64
+ if (quote)
65
+ content.unshift({ type: "reply", data: { id: typeof quote === "boolean" ? message.$id : quote } });
66
+ return await this.$sendMessage({
67
+ ...message.$channel,
68
+ context: "sandbox",
69
+ bot: `${this.$config.name}`,
70
+ content,
71
+ });
72
+ },
73
+ });
74
+ return message;
75
+ }
76
+ async $sendMessage(options) {
77
+ if (!this.$connected)
78
+ return "";
79
+ this.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
80
+ options.bot = this.$config.name;
81
+ options.context = "sandbox";
82
+ this.$config.ws.send(JSON.stringify({
83
+ ...options,
84
+ content: options.content, // 发送消息段数组
85
+ timestamp: Date.now(),
86
+ }));
87
+ return "";
88
+ }
89
+ async $recallMessage(id) {
90
+ // 沙盒不支持撤回消息
91
+ }
92
+ }
93
+ class SandboxAdapter extends Adapter {
94
+ wss;
95
+ constructor(plugin) {
96
+ super(plugin, "sandbox", []);
97
+ }
98
+ createBot(config) {
99
+ const bot = new SandboxBot(this, config);
100
+ // 将 bot 添加到 bots Map 中
101
+ this.bots.set(bot.$id, bot);
102
+ return bot;
103
+ }
104
+ async start() {
105
+ // start 方法会在 mounted 时被调用
106
+ // WebSocket server 的创建在 useContext("router") 中处理
107
+ }
108
+ async setupWebSocket(router) {
109
+ if (this.wss)
110
+ return; // 已经设置过了
111
+ // 创建 WebSocket server
112
+ this.wss = router.ws("/sandbox");
113
+ this.wss.on("connection", (ws, req) => {
114
+ // 为每个连接创建一个唯一的 bot 名称
115
+ const botName = `sandbox-${Math.random().toString(36).slice(2, 9)}`;
116
+ logger.debug(`New sandbox connection: ${botName} from ${req.socket.remoteAddress}`);
117
+ // 创建 bot 配置
118
+ const config = {
119
+ context: "sandbox",
120
+ ws,
121
+ name: botName,
122
+ };
123
+ // 创建并连接 bot
124
+ const bot = this.createBot(config);
125
+ bot.$connect();
126
+ // WebSocket 关闭时清理
127
+ ws.on("close", () => {
128
+ logger.debug(`Sandbox connection closed: ${botName}`);
129
+ this.bots.delete(bot.$id);
130
+ });
131
+ ws.on("error", (error) => {
132
+ logger.error(`Sandbox WebSocket error for ${botName}:`, error);
133
+ });
134
+ });
135
+ logger.info("Sandbox WebSocket server started at /sandbox");
136
+ }
137
+ }
138
+ const { provide } = usePlugin();
139
+ provide({
140
+ name: "sandbox",
141
+ description: "Sandbox Adapter",
142
+ mounted: async (p) => {
143
+ const adapter = new SandboxAdapter(p);
144
+ await adapter.start();
145
+ return adapter;
146
+ },
147
+ dispose: async (adapter) => {
148
+ // 关闭所有 bot 连接
149
+ for (const bot of adapter.bots.values()) {
150
+ await bot.$disconnect();
151
+ }
152
+ // 关闭 WebSocket server
153
+ adapter.wss?.close();
154
+ await adapter.stop();
155
+ },
156
+ });
157
+ // 使用 router 上下文创建 WebSocket server
158
+ plugin.useContext("router", async (router) => {
159
+ // 等待 sandbox adapter 就绪
160
+ plugin.useContext("sandbox", async (adapter) => {
161
+ await adapter.setupWebSocket(router);
162
+ });
163
+ });
164
+ // 使用 web 上下文注册客户端入口
165
+ plugin.useContext("web", (web) => {
166
+ // 注册 Sandbox 适配器的客户端入口文件
167
+ const dispose = web.addEntry({
168
+ production: path.resolve(import.meta.dirname, "../dist/index.js"),
169
+ development: path.resolve(import.meta.dirname, "../client/index.tsx"),
170
+ });
171
+ return dispose;
172
+ });
173
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@zhin.js/adapter-sandbox",
3
+ "version": "1.0.22",
4
+ "description": "zhin adapter for sandbox",
5
+ "type": "module",
6
+ "main": "./lib/index.js",
7
+ "types": "./lib/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./lib/index.d.ts",
11
+ "development": "./src/index.ts",
12
+ "import": "./lib/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "lib",
17
+ "README.md",
18
+ "CHANGELOG.md",
19
+ "client",
20
+ "src",
21
+ "dist"
22
+ ],
23
+ "keywords": [
24
+ "zhin",
25
+ "adapter",
26
+ "process"
27
+ ],
28
+ "author": "lc-cn",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "url": "git+https://github.com/zhinjs/zhin.git",
32
+ "type": "git",
33
+ "directory": "plugins/adapters/process"
34
+ },
35
+ "devDependencies": {
36
+ "@types/react": "^19.2.2",
37
+ "@types/react-dom": "^19.2.1",
38
+ "radix-ui": "^1.4.3",
39
+ "@radix-ui/themes": "^3.2.1",
40
+ "lucide-react": "^0.469.0",
41
+ "typescript": "^5.3.0"
42
+ },
43
+ "peerDependencies": {
44
+ "@zhin.js/console": "1.0.13",
45
+ "@zhin.js/core": "1.0.17",
46
+ "@zhin.js/http": "1.0.8",
47
+ "@zhin.js/client": "1.0.5",
48
+ "zhin.js": "1.0.17"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@zhin.js/http": {
52
+ "optional": true
53
+ },
54
+ "@zhin.js/console": {
55
+ "optional": true
56
+ },
57
+ "@zhin.js/client": {
58
+ "optional": true
59
+ }
60
+ },
61
+ "scripts": {
62
+ "build": "tsc && (test -f ../../services/console/lib/bin.js && node ../../services/console/lib/bin.js build || echo 'Skipping client build: console not built yet')",
63
+ "clean": "rm -rf lib"
64
+ }
65
+ }
package/src/index.ts ADDED
@@ -0,0 +1,235 @@
1
+ import { EventEmitter } from "events";
2
+ import {
3
+ Bot,
4
+ Adapter,
5
+ usePlugin,
6
+ Message,
7
+ SendOptions,
8
+ segment,
9
+ SendContent,
10
+ MessageType,
11
+ MessageElement,
12
+ Plugin,
13
+ } from "zhin.js";
14
+ import type { WebSocket } from "ws";
15
+ import { Router } from "@zhin.js/http";
16
+ import path from "path";
17
+
18
+ export interface SandboxConfig {
19
+ context: "sandbox";
20
+ ws: WebSocket;
21
+ name: string;
22
+ }
23
+
24
+ declare module "zhin.js" {
25
+ namespace Plugin {
26
+ interface Contexts {
27
+ sandbox: SandboxAdapter;
28
+ router: Router;
29
+ web: any;
30
+ }
31
+ }
32
+
33
+ interface RegisteredAdapters {
34
+ sandbox: SandboxAdapter;
35
+ }
36
+ }
37
+
38
+ const plugin = usePlugin();
39
+ const logger = plugin.logger;
40
+
41
+ interface WebSocketMessage {
42
+ type: MessageType;
43
+ id: string;
44
+ content: MessageElement[] | string;
45
+ timestamp: number;
46
+ }
47
+
48
+ export class SandboxBot extends EventEmitter implements Bot<SandboxConfig, { content: MessageElement[]; ts: number }> {
49
+ $connected: boolean = false;
50
+
51
+ get $id() {
52
+ return this.$config.name;
53
+ }
54
+
55
+ private logger = logger;
56
+
57
+ constructor(public adapter: SandboxAdapter, public $config: SandboxConfig) {
58
+ super();
59
+ this.$config.ws.on("message", (data) => {
60
+ const message = JSON.parse(data.toString()) as WebSocketMessage;
61
+ // 确保 content 是 MessageElement[] 格式
62
+ const content: MessageElement[] = typeof message.content === 'string'
63
+ ? [{ type: 'text', data: { text: message.content } }]
64
+ : message.content;
65
+ this.logger.debug(`${this.$config.name} recv ${message.type}(${message.id}):${segment.raw(content)}`);
66
+ const formattedMessage = this.$formatMessage({ content: content, type: message.type, id: message.id, ts: message.timestamp });
67
+ this.adapter.emit("message.receive", formattedMessage);
68
+ });
69
+
70
+ this.$config.ws.on("close", () => {
71
+ this.logger.debug(`Sandbox bot ${this.$config.name} disconnected`);
72
+ this.$connected = false;
73
+ // 从 adapter 中移除 bot
74
+ this.adapter.bots.delete(this.$id);
75
+ });
76
+ }
77
+
78
+ async $connect(): Promise<void> {
79
+ this.$connected = true;
80
+ }
81
+
82
+ async $disconnect(): Promise<void> {
83
+ this.$config.ws.close();
84
+ this.$connected = false;
85
+ }
86
+
87
+ $formatMessage({ content, type, id, ts }: { content: MessageElement[]; id: string; type: MessageType; ts: number }) {
88
+ const message = Message.from(
89
+ { content, ts },
90
+ {
91
+ $id: `${ts}`,
92
+ $adapter: "sandbox" as const,
93
+ $bot: `${this.$config.name}`,
94
+ $sender: {
95
+ id: `${id}`,
96
+ name: `mock`,
97
+ },
98
+ $channel: {
99
+ id: `${id}`,
100
+ type: type,
101
+ },
102
+ $content: content,
103
+ $raw: segment.raw(content),
104
+ $timestamp: ts,
105
+ $recall: async () => {
106
+ await this.$recallMessage(message.$id);
107
+ },
108
+ $reply: async (content: SendContent, quote?: boolean | string): Promise<string> => {
109
+ if (!Array.isArray(content)) content = [content];
110
+ if (quote) content.unshift({ type: "reply", data: { id: typeof quote === "boolean" ? message.$id : quote } });
111
+ return await this.$sendMessage({
112
+ ...message.$channel,
113
+ context: "sandbox",
114
+ bot: `${this.$config.name}`,
115
+ content,
116
+ });
117
+ },
118
+ }
119
+ );
120
+ return message;
121
+ }
122
+
123
+ async $sendMessage(options: SendOptions): Promise<string> {
124
+ if (!this.$connected) return "";
125
+ this.logger.debug(`${this.$config.name} send ${options.type}(${options.id}):${segment.raw(options.content)}`);
126
+ options.bot = this.$config.name;
127
+ options.context = "sandbox";
128
+ this.$config.ws.send(
129
+ JSON.stringify({
130
+ ...options,
131
+ content: options.content, // 发送消息段数组
132
+ timestamp: Date.now(),
133
+ })
134
+ );
135
+ return "";
136
+ }
137
+
138
+ async $recallMessage(id: string): Promise<void> {
139
+ // 沙盒不支持撤回消息
140
+ }
141
+ }
142
+
143
+ class SandboxAdapter extends Adapter<SandboxBot> {
144
+ wss?: ReturnType<Router["ws"]>;
145
+
146
+ constructor(plugin: Plugin) {
147
+ super(plugin, "sandbox", []);
148
+ }
149
+
150
+ createBot(config: SandboxConfig): SandboxBot {
151
+ const bot = new SandboxBot(this, config);
152
+ // 将 bot 添加到 bots Map 中
153
+ this.bots.set(bot.$id, bot);
154
+ return bot;
155
+ }
156
+
157
+ async start(): Promise<void> {
158
+ // start 方法会在 mounted 时被调用
159
+ // WebSocket server 的创建在 useContext("router") 中处理
160
+ }
161
+
162
+ async setupWebSocket(router: Router): Promise<void> {
163
+ if (this.wss) return; // 已经设置过了
164
+ // 创建 WebSocket server
165
+ this.wss = router.ws("/sandbox");
166
+
167
+ this.wss.on("connection", (ws: WebSocket, req) => {
168
+ // 为每个连接创建一个唯一的 bot 名称
169
+ const botName = `sandbox-${Math.random().toString(36).slice(2, 9)}`;
170
+ logger.debug(`New sandbox connection: ${botName} from ${req.socket.remoteAddress}`);
171
+
172
+ // 创建 bot 配置
173
+ const config: SandboxConfig = {
174
+ context: "sandbox",
175
+ ws,
176
+ name: botName,
177
+ };
178
+
179
+ // 创建并连接 bot
180
+ const bot = this.createBot(config);
181
+ bot.$connect();
182
+
183
+ // WebSocket 关闭时清理
184
+ ws.on("close", () => {
185
+ logger.debug(`Sandbox connection closed: ${botName}`);
186
+ this.bots.delete(bot.$id);
187
+ });
188
+
189
+ ws.on("error", (error) => {
190
+ logger.error(`Sandbox WebSocket error for ${botName}:`, error);
191
+ });
192
+ });
193
+
194
+ logger.info("Sandbox WebSocket server started at /sandbox");
195
+ }
196
+ }
197
+
198
+ const { provide } = usePlugin();
199
+
200
+ provide({
201
+ name: "sandbox",
202
+ description: "Sandbox Adapter",
203
+ mounted: async (p: Plugin) => {
204
+ const adapter = new SandboxAdapter(p);
205
+ await adapter.start();
206
+ return adapter;
207
+ },
208
+ dispose: async (adapter: SandboxAdapter) => {
209
+ // 关闭所有 bot 连接
210
+ for (const bot of adapter.bots.values()) {
211
+ await bot.$disconnect();
212
+ }
213
+ // 关闭 WebSocket server
214
+ adapter.wss?.close();
215
+ await adapter.stop();
216
+ },
217
+ });
218
+
219
+ // 使用 router 上下文创建 WebSocket server
220
+ plugin.useContext("router", async (router: Router) => {
221
+ // 等待 sandbox adapter 就绪
222
+ plugin.useContext("sandbox", async (adapter: SandboxAdapter) => {
223
+ await adapter.setupWebSocket(router);
224
+ });
225
+ });
226
+
227
+ // 使用 web 上下文注册客户端入口
228
+ plugin.useContext("web", (web: any) => {
229
+ // 注册 Sandbox 适配器的客户端入口文件
230
+ const dispose = web.addEntry({
231
+ production: path.resolve(import.meta.dirname, "../dist/index.js"),
232
+ development: path.resolve(import.meta.dirname, "../client/index.tsx"),
233
+ });
234
+ return dispose;
235
+ });