@lansenger/openclaw-channel-lansenger 0.0.6 → 1.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -67
- package/dist/src/lansenger-core/processor/message-processor.js +1 -1
- package/dist/src/lansenger-core/send/group.js +1 -1
- package/dist/src/lansenger-core/send/send.js +1 -1
- package/dist/src/lansenger-core/send/text.js +1 -1
- package/dist/src/lansenger-core/services/lansenger-api-service.js +1 -1
- package/openclaw.plugin.json +2 -1
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -1,28 +1,14 @@
|
|
|
1
|
-
# OpenClaw
|
|
1
|
+
# OpenClaw Lansenger Channel Plugin
|
|
2
2
|
|
|
3
3
|
## 项目简介
|
|
4
4
|
|
|
5
|
-
OpenClaw
|
|
5
|
+
OpenClaw Lansenger Channel Plugin 是 OpenClaw 平台的蓝信渠道插件,用于实现 OpenClaw 与蓝信消息平台的集成。该插件支持:
|
|
6
6
|
|
|
7
7
|
- 接收蓝信消息并转发到 OpenClaw
|
|
8
8
|
- 发送消息从 OpenClaw 到蓝信
|
|
9
|
-
- 支持文本、图片、文件等多种消息类型
|
|
10
|
-
- 支持蓝信机器人的各种消息格式
|
|
11
|
-
- 提供配对码机制,确保消息安全
|
|
12
9
|
|
|
13
10
|
## 功能特性
|
|
14
11
|
|
|
15
|
-
### 安全机制
|
|
16
|
-
- 配对码验证,确保只有授权用户可以使用
|
|
17
|
-
- 支持配置允许发送者列表
|
|
18
|
-
- 群聊支持@机器人触发机制
|
|
19
|
-
|
|
20
|
-
### 消息支持
|
|
21
|
-
- 文本消息(支持 Markdown 格式)
|
|
22
|
-
- 图片消息
|
|
23
|
-
- 文件消息
|
|
24
|
-
- 卡片消息(linkCard、appCard 等)
|
|
25
|
-
|
|
26
12
|
### 连接方式
|
|
27
13
|
- WebSocket 长连接
|
|
28
14
|
- 支持蓝信开放平台 API
|
|
@@ -32,34 +18,27 @@ OpenClaw Lanxin Channel Plugin 是 OpenClaw 平台的蓝信渠道插件,用于
|
|
|
32
18
|
### 安装插件
|
|
33
19
|
|
|
34
20
|
```bash
|
|
35
|
-
openclaw plugins install @lansenger/openclaw-
|
|
21
|
+
openclaw plugins install @lansenger/openclaw-lansenger
|
|
36
22
|
```
|
|
37
23
|
|
|
38
|
-
安装成功后会显示:`Installed plugin: openclaw-
|
|
24
|
+
安装成功后会显示:`Installed plugin: openclaw-lansenger`
|
|
39
25
|
|
|
40
26
|
### 初始化配置
|
|
41
27
|
|
|
42
28
|
```bash
|
|
43
|
-
openclaw channels add --channel
|
|
29
|
+
openclaw channels add --channel Lansenger --token "{AppID}:{App Secret}:{Api Gateway URL}"
|
|
44
30
|
```
|
|
45
31
|
|
|
46
32
|
其中:
|
|
47
33
|
- `{AppID}`:蓝信应用的 App ID
|
|
48
34
|
- `{App Secret}`:蓝信应用的 App Secret
|
|
49
|
-
- `{
|
|
50
|
-
|
|
51
|
-
### 配对码机制
|
|
52
|
-
|
|
53
|
-
首次会话时,系统会要求用户输入配对码。用户输入配对码后,插件会验证配对码并授权用户访问。
|
|
35
|
+
- `{Api Gateway URL}`:蓝信开放平台 API 网关地址
|
|
54
36
|
|
|
55
|
-
- 配对码有效期为1小时
|
|
56
|
-
- 每个用户在1小时内只会生成一个配对码
|
|
57
|
-
- 配对码用于在 OpenClaw 中授权用户访问
|
|
58
37
|
|
|
59
38
|
## 蓝信应用配置
|
|
60
39
|
|
|
61
40
|
1. **创建应用**:在蓝信PC客户端(version>=9.5.50)-个人机器人-创建机器人
|
|
62
|
-
2. **获取凭据**:从创建结果页面,获取 App ID 和 App Secret并配置到openclaw
|
|
41
|
+
2. **获取凭据**:从创建结果页面,获取 App ID 和 App Secret/API Gateway URL并配置到openclaw
|
|
63
42
|
|
|
64
43
|
## 使用指南
|
|
65
44
|
|
|
@@ -81,50 +60,12 @@ openclaw channels add --channel lanxin --token "{AppID}:{App Secret}:{domain}"
|
|
|
81
60
|
|
|
82
61
|
## 常见问题
|
|
83
62
|
|
|
84
|
-
### 配对码问题
|
|
85
|
-
- 配对码有效期为1小时
|
|
86
|
-
- 每个用户在1小时内只会生成一个配对码
|
|
87
|
-
- 配对码用于在 OpenClaw 中授权用户访问
|
|
88
63
|
|
|
89
64
|
### 消息类型支持
|
|
90
65
|
- 部分消息类型可能需要蓝信应用具备相应权限
|
|
91
|
-
-
|
|
66
|
+
- 大文件可能会受到蓝信平台的大小限制
|
|
92
67
|
|
|
93
68
|
### 错误处理
|
|
94
69
|
- 查看 OpenClaw 日志了解详细错误信息
|
|
95
70
|
- 检查蓝信应用配置是否正确
|
|
96
71
|
- 确保网络连接正常
|
|
97
|
-
|
|
98
|
-
## 版本历史
|
|
99
|
-
|
|
100
|
-
### 0.0.6
|
|
101
|
-
- 优化代码质量和架构
|
|
102
|
-
- 完善架构设计文档
|
|
103
|
-
- 优化日志系统
|
|
104
|
-
- 修复 UI 配置问题
|
|
105
|
-
|
|
106
|
-
### 0.0.4
|
|
107
|
-
- 添加 Markdown 消息支持
|
|
108
|
-
- 优化媒体文件处理
|
|
109
|
-
- 修复构建错误
|
|
110
|
-
|
|
111
|
-
### 0.0.3
|
|
112
|
-
- 实现配对码机制
|
|
113
|
-
- 支持群聊消息
|
|
114
|
-
- 优化错误处理
|
|
115
|
-
|
|
116
|
-
### 0.0.2
|
|
117
|
-
- 完善消息类型支持
|
|
118
|
-
- 优化配置管理
|
|
119
|
-
|
|
120
|
-
### 0.0.1
|
|
121
|
-
- 初始版本
|
|
122
|
-
- 基本消息收发功能
|
|
123
|
-
|
|
124
|
-
## 贡献
|
|
125
|
-
|
|
126
|
-
欢迎提交 Issue 和 Pull Request 来改进这个项目。
|
|
127
|
-
|
|
128
|
-
## 许可证
|
|
129
|
-
|
|
130
|
-
MIT License
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getBotType as r,sendTextMessage as e,sendLinkCardMessage as t,sendAppCardMessage as o,sendOaCardMessage as a,sendAppArticlesMessage as i,sendSystemMessage as
|
|
1
|
+
import{getBotType as r,sendTextMessage as e,sendLinkCardMessage as t,sendAppCardMessage as o,sendOaCardMessage as a,sendAppArticlesMessage as i,sendSystemMessage as s,sendMessageWithMediaTags as n,logger as d}from"../index.js";export async function processAndSendMessage(m,p,f,c){if(!p?.trim())return{ok:!1,error:"No recipient provided"};const g=r(m),u={hookToken:m.hookToken,isGroup:c};try{if("string"==typeof f)return f?.trim()?await n(g,m,p,f,{...u,msgType:"formatText",formatType:1}):{ok:!1,error:"No message text provided"};const r=f;if(r.text||r.body){const e=r.text||r.body;return e?.trim()?await n(g,m,p,e,{...u,msgType:"formatText",formatType:1}):{ok:!1,error:"No message text provided"}}if(r.mediaUrl||r.mediaUrls){const t=r.mediaUrl||r.mediaUrls?.[0];if(!t)return{ok:!1,error:"No media URL provided"};const o=`Media: ${t}`;return await e(g,m,p,o,u)}if(r.linkCard)return await t(g,m,p,r.linkCard,u);if(r.appCard)return await o(g,m,p,r.appCard,u);if(r.oaCard||r.oacard){const e=r.oaCard||r.oacard;return await a(g,m,p,e,u)}if(r.appArticles)return await i(g,m,p,r.appArticles,u);if(r.systemMessage)return await s(g,m,p,r.systemMessage,u);if(r.toString){const e=r.toString();if(e)return await n(g,m,p,e,{...u,msgType:"formatText",formatType:1})}return{ok:!1,error:"No valid message content found"}}catch(r){return d.error("Error processing message",{error:r,payload:f}),{ok:!1,error:r instanceof Error?r.message:String(r)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getSmartBotApiUrl as e,getWebhookBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as o}from"../config/index.js";import{tokenManager as s}from"../utils/token.js";import{logger as t}from"../utils/error-handling.js";export async function sendSmartBotGroupMessage(r,n,a,g,i){if(!n?.trim())return t.warn("No groupId provided"),{ok:!1,error:"No groupId provided"};try{t.debug("Sending smart bot group message",{groupId:n,msgType:a,outlines:i?.outlines});const d=await s.getAppToken(r),p=e(r.apiGatewayUrl),m=o.smartBot.groupMessage,u=new URLSearchParams;u.append("app_token",d),i?.userToken&&u.append("user_token",i.userToken),t.debug("Sending message to Lanxin API",{apiUrl:p,endpoint:m,groupId:n,msgType:a});const k=await fetch(`${p}${m}?${u.toString()}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({groupId:n,outlines:i?.outlines,msgType:a,msgData:g,entryId:i?.entryId})});if(!k.ok){const e=`HTTP ${k.status}: ${k.statusText}`;return t.error(e,{url:`${p}${m}`,status:k.status}),{ok:!1,error:e}}const c=await k.json();return 0!==c.errCode?(t.error("Lanxin API error",{errCode:c.errCode,errMsg:c.errMsg}),{ok:!1,error:c.errMsg}):(t.info("Group message sent successfully",{messageId:c.data?.msgId,groupId:n,msgType:a}),{ok:!0,messageId:c.data?.msgId})}catch(e){return t.error("Error sending group message",{error:e,groupId:n,msgType:a}),{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupTextMessage(e,r,o,s){return await sendSmartBotGroupMessage(e,r,"formatText",{formatText:{formatType:1,text:o}},s)}export async function sendWebhookBotMessage(e,n,a,g){if(!n?.trim())return t.warn("No hookToken provided"),{ok:!1,error:"No hookToken provided"};try{t.
|
|
1
|
+
import{getSmartBotApiUrl as e,getWebhookBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as o}from"../config/index.js";import{tokenManager as s}from"../utils/token.js";import{logger as t}from"../utils/error-handling.js";export async function sendSmartBotGroupMessage(r,n,a,g,i){if(!n?.trim())return t.warn("No groupId provided"),{ok:!1,error:"No groupId provided"};try{t.debug("Sending smart bot group message",{groupId:n,msgType:a,outlines:i?.outlines});const d=await s.getAppToken(r),p=e(r.apiGatewayUrl),m=o.smartBot.groupMessage,u=new URLSearchParams;u.append("app_token",d),i?.userToken&&u.append("user_token",i.userToken),t.debug("Sending message to Lanxin API",{apiUrl:p,endpoint:m,groupId:n,msgType:a});const k=await fetch(`${p}${m}?${u.toString()}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({groupId:n,outlines:i?.outlines,msgType:a,msgData:g,entryId:i?.entryId})});if(!k.ok){const e=`HTTP ${k.status}: ${k.statusText}`;return t.error(e,{url:`${p}${m}`,status:k.status}),{ok:!1,error:e}}const c=await k.json();return 0!==c.errCode?(t.error("Lanxin API error",{errCode:c.errCode,errMsg:c.errMsg}),{ok:!1,error:c.errMsg}):(t.info("Group message sent successfully",{messageId:c.data?.msgId,groupId:n,msgType:a}),{ok:!0,messageId:c.data?.msgId})}catch(e){return t.error("Error sending group message",{error:e,groupId:n,msgType:a}),{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupTextMessage(e,r,o,s){return await sendSmartBotGroupMessage(e,r,"formatText",{formatText:{formatType:1,text:o}},s)}export async function sendWebhookBotMessage(e,n,a,g){if(!n?.trim())return t.warn("No hookToken provided"),{ok:!1,error:"No hookToken provided"};try{t.info("Sending webhook bot message",{msgType:a,hookToken:n.slice(0,10)+"..."});const i=await s.getAppToken(e),d=r(e.apiGatewayUrl),p=o.webhookBot.message,m=new URLSearchParams;i&&m.append("app_token",i),m.append("hook_token",n),t.debug("Sending message to Lanxin API",{apiUrl:d,endpoint:p,msgType:a});const u=await fetch(`${d}${p}?${m.toString()}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({msgType:a,msgData:g})});if(!u.ok){const e=`HTTP ${u.status}: ${u.statusText}`;return t.error(e,{url:`${d}${p}`,status:u.status}),{ok:!1,error:e}}const k=await u.json();return 0!==k.errCode?(t.error("Lanxin API error",{errCode:k.errCode,errMsg:k.errMsg}),{ok:!1,error:k.errMsg}):(t.info("Webhook message sent successfully",{messageId:k.data?.msgId,msgType:a}),{ok:!0,messageId:k.data?.msgId})}catch(e){return t.error("Error sending webhook message",{error:e,msgType:a}),{ok:!1,error:e instanceof Error?e.message:String(e)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getSmartBotApiUrl as e,getWebhookBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as t,getBotType as o}from"../config/index.js";import{tokenManager as a}from"../utils/token.js";import{logger as n,handleError as s}from"../utils/error-handling.js";import{lansengerApiService as i}from"../services/index.js";import{parseLanxinMediaTags as p}from"../utils/media-tags.js";import{downloadToTempFile as d}from"../utils/media.js";import m from"fs/promises";import c from"path";const u={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".pdf":"application/pdf",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".txt":"text/plain",".html":"text/html",".css":"text/css",".js":"text/javascript",".json":"application/json",".mp3":"audio/mpeg",".mp4":"video/mp4",".zip":"application/zip",".rar":"application/x-rar-compressed",".7z":"application/x-7z-compressed"};function k(e){const r=c.extname(e).toLowerCase();return u[r]||"application/octet-stream"}export async function uploadMediaFile(e,r,t,o){try{n.debug("Uploading media file",{fileName:t,fileType:o});const a=await i.media.uploadMedia(e,r,o);if(!a){const e="No mediaId returned";return n.error(e),{ok:!1,error:e}}return n.info("Media file uploaded successfully",{mediaId:a,fileName:t}),{ok:!0,mediaId:a}}catch(e){return{ok:!1,error:s(e,{operation:"uploadMediaFile"})}}}export async function sendSmartBotPrivateImageMessage(e,r,t){if(!r?.trim())return{ok:!1,error:"No userId provided"};if(!t?.trim())return{ok:!1,error:"No mediaId provided"};try{return await i.message.sendPrivateMessage(e,r,"image",{image:{mediaId:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupImageMessage(e,r,t,o){if(!r?.trim())return{ok:!1,error:"No groupId provided"};if(!t?.trim())return{ok:!1,error:"No mediaId provided"};try{return await i.message.sendGroupMessage(e,r,"image",{image:{mediaId:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendMediaMessage(r,o,n,s,i,p){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if(!i?.trim())return{ok:!1,error:"No mediaId provided"};let d;switch(s.toLowerCase()){case"video":d=1;break;case"image":d=2;break;default:d=3}if("smart"===r){if(p?.isGroup)return await sendSmartBotGroupMessage(o,n,"text",{text:{content:"",mediaType:d,mediaIds:[i]}});{const r=await a.getAppToken(o),s=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,m=await fetch(`${s}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"text",msgData:{text:{content:"",mediaType:d,mediaIds:[i]}}})});if(!m.ok)return{ok:!1,error:`HTTP ${m.status}: ${m.statusText}`};const c=await m.json();return 0!==c.errCode?{ok:!1,error:c.errMsg}:{ok:!0,messageId:c.data?.msgId}}}if("webhook"===r){const e=p?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"text",{text:{content:"",mediaType:d,mediaIds:[i]}}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendLinkCardMessage(r,o,n,s,i){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if(!s?.title||!s?.link)return{ok:!1,error:"Title and link are required for linkCard"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,n,"linkCard",{linkCard:s});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"linkCard",msgData:{linkCard:s}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"linkCard",{linkCard:s}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendAppCardMessage(r,o,n,s,i){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if(!s?.bodyTitle)return{ok:!1,error:"bodyTitle is required for appCard"};if(s.isDynamic&&!s.headStatusInfo)return{ok:!1,error:"headStatusInfo is required for dynamic appCard"};if(s.isDynamic&&!s.headStatusInfo?.description)return{ok:!1,error:"headStatusInfo.description is required for dynamic appCard"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,n,"appCard",{appCard:s});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"appCard",msgData:{appCard:s}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"appCard",{appCard:s}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendOaCardMessage(r,o,n,s,i){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,n,"oacard",{oacard:s});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"oacard",msgData:{oacard:s}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"oacard",{oacard:s}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendAppArticlesMessage(r,o,n,s,i){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if(!s||0===s.length)return{ok:!1,error:"appArticles array is required and must not be empty"};for(const e of s)if(!e.title||!e.imageUrl||!e.url)return{ok:!1,error:"Each article must have title, imageUrl, and url"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,n,"appArticles",{appArticles:s});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"appArticles",msgData:{appArticles:s}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"appArticles",{appArticles:s}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendSystemMessage(r,o,n,s,i){if(!n?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,n,"system",{system:s});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[n],msgType:"system",msgData:{system:s}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"system",{system:s}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendSmartBotPrivateMessage(e,r,t,o){if(!r?.trim())return{ok:!1,error:"No userId provided"};try{const a=r.trim();if(!a)return{ok:!1,error:"Invalid userId"};const n="formatText"===o?.msgType,s=o?.formatType||1;return await i.message.sendPrivateMessage(e,a,n?"formatText":"text",n?{formatText:{formatType:s,text:t}}:{text:{content:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupMessage(e,r,t,o,a){if(!r?.trim())return{ok:!1,error:"No groupId provided"};try{return await i.message.sendGroupMessage(e,r,t,o)}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupTextMessage(e,r,t,o){const a="formatText"===o?.msgType;return await sendSmartBotGroupMessage(e,r,a?"formatText":"text",a?{formatText:{formatType:o?.formatType||1,text:t}}:{text:{content:t}},o)}export async function sendWebhookBotMessage(e,o,n,s){if(!o?.trim())return{ok:!1,error:"No hookToken provided"};try{const i=await a.getAppToken(e),p=r(e.apiGatewayUrl),d=t.webhookBot.message,m=new URLSearchParams;i&&m.append("app_token",i),m.append("hook_token",o);const c=await fetch(`${p}${d}?${m.toString()}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({msgType:n,msgData:s})});if(!c.ok)return{ok:!1,error:`HTTP ${c.status}: ${c.statusText}`};const u=await c.json();return 0!==u.errCode?{ok:!1,error:u.errMsg}:{ok:!0,messageId:u.data?.msgId}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function getSmartBotGroups(e,r){try{const r=(await i.group.getGroups(e)).map(e=>e.groupId);return{ok:!0,totalGroupIds:r.length,groupIds:r}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendTextMessage(e,r,t,o,a){if(!t?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===e)return a?.isGroup?await sendSmartBotGroupTextMessage(r,t,o,{...a}):await sendSmartBotPrivateMessage(r,t,o,{msgType:a?.msgType,formatType:a?.formatType});if("webhook"===e){const e=a?.hookToken||r.hookToken;if(!e)return{ok:!1,error:"No hookToken provided for webhook bot"};const t="formatText"===a?.msgType;return await sendWebhookBotMessage(r,e,t?"formatText":"text",t?{formatText:{formatType:a?.formatType||1,text:o}}:{text:{content:o}})}return{ok:!1,error:`Unknown bot type: ${e}`}}async function f(e,r="unknown"){try{if(e.startsWith("http://")||e.startsWith("https://")){const{path:t}=await d(e,r),o=await m.readFile(t),a=k(t);return{buffer:o,contentType:a,fileName:t.split("/").pop()||"downloaded-file"}}{const r=c.resolve(e),t=await m.readFile(r),o=k(r);return{buffer:t,contentType:o,fileName:c.basename(r)}}}catch(e){throw new Error(`Failed to load file: ${e instanceof Error?e.message:String(e)}`)}}export async function sendMessageWithMediaTags(e,r,t,o,a){if(!t?.trim())return{ok:!1,error:"No recipient provided"};if(!o?.trim())return{ok:!1,error:"No message text provided"};const n=p(o);if(!n){const n=/(^|\n)#|(^|\n)##|(^|\n)###|(^|\n)```|\[.+\]\(.+\)/.test(o);return await sendTextMessage(e,r,t,o,{...a,msgType:n?"formatText":"text",formatType:1})}try{for(const o of n)if("text"===o.type){const n=/(^|\n)#|(^|\n)##|(^|\n)###|(^|\n)```|\[.+\]\(.+\)/.test(o.content),s=await sendTextMessage(e,r,t,o.content,{...a,msgType:n?"formatText":"text",formatType:1});if(!s.ok)return s}else if("image"===o.type||"file"===o.type){const{buffer:n,contentType:s,fileName:i}=await f(o.path,a?.userId||"unknown"),p=await uploadMediaFile(r,n,i,s);if(!p.ok)return{ok:!1,error:p.error};const d=await sendMediaMessage(e,r,t,"image"===o.type?"image":"file",p.mediaId,a);if(!d.ok)return d}return{ok:!0,messageId:""}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendFromOpenClawPush(e,r,t,a="unknown"){if(!r?.trim())return{ok:!1,error:"No recipient provided"};if(!t?.trim())return{ok:!1,error:"No message text provided"};try{const n=o(e);return await sendMessageWithMediaTags(n,e,r,t,{hookToken:e.hookToken,isGroup:!1,userId:a})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}
|
|
1
|
+
import{getSmartBotApiUrl as e,getWebhookBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as t,getBotType as o}from"../config/index.js";import{tokenManager as a}from"../utils/token.js";import{logger as s,handleError as n}from"../utils/error-handling.js";import{lansengerApiService as i}from"../services/index.js";import{parseLanxinMediaTags as p}from"../utils/media-tags.js";import{downloadToTempFile as d}from"../utils/media.js";import m from"fs/promises";import u from"path";const c={".jpg":"image/jpeg",".jpeg":"image/jpeg",".png":"image/png",".gif":"image/gif",".webp":"image/webp",".svg":"image/svg+xml",".pdf":"application/pdf",".doc":"application/msword",".docx":"application/vnd.openxmlformats-officedocument.wordprocessingml.document",".xls":"application/vnd.ms-excel",".xlsx":"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",".ppt":"application/vnd.ms-powerpoint",".pptx":"application/vnd.openxmlformats-officedocument.presentationml.presentation",".txt":"text/plain",".html":"text/html",".css":"text/css",".js":"text/javascript",".json":"application/json",".mp3":"audio/mpeg",".mp4":"video/mp4",".zip":"application/zip",".rar":"application/x-rar-compressed",".7z":"application/x-7z-compressed"};function k(e){const r=u.extname(e).toLowerCase();return c[r]||"application/octet-stream"}export async function uploadMediaFile(e,r,t,o){try{s.debug("Uploading media file",{fileName:t,fileType:o});const a=await i.media.uploadMedia(e,r,o);if(!a){const e="No mediaId returned";return s.error(e),{ok:!1,error:e}}return s.info("Media file uploaded successfully",{mediaId:a,fileName:t}),{ok:!0,mediaId:a}}catch(e){return{ok:!1,error:n(e,{operation:"uploadMediaFile"})}}}export async function sendSmartBotPrivateImageMessage(e,r,t){if(!r?.trim())return{ok:!1,error:"No userId provided"};if(!t?.trim())return{ok:!1,error:"No mediaId provided"};try{return await i.message.sendPrivateMessage(e,r,"image",{image:{mediaId:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupImageMessage(e,r,t,o){if(!r?.trim())return{ok:!1,error:"No groupId provided"};if(!t?.trim())return{ok:!1,error:"No mediaId provided"};try{return await i.message.sendGroupMessage(e,r,"image",{image:{mediaId:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendMediaMessage(r,o,s,n,i,p){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if(!i?.trim())return{ok:!1,error:"No mediaId provided"};let d;switch(n.toLowerCase()){case"video":d=1;break;case"image":d=2;break;default:d=3}if("smart"===r){if(p?.isGroup)return await sendSmartBotGroupMessage(o,s,"text",{text:{content:"",mediaType:d,mediaIds:[i]}});{const r=await a.getAppToken(o),n=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,m=await fetch(`${n}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"text",msgData:{text:{content:"",mediaType:d,mediaIds:[i]}}})});if(!m.ok)return{ok:!1,error:`HTTP ${m.status}: ${m.statusText}`};const u=await m.json();return 0!==u.errCode?{ok:!1,error:u.errMsg}:{ok:!0,messageId:u.data?.msgId}}}if("webhook"===r){const e=p?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"text",{text:{content:"",mediaType:d,mediaIds:[i]}}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendLinkCardMessage(r,o,s,n,i){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if(!n?.title||!n?.link)return{ok:!1,error:"Title and link are required for linkCard"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,s,"linkCard",{linkCard:n});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"linkCard",msgData:{linkCard:n}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"linkCard",{linkCard:n}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendAppCardMessage(r,o,s,n,i){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if(!n?.bodyTitle)return{ok:!1,error:"bodyTitle is required for appCard"};if(n.isDynamic&&!n.headStatusInfo)return{ok:!1,error:"headStatusInfo is required for dynamic appCard"};if(n.isDynamic&&!n.headStatusInfo?.description)return{ok:!1,error:"headStatusInfo.description is required for dynamic appCard"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,s,"appCard",{appCard:n});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"appCard",msgData:{appCard:n}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"appCard",{appCard:n}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendOaCardMessage(r,o,s,n,i){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,s,"oacard",{oacard:n});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"oacard",msgData:{oacard:n}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"oacard",{oacard:n}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendAppArticlesMessage(r,o,s,n,i){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if(!n||0===n.length)return{ok:!1,error:"appArticles array is required and must not be empty"};for(const e of n)if(!e.title||!e.imageUrl||!e.url)return{ok:!1,error:"Each article must have title, imageUrl, and url"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,s,"appArticles",{appArticles:n});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"appArticles",msgData:{appArticles:n}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"appArticles",{appArticles:n}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendSystemMessage(r,o,s,n,i){if(!s?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===r){if(i?.isGroup)return await sendSmartBotGroupMessage(o,s,"system",{system:n});{const r=await a.getAppToken(o),i=e(o.apiGatewayUrl),p=t.smartBot.privateMessage,d=await fetch(`${i}${p}?app_token=${r}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({userIdList:[s],msgType:"system",msgData:{system:n}})});if(!d.ok)return{ok:!1,error:`HTTP ${d.status}: ${d.statusText}`};const m=await d.json();return 0!==m.errCode?{ok:!1,error:m.errMsg}:{ok:!0,messageId:m.data?.msgId}}}if("webhook"===r){const e=i?.hookToken||o.hookToken;return e?await sendWebhookBotMessage(o,e,"system",{system:n}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendSmartBotPrivateMessage(e,r,t,o){if(!r?.trim())return{ok:!1,error:"No userId provided"};try{const a=r.trim();if(!a)return{ok:!1,error:"Invalid userId"};const s="text"!==o?.msgType,n=o?.formatType||1;return await i.message.sendPrivateMessage(e,a,s?"formatText":"text",s?{formatText:{formatType:n,text:t}}:{text:{content:t}})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupMessage(e,r,t,o,a){if(!r?.trim())return{ok:!1,error:"No groupId provided"};try{return await i.message.sendGroupMessage(e,r,t,o)}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendSmartBotGroupTextMessage(e,r,t,o){const a="text"!==o?.msgType;return await sendSmartBotGroupMessage(e,r,a?"formatText":"text",a?{formatText:{formatType:o?.formatType||1,text:t}}:{text:{content:t}},o)}export async function sendWebhookBotMessage(e,o,s,n){if(!o?.trim())return{ok:!1,error:"No hookToken provided"};try{const i=await a.getAppToken(e),p=r(e.apiGatewayUrl),d=t.webhookBot.message,m=new URLSearchParams;i&&m.append("app_token",i),m.append("hook_token",o);const u=await fetch(`${p}${d}?${m.toString()}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({msgType:s,msgData:n})});if(!u.ok)return{ok:!1,error:`HTTP ${u.status}: ${u.statusText}`};const c=await u.json();return 0!==c.errCode?{ok:!1,error:c.errMsg}:{ok:!0,messageId:c.data?.msgId}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function getSmartBotGroups(e,r){try{const r=(await i.group.getGroups(e)).map(e=>e.groupId);return{ok:!0,totalGroupIds:r.length,groupIds:r}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendTextMessage(e,r,t,o,a){if(!t?.trim())return{ok:!1,error:"No recipient provided"};if("smart"===e)return a?.isGroup?await sendSmartBotGroupTextMessage(r,t,o,{...a}):await sendSmartBotPrivateMessage(r,t,o,{msgType:a?.msgType,formatType:a?.formatType});if("webhook"===e){const e=a?.hookToken||r.hookToken;if(!e)return{ok:!1,error:"No hookToken provided for webhook bot"};const t="text"!==a?.msgType;return await sendWebhookBotMessage(r,e,t?"formatText":"text",t?{formatText:{formatType:a?.formatType||1,text:o}}:{text:{content:o}})}return{ok:!1,error:`Unknown bot type: ${e}`}}async function f(e,r="unknown"){try{if(e.startsWith("http://")||e.startsWith("https://")){const{path:t}=await d(e,r),o=await m.readFile(t),a=k(t);return{buffer:o,contentType:a,fileName:t.split("/").pop()||"downloaded-file"}}{const r=u.resolve(e),t=await m.readFile(r),o=k(r);return{buffer:t,contentType:o,fileName:u.basename(r)}}}catch(e){throw new Error(`Failed to load file: ${e instanceof Error?e.message:String(e)}`)}}export async function sendMessageWithMediaTags(e,r,t,o,a){if(!t?.trim())return{ok:!1,error:"No recipient provided"};if(!o?.trim())return{ok:!1,error:"No message text provided"};const s=p(o);if(!s)return await sendTextMessage(e,r,t,o,{...a,msgType:a?.msgType||"formatText",formatType:a?.formatType||1});try{for(const o of s)if("text"===o.type){const s=await sendTextMessage(e,r,t,o.content,{...a,msgType:a?.msgType||"formatText",formatType:a?.formatType||1});if(!s.ok)return s}else if("image"===o.type||"file"===o.type){const{buffer:s,contentType:n,fileName:i}=await f(o.path,a?.userId||"unknown"),p=await uploadMediaFile(r,s,i,n);if(!p.ok)return{ok:!1,error:p.error};const d=await sendMediaMessage(e,r,t,"image"===o.type?"image":"file",p.mediaId,a);if(!d.ok)return d}return{ok:!0,messageId:""}}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}export async function sendFromOpenClawPush(e,r,t,a="unknown"){if(!r?.trim())return{ok:!1,error:"No recipient provided"};if(!t?.trim())return{ok:!1,error:"No message text provided"};try{const s=o(e);return await sendMessageWithMediaTags(s,e,r,t,{hookToken:e.hookToken,isGroup:!1,userId:a})}catch(e){return{ok:!1,error:e instanceof Error?e.message:String(e)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{getSmartBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as e,getBotType as t}from"../config/index.js";import{tokenManager as o}from"../utils/token.js";import{sendSmartBotGroupMessage as s,sendWebhookBotMessage as n}from"./group.js";import{logger as a}from"../utils/error-handling.js";export async function sendSmartBotPrivateMessage(t,s,n,i="formatText",m=1){if(!s?.trim())return{ok:!1,error:"No userId provided"};try{a.
|
|
1
|
+
import{getSmartBotApiUrl as r}from"../domain/lansenger-domain.js";import{API_ENDPOINTS as e,getBotType as t}from"../config/index.js";import{tokenManager as o}from"../utils/token.js";import{sendSmartBotGroupMessage as s,sendWebhookBotMessage as n}from"./group.js";import{logger as a}from"../utils/error-handling.js";export async function sendSmartBotPrivateMessage(t,s,n,i="formatText",m=1){if(!s?.trim())return{ok:!1,error:"No userId provided"};try{a.info("Sending smart bot private message",{userId:s,msgType:i,formatType:m,text:n.slice(0,80)});const d=await o.getAppToken(t),p=r(t.apiGatewayUrl),u=e.smartBot.privateMessage,g=s.trim();if(!g)return a.warn("Invalid userId",{userId:s}),{ok:!1,error:"Invalid userId"};let f;f="formatText"===i?{formatText:{formatType:m,text:n}}:{text:{content:n}};let c={userIdList:[g],msgType:i,msgData:f};a.info("Sending message to Lanxin API",{apiUrl:p,endpoint:u,userId:g,msgType:i,body:c});const T=await fetch(`${p}${u}?app_token=${d}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(c)});if(!T.ok){const r=`HTTP ${T.status}: ${T.statusText}`;return a.error(r,{url:`${p}${u}`,status:T.status}),{ok:!1,error:r}}const k=await T.json();return 0!==k.errCode?(a.error("Lanxin API error",{errCode:k.errCode,errMsg:k.errMsg,apiUrl:p,endpoint:u}),{ok:!1,error:k.errMsg}):(a.info("Message sent successfully",{messageId:k.data?.msgId,userId:g,msgType:i}),{ok:!0,messageId:k.data?.msgId})}catch(r){return a.error("Error sending message",{error:r,userId:s,msgType:i}),{ok:!1,error:r instanceof Error?r.message:String(r)}}}export async function sendSmartBotFormatTextMessage(r,e,t,o=1){return await sendSmartBotPrivateMessage(r,e,t,"formatText",o)}export async function sendTextMessage(r,e,t,o,a){if(!t?.trim())return{ok:!1,error:"No recipient provided"};const i=a?.msgType||"text",m=a?.formatType||1;if("smart"===r)return a?.isGroup?await s(e,t,"formatText",{formatText:{formatType:1,text:o}}):await sendSmartBotPrivateMessage(e,t,o,i,m);if("webhook"===r){const r=a?.hookToken||e.hookToken;return r?await n(e,r,"text",{text:{content:o}}):{ok:!1,error:"No hookToken provided for webhook bot"}}return{ok:!1,error:`Unknown bot type: ${r}`}}export async function sendFromOpenClawPush(r,e,o){if(!e?.trim())return{ok:!1,error:"No recipient provided"};if(!o?.trim())return{ok:!1,error:"No message text provided"};try{const s=t(r);return await sendTextMessage(s,r,e,o,{hookToken:r.hookToken,isGroup:!1})}catch(r){return{ok:!1,error:r instanceof Error?r.message:String(r)}}}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{API_ENDPOINTS as t,getApiGatewayUrl as e}from"../config/index.js";import{tokenManager as r}from"../utils/token.js";import{logger as s,createLansengerError as a,LansengerErrorType as o}from"../utils/error-handling.js";class n{async getAppToken(t){return await r.getAppToken(t)}async fetchApi(t,r,n={}){let i=null;for(let c=1;c<=3;c++)try{const p=await this.getAppToken(t),d=`${e(t)}${r}?app_token=${p}`;s.info(`API 请求 (尝试 ${c}/3): ${d}`);const u=await fetch(d,{...n,headers:{"Content-Type":"application/json",...n.headers}});if(!u.ok){const t=`HTTP ${u.status}: ${u.statusText}`;if(s.warn(`API 请求失败 (尝试 ${c}/3): ${t}`),i=a(t,o.NETWORK,`HTTP_${u.status}`,{url:d,status:u.status,statusText:u.statusText}),u.status>=500||429===u.status){const t=1e3*Math.pow(2,c-1);s.info(`等待 ${t}ms 后重试...`),await new Promise(e=>setTimeout(e,t));continue}throw i}const f=await u.json();if(0!==f.errCode){const t=f.errMsg;throw s.warn(`API 错误 (尝试 ${c}/3): ${t}`),i=a(t,o.API,`ERR_${f.errCode}`,{errCode:f.errCode,errMsg:f.errMsg}),i}return s.info(`API 请求成功 (尝试 ${c}/3)`),f}catch(t){if(s.warn(`API 请求异常 (尝试 ${c}/3): ${t instanceof Error?t.message:String(t)}`),i=t instanceof Error?t:new Error(String(t)),c<3){const t=1e3*Math.pow(2,c-1);s.info(`等待 ${t}ms 后重试...`),await new Promise(e=>setTimeout(e,t))}}if(i)throw i;throw new Error("API 请求失败,已达到最大重试次数")}}export class StaffApiService extends n{async getStaffInfo(t,e){try{const r=`/v1/staffs/${encodeURIComponent(e)}/fetch`,n=await this.fetchApi(t,r);if(!n.data)throw a("未返回员工数据",o.API,"MISSING_DATA");return s.info(`成功获取员工信息: ${e}: ${n.data.name}`),n.data}catch(t){return s.error("获取员工信息失败",{error:t,staffId:e}),null}}}export class MessageApiService extends n{async sendPrivateMessage(e,r,a,o){try{const n=t.smartBot.privateMessage,i={userIdList:[r],msgType:a,msgData:o};s.
|
|
1
|
+
import{API_ENDPOINTS as t,getApiGatewayUrl as e}from"../config/index.js";import{tokenManager as r}from"../utils/token.js";import{logger as s,createLansengerError as a,LansengerErrorType as o}from"../utils/error-handling.js";class n{async getAppToken(t){return await r.getAppToken(t)}async fetchApi(t,r,n={}){let i=null;for(let c=1;c<=3;c++)try{const p=await this.getAppToken(t),d=`${e(t)}${r}?app_token=${p}`;s.info(`API 请求 (尝试 ${c}/3): ${d}`);const u=await fetch(d,{...n,headers:{"Content-Type":"application/json",...n.headers}});if(!u.ok){const t=`HTTP ${u.status}: ${u.statusText}`;if(s.warn(`API 请求失败 (尝试 ${c}/3): ${t}`),i=a(t,o.NETWORK,`HTTP_${u.status}`,{url:d,status:u.status,statusText:u.statusText}),u.status>=500||429===u.status){const t=1e3*Math.pow(2,c-1);s.info(`等待 ${t}ms 后重试...`),await new Promise(e=>setTimeout(e,t));continue}throw i}const f=await u.json();if(0!==f.errCode){const t=f.errMsg;throw s.warn(`API 错误 (尝试 ${c}/3): ${t}`),i=a(t,o.API,`ERR_${f.errCode}`,{errCode:f.errCode,errMsg:f.errMsg}),i}return s.info(`API 请求成功 (尝试 ${c}/3)`),f}catch(t){if(s.warn(`API 请求异常 (尝试 ${c}/3): ${t instanceof Error?t.message:String(t)}`),i=t instanceof Error?t:new Error(String(t)),c<3){const t=1e3*Math.pow(2,c-1);s.info(`等待 ${t}ms 后重试...`),await new Promise(e=>setTimeout(e,t))}}if(i)throw i;throw new Error("API 请求失败,已达到最大重试次数")}}export class StaffApiService extends n{async getStaffInfo(t,e){try{const r=`/v1/staffs/${encodeURIComponent(e)}/fetch`,n=await this.fetchApi(t,r);if(!n.data)throw a("未返回员工数据",o.API,"MISSING_DATA");return s.info(`成功获取员工信息: ${e}: ${n.data.name}`),n.data}catch(t){return s.error("获取员工信息失败",{error:t,staffId:e}),null}}}export class MessageApiService extends n{async sendPrivateMessage(e,r,a,o){try{const n=t.smartBot.privateMessage,i={userIdList:[r],msgType:a,msgData:o};s.info("发送私聊消息请求:",{userId:r,msgType:a,requestBody:JSON.stringify(i)});const c=await this.fetchApi(e,n,{method:"POST",body:JSON.stringify(i)});return s.info(`私聊消息发送成功: ${c.data?.msgId}`),{ok:!0,messageId:c.data?.msgId}}catch(t){return s.error("发送私聊消息失败",{error:t,userId:r}),{ok:!1,error:t instanceof Error?t.message:String(t)}}}async sendGroupMessage(e,r,a,o){try{const n=t.smartBot.groupMessage,i={groupId:r,msgType:a,msgData:o};s.debug("发送群消息请求:",{groupId:r,msgType:a,requestBody:JSON.stringify(i)});const c=await this.fetchApi(e,n,{method:"POST",body:JSON.stringify(i)});return s.info(`群消息发送成功: ${c.data?.msgId}`),{ok:!0,messageId:c.data?.msgId}}catch(t){return s.error("发送群消息失败",{error:t,groupId:r}),{ok:!1,error:t instanceof Error?t.message:String(t)}}}}export class GroupApiService extends n{async getGroups(e){try{const r=t.smartBot.groupsList,a=await this.fetchApi(e,r);return s.info(`成功获取 ${a.data?.groups?.length||0} 个群组`),a.data?.groups||[]}catch(t){return s.error("获取群列表失败",{error:t}),[]}}}export class MediaApiService extends n{async uploadMedia(t,r,n){try{const i=await this.getAppToken(t),c=e(t);let p="2";"video"===n?p="1":"audio"===n&&(p="3");const d=`${c}${"/v1/medias/create"}?type=${p}&app_token=${i}`;s.info("上传媒体文件"),s.info(`请求 URL: ${d}`);const u=new FormData;u.append("media",r);const f=await fetch(d,{method:"POST",body:u});if(!f.ok)throw a(`HTTP ${f.status}: ${f.statusText}`,o.NETWORK,`HTTP_${f.status}`,{url:d,status:f.status,statusText:f.statusText});const g=await f.json();if(0!==g.errCode)throw a(g.errMsg,o.API,`ERR_${g.errCode}`,{errCode:g.errCode,errMsg:g.errMsg});return s.info(`媒体文件上传成功: ${g.data?.mediaId}`),g.data?.mediaId||null}catch(t){return s.error("上传媒体文件失败",{error:t}),null}}async getMediaUrl(t,r){try{const n=await this.getAppToken(t),i=e(t),c=`${i}${`/v1/medias/${encodeURIComponent(r)}/path/fetch`}?app_token=${n}`;s.info(`获取媒体文件 URL: ${r}`),s.info(`请求 URL: ${c}`);const p=await fetch(c,{method:"GET"});if(!p.ok)throw a(`HTTP ${p.status}: ${p.statusText}`,o.NETWORK,`HTTP_${p.status}`,{url:c,status:p.status,statusText:p.statusText});const d=await p.json();if(0!==d.errCode)throw a(d.errMsg,o.API,`ERR_${d.errCode}`,{errCode:d.errCode,errMsg:d.errMsg});return s.info(`媒体文件 URL 获取成功: ${d.data?.mediaPath}`),d.data?.mediaPath||null}catch(t){return s.error("获取媒体文件 URL 失败",{error:t,mediaId:r}),null}}}export class DepartmentApiService extends n{async getDepartmentInfo(t,e){try{const r=`/v1/depts/${encodeURIComponent(e)}/fetch`,a=await this.fetchApi(t,r);return s.info(`成功获取部门信息: ${e}`),a.data||null}catch(t){return s.error("获取部门信息失败",{error:t,deptId:e}),null}}async getDepartmentList(t){try{const e="/v1/depts/list",r=await this.fetchApi(t,e);return s.info(`成功获取 ${r.data?.depts?.length||0} 个部门`),r.data?.depts||[]}catch(t){return s.error("获取部门列表失败",{error:t}),[]}}}export class WebSocketApiService extends n{async createWebSocketEndpoint(t){try{const r=e(t),n=`${r}${"/v1/ws/endpoint/create"}`;s.info(`创建 WebSocket 端点: ${n}`);const i=await fetch(n,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({appId:t.appId,secret:t.appSecret})});if(!i.ok)throw a(`HTTP ${i.status}: ${i.statusText}`,o.NETWORK,`HTTP_${i.status}`,{url:n,status:i.status,statusText:i.statusText});const c=await i.json();if(0!==c.errCode)throw a(c.errMsg,o.API,`ERR_${c.errCode}`,{errCode:c.errCode,errMsg:c.errMsg});if(!c.data?.wsEndpoint)throw a("未返回 WebSocket 端点",o.API,"MISSING_DATA");return s.info(`WebSocket 端点创建成功: ${c.data.wsEndpoint}`),s.info(`心跳间隔: ${c.data.pingInterval} 秒`),c.data.wsEndpoint}catch(t){return s.error("创建 WebSocket 端点失败",{error:t}),null}}}export class LansengerApiService{staff;message;group;media;department;websocket;constructor(){this.staff=new StaffApiService,this.message=new MessageApiService,this.group=new GroupApiService,this.media=new MediaApiService,this.department=new DepartmentApiService,this.websocket=new WebSocketApiService}async getAppToken(t){return await r.getAppToken(t)}async healthCheck(t){try{const r=await this.getAppToken(t),s=`${e(t)}/v1/app/status?app_token=${r}`;return(await fetch(s,{method:"GET",headers:{"Content-Type":"application/json"}})).ok}catch(t){return s.error("健康检查失败",{error:t}),!1}}}export const lansengerApiService=new LansengerApiService;
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
"id": "openclaw-channel-lansenger",
|
|
3
3
|
"name": "Lansenger",
|
|
4
4
|
"description": "Lansenger (蓝信) channel plugin — WebSocket long-connection bot",
|
|
5
|
-
"version": "
|
|
5
|
+
"version": "1.1.4",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"kind": "channel",
|
|
8
8
|
"channels": [
|
|
9
9
|
"Lansenger"
|
|
10
10
|
],
|
|
11
|
+
"hooks": {},
|
|
11
12
|
"permissions": [
|
|
12
13
|
"config.write"
|
|
13
14
|
],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lansenger/openclaw-channel-lansenger",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "蓝信机器人插件 - 让 AI 助手接入蓝信,无需服务器 | Lanxin/Lark channel plugin for OpenClaw",
|
|
6
6
|
"author": "lxfe",
|
|
@@ -42,15 +42,34 @@
|
|
|
42
42
|
"LICENSE"
|
|
43
43
|
],
|
|
44
44
|
"openclaw": {
|
|
45
|
+
"hooks": {
|
|
46
|
+
"install": "",
|
|
47
|
+
"remove": "",
|
|
48
|
+
"update": ""
|
|
49
|
+
},
|
|
45
50
|
"extensions": [
|
|
46
51
|
"./dist/index.js"
|
|
47
52
|
],
|
|
53
|
+
"channels": [
|
|
54
|
+
"Lansenger"
|
|
55
|
+
],
|
|
48
56
|
"channel": {
|
|
49
57
|
"id": "Lansenger",
|
|
50
58
|
"label": "Lansenger",
|
|
51
|
-
"
|
|
59
|
+
"selectionLabel": "Lansenger (蓝信)",
|
|
60
|
+
"blurb": "蓝信机器人插件,使用 WebSocket 长连接,无需公网 IP。",
|
|
61
|
+
"order": 80,
|
|
62
|
+
"aliases": [
|
|
63
|
+
"lansenger",
|
|
64
|
+
"lx"
|
|
65
|
+
]
|
|
52
66
|
},
|
|
53
|
-
"
|
|
67
|
+
"install": {
|
|
68
|
+
"minHostVersion": ">=2026.3.13",
|
|
69
|
+
"npmSpec": "@lansenger/openclaw-channel-lansenger",
|
|
70
|
+
"localPath": ".",
|
|
71
|
+
"defaultChoice": "npm"
|
|
72
|
+
}
|
|
54
73
|
},
|
|
55
74
|
"scripts": {
|
|
56
75
|
"buildTest": "node scripts/build.js all",
|
|
@@ -68,7 +87,8 @@
|
|
|
68
87
|
"src": "node scripts/switch-entry.js src",
|
|
69
88
|
"restartOpenClaw": "openclaw gateway restart",
|
|
70
89
|
"publish:qax": "node scripts/publish.js",
|
|
71
|
-
"publish:npm": "node scripts/publish-npm.js"
|
|
90
|
+
"publish:npm": "node scripts/publish-npm.js",
|
|
91
|
+
"publish:clawhub": "node scripts/publish-clawhub.js"
|
|
72
92
|
},
|
|
73
93
|
"peerDependencies": {
|
|
74
94
|
"openclaw": ">=2026.3.13"
|