alemonjs 2.1.0-alpha.26 → 2.1.0-alpha.28

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.
@@ -1,17 +1,295 @@
1
+ import * as flattedJSON from 'flatted';
2
+ import { onProcessor } from '../app/event-processor.js';
3
+ import { join, dirname } from 'path';
4
+ import { existsSync, mkdirSync, watch, readFileSync, writeFile } from 'fs';
5
+ import _ from 'lodash';
6
+ import { readFile } from 'fs/promises';
7
+ import { actionResolves, actionTimeouts, apiResolves, apiTimeouts } from './config.js';
8
+ import '../app/define-chidren.js';
9
+ import '../app/event-group.js';
10
+ import '../app/event-middleware.js';
11
+ import '../app/event-response.js';
12
+ import '../app/hook-use-api.js';
13
+ import '../typing/event/actions.js';
14
+ import 'yaml';
15
+ import { ResultCode } from '../core/code.js';
16
+ import 'node:fs';
17
+ import 'log4js';
18
+ import '../app/load.js';
19
+ import '../app/message-api.js';
20
+ import '../app/message-format.js';
21
+ import { createResult } from '../core/utils.js';
22
+ import 'ws';
23
+ import 'koa';
24
+ import './router.js';
25
+ import '@koa/cors';
26
+
1
27
  /**
2
28
  * @param ws
3
29
  * @param request
4
30
  */
5
- const connectionTestOne = (ws, request) => {
31
+ const connectionTestOne = (ws, _request) => {
32
+ if (global.testoneClient) {
33
+ delete global.testoneClient;
34
+ }
35
+ global.testoneClient = ws;
36
+ // 确保目录存在
37
+ const testonePath = join(process.cwd(), 'testone');
38
+ if (!existsSync(testonePath)) {
39
+ mkdirSync(testonePath, { recursive: true });
40
+ }
41
+ const fileWatchers = new Map();
42
+ // 通用的文件监听函数
43
+ const watchFile = (filePath, _type, handler) => {
44
+ try {
45
+ // 如果文件存在,直接监听
46
+ if (existsSync(filePath)) {
47
+ const watcher = watch(filePath, { persistent: true }, (eventType, filename) => {
48
+ if (eventType === 'change' && filename) {
49
+ handler();
50
+ }
51
+ });
52
+ fileWatchers.set(filePath, watcher);
53
+ }
54
+ }
55
+ catch (error) {
56
+ logger.error(`监听文件失败: ${filePath}`, error);
57
+ }
58
+ };
59
+ // 监听整个目录,捕获新文件创建
60
+ const dirWatcher = watch(testonePath, { persistent: true }, (eventType, filename) => {
61
+ if (!filename)
62
+ return;
63
+ const filePath = join(testonePath, filename);
64
+ // 如果是新创建的文件,开始监听它
65
+ if (eventType === 'rename' && existsSync(filePath) && !fileWatchers.has(filePath)) {
66
+ if (filename === 'commands.json') {
67
+ watchFile(filePath, 'commands', onCommands);
68
+ onCommands(); // 立即触发一次
69
+ }
70
+ else if (filename === 'users.json') {
71
+ watchFile(filePath, 'users', onUsers);
72
+ onUsers();
73
+ }
74
+ else if (filename === 'channels.json') {
75
+ watchFile(filePath, 'channels', onChannels);
76
+ onChannels();
77
+ }
78
+ }
79
+ });
80
+ const commandsPath = join(testonePath, 'commands.json');
81
+ const onCommands = _.debounce(() => {
82
+ if (!existsSync(commandsPath))
83
+ return;
84
+ readFile(commandsPath, 'utf-8')
85
+ .then(data => {
86
+ const commands = JSON.parse(data);
87
+ global.testoneClient?.send(flattedJSON.stringify({
88
+ type: 'commands',
89
+ payload: commands
90
+ }));
91
+ })
92
+ .catch(error => {
93
+ logger.error('读取 commands.json 失败:', error);
94
+ });
95
+ }, 1000);
96
+ const usersPath = join(testonePath, 'users.json');
97
+ const onUsers = _.debounce(() => {
98
+ if (!existsSync(usersPath))
99
+ return;
100
+ readFile(usersPath, 'utf-8')
101
+ .then(data => {
102
+ const users = JSON.parse(data);
103
+ global.testoneClient?.send(flattedJSON.stringify({
104
+ type: 'users',
105
+ payload: users
106
+ }));
107
+ })
108
+ .catch(error => {
109
+ logger.error('读取 users.json 失败:', error);
110
+ });
111
+ }, 1000);
112
+ const channelsPath = join(testonePath, 'channels.json');
113
+ const onChannels = _.debounce(() => {
114
+ if (!existsSync(channelsPath))
115
+ return;
116
+ readFile(channelsPath, 'utf-8')
117
+ .then(data => {
118
+ const channels = JSON.parse(data);
119
+ global.testoneClient?.send(flattedJSON.stringify({
120
+ type: 'channels',
121
+ payload: channels
122
+ }));
123
+ })
124
+ .catch(error => {
125
+ logger.error('读取 channels.json 失败:', error);
126
+ });
127
+ }, 1000);
128
+ // 初始化时监听已存在的文件
129
+ watchFile(commandsPath, 'commands', onCommands);
130
+ watchFile(usersPath, 'users', onUsers);
131
+ watchFile(channelsPath, 'channels', onChannels);
132
+ const userPath = join(testonePath, 'user.json');
133
+ const botPath = join(testonePath, 'bot.json');
134
+ const privateMessagePath = join(testonePath, '.cache', 'private.message.json');
135
+ const publicMessagePath = join(testonePath, '.cache', 'public.message.json');
136
+ const cacheDir = dirname(privateMessagePath);
137
+ mkdirSync(cacheDir, { recursive: true });
138
+ const initData = () => {
139
+ try {
140
+ const commandsData = existsSync(commandsPath)
141
+ ? JSON.parse(readFileSync(commandsPath, 'utf-8'))
142
+ : [];
143
+ const usersData = existsSync(usersPath) ? JSON.parse(readFileSync(usersPath, 'utf-8')) : [];
144
+ const channelsData = existsSync(channelsPath)
145
+ ? JSON.parse(readFileSync(channelsPath, 'utf-8'))
146
+ : [];
147
+ const userData = existsSync(userPath) ? JSON.parse(readFileSync(userPath, 'utf-8')) : null;
148
+ const botData = existsSync(botPath) ? JSON.parse(readFileSync(botPath, 'utf-8')) : null;
149
+ const privateMessage = existsSync(privateMessagePath)
150
+ ? JSON.parse(readFileSync(privateMessagePath, 'utf-8'))
151
+ : [];
152
+ const publicMessage = existsSync(publicMessagePath)
153
+ ? JSON.parse(readFileSync(publicMessagePath, 'utf-8'))
154
+ : [];
155
+ global.testoneClient?.send(flattedJSON.stringify({
156
+ type: 'init.data',
157
+ payload: {
158
+ commands: commandsData,
159
+ users: usersData,
160
+ channels: channelsData,
161
+ user: userData,
162
+ bot: botData,
163
+ privateMessage: privateMessage,
164
+ publicMessage: publicMessage
165
+ }
166
+ }));
167
+ }
168
+ catch (error) {
169
+ logger.error('初始化数据失败:', error);
170
+ }
171
+ };
172
+ const onDeleteMessage = (type, CreateAt) => {
173
+ const messagePath = type === 'private' ? privateMessagePath : publicMessagePath;
174
+ if (existsSync(messagePath)) {
175
+ try {
176
+ const messages = JSON.parse(readFileSync(messagePath, 'utf-8'));
177
+ const updatedMessages = messages.filter((msg) => msg.CreateAt !== CreateAt);
178
+ console.log('updatedMessages', updatedMessages);
179
+ writeFile(messagePath, JSON.stringify(updatedMessages, null, 2), (error) => {
180
+ if (error) {
181
+ logger.error(`写入 ${type} 消息失败:`, error);
182
+ }
183
+ });
184
+ }
185
+ catch (error) {
186
+ logger.error(`读取 ${type} 消息失败:`, error);
187
+ }
188
+ }
189
+ };
190
+ const onSaveMessage = (type, message) => {
191
+ const messagePath = type === 'private' ? privateMessagePath : publicMessagePath;
192
+ const messages = existsSync(messagePath)
193
+ ? JSON.parse(readFileSync(messagePath, 'utf-8'))
194
+ : [];
195
+ messages.push(message);
196
+ writeFile(messagePath, JSON.stringify(messages, null, 2), (error) => {
197
+ if (error) {
198
+ logger.error(`写入 ${type} 消息失败:`, error);
199
+ }
200
+ });
201
+ };
6
202
  // 处理消息事件
7
- ws.on('message', (message) => {
8
- // 支持 读取 文件信息
9
- logger.info(message.toString());
203
+ global.testoneClient.on('message', (message) => {
204
+ try {
205
+ // 解析消息
206
+ const parsedMessage = flattedJSON.parse(message.toString());
207
+ // 如果是一个对象,且有 name 属性,说明是一个事件请求
208
+ if (parsedMessage.name) {
209
+ // 如果有 name,说明是一个事件请求。要进行处理
210
+ onProcessor(parsedMessage.name, parsedMessage, parsedMessage.value);
211
+ // 消息写入
212
+ }
213
+ else if (parsedMessage?.actionId) {
214
+ // 如果有 actionId
215
+ const resolve = actionResolves.get(parsedMessage.actionId);
216
+ if (resolve) {
217
+ actionResolves.delete(parsedMessage.actionId);
218
+ // 清除超时器
219
+ const timeout = actionTimeouts.get(parsedMessage.actionId);
220
+ if (timeout) {
221
+ actionTimeouts.delete(parsedMessage.actionId);
222
+ clearTimeout(timeout);
223
+ }
224
+ // 调用回调函数
225
+ if (Array.isArray(parsedMessage.payload)) {
226
+ resolve(parsedMessage.payload);
227
+ }
228
+ else {
229
+ // 错误处理
230
+ resolve([createResult(ResultCode.Fail, '消费处理错误', null)]);
231
+ }
232
+ }
233
+ }
234
+ else if (parsedMessage?.apiId) {
235
+ // 如果有 apiId,说明是一个接口请求。要进行处理
236
+ const resolve = apiResolves.get(parsedMessage.apiId);
237
+ if (resolve) {
238
+ apiResolves.delete(parsedMessage.apiId);
239
+ // 清除超时器
240
+ const timeout = apiTimeouts.get(parsedMessage.apiId);
241
+ if (timeout) {
242
+ apiTimeouts.delete(parsedMessage.apiId);
243
+ clearTimeout(timeout);
244
+ }
245
+ // 调用回调函数
246
+ if (Array.isArray(parsedMessage.payload)) {
247
+ resolve(parsedMessage.payload);
248
+ }
249
+ else {
250
+ // 错误处理
251
+ resolve([createResult(ResultCode.Fail, '接口处理错误', null)]);
252
+ }
253
+ }
254
+ }
255
+ else if (parsedMessage.type === 'init.data') {
256
+ initData();
257
+ }
258
+ else if (parsedMessage.type === 'commands') {
259
+ onChannels();
260
+ }
261
+ else if (parsedMessage.type === 'users') {
262
+ onUsers();
263
+ }
264
+ else if (parsedMessage.type === 'channels') {
265
+ onChannels();
266
+ }
267
+ else if (parsedMessage.type === 'private.message.delete') {
268
+ const CreateAt = parsedMessage.payload.CreateAt;
269
+ onDeleteMessage('private', CreateAt);
270
+ }
271
+ else if (parsedMessage.type === 'public.message.delete') {
272
+ const CreateAt = parsedMessage.payload.CreateAt;
273
+ onDeleteMessage('public', CreateAt);
274
+ }
275
+ else if (parsedMessage.type === 'private.message.save') {
276
+ onSaveMessage('private', parsedMessage.payload);
277
+ }
278
+ else if (parsedMessage.type === 'public.message.save') {
279
+ onSaveMessage('public', parsedMessage.payload);
280
+ }
281
+ }
282
+ catch (error) {
283
+ logger.error('客户端解析消息失败:', error);
284
+ }
10
285
  });
11
286
  // 处理关闭事件
12
- ws.on('close', () => {
287
+ global.testoneClient.on('close', () => {
13
288
  logger.info('WebSocket connection closed');
14
- // 可以在这里处理连接关闭的逻辑
289
+ // 清理所有文件监听器
290
+ fileWatchers.forEach(watcher => watcher.close());
291
+ fileWatchers.clear();
292
+ dirWatcher.close();
15
293
  });
16
294
  };
17
295
 
@@ -0,0 +1,13 @@
1
+ import { Result } from '../core/utils.js';
2
+ import '../global.js';
3
+ import { Actions } from '../typing/actions.js';
4
+ import { Apis } from '../typing/apis.js';
5
+
6
+ type CBPClientOptions = {
7
+ open?: () => void;
8
+ isFullReceive?: boolean;
9
+ };
10
+ type ActionReplyFunc = (data: Actions, consume: (payload: Result[]) => void) => void;
11
+ type ApiReplyFunc = (data: Apis, consume: (payload: Result[]) => void) => void;
12
+
13
+ export type { ActionReplyFunc, ApiReplyFunc, CBPClientOptions };
package/lib/index.d.ts CHANGED
@@ -33,7 +33,9 @@ export { createDataFormat, createEventValue, format, sendToChannel, sendToUser }
33
33
  export { Ark, BT, Image, ImageFile, ImageURL, Link, MD, Mention, Text } from './app/message-format.js';
34
34
  export { ChildrenApp, Core, Logger, Middleware, ProcessorEventAutoClearMap, ProcessorEventUserAudoClearMap, Response, State, StateSubscribe, SubscribeList } from './app/store.js';
35
35
  export { Result, createEventName, createHash, createResult, getInputExportPath, getRecursiveDirFiles, showErrorModule, stringToNumber, useUserHashKey } from './core/utils.js';
36
- export { cbpClient, cbpPlatform } from './cbp/connect.js';
36
+ export { cbpClient } from './cbp/client.js';
37
+ export { cbpServer } from './cbp/server.js';
38
+ export { cbpPlatform } from './cbp/platform.js';
37
39
  export { run, start } from './main.js';
38
40
  export { ResultCode } from './core/code.js';
39
41
  export { DataText } from './typing/message/text.js';
package/lib/index.js CHANGED
@@ -14,6 +14,8 @@ export { createDataFormat, createEventValue, format, sendToChannel, sendToUser }
14
14
  export { Ark, BT, Image, ImageFile, ImageURL, Link, MD, Mention, Text } from './app/message-format.js';
15
15
  export { ChildrenApp, Core, Logger, Middleware, ProcessorEventAutoClearMap, ProcessorEventUserAudoClearMap, Response, State, StateSubscribe, SubscribeList } from './app/store.js';
16
16
  export { createEventName, createHash, createResult, getInputExportPath, getRecursiveDirFiles, showErrorModule, stringToNumber, useUserHashKey } from './core/utils.js';
17
- export { cbpClient, cbpPlatform } from './cbp/connect.js';
17
+ export { cbpClient } from './cbp/client.js';
18
+ export { cbpServer } from './cbp/server.js';
19
+ export { cbpPlatform } from './cbp/platform.js';
18
20
  export { run, start } from './main.js';
19
21
  export { ResultCode } from './core/code.js';
package/lib/main.js CHANGED
@@ -13,10 +13,13 @@ import 'node:fs';
13
13
  import 'log4js';
14
14
  import './app/message-api.js';
15
15
  import './app/message-format.js';
16
- import { cbpClient } from './cbp/connect.js';
16
+ import { cbpClient } from './cbp/client.js';
17
+ import { cbpServer } from './cbp/server.js';
18
+ import 'ws';
19
+ import './cbp/config.js';
20
+ import 'flatted';
17
21
  import { join } from 'path';
18
22
  import { existsSync } from 'fs';
19
- import { cbpServer } from './cbp/index.js';
20
23
  import { default_port, file_prefix_common, default_platform_common_prefix } from './core/variable.js';
21
24
 
22
25
  const loadState = () => {
@@ -86,6 +89,8 @@ const start = async (options = {}) => {
86
89
  const login = options?.login || cfg.argv?.login || cfg.value?.login;
87
90
  // 不登录平台
88
91
  if (!platform && !login) {
92
+ // 没有指定平台和登录名,则启动 sandbox 模式
93
+ global.sandbox = true;
89
94
  return;
90
95
  }
91
96
  // 如果存在
@@ -33,10 +33,61 @@ type ActionMentionGet = {
33
33
  event: any;
34
34
  };
35
35
  };
36
+ type ActionMessageDelete = {
37
+ action: 'message.delete';
38
+ payload: {
39
+ MessageId: string;
40
+ };
41
+ };
42
+ type ActionFileSendChannel = {
43
+ action: 'file.send.channel';
44
+ payload: {
45
+ ChannelId: string;
46
+ params: {
47
+ file: string;
48
+ name?: string;
49
+ folder?: string;
50
+ };
51
+ };
52
+ };
53
+ type ActionFileSendUser = {
54
+ action: 'file.send.user';
55
+ payload: {
56
+ UserId: string;
57
+ params: {
58
+ file: string;
59
+ name?: string;
60
+ };
61
+ };
62
+ };
63
+ type ActionMessageForwardUser = {
64
+ action: 'message.forward.user';
65
+ payload: {
66
+ UserId: string;
67
+ params: {
68
+ time?: number;
69
+ content: DataEnums[];
70
+ user_id?: string;
71
+ nickname?: string;
72
+ }[];
73
+ };
74
+ };
75
+ type ActionMessageForwardChannel = {
76
+ action: 'message.forward.channel';
77
+ payload: {
78
+ ChannelId: string;
79
+ params: {
80
+ time?: number;
81
+ content: DataEnums[];
82
+ user_id?: string;
83
+ nickname?: string;
84
+ }[];
85
+ };
86
+ };
36
87
  type base = {
37
88
  actionId?: string;
38
89
  DeviceId?: string;
39
90
  };
40
- type Actions = (ActionMessageSend | ActionMentionGet | ActionMessageSendChannel | ActionMessageSendUser) & base;
91
+ type Actions = (ActionMessageSend | ActionMentionGet | ActionMessageSendChannel | ActionMessageSendUser | ActionMessageDelete | ActionFileSendChannel | ActionFileSendUser | ActionMessageForwardUser | ActionMessageForwardChannel) & base;
41
92
 
42
- export type { ActionMentionGet, ActionMessageSend, ActionMessageSendChannel, ActionMessageSendUser, Actions };
93
+ export type { ActionFileSendChannel, ActionFileSendUser, ActionMentionGet, ActionMessageDelete, ActionMessageForwardChannel, ActionMessageForwardUser, ActionMessageSend, ActionMessageSendChannel, ActionMessageSendUser, Actions };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.0-alpha.26",
3
+ "version": "2.1.0-alpha.28",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",