alemonjs 2.1.0-alpha.53 → 2.1.0-alpha.54

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.
Files changed (62) hide show
  1. package/lib/adapter.js +111 -0
  2. package/lib/app/load.d.ts +14 -0
  3. package/lib/app/load.js +167 -0
  4. package/lib/cbp/actions.js +48 -0
  5. package/lib/cbp/api.js +48 -0
  6. package/lib/cbp/client.d.ts +10 -0
  7. package/lib/cbp/client.js +159 -0
  8. package/lib/cbp/config.js +43 -0
  9. package/lib/cbp/connect.d.ts +27 -0
  10. package/lib/cbp/connect.js +62 -0
  11. package/lib/cbp/connects/client.js +0 -1
  12. package/lib/cbp/hello.html.js +47 -0
  13. package/lib/cbp/platform.d.ts +18 -0
  14. package/lib/cbp/platform.js +170 -0
  15. package/lib/cbp/router.js +297 -0
  16. package/lib/cbp/routers/router.js +1 -1
  17. package/lib/cbp/server/main.js +0 -1
  18. package/lib/cbp/server.d.ts +8 -0
  19. package/lib/cbp/server.js +449 -0
  20. package/lib/cbp/testone.js +288 -0
  21. package/lib/core/code.d.ts +16 -0
  22. package/lib/core/code.js +17 -0
  23. package/lib/datastructure/SinglyLinkedList.d.ts +17 -0
  24. package/lib/datastructure/SinglyLinkedList.js +73 -0
  25. package/lib/index.d.ts +0 -1
  26. package/lib/index.js +0 -1
  27. package/lib/typing/actions.d.ts +93 -0
  28. package/lib/typing/apis.d.ts +18 -0
  29. package/lib/typing/client/index.d.ts +67 -0
  30. package/lib/typing/cycle/index.d.ts +42 -0
  31. package/lib/typing/event/actions.d.ts +37 -0
  32. package/lib/typing/event/actions.js +72 -0
  33. package/lib/typing/event/base/expansion.d.ts +5 -0
  34. package/lib/typing/event/base/guild.d.ts +18 -0
  35. package/lib/typing/event/base/message.d.ts +28 -0
  36. package/lib/typing/event/base/platform.d.ts +16 -0
  37. package/lib/typing/event/base/user.d.ts +34 -0
  38. package/lib/typing/event/channal/index.d.ts +13 -0
  39. package/lib/typing/event/guild/index.d.ts +13 -0
  40. package/lib/typing/event/index.d.ts +94 -0
  41. package/lib/typing/event/interaction/index.d.ts +14 -0
  42. package/lib/typing/event/map.d.ts +36 -0
  43. package/lib/typing/event/member/index.d.ts +14 -0
  44. package/lib/typing/event/message/message.d.ts +23 -0
  45. package/lib/typing/event/message/private.message.d.ts +16 -0
  46. package/lib/typing/event/request/index.d.ts +13 -0
  47. package/lib/typing/logger/index.d.ts +49 -0
  48. package/lib/typing/message/ark.d.ts +46 -0
  49. package/lib/typing/message/button.d.ts +32 -0
  50. package/lib/typing/message/image.d.ts +23 -0
  51. package/lib/typing/message/index.d.ts +16 -0
  52. package/lib/typing/message/link.d.ts +12 -0
  53. package/lib/typing/message/markdown.d.ts +91 -0
  54. package/lib/typing/message/mention.d.ts +16 -0
  55. package/lib/typing/message/text.d.ts +12 -0
  56. package/lib/typing/package/index.d.ts +12 -0
  57. package/lib/typing/state/index.d.ts +5 -0
  58. package/lib/typing/store/res.d.ts +75 -0
  59. package/lib/typing/subscribe/index.d.ts +30 -0
  60. package/package.json +1 -1
  61. package/lib/app.d.ts +0 -18
  62. package/lib/app.js +0 -82
@@ -113,7 +113,6 @@ const cbpClient = (url, options = {}) => {
113
113
  }
114
114
  }
115
115
  else if (parsedMessage.name) {
116
- console.log('--- parsedMessage ----', parsedMessage);
117
116
  onProcessor(parsedMessage.name, parsedMessage, parsedMessage.value);
118
117
  }
119
118
  }
@@ -0,0 +1,47 @@
1
+ const html = `
2
+ <!DOCTYPE html>
3
+ <html>
4
+ <head>
5
+ <title>欢迎使用 AlemonJS!</title>
6
+ <style>
7
+ body {
8
+ width: 35em;
9
+ margin: 0 auto;
10
+ font-family: Tahoma, Verdana, Arial, sans-serif;
11
+ background-color: #f8f8f8;
12
+ color: #333;
13
+ }
14
+ h1 {
15
+ color: #0099cc;
16
+ margin-top: 2em;
17
+ }
18
+ .footer {
19
+ margin-top: 3em;
20
+ font-size: 0.9em;
21
+ color: #888;
22
+ }
23
+ a {
24
+ color: #0099cc;
25
+ }
26
+ </style>
27
+ </head>
28
+ <body>
29
+ <h1>AlemonJS 启动成功!</h1>
30
+ <p>
31
+ 恭喜,您的服务已成功通过 <a href="https://alemonjs.com" target="_blank">AlemonJS 框架</a> 启动。
32
+ </p>
33
+ <p>
34
+ 如果想访问主应用,请访问, <a href="/app" target="_blank">/app</a>(对应根目录index.html)
35
+ </p>
36
+ <p>
37
+ 如果想访问其他应用,请访问 /apps/[package-name]</a>。(对应/packages/[package-name]/index.html)
38
+ </p>
39
+ <div class="footer">
40
+ <p>感谢选择 AlemonJS。</p>
41
+ </div>
42
+ </body>
43
+ </html>
44
+
45
+ `;
46
+
47
+ export { html };
@@ -0,0 +1,18 @@
1
+ import { EventsEnum } from '../types/event/map.js';
2
+ import { ActionReplyFunc, ApiReplyFunc } from './typings.js';
3
+
4
+ /**
5
+ * CBP 平台端
6
+ * @param url
7
+ * @param options
8
+ * @returns
9
+ */
10
+ declare const cbpPlatform: (url: string, options?: {
11
+ open: () => void;
12
+ }) => {
13
+ send: (data: EventsEnum) => void;
14
+ onactions: (reply: ActionReplyFunc) => void;
15
+ onapis: (reply: ApiReplyFunc) => void;
16
+ };
17
+
18
+ export { cbpPlatform };
@@ -0,0 +1,170 @@
1
+ import { WebSocket } from 'ws';
2
+ import { ResultCode } from '../core/variable.js';
3
+ import { deviceId, DEVICE_ID_HEADER, USER_AGENT_HEADER, reconnectInterval } from './config.js';
4
+ import * as flattedJSON from 'flatted';
5
+ import { useHeartbeat } from './connect.js';
6
+
7
+ /**
8
+ * CBP 平台端
9
+ * @param url
10
+ * @param options
11
+ * @returns
12
+ */
13
+ const cbpPlatform = (url, options = {
14
+ open: () => { }
15
+ }) => {
16
+ if (global.chatbotPlatform) {
17
+ delete global.chatbotPlatform;
18
+ }
19
+ const { open = () => { } } = options;
20
+ const [heartbeatControl] = useHeartbeat({
21
+ ping: () => {
22
+ global?.chatbotPlatform?.ping?.();
23
+ },
24
+ isConnected: () => {
25
+ return global?.chatbotPlatform && global?.chatbotPlatform?.readyState === WebSocket.OPEN;
26
+ },
27
+ terminate: () => {
28
+ try {
29
+ global?.chatbotPlatform?.terminate?.();
30
+ }
31
+ catch (error) {
32
+ logger.debug({
33
+ code: ResultCode.Fail,
34
+ message: '强制断开连接失败',
35
+ data: error
36
+ });
37
+ }
38
+ }
39
+ });
40
+ /**
41
+ * 发送数据
42
+ * @param data
43
+ */
44
+ const send = (data) => {
45
+ if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
46
+ data.DeviceId = deviceId; // 设置设备 ID
47
+ global.chatbotPlatform.send(flattedJSON.stringify(data));
48
+ }
49
+ };
50
+ const actionReplys = [];
51
+ const apiReplys = [];
52
+ /**
53
+ * 消费数据
54
+ * @param data
55
+ * @param payload
56
+ */
57
+ const replyAction = (data, payload) => {
58
+ if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
59
+ // 透传消费。也就是对应的设备进行处理消费。
60
+ global.chatbotPlatform.send(flattedJSON.stringify({
61
+ action: data.action,
62
+ payload: payload,
63
+ actionId: data.actionId,
64
+ DeviceId: data.DeviceId
65
+ }));
66
+ }
67
+ };
68
+ const replyApi = (data, payload) => {
69
+ if (global.chatbotPlatform && global.chatbotPlatform.readyState === WebSocket.OPEN) {
70
+ // 透传消费。也就是对应的设备进行处理消费。
71
+ global.chatbotPlatform.send(flattedJSON.stringify({
72
+ action: data.action,
73
+ apiId: data.apiId,
74
+ DeviceId: data.DeviceId,
75
+ payload: payload
76
+ }));
77
+ }
78
+ };
79
+ /**
80
+ * 接收行为
81
+ * @param reply
82
+ */
83
+ const onactions = (reply) => {
84
+ actionReplys.push(reply);
85
+ };
86
+ /**
87
+ * 接收接口
88
+ */
89
+ const onapis = (reply) => {
90
+ apiReplys.push(reply);
91
+ };
92
+ /**
93
+ * 启动 WebSocket 连接
94
+ */
95
+ const start = () => {
96
+ global.chatbotPlatform = new WebSocket(url, {
97
+ headers: {
98
+ [USER_AGENT_HEADER]: 'platform',
99
+ [DEVICE_ID_HEADER]: deviceId
100
+ }
101
+ });
102
+ global.chatbotPlatform.on('open', () => {
103
+ open();
104
+ heartbeatControl.start(); // 启动心跳
105
+ });
106
+ global.chatbotPlatform.on('pong', () => {
107
+ heartbeatControl.pong(); // 更新 pong 时间
108
+ });
109
+ global.chatbotPlatform.on('message', message => {
110
+ try {
111
+ const data = flattedJSON.parse(message.toString());
112
+ logger.debug({
113
+ code: ResultCode.Ok,
114
+ message: '平台端接收消息',
115
+ data: data
116
+ });
117
+ if (data.apiId) {
118
+ for (const cb of apiReplys) {
119
+ void cb(data,
120
+ // 传入一个消费函数
121
+ val => replyApi(data, val));
122
+ }
123
+ }
124
+ else if (data.actionId) {
125
+ for (const cb of actionReplys) {
126
+ void cb(data,
127
+ // 传入一个消费函数
128
+ val => replyAction(data, val));
129
+ }
130
+ }
131
+ }
132
+ catch (error) {
133
+ logger.error({
134
+ code: ResultCode.Fail,
135
+ message: '解析消息失败',
136
+ data: error
137
+ });
138
+ }
139
+ });
140
+ global.chatbotPlatform.on('close', err => {
141
+ heartbeatControl.stop(); // 停止心跳
142
+ logger.warn({
143
+ code: ResultCode.Fail,
144
+ message: '平台端连接关闭,尝试重新连接...',
145
+ data: err
146
+ });
147
+ delete global.chatbotPlatform;
148
+ // 重新连接逻辑
149
+ setTimeout(() => {
150
+ start(); // 重新连接
151
+ }, reconnectInterval); // 6秒后重连
152
+ });
153
+ global.chatbotPlatform.on('error', err => {
154
+ logger.error({
155
+ code: ResultCode.Fail,
156
+ message: '平台端错误',
157
+ data: err
158
+ });
159
+ });
160
+ };
161
+ start();
162
+ const client = {
163
+ send,
164
+ onactions,
165
+ onapis
166
+ };
167
+ return client;
168
+ };
169
+
170
+ export { cbpPlatform };
@@ -0,0 +1,297 @@
1
+ import KoaRouter from 'koa-router';
2
+ import fs, { existsSync } from 'fs';
3
+ import path, { join, dirname } from 'path';
4
+ import mime from 'mime-types';
5
+ import { createRequire } from 'module';
6
+ import { html } from './hello.html.js';
7
+
8
+ const require = createRequire(import.meta.url);
9
+ const mainDirMap = new Map();
10
+ const formatPath = (path) => {
11
+ if (!path || path === '/') {
12
+ return '/index.html';
13
+ }
14
+ const pates = path.split('/');
15
+ const lastPath = pates[pates.length - 1];
16
+ if (lastPath.includes('.')) {
17
+ return path;
18
+ }
19
+ path += '.html';
20
+ return path;
21
+ };
22
+ // 输入一个文件路径。
23
+ const getModuelFile = (dir) => {
24
+ const dirMap = {
25
+ '.js': `${dir}.js`,
26
+ '.jsx': `${dir}.jsx`,
27
+ '.mjs': `${dir}.mjs`,
28
+ '.cjs': `${dir}.cjs`,
29
+ '/index.js': `${dir}/index.js`,
30
+ '/index.jsx': `${dir}/index.jsx`,
31
+ '/index.mjs': `${dir}/index.mjs`,
32
+ '/index.cjs': `${dir}/index.cjs`,
33
+ '.ts': `${dir}.ts`,
34
+ '.tsx': `${dir}.tsx`,
35
+ '/index.ts': `${dir}/index.ts`,
36
+ '/index.tsx': `${dir}/index.tsx`
37
+ };
38
+ for (const key in dirMap) {
39
+ const filePath = dirMap[key];
40
+ if (existsSync(filePath) && fs.statSync(filePath)) {
41
+ return filePath;
42
+ }
43
+ }
44
+ return '';
45
+ };
46
+ const router = new KoaRouter({
47
+ prefix: '/'
48
+ });
49
+ router.get('/', ctx => {
50
+ ctx.status = 200;
51
+ ctx.set('Content-Type', 'text/html; charset=utf-8');
52
+ ctx.body = html;
53
+ });
54
+ // 响应服务在线
55
+ router.get('api/online', ctx => {
56
+ ctx.status = 200;
57
+ ctx.body = {
58
+ code: 200,
59
+ message: 'service online',
60
+ data: null
61
+ };
62
+ });
63
+ router.all('app/{*path}', async (ctx) => {
64
+ if (!process.env.input) {
65
+ ctx.status = 400;
66
+ ctx.body = {
67
+ code: 400,
68
+ message: '没有主要入口文件',
69
+ data: null
70
+ };
71
+ return;
72
+ }
73
+ const rootPath = process.cwd();
74
+ const apiPath = '/app/api';
75
+ if (ctx.path.startsWith(apiPath)) {
76
+ const mainPath = join(rootPath, process.env.input);
77
+ // 路径
78
+ if (!existsSync(mainPath)) {
79
+ ctx.status = 400;
80
+ ctx.body = {
81
+ code: 400,
82
+ message: '未找到主要入口文件',
83
+ data: 'existsSync input'
84
+ };
85
+ return;
86
+ }
87
+ const mainDir = dirname(mainPath);
88
+ try {
89
+ const dir = join(mainDir, 'route', ctx.path?.replace(apiPath, '/api') || '');
90
+ const dirs = dir.split('/');
91
+ const fileName = dirs[dirs.length - 1];
92
+ if (fileName.includes('.')) {
93
+ ctx.status = 404;
94
+ ctx.body = {
95
+ code: 404,
96
+ message: `API '${ctx.path}' 未找到。`,
97
+ data: 'existsSync route filename'
98
+ };
99
+ return;
100
+ }
101
+ const modulePath = getModuelFile(dir);
102
+ if (!modulePath) {
103
+ ctx.status = 404;
104
+ ctx.body = {
105
+ code: 404,
106
+ message: `API '${ctx.path}' 未找到。`,
107
+ data: 'existsSync modulePath'
108
+ };
109
+ return;
110
+ }
111
+ const apiModule = await import(`file://${modulePath}`);
112
+ if (!apiModule[ctx.method] || typeof apiModule[ctx.method] !== 'function') {
113
+ ctx.status = 405;
114
+ return;
115
+ }
116
+ await apiModule[ctx.method](ctx);
117
+ }
118
+ catch (err) {
119
+ console.error(`Error handling API request ${ctx.path}`);
120
+ ctx.status = 500;
121
+ ctx.body = {
122
+ code: 500,
123
+ message: '处理 API 请求时发生错误。',
124
+ error: err.message
125
+ };
126
+ }
127
+ return;
128
+ }
129
+ // 如果不是 get请求。即不响应
130
+ if (ctx.method !== 'GET') {
131
+ ctx.status = 405;
132
+ return;
133
+ }
134
+ let root = '';
135
+ const resourcePath = formatPath(ctx.params?.path);
136
+ try {
137
+ const pkg = require(path.join(rootPath, 'package.json')) ?? {};
138
+ root = pkg.alemonjs?.web?.root ?? '';
139
+ }
140
+ catch (err) {
141
+ ctx.status = 500;
142
+ ctx.body = {
143
+ code: 500,
144
+ message: '加载 package.json 时发生错误。',
145
+ error: err.message
146
+ };
147
+ return;
148
+ }
149
+ const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
150
+ try {
151
+ // 返回文件
152
+ const file = await fs.promises.readFile(fullPath);
153
+ const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
154
+ ctx.set('Content-Type', mimeType); // 自动设置响应头
155
+ ctx.body = file;
156
+ ctx.status = 200;
157
+ }
158
+ catch (err) {
159
+ if (err?.status === 404) {
160
+ ctx.status = 404;
161
+ ctx.body = {
162
+ code: 404,
163
+ message: '资源中未找到。',
164
+ data: null
165
+ };
166
+ }
167
+ else {
168
+ ctx.status = 500;
169
+ ctx.body = {
170
+ code: 500,
171
+ message: '加载资源时发生服务器错误。',
172
+ error: err.message
173
+ };
174
+ }
175
+ }
176
+ });
177
+ router.all('app', ctx => {
178
+ ctx.redirect('/app/');
179
+ });
180
+ router.all('apps/:app/{*path}', async (ctx) => {
181
+ const appName = ctx.params.app;
182
+ const apiPath = `/apps/${appName}/api`;
183
+ if (ctx.path.startsWith(apiPath)) {
184
+ try {
185
+ if (!mainDirMap.has(appName)) {
186
+ const mainPath = require.resolve(appName);
187
+ // 不存在 main
188
+ if (!existsSync(mainPath)) {
189
+ ctx.status = 400;
190
+ ctx.body = {
191
+ code: 400,
192
+ message: '未找到主要入口文件',
193
+ data: null
194
+ };
195
+ return;
196
+ }
197
+ const mainDir = dirname(mainPath);
198
+ mainDirMap.set(appName, mainDir);
199
+ }
200
+ const dir = join(mainDirMap.get(appName), 'route', ctx.path?.replace(apiPath, '/api') || '');
201
+ const dirs = dir.split('/');
202
+ const fileName = dirs[dirs.length - 1];
203
+ if (fileName.includes('.')) {
204
+ ctx.status = 404;
205
+ ctx.body = {
206
+ code: 404,
207
+ message: `API 'route/${ctx.path}' 未找到。`,
208
+ data: null
209
+ };
210
+ return;
211
+ }
212
+ const modulePath = getModuelFile(dir);
213
+ if (!modulePath) {
214
+ ctx.status = 404;
215
+ ctx.body = {
216
+ code: 404,
217
+ message: `API 'route/${ctx.path}' 未找到。`,
218
+ data: null
219
+ };
220
+ return;
221
+ }
222
+ const apiModule = await import(`file://${modulePath}`);
223
+ if (!apiModule[ctx.method] || typeof apiModule[ctx.method] !== 'function') {
224
+ ctx.status = 405;
225
+ return;
226
+ }
227
+ await apiModule[ctx.method](ctx);
228
+ }
229
+ catch (err) {
230
+ logger.warn(`Error request ${ctx.path}:`, err?.message || '');
231
+ ctx.status = 500;
232
+ ctx.body = {
233
+ code: 500,
234
+ message: '处理 API 请求时发生错误。',
235
+ error: err.message
236
+ };
237
+ }
238
+ return;
239
+ }
240
+ // 如果不是 get请求。即不响应
241
+ if (ctx.method !== 'GET') {
242
+ ctx.status = 405;
243
+ return;
244
+ }
245
+ // 不是 packages,而是 node_modules。需要是模块化
246
+ const rootPath = path.join(process.cwd(), 'node_modules', appName);
247
+ const resourcePath = formatPath(ctx.params?.path);
248
+ let root = '';
249
+ try {
250
+ const pkg = require(`${appName}/package`) ?? {};
251
+ root = pkg?.alemonjs?.web?.root ?? '';
252
+ }
253
+ catch (err) {
254
+ ctx.status = 500;
255
+ ctx.body = {
256
+ code: 500,
257
+ message: '加载 package.json 时发生错误。',
258
+ error: err.message
259
+ };
260
+ return;
261
+ }
262
+ const fullPath = root ? path.join(rootPath, root, resourcePath) : path.join(rootPath, resourcePath);
263
+ try {
264
+ // 返回文件
265
+ const file = await fs.promises.readFile(fullPath);
266
+ const mimeType = mime.lookup(fullPath) || 'application/octet-stream';
267
+ ctx.set('Content-Type', mimeType); // 自动设置响应头
268
+ ctx.body = file;
269
+ ctx.status = 200;
270
+ }
271
+ catch (err) {
272
+ if (err?.status === 404) {
273
+ ctx.status = 404;
274
+ ctx.body = {
275
+ code: 404,
276
+ message: `资源 '${resourcePath}' 在子应用 '${appName}' 中未找到。`,
277
+ data: null
278
+ };
279
+ }
280
+ else {
281
+ logger.warn(`Error request ${ctx.path}:`, err?.message || '');
282
+ ctx.status = 500;
283
+ ctx.body = {
284
+ code: 500,
285
+ message: `加载子应用 '${appName}' 资源时发生服务器错误。`,
286
+ error: err.message
287
+ };
288
+ }
289
+ }
290
+ });
291
+ router.all('apps/:name', ctx => {
292
+ if (ctx.path === `/apps/${ctx.params.name}`) {
293
+ ctx.redirect(`/apps/${ctx.params.name}/`);
294
+ }
295
+ });
296
+
297
+ export { router as default };
@@ -80,7 +80,7 @@ router.all('app/{*path}', async (ctx) => {
80
80
  await runMiddlewares(middlewares, ctx, handler);
81
81
  }
82
82
  catch (err) {
83
- console.error(`Error handling API request ${ctx.path}`);
83
+ logger.error(`Error handling API request ${ctx.path}`);
84
84
  ctx.status = 500;
85
85
  ctx.body = {
86
86
  code: 500,
@@ -198,7 +198,6 @@ const setPlatformClient = (originId, ws) => {
198
198
  }
199
199
  platformClient.set(originId, ws);
200
200
  ws.on('message', (message) => {
201
- console.log('平台消息', message);
202
201
  try {
203
202
  const parsedMessage = flattedJSON.parse(message.toString());
204
203
  logger.debug({
@@ -0,0 +1,8 @@
1
+ /**
2
+ * CBP 服务器
3
+ * @param port
4
+ * @param listeningListener
5
+ */
6
+ declare const cbpServer: (port: number, listeningListener?: () => void) => void;
7
+
8
+ export { cbpServer };