node-karin 0.11.15 → 0.12.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.
@@ -23,7 +23,7 @@ export declare class AdapterInput implements KarinAdapter {
23
23
  message_id: string;
24
24
  }>;
25
25
  getAvatarUrl(): string;
26
- getGroupAvatar(): string;
26
+ getGroupAvatarUrl(): string;
27
27
  GetCurrentAccount(): Promise<{
28
28
  account_uid: string;
29
29
  account_uin: string;
@@ -148,7 +148,7 @@ export class AdapterInput {
148
148
  getAvatarUrl() {
149
149
  return 'https://p.qlogo.cn/gh/967068507/967068507/0';
150
150
  }
151
- getGroupAvatar() {
151
+ getGroupAvatarUrl() {
152
152
  return 'https://p.qlogo.cn/gh/967068507/967068507/0';
153
153
  }
154
154
  async GetCurrentAccount() {
@@ -60,7 +60,7 @@ export declare class AdapterOneBot11 implements KarinAdapter {
60
60
  * @param history - 历史头像记录,默认`0`,若要获取历史群头像则填写1,2,3...
61
61
  * @returns - 群头像的url地址
62
62
  */
63
- getGroupAvatar(group_id: string, size?: number, history?: number): string;
63
+ getGroupAvatarUrl(group_id: string, size?: number, history?: number): string;
64
64
  /**
65
65
  * 发送消息
66
66
  *
@@ -161,7 +161,7 @@ export class AdapterOneBot11 {
161
161
  * @param history - 历史头像记录,默认`0`,若要获取历史群头像则填写1,2,3...
162
162
  * @returns - 群头像的url地址
163
163
  */
164
- getGroupAvatar(group_id, size = 0, history = 0) {
164
+ getGroupAvatarUrl(group_id, size = 0, history = 0) {
165
165
  return `https://p.qlogo.cn/gh/${group_id}/${group_id}${history ? '_' + history : ''}/` + size;
166
166
  }
167
167
  /**
@@ -111,11 +111,27 @@ export declare class Karin extends Listeners {
111
111
  reject: (msg?: string) => void) => Promise<any>, options?: Omit<Options, 'log'>): HandlerInfo;
112
112
  /**
113
113
  * 构建contact
114
- * @param peer - 群号或者id
115
- * @param isGroup - 是否是群聊
114
+ * @param scene - 场景
115
+ * @param peer - 群号或者用户id
116
116
  * @param sub_peer - 子id
117
117
  */
118
- contact(peer: string, isGroup?: boolean, sub_peer?: string): Contact;
118
+ contact(scene: Contact['scene'], peer: Contact['peer'], sub_peer?: Contact['sub_peer']): Contact;
119
+ /**
120
+ * 构建group_contact
121
+ * @param peer - 群号
122
+ */
123
+ contactGroup(peer: Contact['peer']): Contact;
124
+ /**
125
+ * 构建friend_contact
126
+ * @param peer - 用户id
127
+ */
128
+ contactFriend(peer: Contact['peer']): Contact;
129
+ /**
130
+ * 构建guild_contact
131
+ * @param peer - 频道id
132
+ * @param sub_peer - 子频道id
133
+ */
134
+ contactGuild(peer: Contact['peer'], sub_peer: string): Contact;
119
135
  /**
120
136
  * 快速渲染
121
137
  * @param file - 文件路径、http地址
@@ -97,23 +97,34 @@ export class Karin extends Listeners {
97
97
  }
98
98
  /**
99
99
  * 构建contact
100
- * @param peer - 群号或者id
101
- * @param isGroup - 是否是群聊
100
+ * @param scene - 场景
101
+ * @param peer - 群号或者用户id
102
102
  * @param sub_peer - 子id
103
103
  */
104
- contact(peer, isGroup = true, sub_peer) {
105
- if (isGroup) {
106
- return {
107
- peer,
108
- scene: "group" /* Scene.Group */,
109
- sub_peer: sub_peer || '',
110
- };
111
- }
112
- return {
113
- peer,
114
- scene: "friend" /* Scene.Private */,
115
- sub_peer: sub_peer || '',
116
- };
104
+ contact(scene, peer, sub_peer) {
105
+ return { scene, peer, sub_peer };
106
+ }
107
+ /**
108
+ * 构建group_contact
109
+ * @param peer - 群号
110
+ */
111
+ contactGroup(peer) {
112
+ return { scene: "group" /* Scene.Group */, peer };
113
+ }
114
+ /**
115
+ * 构建friend_contact
116
+ * @param peer - 用户id
117
+ */
118
+ contactFriend(peer) {
119
+ return { scene: "friend" /* Scene.Private */, peer };
120
+ }
121
+ /**
122
+ * 构建guild_contact
123
+ * @param peer - 频道id
124
+ * @param sub_peer - 子频道id
125
+ */
126
+ contactGuild(peer, sub_peer) {
127
+ return { scene: "guild" /* Scene.Guild */, peer, sub_peer };
117
128
  }
118
129
  /**
119
130
  * - 渲染
@@ -14,10 +14,6 @@ export declare class MessageHandler extends EventBaseHandler {
14
14
  * 响应模式检查 返回false表示未通过
15
15
  */
16
16
  getMode(): boolean;
17
- /**
18
- * 打印
19
- */
20
- print(): void;
21
17
  /**
22
18
  * 处理消息
23
19
  */
@@ -186,11 +186,6 @@ export class MessageHandler extends EventBaseHandler {
186
186
  logger.debug(`[消息拦截][${this.e.group_id}][${this.e.user_id}] 响应模式不匹配`);
187
187
  return false;
188
188
  }
189
- /**
190
- * 打印
191
- */
192
- print() {
193
- }
194
189
  /**
195
190
  * 处理消息
196
191
  */
@@ -7,13 +7,24 @@ export declare class RenderClient extends RenderBase {
7
7
  id: string;
8
8
  index: number;
9
9
  retry: number;
10
+ short: boolean;
10
11
  reg: RegExp;
11
12
  ws: WebSocket;
13
+ protocol?: {
14
+ application: string;
15
+ short: boolean;
16
+ cache: boolean;
17
+ vue: boolean;
18
+ };
12
19
  constructor(url: string);
13
20
  /**
14
21
  * 初始化
15
22
  */
16
23
  start(): Promise<void>;
24
+ /**
25
+ * 创建短连接
26
+ */
27
+ link(): Promise<void>;
17
28
  /**
18
29
  * 心跳
19
30
  */
@@ -1,9 +1,10 @@
1
1
  import fs from 'fs';
2
+ import axios from 'axios';
2
3
  import WebSocket from 'ws';
3
4
  import { render } from './app.js';
4
5
  import { RenderBase } from './base.js';
5
6
  import { createHash, randomUUID } from 'crypto';
6
- import { listener } from '../core/index.js';
7
+ import { karin } from '../core/index.js';
7
8
  import { common, logger } from '../utils/index.js';
8
9
  export class RenderClient extends RenderBase {
9
10
  url;
@@ -11,8 +12,11 @@ export class RenderClient extends RenderBase {
11
12
  id;
12
13
  index;
13
14
  retry;
15
+ short;
14
16
  reg;
15
17
  ws;
18
+ // NOTE: 渲染器协议暂定方案,目前仅short用于短连接模式确认,其他无用
19
+ protocol;
16
20
  constructor(url) {
17
21
  super();
18
22
  this.url = url;
@@ -20,6 +24,7 @@ export class RenderClient extends RenderBase {
20
24
  this.id = 'puppeteer';
21
25
  this.index = 0;
22
26
  this.retry = 0;
27
+ this.short = false;
23
28
  this.reg = new RegExp(`(${process.cwd().replace(/\\/g, '\\\\')}|${process.cwd().replace(/\\/g, '/')})`, 'g');
24
29
  }
25
30
  /**
@@ -64,22 +69,73 @@ export class RenderClient extends RenderBase {
64
69
  this.ws.close();
65
70
  });
66
71
  }
72
+ /**
73
+ * 创建短连接
74
+ */
75
+ async link() {
76
+ return new Promise((resolve, reject) => {
77
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
78
+ return resolve();
79
+ }
80
+ logger.debug(`[渲染器:${this.id}][正向WS] 创建短连接`);
81
+ /** 连接ws */
82
+ this.ws = new WebSocket(this.url);
83
+ /** 建立连接 */
84
+ this.ws.on('open', () => {
85
+ logger.mark(`[渲染器:${this.id}][WebSocket] 建立连接:${logger.green(this.url)}`);
86
+ /** 监听消息 */
87
+ this.ws.on('message', data => this.message(data.toString()));
88
+ resolve();
89
+ });
90
+ /** 监听断开 */
91
+ this.ws.once('close', async () => {
92
+ logger.debug(`[渲染器:${this.id}][正向WS] 关闭短连接`);
93
+ /** 停止监听 */
94
+ this.ws.removeAllListeners();
95
+ });
96
+ /** 监听错误 */
97
+ this.ws.on('error', async (e) => {
98
+ logger.debug(e);
99
+ this.ws.close();
100
+ });
101
+ });
102
+ }
67
103
  /**
68
104
  * 心跳
69
105
  */
70
106
  async heartbeat() {
71
107
  /** 无限循环 错误则停止 */
72
108
  while (true) {
73
- try {
74
- this.ws.send(JSON.stringify({ action: 'heartbeat' }));
75
- logger.debug(`[渲染器:${this.id}] 心跳:${this.url}`);
109
+ if (this.short) {
110
+ try {
111
+ const res = await axios.head(this.url);
112
+ if (res.status === 200) {
113
+ logger.debug(`[渲染器:${this.id}] 心跳:${this.url}`);
114
+ }
115
+ else {
116
+ logger.debug(`[渲染器:${this.id}] 心跳失败:服务器错误,错误代码 ${res.status}`);
117
+ break;
118
+ }
119
+ }
120
+ catch (e) {
121
+ logger.debug(`[渲染器:${this.id}] 心跳失败:`, e);
122
+ break;
123
+ }
124
+ // NOTE: 真的需要心跳的这么快吗
125
+ await common.sleep(60 * 1000);
76
126
  }
77
- catch (e) {
78
- logger.debug(`[渲染器:${this.id}] 心跳失败:`, e);
79
- this.ws.close();
80
- break;
127
+ else {
128
+ try {
129
+ this.ws.send(JSON.stringify({ action: 'heartbeat' }));
130
+ logger.debug(`[渲染器:${this.id}] 心跳:${this.url}`);
131
+ }
132
+ catch (e) {
133
+ logger.debug(`[渲染器:${this.id}] 心跳失败:`, e);
134
+ this.ws.close();
135
+ break;
136
+ }
137
+ await common.sleep(5000);
81
138
  }
82
- await common.sleep(5000);
83
139
  }
84
140
  }
85
141
  /**
@@ -101,7 +157,28 @@ export class RenderClient extends RenderBase {
101
157
  }
102
158
  /** 渲染结果 */
103
159
  case 'renderRes': {
104
- listener.emit(data.echo, data);
160
+ karin.emit(data.echo, data);
161
+ break;
162
+ }
163
+ /** 超时 */
164
+ case 'timeout': {
165
+ logger.debug(`[渲染器:${this.id}][正向WS] 处理超时`);
166
+ break;
167
+ }
168
+ /** 确认协议 */
169
+ case 'protocol': {
170
+ // 已确认协议,跳过
171
+ if (this.protocol)
172
+ break;
173
+ if (data.data)
174
+ this.protocol = data.data;
175
+ // 短连接模式
176
+ if (data.data?.short) {
177
+ logger.debug(`[渲染器:${this.id}][正向WS] 切换到短连接模式`);
178
+ this.short = data.data?.short;
179
+ this.ws.removeAllListeners();
180
+ this.ws.close();
181
+ }
105
182
  break;
106
183
  }
107
184
  /** 未知数据 */
@@ -146,9 +223,12 @@ export class RenderClient extends RenderBase {
146
223
  data.file = file;
147
224
  const req = JSON.stringify({ echo, action, data });
148
225
  logger.debug(`[渲染器:${this.id}:${this.index}][正向WS] 请求:${this.url} \nhtml: ${options.file} \ndata: ${JSON.stringify(data)}`);
226
+ if (this.short) {
227
+ await this.link();
228
+ }
149
229
  this.ws.send(req);
150
230
  return new Promise((resolve, reject) => {
151
- listener.once(echo, (data) => {
231
+ karin.once(echo, (data) => {
152
232
  if (data.ok)
153
233
  return resolve(data.data);
154
234
  reject(new Error(JSON.stringify(data)));
@@ -109,7 +109,7 @@ export interface KarinAdapter {
109
109
  * @param history - 历史头像记录,默认`0`,若要获取历史群头像则填写1,2,3...
110
110
  * @returns 头像的url地址
111
111
  */
112
- getGroupAvatar(group_id: string, size?: 0 | 40 | 100 | 140, history?: number): string;
112
+ getGroupAvatarUrl(group_id: string, size?: 0 | 40 | 100 | 140, history?: number): string;
113
113
  /**
114
114
  * 发送消息
115
115
  * @param contact - 联系人信息
@@ -1,5 +1,5 @@
1
1
  import chalk from 'chalk';
2
- export type LoggerLevel = 'trace' | 'debug' | 'mark' | 'info' | 'mark' | 'warn' | 'error' | 'fatal';
2
+ export type LoggerLevel = 'all' | 'trace' | 'debug' | 'mark' | 'info' | 'mark' | 'warn' | 'error' | 'fatal' | 'off';
3
3
  export interface Logger {
4
4
  /**
5
5
  * 颜色模块
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import Yaml from 'yaml';
4
- import axios from 'axios';
4
+ import axios, { AxiosError } from 'axios';
5
5
  import lodash from 'lodash';
6
6
  import { promisify } from 'util';
7
7
  import { fileURLToPath } from 'url';
@@ -38,7 +38,12 @@ export class Common {
38
38
  return true;
39
39
  }
40
40
  catch (err) {
41
- logger.error(`下载文件错误:${err}`);
41
+ if (err instanceof AxiosError) {
42
+ logger.error(`下载文件错误:${err.stack}`);
43
+ }
44
+ else {
45
+ logger.error(`下载文件错误:${err}`);
46
+ }
42
47
  return false;
43
48
  }
44
49
  }
@@ -55,7 +60,7 @@ export class Common {
55
60
  return await axios.get(url, param);
56
61
  }
57
62
  catch (error) {
58
- logger.debug(error);
63
+ error instanceof AxiosError ? logger.debug(error.stack) : logger.debug(error);
59
64
  return null;
60
65
  }
61
66
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-karin",
3
- "version": "0.11.15",
3
+ "version": "0.12.1",
4
4
  "private": false,
5
5
  "description": "基于 Kritor 进行开发的nodejs机器人框架",
6
6
  "homepage": "https://github.com/KarinJS/Karin",
@@ -133,7 +133,7 @@
133
133
  ],
134
134
  "scripts": {
135
135
  ".": "node lib/cli/start.js .",
136
- "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
136
+ "build": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json && npm run fix:all",
137
137
  "build:npm": "tsc --project tsconfig.json && tsc-alias -p tsconfig.json",
138
138
  "dev": "node lib/cli/start.js dev",
139
139
  "fix": "eslint lib/**/*.js --fix",
@@ -143,8 +143,13 @@
143
143
  "start": "node lib/cli/start.js start"
144
144
  },
145
145
  "dependencies": {
146
+ "@types/express": "^4.17.21",
147
+ "@types/lodash": "^4.17.7",
148
+ "@types/node": "^22.5.0",
149
+ "@types/node-schedule": "^2.1.7",
150
+ "@types/ws": "^8.5.12",
146
151
  "art-template": "4.13.2",
147
- "axios": "1.7.3",
152
+ "axios": "1.7.5",
148
153
  "chalk": "5.3.0",
149
154
  "chokidar": "3.6.0",
150
155
  "commander": "^12.1.0",
@@ -1,30 +0,0 @@
1
- import WebSocket from 'ws';
2
- import { RenderBase } from './base.js';
3
- import { KarinRenderType, RenderResult } from '../types/index.js';
4
- export declare class RenderClientEven extends RenderBase {
5
- url: string;
6
- type: string;
7
- id: string;
8
- index: number;
9
- retry: number;
10
- reg: RegExp;
11
- ws: WebSocket;
12
- constructor(url: string);
13
- /**
14
- * 初始化
15
- */
16
- start(): Promise<void>;
17
- /**
18
- * 创建连接
19
- */
20
- link(): Promise<void>;
21
- /**
22
- * 接受消息
23
- */
24
- message(str: string): Promise<void>;
25
- /**
26
- * 渲染标准方法
27
- * @param options 渲染参数
28
- */
29
- render<T extends KarinRenderType>(options: T): Promise<RenderResult<T>>;
30
- }
@@ -1,156 +0,0 @@
1
- import fs from 'fs';
2
- import axios from 'axios';
3
- import WebSocket from 'ws';
4
- import { render } from './app.js';
5
- import { RenderBase } from './base.js';
6
- import { createHash, randomUUID } from 'crypto';
7
- import { listener } from '../core/index.js';
8
- import { logger } from '../utils/index.js';
9
- export class RenderClientEven extends RenderBase {
10
- url;
11
- type;
12
- id;
13
- index;
14
- retry;
15
- reg;
16
- ws;
17
- constructor(url) {
18
- super();
19
- this.url = url;
20
- this.type = 'image';
21
- this.id = 'puppeteer';
22
- this.index = 0;
23
- this.retry = 0;
24
- this.reg = new RegExp(`(${process.cwd().replace(/\\/g, '\\\\')}|${process.cwd().replace(/\\/g, '/')})`, 'g');
25
- }
26
- /**
27
- * 初始化
28
- */
29
- async start() {
30
- try {
31
- const response = await axios.head(this.url);
32
- if (response.status === 200) {
33
- logger.mark(`[渲染器:${this.id}][WebSocket] 注册渲染器:${logger.green(this.url)}`);
34
- try {
35
- this.index = render.app({ id: this.id, type: this.type, render: this.render.bind(this) });
36
- }
37
- catch (error) {
38
- logger.error(`[渲染器:${this.id}] 注册渲染器失败:`, error);
39
- }
40
- }
41
- else {
42
- logger.error(`[渲染器:${this.id}] 注册渲染器失败:渲染器发生错误`);
43
- }
44
- }
45
- catch (error) {
46
- logger.error(`[渲染器:${this.id}] 注册渲染器失败:`, error);
47
- }
48
- }
49
- /**
50
- * 创建连接
51
- */
52
- async link() {
53
- return new Promise((resolve, reject) => {
54
- if (this.ws && this.ws.readyState === WebSocket.OPEN) {
55
- return resolve();
56
- }
57
- /** 连接ws */
58
- this.ws = new WebSocket(this.url);
59
- /** 建立连接 */
60
- this.ws.on('open', () => {
61
- logger.mark(`[渲染器:${this.id}][WebSocket] 建立连接:${logger.green(this.url)}`);
62
- /** 监听消息 */
63
- this.ws.on('message', data => this.message(data.toString()));
64
- resolve();
65
- });
66
- /** 监听断开 */
67
- this.ws.once('close', async () => {
68
- /** 停止监听 */
69
- this.ws.removeAllListeners();
70
- });
71
- /** 监听错误 */
72
- this.ws.on('error', async (e) => {
73
- this.ws.close();
74
- });
75
- });
76
- }
77
- /**
78
- * 接受消息
79
- */
80
- async message(str) {
81
- const data = JSON.parse(str);
82
- switch (data.action) {
83
- /** 静态文件 */
84
- case 'static': {
85
- const filePath = decodeURIComponent(data.params.file);
86
- logger.debug(`[渲染器:${this.id}][正向WS] 访问静态文件:${filePath}`);
87
- const file = fs.readFileSync('.' + filePath);
88
- const md5 = createHash('md5').update(file).digest('hex');
89
- const params = data.params.md5?.includes(md5)
90
- ? { echo: data.echo, action: 'static', status: 'ok', data: { verifiedMd5: md5 } }
91
- : { echo: data.echo, action: 'static', status: 'ok', data: { file } };
92
- return this.ws.send(JSON.stringify(params));
93
- }
94
- /** 渲染结果 */
95
- case 'renderRes': {
96
- listener.emit(data.echo, data);
97
- break;
98
- }
99
- /** 超时 */
100
- case 'timeout': {
101
- logger.debug(`[渲染器:${this.id}][正向WS] 处理超时`);
102
- break;
103
- }
104
- /** 未知数据 */
105
- default: {
106
- logger.warn(`[渲染器:${this.id}] 收到未知数据:`, data);
107
- }
108
- }
109
- }
110
- /**
111
- * 渲染标准方法
112
- * @param options 渲染参数
113
- */
114
- async render(options) {
115
- /** 渲染模板 */
116
- let file = options.file;
117
- let action = 'renderHtml';
118
- if (options.file.includes('http') || options.vue) {
119
- action = 'render';
120
- }
121
- else {
122
- file = this.dealTpl(options);
123
- /** 判断是本地karin-puppeteer还是远程 */
124
- if (!/127\.0\.0\.1|localhost/.test(this.url)) {
125
- file = fs.readFileSync(file, 'utf-8').replace(this.reg, '');
126
- }
127
- else {
128
- action = 'render';
129
- file = 'file://' + file;
130
- }
131
- }
132
- if (!file) {
133
- logger.error(`[渲染器:${this.id}:${this.index}] 渲染文件不存在:${options.file}`);
134
- return '';
135
- }
136
- /** 编码 */
137
- file = encodeURIComponent(file);
138
- const data = options;
139
- const echo = randomUUID();
140
- /** 移除掉模板参数 */
141
- if (data.data)
142
- delete data.data;
143
- data.file = file;
144
- const req = JSON.stringify({ echo, action, data });
145
- logger.debug(`[渲染器:${this.id}:${this.index}][正向WS] 请求:${this.url} \nhtml: ${options.file} \ndata: ${JSON.stringify(data)}`);
146
- await this.link();
147
- this.ws.send(req);
148
- return new Promise((resolve, reject) => {
149
- listener.once(echo, (data) => {
150
- if (data.ok)
151
- return resolve(data.data);
152
- reject(new Error(JSON.stringify(data)));
153
- });
154
- });
155
- }
156
- }