alemonjs 2.1.14 → 2.1.16-alpha.1

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_CONFIG.md CHANGED
@@ -12,9 +12,6 @@ input: 'lib/index.js' # 应用入口文件路径,快捷参数 --input
12
12
  login: 'discord' # 登录平台标识,快捷参数 --login
13
13
  url: 'ws://127.0.0.1:17117' # CBP服务器连接地址,快捷参数 --url
14
14
  is_full_receive: false # 是否全量接收消息(用于分流处理)
15
-
16
- # === CBP(Chat Bot Protocol)配置 ===
17
- loadBalanceStrategy: 'least-connections' # 负载均衡策略,快捷参数 --loadBalanceStrategy
18
15
  ```
19
16
 
20
17
  ### 权限管理
@@ -182,45 +179,6 @@ ChatBot Protocol
182
179
 
183
180
  ALemonJS 的核心通信协议,负责客户端与平台之间的消息传输和负载均衡。
184
181
 
185
- ### 负载均衡策略
186
-
187
- ```yaml
188
- # 负载均衡策略配置
189
- loadBalanceStrategy: 'least-connections' # 默认策略
190
-
191
- # 支持的策略类型:
192
- # - 'round-robin': 轮询算法
193
- # - 'least-connections': 最少连接算法(推荐)
194
- # - 'random': 随机算法
195
- # - 'first-available': 首个可用算法
196
-
197
- # 支持的策略类型:
198
- # - 'round-robin': 轮询算法
199
- # - 'least-connections': 最少连接算法(推荐)
200
- # - 'random': 随机算法
201
- # - 'first-available': 首个可用算法
202
-
203
- #1. **轮询算法 (round-robin)**
204
- # - 按固定顺序依次分配请求
205
- # - 适用于客户端处理能力相近的场景
206
- # - 请求分布均匀
207
-
208
- #2. **最少连接算法 (least-connections)**
209
- # - 优选当前连接数最少的客户端
210
- # - 适用于客户端处理能力不同的场景
211
- # - 提供最优的负载分配
212
-
213
- #3. **随机算法 (random)**
214
- # - 随机选择可用客户端
215
- # - 简单的概率性分布
216
- # - 适用于临时测试场景
217
-
218
- #4. **首个可用算法 (first-available)**
219
- # - 总是选择第一个健康的客户端
220
- # - 类似主备模式
221
- # - 适用于有优先级需求的场景
222
- ```
223
-
224
182
  ### CBP 连接配置
225
183
 
226
184
  ```yaml
@@ -3,14 +3,18 @@ declare class ListNode<T> {
3
3
  next: ListNode<T> | null;
4
4
  constructor(data: T);
5
5
  }
6
- export declare class SinglyLinkedList<T> {
6
+ export declare class SinglyLinkedList<T extends Record<string, any>> {
7
7
  private head;
8
8
  private size;
9
9
  private current;
10
+ private indexMap;
10
11
  constructor(initialValues?: T[]);
11
12
  append(data: T): void;
13
+ findByKey<K extends keyof T>(key: K, value: T[K]): ListNode<T> | null;
14
+ updateById(id: any, updater: (data: T) => T): boolean;
12
15
  popNext(): ListNode<T> | null;
13
16
  removeCurrent(): void;
17
+ removeById(id: any): boolean;
14
18
  getSize(): number;
15
19
  }
16
20
  export {};
@@ -10,10 +10,12 @@ class SinglyLinkedList {
10
10
  head;
11
11
  size;
12
12
  current;
13
+ indexMap;
13
14
  constructor(initialValues) {
14
15
  this.head = null;
15
16
  this.size = 0;
16
17
  this.current = null;
18
+ this.indexMap = new Map();
17
19
  if (initialValues) {
18
20
  initialValues.forEach(value => this.append(value));
19
21
  }
@@ -30,8 +32,38 @@ class SinglyLinkedList {
30
32
  }
31
33
  current.next = newNode;
32
34
  }
35
+ if (data.id !== undefined) {
36
+ this.indexMap.set(data.id, newNode);
37
+ }
33
38
  this.size++;
34
39
  }
40
+ findByKey(key, value) {
41
+ if (key === 'id' && this.indexMap.has(value)) {
42
+ return this.indexMap.get(value);
43
+ }
44
+ let current = this.head;
45
+ while (current) {
46
+ if (current.data[key] === value) {
47
+ return current;
48
+ }
49
+ current = current.next;
50
+ }
51
+ return null;
52
+ }
53
+ updateById(id, updater) {
54
+ const node = this.findByKey('id', id);
55
+ if (!node) {
56
+ return false;
57
+ }
58
+ const oldData = node.data;
59
+ const newData = updater(oldData);
60
+ node.data = newData;
61
+ if (oldData.id !== newData.id) {
62
+ this.indexMap.delete(oldData.id);
63
+ this.indexMap.set(newData.id, node);
64
+ }
65
+ return true;
66
+ }
35
67
  popNext() {
36
68
  if (!this.current) {
37
69
  this.current = this.head;
@@ -42,9 +74,12 @@ class SinglyLinkedList {
42
74
  return this.current;
43
75
  }
44
76
  removeCurrent() {
45
- if (!this.head) {
77
+ if (!this.head || !this.current) {
46
78
  return;
47
79
  }
80
+ if (this.current.data.id !== undefined) {
81
+ this.indexMap.delete(this.current.data.id);
82
+ }
48
83
  if (this.current === this.head) {
49
84
  this.head = this.head.next;
50
85
  this.current = null;
@@ -61,6 +96,32 @@ class SinglyLinkedList {
61
96
  this.size--;
62
97
  }
63
98
  }
99
+ removeById(id) {
100
+ const node = this.findByKey('id', id);
101
+ if (!node) {
102
+ return false;
103
+ }
104
+ if (node === this.current) {
105
+ this.removeCurrent();
106
+ }
107
+ else {
108
+ if (node === this.head) {
109
+ this.head = node.next;
110
+ }
111
+ else {
112
+ let previous = this.head;
113
+ while (previous && previous.next !== node) {
114
+ previous = previous.next;
115
+ }
116
+ if (previous) {
117
+ previous.next = node.next;
118
+ }
119
+ }
120
+ this.indexMap.delete(id);
121
+ this.size--;
122
+ }
123
+ return true;
124
+ }
64
125
  getSize() {
65
126
  return this.size;
66
127
  }
@@ -0,0 +1,5 @@
1
+ export declare const SubscribeStatus: {
2
+ active: 'active';
3
+ processing: 'processing';
4
+ paused: 'paused';
5
+ };
@@ -0,0 +1,7 @@
1
+ const SubscribeStatus = {
2
+ active: 'active',
3
+ processing: 'processing',
4
+ paused: 'paused'
5
+ };
6
+
7
+ export { SubscribeStatus };
@@ -0,0 +1,2 @@
1
+ import { DefineChildrenFunc } from '../types';
2
+ export declare const defineChildren: DefineChildrenFunc;
@@ -0,0 +1,19 @@
1
+ import { ResultCode } from '../core/variable.js';
2
+
3
+ const defineChildren = callback => {
4
+ if (typeof callback === 'function' || typeof callback === 'object') {
5
+ return {
6
+ _name: 'app',
7
+ callback
8
+ };
9
+ }
10
+ logger.error({
11
+ code: ResultCode.FailParams,
12
+ message: 'Invalid callback: callback must be a object or function',
13
+ data: null
14
+ });
15
+ throw new Error('Invalid callback: callback must be a object or function');
16
+ };
17
+ global.defineChildren = defineChildren;
18
+
19
+ export { defineChildren };
File without changes
@@ -0,0 +1,5 @@
1
+ type Options = {
2
+ main: () => any;
3
+ };
4
+ export declare const definePlatform: (options: Options) => () => any;
5
+ export {};
@@ -0,0 +1,32 @@
1
+ const definePlatform = (options) => {
2
+ const mainProcess = () => {
3
+ ['SIGINT', 'SIGTERM', 'SIGQUIT', 'disconnect'].forEach(sig => {
4
+ process?.on?.(sig, () => {
5
+ logger.info?.(`[@alemonjs/qq-bot][${sig}] 收到信号,正在关闭...`);
6
+ setImmediate(() => process.exit(0));
7
+ });
8
+ });
9
+ process?.on?.('exit', code => {
10
+ logger.info?.(`[@alemonjs/qq-bot][exit] 进程退出,code=${code}`);
11
+ });
12
+ process.on('message', msg => {
13
+ try {
14
+ const data = typeof msg === 'string' ? JSON.parse(msg) : msg;
15
+ if (data?.type === 'start') {
16
+ options.main();
17
+ }
18
+ else if (data?.type === 'stop') {
19
+ process.exit(0);
20
+ }
21
+ }
22
+ catch { }
23
+ });
24
+ if (process.send) {
25
+ process.send(JSON.stringify({ type: 'ready' }));
26
+ }
27
+ };
28
+ mainProcess();
29
+ return options.main;
30
+ };
31
+
32
+ export { definePlatform };
@@ -1,5 +1,5 @@
1
1
  import { Next, Events, EventCycleEnum, EventKeys } from '../types';
2
- export declare const expendSubscribe: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next, chioce: EventCycleEnum) => void;
2
+ export declare const expendSubscribe: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next, choose: EventCycleEnum) => void;
3
3
  export declare const expendSubscribeCreate: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next) => void;
4
4
  export declare const expendSubscribeMount: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next) => void;
5
5
  export declare const expendSubscribeUnmount: <T extends EventKeys>(valueEvent: Events[T], select: T, next: Next) => void;
@@ -1,15 +1,23 @@
1
1
  import { SubscribeList } from './store.js';
2
+ import { SubscribeStatus } from './config.js';
2
3
 
3
- const expendSubscribe = (valueEvent, select, next, chioce) => {
4
- const subList = new SubscribeList(chioce, select);
4
+ const expendSubscribe = (valueEvent, select, next, choose) => {
5
+ const subList = new SubscribeList(choose, select);
5
6
  const nextObserver = (cn, ...cns) => {
6
7
  if (cn) {
7
8
  next(...cns);
8
9
  return;
9
10
  }
10
11
  const item = subList.value.popNext();
12
+ const selects = item.data.selects;
13
+ const ID = item.data.id;
11
14
  if (!item) {
12
- nextObserver(true);
15
+ nextObserver();
16
+ return;
17
+ }
18
+ if (item.data.status === SubscribeStatus.paused) {
19
+ subList.value.removeCurrent();
20
+ nextObserver();
13
21
  return;
14
22
  }
15
23
  for (const key in item.data.keys) {
@@ -18,38 +26,33 @@ const expendSubscribe = (valueEvent, select, next, chioce) => {
18
26
  return;
19
27
  }
20
28
  }
21
- const clear = () => {
22
- const selects = item.data.selects;
23
- const ID = item.data.id;
29
+ const onPaused = () => {
24
30
  for (const select of selects) {
25
- const subList = new SubscribeList(chioce, select);
26
- const remove = () => {
27
- const item = subList.value.popNext();
28
- if (!item || item.data.id !== ID) {
29
- remove();
30
- return;
31
- }
32
- subList.value.removeCurrent();
33
- };
34
- remove();
31
+ const subList = new SubscribeList(choose, select);
32
+ subList.value.updateById(ID, (data) => ({
33
+ status: SubscribeStatus.paused,
34
+ ...data
35
+ }));
35
36
  }
36
37
  };
37
- const restore = () => {
38
- const selects = item.data.selects;
38
+ const onActive = () => {
39
39
  for (const select of selects) {
40
- const subList = new SubscribeList(chioce, select);
41
- subList.value.append(item.data);
40
+ const subList = new SubscribeList(choose, select);
41
+ subList.value.updateById(ID, (data) => ({
42
+ status: SubscribeStatus.active,
43
+ ...data
44
+ }));
42
45
  }
43
46
  };
44
- clear();
47
+ onPaused();
45
48
  const Continue = (cn, ...cns) => {
46
- restore();
49
+ onActive();
47
50
  if (cn) {
48
51
  nextObserver(...cns);
49
52
  return;
50
53
  }
51
54
  if (typeof cn === 'boolean') {
52
- clear();
55
+ onPaused();
53
56
  nextObserver(...cns);
54
57
  }
55
58
  };
@@ -1,5 +1,6 @@
1
1
  import { ResultCode } from '../core/variable.js';
2
2
  import { SubscribeList } from './store.js';
3
+ import { SubscribeStatus } from './config.js';
3
4
 
4
5
  const useSubscribe = (event, selects) => {
5
6
  if (typeof event !== 'object') {
@@ -44,6 +45,7 @@ const useSubscribe = (event, selects) => {
44
45
  selects: curSelects,
45
46
  keys: values,
46
47
  current: callback,
48
+ status: SubscribeStatus.active,
47
49
  id: ID
48
50
  });
49
51
  }
@@ -67,15 +69,10 @@ const useSubscribe = (event, selects) => {
67
69
  const ID = value.id;
68
70
  for (const select of selects) {
69
71
  const subList = new SubscribeList(value.choose, select);
70
- const remove = () => {
71
- const item = subList.value.popNext();
72
- if (!item || item.data.id !== ID) {
73
- remove();
74
- return;
75
- }
76
- subList.value.removeCurrent();
77
- };
78
- remove();
72
+ subList.value.updateById(ID, (data) => ({
73
+ status: SubscribeStatus.paused,
74
+ ...data
75
+ }));
79
76
  }
80
77
  };
81
78
  const subscribe = {
@@ -1,6 +1,7 @@
1
1
  export * from './store.js';
2
2
  export * from './load_modules/index.js';
3
- export * from './define-chidren.js';
3
+ export * from './define-children.js';
4
+ export * from './define-platform.js';
4
5
  export * from './define-response.js';
5
6
  export * from './define-middleware.js';
6
7
  export * from './event-group.js';
package/lib/app/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, ProcessorEventAutoClearMap, ProcessorEventUserAudoClearMap, Response, ResponseMiddleware, ResponseRouter, State, StateSubscribe, SubscribeList, core, logger } from './store.js';
2
2
  export { loadModels, run } from './load_modules/load.js';
3
3
  export { loadChildren, loadChildrenFile } from './load_modules/loadChild.js';
4
- export { defineChildren } from './define-chidren.js';
4
+ export { defineChildren } from './define-children.js';
5
+ export { definePlatform } from './define-platform.js';
5
6
  export { defineResponse, lazy } from './define-response.js';
6
7
  export { defineMiddleware } from './define-middleware.js';
7
8
  export { onGroup } from './event-group.js';
@@ -6,7 +6,7 @@ import 'path';
6
6
  import 'yaml';
7
7
  import { ResultCode } from '../../core/variable.js';
8
8
  import '../../app/load_modules/loadChild.js';
9
- import '../../app/define-chidren.js';
9
+ import '../../app/define-children.js';
10
10
  import '../../app/define-response.js';
11
11
  import '../../app/define-middleware.js';
12
12
  import '../../app/event-group.js';
@@ -126,7 +126,7 @@ const handleEvent = (message, ID) => {
126
126
  const setChildrenClient = (originId, ws) => {
127
127
  childrenClient.set(originId, ws);
128
128
  ws.on('message', (message) => {
129
- if (global.sandbox) {
129
+ if (global.__sandbox) {
130
130
  if (global.testoneClient && global.testoneClient.readyState === WebSocket.OPEN) {
131
131
  global.testoneClient.send(message.toString());
132
132
  }
@@ -162,7 +162,7 @@ const setChildrenClient = (originId, ws) => {
162
162
  const setFullClient = (originId, ws) => {
163
163
  fullClient.set(originId, ws);
164
164
  ws.on('message', (message) => {
165
- if (global.sandbox) {
165
+ if (global.__sandbox) {
166
166
  if (global.testoneClient && global.testoneClient.readyState === WebSocket.OPEN) {
167
167
  global.testoneClient.send(message.toString());
168
168
  }
@@ -318,7 +318,7 @@ const cbpServer = (port, listeningListener) => {
318
318
  global.chatbotServer = new WebSocketServer({ server });
319
319
  global.chatbotServer.on('connection', (ws, request) => {
320
320
  if (request.url === '/testone') {
321
- if (!global.sandbox) {
321
+ if (!global.__sandbox) {
322
322
  ws.close(4000, 'Sandbox mode required');
323
323
  return;
324
324
  }
package/lib/client.js CHANGED
@@ -16,7 +16,7 @@ import 'fs/promises';
16
16
  import './app/store.js';
17
17
  import { loadModels } from './app/load_modules/load.js';
18
18
  import './app/load_modules/loadChild.js';
19
- import './app/define-chidren.js';
19
+ import './app/define-children.js';
20
20
  import './app/define-response.js';
21
21
  import './app/define-middleware.js';
22
22
  import './app/event-group.js';
@@ -55,9 +55,16 @@ class ConfigCore {
55
55
  }
56
56
  get value() {
57
57
  if (!this.#value) {
58
- return this.#update();
58
+ this.#update();
59
+ return {
60
+ ...(this.#value || {}),
61
+ ...(global?.__options || {})
62
+ };
59
63
  }
60
- return this.#value;
64
+ return {
65
+ ...(this.#value || {}),
66
+ ...(global?.__options || {})
67
+ };
61
68
  }
62
69
  saveValue(value) {
63
70
  if (!this.#dir) {
@@ -119,12 +126,12 @@ class ConfigCore {
119
126
  }
120
127
  }
121
128
  const getConfig = () => {
122
- if (global?.config) {
123
- return global.config;
129
+ if (global?.__config) {
130
+ return global.__config;
124
131
  }
125
132
  const configDir = process.env.CFG_PATH || 'alemon.config.yaml';
126
- global.config = new ConfigCore(configDir);
127
- return global.config;
133
+ global.__config = new ConfigCore(configDir);
134
+ return global.__config;
128
135
  };
129
136
  const getConfigValue = () => getConfig()?.value || {};
130
137
 
package/lib/index.js CHANGED
@@ -8,7 +8,8 @@ export { cbpServer } from './cbp/server/main.js';
8
8
  export { ChildrenApp, Core, Logger, Middleware, MiddlewareRouter, ProcessorEventAutoClearMap, ProcessorEventUserAudoClearMap, Response, ResponseMiddleware, ResponseRouter, State, StateSubscribe, SubscribeList, core, logger } from './app/store.js';
9
9
  export { loadModels, run } from './app/load_modules/load.js';
10
10
  export { loadChildren, loadChildrenFile } from './app/load_modules/loadChild.js';
11
- export { defineChildren } from './app/define-chidren.js';
11
+ export { defineChildren } from './app/define-children.js';
12
+ export { definePlatform } from './app/define-platform.js';
12
13
  export { defineResponse, lazy } from './app/define-response.js';
13
14
  export { defineMiddleware } from './app/define-middleware.js';
14
15
  export { onGroup } from './app/event-group.js';
package/lib/main.js CHANGED
@@ -14,7 +14,7 @@ const startPlatform = (options) => {
14
14
  const platform = createOptionsByKey(options, 'platform', '');
15
15
  const login = createOptionsByKey(options, 'login', '');
16
16
  if (!platform && !login) {
17
- global.sandbox = true;
17
+ global.__sandbox = true;
18
18
  return;
19
19
  }
20
20
  if (platform) {
@@ -49,6 +49,7 @@ const start = (options = {}) => {
49
49
  if (typeof options === 'string') {
50
50
  options = { input: options };
51
51
  }
52
+ global.__options = options;
52
53
  const port = createOptionsByKey(options, 'port', defaultPort);
53
54
  const serverPort = createOptionsByKey(options, 'serverPort', '');
54
55
  process.env.port = port;
@@ -1,13 +1,74 @@
1
1
  type ServerOptions = {
2
2
  port?: number;
3
+ serverPort?: number;
3
4
  };
4
5
  type ClientOptions = {
5
6
  url?: string;
6
7
  };
7
- export type StartOptions = ServerOptions & ClientOptions & {
8
+ type AppOptions = {
8
9
  input?: string;
9
10
  platform?: string;
10
11
  login?: string;
11
12
  is_full_receive?: boolean;
12
13
  };
14
+ type MasterOptions = {
15
+ master_key?: {
16
+ [key: string]: boolean;
17
+ };
18
+ master_id?: {
19
+ [key: string]: boolean;
20
+ };
21
+ };
22
+ type BotOptions = {
23
+ bot_key?: {
24
+ [key: string]: boolean;
25
+ };
26
+ bot_id?: {
27
+ [key: string]: boolean;
28
+ };
29
+ };
30
+ type MessageOptions = {
31
+ disabled_text_regular?: string;
32
+ disabled_selects?: {
33
+ [key: string]: boolean;
34
+ };
35
+ disabled_user_id?: {
36
+ [key: string]: boolean;
37
+ };
38
+ disabled_user_key?: {
39
+ [key: string]: boolean;
40
+ };
41
+ redirect_text_regular?: string;
42
+ redirect_text_target?: string;
43
+ mapping_text?: {
44
+ regular?: string;
45
+ target?: string;
46
+ }[];
47
+ };
48
+ type ProcessorOptions = {
49
+ processor?: {
50
+ repeated_event_time?: number;
51
+ repeated_user_time?: number;
52
+ };
53
+ };
54
+ type AppsOptions = {
55
+ apps?: {
56
+ [key: string]: boolean;
57
+ };
58
+ };
59
+ type CBPOptions = {
60
+ cbp?: {
61
+ timeout?: number;
62
+ reconnectInterval?: string;
63
+ heartbeatInterval?: string;
64
+ healthCheckInterval?: string;
65
+ headers?: {
66
+ 'user-agent': string;
67
+ 'x-device-id': string;
68
+ 'x-full-receive': '0' | '1';
69
+ [key: string]: string;
70
+ };
71
+ };
72
+ };
73
+ export type StartOptions = ServerOptions & ClientOptions & AppOptions & MasterOptions & BotOptions & MessageOptions & ProcessorOptions & AppsOptions & CBPOptions;
13
74
  export {};
@@ -7,6 +7,7 @@ export type SubscribeValue = {
7
7
  keys: {
8
8
  [key: string]: string | number | boolean;
9
9
  };
10
+ status?: 'active' | 'processing' | 'paused';
10
11
  current: Function;
11
12
  id: string;
12
13
  };
package/lib/utils.js CHANGED
@@ -125,15 +125,15 @@ async function createPicFrom(options) {
125
125
  }
126
126
  const getPublicIP = async (options = {}) => {
127
127
  const { force, ...config } = options;
128
- if (global.publicip && !force) {
129
- return global.publicip;
128
+ if (global.__publicIp && !force) {
129
+ return global.__publicIp;
130
130
  }
131
131
  return await publicIp({
132
132
  onlyHttps: true,
133
133
  ...config
134
134
  }).then(ip => {
135
- global.publicip = ip;
136
- return global.publicip;
135
+ global.__publicIp = ip;
136
+ return global.__publicIp;
137
137
  });
138
138
  };
139
139
  class Regular extends RegExp {
package/package.json CHANGED
@@ -1,20 +1,18 @@
1
1
  {
2
2
  "name": "alemonjs",
3
- "version": "2.1.14",
3
+ "version": "2.1.16-alpha.1",
4
4
  "description": "bot script",
5
5
  "author": "lemonade",
6
6
  "license": "MIT",
7
7
  "type": "module",
8
8
  "main": "lib/index.js",
9
9
  "scripts": {
10
- "build": "lvy build",
11
- "bundle": "vite build"
10
+ "build": "lvy build"
12
11
  },
13
12
  "types": "lib",
14
13
  "exports": {
15
14
  "./package": "./package.json",
16
15
  ".": {
17
- "browser": "./dist/alemonjs.es.js",
18
16
  "import": "./lib/index.js",
19
17
  "types": "./lib/index.d.ts",
20
18
  "require": "./dist/index.js"