@openclaw-china/shared 0.1.0
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/package.json +15 -0
- package/src/http/client.ts +141 -0
- package/src/http/index.ts +2 -0
- package/src/http/retry.ts +110 -0
- package/src/index.ts +7 -0
- package/src/logger/index.ts +1 -0
- package/src/logger/logger.ts +51 -0
- package/src/policy/dm-policy.ts +82 -0
- package/src/policy/group-policy.ts +93 -0
- package/src/policy/index.ts +2 -0
- package/src/types/common.ts +24 -0
- package/tsconfig.json +8 -0
package/package.json
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用 HTTP 客户端封装
|
|
3
|
+
*
|
|
4
|
+
* 提供带超时和错误处理的 HTTP 请求功能
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* HTTP 请求选项
|
|
9
|
+
*/
|
|
10
|
+
export interface HttpRequestOptions {
|
|
11
|
+
/** 请求超时时间(毫秒),默认 30000 */
|
|
12
|
+
timeout?: number;
|
|
13
|
+
/** 请求头 */
|
|
14
|
+
headers?: Record<string, string>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* HTTP 错误
|
|
19
|
+
*/
|
|
20
|
+
export class HttpError extends Error {
|
|
21
|
+
constructor(
|
|
22
|
+
message: string,
|
|
23
|
+
public readonly status: number,
|
|
24
|
+
public readonly body?: string
|
|
25
|
+
) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "HttpError";
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 超时错误
|
|
33
|
+
*/
|
|
34
|
+
export class TimeoutError extends Error {
|
|
35
|
+
constructor(message: string, public readonly timeoutMs: number) {
|
|
36
|
+
super(message);
|
|
37
|
+
this.name = "TimeoutError";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 发送 HTTP POST 请求
|
|
43
|
+
*
|
|
44
|
+
* @param url 请求 URL
|
|
45
|
+
* @param body 请求体
|
|
46
|
+
* @param options 请求选项
|
|
47
|
+
* @returns 响应数据
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```ts
|
|
51
|
+
* const data = await httpPost("https://api.example.com/token", { key: "value" }, { timeout: 10000 });
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export async function httpPost<T = unknown>(
|
|
55
|
+
url: string,
|
|
56
|
+
body: unknown,
|
|
57
|
+
options?: HttpRequestOptions
|
|
58
|
+
): Promise<T> {
|
|
59
|
+
const { timeout = 30000, headers = {} } = options ?? {};
|
|
60
|
+
|
|
61
|
+
const controller = new AbortController();
|
|
62
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const response = await fetch(url, {
|
|
66
|
+
method: "POST",
|
|
67
|
+
headers: {
|
|
68
|
+
"Content-Type": "application/json",
|
|
69
|
+
...headers,
|
|
70
|
+
},
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
signal: controller.signal,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
if (!response.ok) {
|
|
76
|
+
const responseBody = await response.text().catch(() => "");
|
|
77
|
+
throw new HttpError(
|
|
78
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
79
|
+
response.status,
|
|
80
|
+
responseBody
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return (await response.json()) as T;
|
|
85
|
+
} catch (err) {
|
|
86
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
87
|
+
throw new TimeoutError(`Request timeout after ${timeout}ms`, timeout);
|
|
88
|
+
}
|
|
89
|
+
throw err;
|
|
90
|
+
} finally {
|
|
91
|
+
clearTimeout(timeoutId);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 发送 HTTP GET 请求
|
|
97
|
+
*
|
|
98
|
+
* @param url 请求 URL
|
|
99
|
+
* @param options 请求选项
|
|
100
|
+
* @returns 响应数据
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* ```ts
|
|
104
|
+
* const data = await httpGet("https://api.example.com/data", { timeout: 10000 });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export async function httpGet<T = unknown>(
|
|
108
|
+
url: string,
|
|
109
|
+
options?: HttpRequestOptions
|
|
110
|
+
): Promise<T> {
|
|
111
|
+
const { timeout = 30000, headers = {} } = options ?? {};
|
|
112
|
+
|
|
113
|
+
const controller = new AbortController();
|
|
114
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const response = await fetch(url, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
headers,
|
|
120
|
+
signal: controller.signal,
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
if (!response.ok) {
|
|
124
|
+
const responseBody = await response.text().catch(() => "");
|
|
125
|
+
throw new HttpError(
|
|
126
|
+
`HTTP ${response.status}: ${response.statusText}`,
|
|
127
|
+
response.status,
|
|
128
|
+
responseBody
|
|
129
|
+
);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return (await response.json()) as T;
|
|
133
|
+
} catch (err) {
|
|
134
|
+
if (err instanceof Error && err.name === "AbortError") {
|
|
135
|
+
throw new TimeoutError(`Request timeout after ${timeout}ms`, timeout);
|
|
136
|
+
}
|
|
137
|
+
throw err;
|
|
138
|
+
} finally {
|
|
139
|
+
clearTimeout(timeoutId);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP 重试策略
|
|
3
|
+
*
|
|
4
|
+
* 提供可配置的重试逻辑
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* 重试选项
|
|
9
|
+
*/
|
|
10
|
+
export interface RetryOptions {
|
|
11
|
+
/** 最大重试次数,默认 3 */
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
/** 初始延迟时间(毫秒),默认 1000 */
|
|
14
|
+
initialDelay?: number;
|
|
15
|
+
/** 最大延迟时间(毫秒),默认 10000 */
|
|
16
|
+
maxDelay?: number;
|
|
17
|
+
/** 延迟倍数(指数退避),默认 2 */
|
|
18
|
+
backoffMultiplier?: number;
|
|
19
|
+
/** 判断是否应该重试的函数 */
|
|
20
|
+
shouldRetry?: (error: unknown, attempt: number) => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 默认的重试判断函数
|
|
25
|
+
* 对于网络错误和 5xx 错误进行重试
|
|
26
|
+
*/
|
|
27
|
+
export function defaultShouldRetry(error: unknown): boolean {
|
|
28
|
+
if (error instanceof Error) {
|
|
29
|
+
// 网络错误
|
|
30
|
+
if (error.name === "TypeError" || error.name === "TimeoutError") {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
// HTTP 5xx 错误
|
|
34
|
+
if ("status" in error && typeof (error as { status: number }).status === "number") {
|
|
35
|
+
const status = (error as { status: number }).status;
|
|
36
|
+
return status >= 500 && status < 600;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 计算延迟时间(指数退避)
|
|
44
|
+
*/
|
|
45
|
+
function calculateDelay(
|
|
46
|
+
attempt: number,
|
|
47
|
+
initialDelay: number,
|
|
48
|
+
maxDelay: number,
|
|
49
|
+
backoffMultiplier: number
|
|
50
|
+
): number {
|
|
51
|
+
const delay = initialDelay * Math.pow(backoffMultiplier, attempt - 1);
|
|
52
|
+
return Math.min(delay, maxDelay);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 延迟执行
|
|
57
|
+
*/
|
|
58
|
+
function sleep(ms: number): Promise<void> {
|
|
59
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 带重试的异步函数执行器
|
|
64
|
+
*
|
|
65
|
+
* @param fn 要执行的异步函数
|
|
66
|
+
* @param options 重试选项
|
|
67
|
+
* @returns 函数执行结果
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
* ```ts
|
|
71
|
+
* const result = await withRetry(
|
|
72
|
+
* () => httpPost(url, body),
|
|
73
|
+
* { maxRetries: 3, initialDelay: 1000 }
|
|
74
|
+
* );
|
|
75
|
+
* ```
|
|
76
|
+
*/
|
|
77
|
+
export async function withRetry<T>(
|
|
78
|
+
fn: () => Promise<T>,
|
|
79
|
+
options?: RetryOptions
|
|
80
|
+
): Promise<T> {
|
|
81
|
+
const {
|
|
82
|
+
maxRetries = 3,
|
|
83
|
+
initialDelay = 1000,
|
|
84
|
+
maxDelay = 10000,
|
|
85
|
+
backoffMultiplier = 2,
|
|
86
|
+
shouldRetry = defaultShouldRetry,
|
|
87
|
+
} = options ?? {};
|
|
88
|
+
|
|
89
|
+
let lastError: unknown;
|
|
90
|
+
|
|
91
|
+
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
92
|
+
try {
|
|
93
|
+
return await fn();
|
|
94
|
+
} catch (error) {
|
|
95
|
+
lastError = error;
|
|
96
|
+
|
|
97
|
+
// 如果是最后一次尝试或不应该重试,直接抛出错误
|
|
98
|
+
if (attempt > maxRetries || !shouldRetry(error, attempt)) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 计算延迟并等待
|
|
103
|
+
const delay = calculateDelay(attempt, initialDelay, maxDelay, backoffMultiplier);
|
|
104
|
+
await sleep(delay);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// 理论上不会到达这里,但为了类型安全
|
|
109
|
+
throw lastError;
|
|
110
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./logger.js";
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用日志工具
|
|
3
|
+
*
|
|
4
|
+
* 提供分级日志功能:
|
|
5
|
+
* - info: 关键业务日志(默认显示)
|
|
6
|
+
* - debug: 调试日志(带 [DEBUG] 标记)
|
|
7
|
+
* - error: 错误日志
|
|
8
|
+
* - warn: 警告日志
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export type LogLevel = "debug" | "info" | "warn" | "error";
|
|
12
|
+
|
|
13
|
+
export interface Logger {
|
|
14
|
+
debug: (msg: string) => void;
|
|
15
|
+
info: (msg: string) => void;
|
|
16
|
+
warn: (msg: string) => void;
|
|
17
|
+
error: (msg: string) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface LoggerOptions {
|
|
21
|
+
log?: (msg: string) => void;
|
|
22
|
+
error?: (msg: string) => void;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 创建带前缀的日志器
|
|
27
|
+
*
|
|
28
|
+
* @param prefix 日志前缀(如 "dingtalk", "feishu")
|
|
29
|
+
* @param opts 可选的日志输出函数
|
|
30
|
+
* @returns Logger 实例
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* const logger = createLogger("dingtalk");
|
|
35
|
+
* logger.debug("connecting..."); // [dingtalk] [DEBUG] connecting...
|
|
36
|
+
* logger.info("connected"); // [dingtalk] connected
|
|
37
|
+
* logger.warn("slow response"); // [dingtalk] [WARN] slow response
|
|
38
|
+
* logger.error("failed"); // [dingtalk] [ERROR] failed
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function createLogger(prefix: string, opts?: LoggerOptions): Logger {
|
|
42
|
+
const logFn = opts?.log ?? console.log;
|
|
43
|
+
const errorFn = opts?.error ?? console.error;
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
debug: (msg: string) => logFn(`[${prefix}] [DEBUG] ${msg}`),
|
|
47
|
+
info: (msg: string) => logFn(`[${prefix}] ${msg}`),
|
|
48
|
+
warn: (msg: string) => logFn(`[${prefix}] [WARN] ${msg}`),
|
|
49
|
+
error: (msg: string) => errorFn(`[${prefix}] [ERROR] ${msg}`),
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DM 策略引擎
|
|
3
|
+
*
|
|
4
|
+
* 实现 open/pairing/allowlist 策略检查
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* DM 策略类型
|
|
9
|
+
* - open: 允许所有单聊消息
|
|
10
|
+
* - pairing: 配对模式(允许所有,配对逻辑由上层处理)
|
|
11
|
+
* - allowlist: 仅允许白名单中的发送者
|
|
12
|
+
*/
|
|
13
|
+
export type DmPolicyType = "open" | "pairing" | "allowlist";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 策略检查结果
|
|
17
|
+
*/
|
|
18
|
+
export interface PolicyCheckResult {
|
|
19
|
+
/** 是否允许处理该消息 */
|
|
20
|
+
allowed: boolean;
|
|
21
|
+
/** 拒绝原因(如果被拒绝) */
|
|
22
|
+
reason?: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* DM 策略检查参数
|
|
27
|
+
*/
|
|
28
|
+
export interface DmPolicyCheckParams {
|
|
29
|
+
/** DM 策略类型 */
|
|
30
|
+
dmPolicy: DmPolicyType;
|
|
31
|
+
/** 发送者 ID */
|
|
32
|
+
senderId: string;
|
|
33
|
+
/** 白名单(allowlist 策略时使用) */
|
|
34
|
+
allowFrom?: string[];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 检查单聊策略
|
|
39
|
+
*
|
|
40
|
+
* @param params 检查参数
|
|
41
|
+
* @returns 策略检查结果
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* // 开放策略
|
|
46
|
+
* checkDmPolicy({ dmPolicy: "open", senderId: "user1" });
|
|
47
|
+
* // => { allowed: true }
|
|
48
|
+
*
|
|
49
|
+
* // 白名单策略
|
|
50
|
+
* checkDmPolicy({ dmPolicy: "allowlist", senderId: "user1", allowFrom: ["user1", "user2"] });
|
|
51
|
+
* // => { allowed: true }
|
|
52
|
+
*
|
|
53
|
+
* checkDmPolicy({ dmPolicy: "allowlist", senderId: "user3", allowFrom: ["user1", "user2"] });
|
|
54
|
+
* // => { allowed: false, reason: "sender user3 not in DM allowlist" }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
export function checkDmPolicy(params: DmPolicyCheckParams): PolicyCheckResult {
|
|
58
|
+
const { dmPolicy, senderId, allowFrom = [] } = params;
|
|
59
|
+
|
|
60
|
+
switch (dmPolicy) {
|
|
61
|
+
case "open":
|
|
62
|
+
// 开放策略:允许所有单聊消息
|
|
63
|
+
return { allowed: true };
|
|
64
|
+
|
|
65
|
+
case "pairing":
|
|
66
|
+
// 配对策略:允许所有单聊消息(配对逻辑由上层处理)
|
|
67
|
+
return { allowed: true };
|
|
68
|
+
|
|
69
|
+
case "allowlist":
|
|
70
|
+
// 白名单策略:仅允许 allowFrom 中的发送者
|
|
71
|
+
if (allowFrom.includes(senderId)) {
|
|
72
|
+
return { allowed: true };
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
allowed: false,
|
|
76
|
+
reason: `sender ${senderId} not in DM allowlist`,
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
default:
|
|
80
|
+
return { allowed: true };
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 群组策略引擎
|
|
3
|
+
*
|
|
4
|
+
* 实现 open/allowlist/disabled 策略检查
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { PolicyCheckResult } from "./dm-policy.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 群组策略类型
|
|
11
|
+
* - open: 允许所有群聊消息
|
|
12
|
+
* - allowlist: 仅允许白名单中的群组
|
|
13
|
+
* - disabled: 禁用所有群聊消息
|
|
14
|
+
*/
|
|
15
|
+
export type GroupPolicyType = "open" | "allowlist" | "disabled";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* 群组策略检查参数
|
|
19
|
+
*/
|
|
20
|
+
export interface GroupPolicyCheckParams {
|
|
21
|
+
/** 群组策略类型 */
|
|
22
|
+
groupPolicy: GroupPolicyType;
|
|
23
|
+
/** 会话 ID(群组 ID) */
|
|
24
|
+
conversationId: string;
|
|
25
|
+
/** 群组白名单(allowlist 策略时使用) */
|
|
26
|
+
groupAllowFrom?: string[];
|
|
27
|
+
/** 是否要求 @提及机器人 */
|
|
28
|
+
requireMention: boolean;
|
|
29
|
+
/** 是否 @提及了机器人 */
|
|
30
|
+
mentionedBot: boolean;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 检查群聊策略
|
|
35
|
+
*
|
|
36
|
+
* @param params 检查参数
|
|
37
|
+
* @returns 策略检查结果
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* ```ts
|
|
41
|
+
* // 禁用策略
|
|
42
|
+
* checkGroupPolicy({ groupPolicy: "disabled", conversationId: "g1", requireMention: false, mentionedBot: false });
|
|
43
|
+
* // => { allowed: false, reason: "group messages disabled" }
|
|
44
|
+
*
|
|
45
|
+
* // 开放策略 + 要求 @提及
|
|
46
|
+
* checkGroupPolicy({ groupPolicy: "open", conversationId: "g1", requireMention: true, mentionedBot: false });
|
|
47
|
+
* // => { allowed: false, reason: "message did not mention bot" }
|
|
48
|
+
*
|
|
49
|
+
* // 白名单策略
|
|
50
|
+
* checkGroupPolicy({ groupPolicy: "allowlist", conversationId: "g1", groupAllowFrom: ["g1"], requireMention: false, mentionedBot: false });
|
|
51
|
+
* // => { allowed: true }
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export function checkGroupPolicy(params: GroupPolicyCheckParams): PolicyCheckResult {
|
|
55
|
+
const { groupPolicy, conversationId, groupAllowFrom = [], requireMention, mentionedBot } = params;
|
|
56
|
+
|
|
57
|
+
// 首先检查群聊策略
|
|
58
|
+
switch (groupPolicy) {
|
|
59
|
+
case "disabled":
|
|
60
|
+
// 禁用策略:拒绝所有群聊消息
|
|
61
|
+
return {
|
|
62
|
+
allowed: false,
|
|
63
|
+
reason: "group messages disabled",
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
case "allowlist":
|
|
67
|
+
// 白名单策略:仅允许 groupAllowFrom 中的群组
|
|
68
|
+
if (!groupAllowFrom.includes(conversationId)) {
|
|
69
|
+
return {
|
|
70
|
+
allowed: false,
|
|
71
|
+
reason: `group ${conversationId} not in allowlist`,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
break;
|
|
75
|
+
|
|
76
|
+
case "open":
|
|
77
|
+
// 开放策略:允许所有群聊
|
|
78
|
+
break;
|
|
79
|
+
|
|
80
|
+
default:
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// 然后检查 @提及要求
|
|
85
|
+
if (requireMention && !mentionedBot) {
|
|
86
|
+
return {
|
|
87
|
+
allowed: false,
|
|
88
|
+
reason: "message did not mention bot",
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return { allowed: true };
|
|
93
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// 共享类型定义
|
|
2
|
+
|
|
3
|
+
export type DmPolicy = "open" | "pairing" | "allowlist";
|
|
4
|
+
export type GroupPolicy = "open" | "allowlist" | "disabled";
|
|
5
|
+
|
|
6
|
+
export interface ParsedMessage {
|
|
7
|
+
chatId: string;
|
|
8
|
+
messageId: string;
|
|
9
|
+
senderId: string;
|
|
10
|
+
senderName?: string;
|
|
11
|
+
chatType: "direct" | "group";
|
|
12
|
+
content: string;
|
|
13
|
+
contentType: string;
|
|
14
|
+
mentionedBot: boolean;
|
|
15
|
+
replyToMessageId?: string;
|
|
16
|
+
timestamp: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface HistoryEntry {
|
|
20
|
+
sender: string;
|
|
21
|
+
body: string;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
messageId: string;
|
|
24
|
+
}
|