@openclaw/zalouser 2026.3.11 → 2026.3.12

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.
@@ -0,0 +1,537 @@
1
+ import { TextStyle, type Style } from "./zca-client.js";
2
+
3
+ type InlineStyle = (typeof TextStyle)[keyof typeof TextStyle];
4
+
5
+ type LineStyle = {
6
+ lineIndex: number;
7
+ style: InlineStyle;
8
+ indentSize?: number;
9
+ };
10
+
11
+ type Segment = {
12
+ text: string;
13
+ styles: InlineStyle[];
14
+ };
15
+
16
+ type InlineMarker = {
17
+ pattern: RegExp;
18
+ extractText: (match: RegExpExecArray) => string;
19
+ resolveStyles?: (match: RegExpExecArray) => InlineStyle[];
20
+ literal?: boolean;
21
+ };
22
+
23
+ type ResolvedInlineMatch = {
24
+ match: RegExpExecArray;
25
+ marker: InlineMarker;
26
+ styles: InlineStyle[];
27
+ text: string;
28
+ priority: number;
29
+ };
30
+
31
+ type FenceMarker = {
32
+ char: "`" | "~";
33
+ length: number;
34
+ indent: number;
35
+ };
36
+
37
+ type ActiveFence = FenceMarker & {
38
+ quoteIndent: number;
39
+ };
40
+
41
+ const TAG_STYLE_MAP: Record<string, InlineStyle | null> = {
42
+ red: TextStyle.Red,
43
+ orange: TextStyle.Orange,
44
+ yellow: TextStyle.Yellow,
45
+ green: TextStyle.Green,
46
+ small: null,
47
+ big: TextStyle.Big,
48
+ underline: TextStyle.Underline,
49
+ };
50
+
51
+ const INLINE_MARKERS: InlineMarker[] = [
52
+ {
53
+ pattern: /`([^`\n]+)`/g,
54
+ extractText: (match) => match[0],
55
+ literal: true,
56
+ },
57
+ {
58
+ pattern: /\\([*_~#\\{}>+\-`])/g,
59
+ extractText: (match) => match[1],
60
+ literal: true,
61
+ },
62
+ {
63
+ pattern: new RegExp(`\\{(${Object.keys(TAG_STYLE_MAP).join("|")})\\}(.+?)\\{/\\1\\}`, "g"),
64
+ extractText: (match) => match[2],
65
+ resolveStyles: (match) => {
66
+ const style = TAG_STYLE_MAP[match[1]];
67
+ return style ? [style] : [];
68
+ },
69
+ },
70
+ {
71
+ pattern: /(?<!\*)\*\*\*(?=\S)([^\n]*?\S)(?<!\*)\*\*\*(?!\*)/g,
72
+ extractText: (match) => match[1],
73
+ resolveStyles: () => [TextStyle.Bold, TextStyle.Italic],
74
+ },
75
+ {
76
+ pattern: /(?<!\*)\*\*(?![\s*])([^\n]*?\S)(?<!\*)\*\*(?!\*)/g,
77
+ extractText: (match) => match[1],
78
+ resolveStyles: () => [TextStyle.Bold],
79
+ },
80
+ {
81
+ pattern: /(?<![\w_])__(?![\s_])([^\n]*?\S)(?<!_)__(?![\w_])/g,
82
+ extractText: (match) => match[1],
83
+ resolveStyles: () => [TextStyle.Bold],
84
+ },
85
+ {
86
+ pattern: /(?<!~)~~(?=\S)([^\n]*?\S)(?<!~)~~(?!~)/g,
87
+ extractText: (match) => match[1],
88
+ resolveStyles: () => [TextStyle.StrikeThrough],
89
+ },
90
+ {
91
+ pattern: /(?<!\*)\*(?![\s*])([^\n]*?\S)(?<!\*)\*(?!\*)/g,
92
+ extractText: (match) => match[1],
93
+ resolveStyles: () => [TextStyle.Italic],
94
+ },
95
+ {
96
+ pattern: /(?<![\w_])_(?![\s_])([^\n]*?\S)(?<!_)_(?![\w_])/g,
97
+ extractText: (match) => match[1],
98
+ resolveStyles: () => [TextStyle.Italic],
99
+ },
100
+ ];
101
+
102
+ export function parseZalouserTextStyles(input: string): { text: string; styles: Style[] } {
103
+ const allStyles: Style[] = [];
104
+
105
+ const escapeMap: string[] = [];
106
+ const lines = input.replace(/\r\n?/g, "\n").split("\n");
107
+ const lineStyles: LineStyle[] = [];
108
+ const processedLines: string[] = [];
109
+ let activeFence: ActiveFence | null = null;
110
+
111
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
112
+ const rawLine = lines[lineIndex];
113
+ const { text: unquotedLine, indent: baseIndent } = stripQuotePrefix(rawLine);
114
+
115
+ if (activeFence) {
116
+ const codeLine =
117
+ activeFence.quoteIndent > 0
118
+ ? stripQuotePrefix(rawLine, activeFence.quoteIndent).text
119
+ : rawLine;
120
+ if (isClosingFence(codeLine, activeFence)) {
121
+ activeFence = null;
122
+ continue;
123
+ }
124
+ processedLines.push(
125
+ escapeLiteralText(
126
+ normalizeCodeBlockLeadingWhitespace(stripCodeFenceIndent(codeLine, activeFence.indent)),
127
+ escapeMap,
128
+ ),
129
+ );
130
+ continue;
131
+ }
132
+
133
+ let line = unquotedLine;
134
+ const openingFence = resolveOpeningFence(rawLine);
135
+ if (openingFence) {
136
+ const fenceLine = openingFence.quoteIndent > 0 ? unquotedLine : rawLine;
137
+ if (!hasClosingFence(lines, lineIndex + 1, openingFence)) {
138
+ processedLines.push(escapeLiteralText(fenceLine, escapeMap));
139
+ activeFence = openingFence;
140
+ continue;
141
+ }
142
+ activeFence = openingFence;
143
+ continue;
144
+ }
145
+
146
+ const outputLineIndex = processedLines.length;
147
+ if (isIndentedCodeBlockLine(line)) {
148
+ if (baseIndent > 0) {
149
+ lineStyles.push({
150
+ lineIndex: outputLineIndex,
151
+ style: TextStyle.Indent,
152
+ indentSize: baseIndent,
153
+ });
154
+ }
155
+ processedLines.push(escapeLiteralText(normalizeCodeBlockLeadingWhitespace(line), escapeMap));
156
+ continue;
157
+ }
158
+
159
+ const { text: markdownLine, size: markdownPadding } = stripOptionalMarkdownPadding(line);
160
+
161
+ const headingMatch = markdownLine.match(/^(#{1,4})\s(.*)$/);
162
+ if (headingMatch) {
163
+ const depth = headingMatch[1].length;
164
+ lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Bold });
165
+ if (depth === 1) {
166
+ lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.Big });
167
+ }
168
+ if (baseIndent > 0) {
169
+ lineStyles.push({
170
+ lineIndex: outputLineIndex,
171
+ style: TextStyle.Indent,
172
+ indentSize: baseIndent,
173
+ });
174
+ }
175
+ processedLines.push(headingMatch[2]);
176
+ continue;
177
+ }
178
+
179
+ const indentMatch = markdownLine.match(/^(\s+)(.*)$/);
180
+ let indentLevel = 0;
181
+ let content = markdownLine;
182
+ if (indentMatch) {
183
+ indentLevel = clampIndent(indentMatch[1].length);
184
+ content = indentMatch[2];
185
+ }
186
+ const totalIndent = Math.min(5, baseIndent + indentLevel);
187
+
188
+ if (/^[-*+]\s\[[ xX]\]\s/.test(content)) {
189
+ if (totalIndent > 0) {
190
+ lineStyles.push({
191
+ lineIndex: outputLineIndex,
192
+ style: TextStyle.Indent,
193
+ indentSize: totalIndent,
194
+ });
195
+ }
196
+ processedLines.push(content);
197
+ continue;
198
+ }
199
+
200
+ const orderedListMatch = content.match(/^(\d+)\.\s(.*)$/);
201
+ if (orderedListMatch) {
202
+ if (totalIndent > 0) {
203
+ lineStyles.push({
204
+ lineIndex: outputLineIndex,
205
+ style: TextStyle.Indent,
206
+ indentSize: totalIndent,
207
+ });
208
+ }
209
+ lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.OrderedList });
210
+ processedLines.push(orderedListMatch[2]);
211
+ continue;
212
+ }
213
+
214
+ const unorderedListMatch = content.match(/^[-*+]\s(.*)$/);
215
+ if (unorderedListMatch) {
216
+ if (totalIndent > 0) {
217
+ lineStyles.push({
218
+ lineIndex: outputLineIndex,
219
+ style: TextStyle.Indent,
220
+ indentSize: totalIndent,
221
+ });
222
+ }
223
+ lineStyles.push({ lineIndex: outputLineIndex, style: TextStyle.UnorderedList });
224
+ processedLines.push(unorderedListMatch[1]);
225
+ continue;
226
+ }
227
+
228
+ if (markdownPadding > 0) {
229
+ if (baseIndent > 0) {
230
+ lineStyles.push({
231
+ lineIndex: outputLineIndex,
232
+ style: TextStyle.Indent,
233
+ indentSize: baseIndent,
234
+ });
235
+ }
236
+ processedLines.push(line);
237
+ continue;
238
+ }
239
+
240
+ if (totalIndent > 0) {
241
+ lineStyles.push({
242
+ lineIndex: outputLineIndex,
243
+ style: TextStyle.Indent,
244
+ indentSize: totalIndent,
245
+ });
246
+ processedLines.push(content);
247
+ continue;
248
+ }
249
+
250
+ processedLines.push(line);
251
+ }
252
+
253
+ const segments = parseInlineSegments(processedLines.join("\n"));
254
+
255
+ let plainText = "";
256
+ for (const segment of segments) {
257
+ const start = plainText.length;
258
+ plainText += segment.text;
259
+ for (const style of segment.styles) {
260
+ allStyles.push({ start, len: segment.text.length, st: style } as Style);
261
+ }
262
+ }
263
+
264
+ if (escapeMap.length > 0) {
265
+ const escapeRegex = /\x01(\d+)\x02/g;
266
+ const shifts: Array<{ pos: number; delta: number }> = [];
267
+ let cumulativeDelta = 0;
268
+
269
+ for (const match of plainText.matchAll(escapeRegex)) {
270
+ const escapeIndex = Number.parseInt(match[1], 10);
271
+ cumulativeDelta += match[0].length - escapeMap[escapeIndex].length;
272
+ shifts.push({ pos: (match.index ?? 0) + match[0].length, delta: cumulativeDelta });
273
+ }
274
+
275
+ for (const style of allStyles) {
276
+ let startDelta = 0;
277
+ let endDelta = 0;
278
+ const end = style.start + style.len;
279
+ for (const shift of shifts) {
280
+ if (shift.pos <= style.start) {
281
+ startDelta = shift.delta;
282
+ }
283
+ if (shift.pos <= end) {
284
+ endDelta = shift.delta;
285
+ }
286
+ }
287
+ style.start -= startDelta;
288
+ style.len -= endDelta - startDelta;
289
+ }
290
+
291
+ plainText = plainText.replace(
292
+ escapeRegex,
293
+ (_match, index) => escapeMap[Number.parseInt(index, 10)],
294
+ );
295
+ }
296
+
297
+ const finalLines = plainText.split("\n");
298
+ let offset = 0;
299
+ for (let lineIndex = 0; lineIndex < finalLines.length; lineIndex += 1) {
300
+ const lineLength = finalLines[lineIndex].length;
301
+ if (lineLength > 0) {
302
+ for (const lineStyle of lineStyles) {
303
+ if (lineStyle.lineIndex !== lineIndex) {
304
+ continue;
305
+ }
306
+
307
+ if (lineStyle.style === TextStyle.Indent) {
308
+ allStyles.push({
309
+ start: offset,
310
+ len: lineLength,
311
+ st: TextStyle.Indent,
312
+ indentSize: lineStyle.indentSize,
313
+ });
314
+ } else {
315
+ allStyles.push({ start: offset, len: lineLength, st: lineStyle.style } as Style);
316
+ }
317
+ }
318
+ }
319
+ offset += lineLength + 1;
320
+ }
321
+
322
+ return { text: plainText, styles: allStyles };
323
+ }
324
+
325
+ function clampIndent(spaceCount: number): number {
326
+ return Math.min(5, Math.max(1, Math.floor(spaceCount / 2)));
327
+ }
328
+
329
+ function stripOptionalMarkdownPadding(line: string): { text: string; size: number } {
330
+ const match = line.match(/^( {1,3})(?=\S)/);
331
+ if (!match) {
332
+ return { text: line, size: 0 };
333
+ }
334
+ return {
335
+ text: line.slice(match[1].length),
336
+ size: match[1].length,
337
+ };
338
+ }
339
+
340
+ function hasClosingFence(lines: string[], startIndex: number, fence: ActiveFence): boolean {
341
+ for (let index = startIndex; index < lines.length; index += 1) {
342
+ const candidate =
343
+ fence.quoteIndent > 0 ? stripQuotePrefix(lines[index], fence.quoteIndent).text : lines[index];
344
+ if (isClosingFence(candidate, fence)) {
345
+ return true;
346
+ }
347
+ }
348
+ return false;
349
+ }
350
+
351
+ function resolveOpeningFence(line: string): ActiveFence | null {
352
+ const directFence = parseFenceMarker(line);
353
+ if (directFence) {
354
+ return { ...directFence, quoteIndent: 0 };
355
+ }
356
+
357
+ const quoted = stripQuotePrefix(line);
358
+ if (quoted.indent === 0) {
359
+ return null;
360
+ }
361
+
362
+ const quotedFence = parseFenceMarker(quoted.text);
363
+ if (!quotedFence) {
364
+ return null;
365
+ }
366
+
367
+ return {
368
+ ...quotedFence,
369
+ quoteIndent: quoted.indent,
370
+ };
371
+ }
372
+
373
+ function stripQuotePrefix(
374
+ line: string,
375
+ maxDepth = Number.POSITIVE_INFINITY,
376
+ ): { text: string; indent: number } {
377
+ let cursor = 0;
378
+ while (cursor < line.length && cursor < 3 && line[cursor] === " ") {
379
+ cursor += 1;
380
+ }
381
+
382
+ let removedDepth = 0;
383
+ let consumedCursor = cursor;
384
+ while (removedDepth < maxDepth && consumedCursor < line.length && line[consumedCursor] === ">") {
385
+ removedDepth += 1;
386
+ consumedCursor += 1;
387
+ if (line[consumedCursor] === " ") {
388
+ consumedCursor += 1;
389
+ }
390
+ }
391
+
392
+ if (removedDepth === 0) {
393
+ return { text: line, indent: 0 };
394
+ }
395
+
396
+ return {
397
+ text: line.slice(consumedCursor),
398
+ indent: Math.min(5, removedDepth),
399
+ };
400
+ }
401
+
402
+ function parseFenceMarker(line: string): FenceMarker | null {
403
+ const match = line.match(/^([ ]{0,3})(`{3,}|~{3,})(.*)$/);
404
+ if (!match) {
405
+ return null;
406
+ }
407
+
408
+ const marker = match[2];
409
+ const char = marker[0];
410
+ if (char !== "`" && char !== "~") {
411
+ return null;
412
+ }
413
+
414
+ return {
415
+ char,
416
+ length: marker.length,
417
+ indent: match[1].length,
418
+ };
419
+ }
420
+
421
+ function isClosingFence(line: string, fence: FenceMarker): boolean {
422
+ const match = line.match(/^([ ]{0,3})(`{3,}|~{3,})[ \t]*$/);
423
+ if (!match) {
424
+ return false;
425
+ }
426
+ return match[2][0] === fence.char && match[2].length >= fence.length;
427
+ }
428
+
429
+ function escapeLiteralText(input: string, escapeMap: string[]): string {
430
+ return input.replace(/[\\*_~{}`]/g, (ch) => {
431
+ const index = escapeMap.length;
432
+ escapeMap.push(ch);
433
+ return `\x01${index}\x02`;
434
+ });
435
+ }
436
+
437
+ function parseInlineSegments(text: string, inheritedStyles: InlineStyle[] = []): Segment[] {
438
+ const segments: Segment[] = [];
439
+ let cursor = 0;
440
+
441
+ while (cursor < text.length) {
442
+ const nextMatch = findNextInlineMatch(text, cursor);
443
+ if (!nextMatch) {
444
+ pushSegment(segments, text.slice(cursor), inheritedStyles);
445
+ break;
446
+ }
447
+
448
+ if (nextMatch.match.index > cursor) {
449
+ pushSegment(segments, text.slice(cursor, nextMatch.match.index), inheritedStyles);
450
+ }
451
+
452
+ const combinedStyles = [...inheritedStyles, ...nextMatch.styles];
453
+ if (nextMatch.marker.literal) {
454
+ pushSegment(segments, nextMatch.text, combinedStyles);
455
+ } else {
456
+ segments.push(...parseInlineSegments(nextMatch.text, combinedStyles));
457
+ }
458
+
459
+ cursor = nextMatch.match.index + nextMatch.match[0].length;
460
+ }
461
+
462
+ return segments;
463
+ }
464
+
465
+ function findNextInlineMatch(text: string, startIndex: number): ResolvedInlineMatch | null {
466
+ let bestMatch: ResolvedInlineMatch | null = null;
467
+
468
+ for (const [priority, marker] of INLINE_MARKERS.entries()) {
469
+ const regex = new RegExp(marker.pattern.source, marker.pattern.flags);
470
+ regex.lastIndex = startIndex;
471
+ const match = regex.exec(text);
472
+ if (!match) {
473
+ continue;
474
+ }
475
+
476
+ if (
477
+ bestMatch &&
478
+ (match.index > bestMatch.match.index ||
479
+ (match.index === bestMatch.match.index && priority > bestMatch.priority))
480
+ ) {
481
+ continue;
482
+ }
483
+
484
+ bestMatch = {
485
+ match,
486
+ marker,
487
+ text: marker.extractText(match),
488
+ styles: marker.resolveStyles?.(match) ?? [],
489
+ priority,
490
+ };
491
+ }
492
+
493
+ return bestMatch;
494
+ }
495
+
496
+ function pushSegment(segments: Segment[], text: string, styles: InlineStyle[]): void {
497
+ if (!text) {
498
+ return;
499
+ }
500
+
501
+ const lastSegment = segments.at(-1);
502
+ if (lastSegment && sameStyles(lastSegment.styles, styles)) {
503
+ lastSegment.text += text;
504
+ return;
505
+ }
506
+
507
+ segments.push({
508
+ text,
509
+ styles: [...styles],
510
+ });
511
+ }
512
+
513
+ function sameStyles(left: InlineStyle[], right: InlineStyle[]): boolean {
514
+ return left.length === right.length && left.every((style, index) => style === right[index]);
515
+ }
516
+
517
+ function normalizeCodeBlockLeadingWhitespace(line: string): string {
518
+ return line.replace(/^[ \t]+/, (leadingWhitespace) =>
519
+ leadingWhitespace.replace(/\t/g, "\u00A0\u00A0\u00A0\u00A0").replace(/ /g, "\u00A0"),
520
+ );
521
+ }
522
+
523
+ function isIndentedCodeBlockLine(line: string): boolean {
524
+ return /^(?: {4,}|\t)/.test(line);
525
+ }
526
+
527
+ function stripCodeFenceIndent(line: string, indent: number): string {
528
+ let consumed = 0;
529
+ let cursor = 0;
530
+
531
+ while (cursor < line.length && consumed < indent && line[cursor] === " ") {
532
+ cursor += 1;
533
+ consumed += 1;
534
+ }
535
+
536
+ return line.slice(cursor);
537
+ }
package/src/types.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { Style } from "./zca-client.js";
2
+
1
3
  export type ZcaFriend = {
2
4
  userId: string;
3
5
  displayName: string;
@@ -59,6 +61,10 @@ export type ZaloSendOptions = {
59
61
  caption?: string;
60
62
  isGroup?: boolean;
61
63
  mediaLocalRoots?: readonly string[];
64
+ textMode?: "markdown" | "plain";
65
+ textChunkMode?: "length" | "newline";
66
+ textChunkLimit?: number;
67
+ textStyles?: Style[];
62
68
  };
63
69
 
64
70
  export type ZaloSendResult = {
@@ -91,6 +97,7 @@ type ZalouserSharedConfig = {
91
97
  enabled?: boolean;
92
98
  name?: string;
93
99
  profile?: string;
100
+ dangerouslyAllowNameMatching?: boolean;
94
101
  dmPolicy?: "pairing" | "allowlist" | "open" | "disabled";
95
102
  allowFrom?: Array<string | number>;
96
103
  historyLimit?: number;
package/src/zalo-js.ts CHANGED
@@ -20,6 +20,7 @@ import type {
20
20
  } from "./types.js";
21
21
  import {
22
22
  LoginQRCallbackEventType,
23
+ TextStyle,
23
24
  ThreadType,
24
25
  Zalo,
25
26
  type API,
@@ -136,6 +137,39 @@ function toErrorMessage(error: unknown): string {
136
137
  return String(error);
137
138
  }
138
139
 
140
+ function clampTextStyles(
141
+ text: string,
142
+ styles?: ZaloSendOptions["textStyles"],
143
+ ): ZaloSendOptions["textStyles"] {
144
+ if (!styles || styles.length === 0) {
145
+ return undefined;
146
+ }
147
+ const maxLength = text.length;
148
+ const clamped = styles
149
+ .map((style) => {
150
+ const start = Math.max(0, Math.min(style.start, maxLength));
151
+ const end = Math.min(style.start + style.len, maxLength);
152
+ if (end <= start) {
153
+ return null;
154
+ }
155
+ if (style.st === TextStyle.Indent) {
156
+ return {
157
+ start,
158
+ len: end - start,
159
+ st: style.st,
160
+ indentSize: style.indentSize,
161
+ };
162
+ }
163
+ return {
164
+ start,
165
+ len: end - start,
166
+ st: style.st,
167
+ };
168
+ })
169
+ .filter((style): style is NonNullable<typeof style> => style !== null);
170
+ return clamped.length > 0 ? clamped : undefined;
171
+ }
172
+
139
173
  function toNumberId(value: unknown): string {
140
174
  if (typeof value === "number" && Number.isFinite(value)) {
141
175
  return String(Math.trunc(value));
@@ -1018,11 +1052,16 @@ export async function sendZaloTextMessage(
1018
1052
  kind: media.kind,
1019
1053
  });
1020
1054
  const payloadText = (text || options.caption || "").slice(0, 2000);
1055
+ const textStyles = clampTextStyles(payloadText, options.textStyles);
1021
1056
 
1022
1057
  if (media.kind === "audio") {
1023
1058
  let textMessageId: string | undefined;
1024
1059
  if (payloadText) {
1025
- const textResponse = await api.sendMessage(payloadText, trimmedThreadId, type);
1060
+ const textResponse = await api.sendMessage(
1061
+ textStyles ? { msg: payloadText, styles: textStyles } : payloadText,
1062
+ trimmedThreadId,
1063
+ type,
1064
+ );
1026
1065
  textMessageId = extractSendMessageId(textResponse);
1027
1066
  }
1028
1067
 
@@ -1055,6 +1094,7 @@ export async function sendZaloTextMessage(
1055
1094
  const response = await api.sendMessage(
1056
1095
  {
1057
1096
  msg: payloadText,
1097
+ ...(textStyles ? { styles: textStyles } : {}),
1058
1098
  attachments: [
1059
1099
  {
1060
1100
  data: media.buffer,
@@ -1071,7 +1111,13 @@ export async function sendZaloTextMessage(
1071
1111
  return { ok: true, messageId: extractSendMessageId(response) };
1072
1112
  }
1073
1113
 
1074
- const response = await api.sendMessage(text.slice(0, 2000), trimmedThreadId, type);
1114
+ const payloadText = text.slice(0, 2000);
1115
+ const textStyles = clampTextStyles(payloadText, options.textStyles);
1116
+ const response = await api.sendMessage(
1117
+ textStyles ? { msg: payloadText, styles: textStyles } : payloadText,
1118
+ trimmedThreadId,
1119
+ type,
1120
+ );
1075
1121
  return { ok: true, messageId: extractSendMessageId(response) };
1076
1122
  } catch (error) {
1077
1123
  return { ok: false, error: toErrorMessage(error) };
package/src/zca-client.ts CHANGED
@@ -28,6 +28,39 @@ export const Reactions = ReactionsRuntime as Record<string, string> & {
28
28
  NONE: string;
29
29
  };
30
30
 
31
+ // Mirror zca-js sendMessage style constants locally because the package root
32
+ // typing surface does not consistently expose TextStyle/Style to tsgo.
33
+ export const TextStyle = {
34
+ Bold: "b",
35
+ Italic: "i",
36
+ Underline: "u",
37
+ StrikeThrough: "s",
38
+ Red: "c_db342e",
39
+ Orange: "c_f27806",
40
+ Yellow: "c_f7b503",
41
+ Green: "c_15a85f",
42
+ Small: "f_13",
43
+ Big: "f_18",
44
+ UnorderedList: "lst_1",
45
+ OrderedList: "lst_2",
46
+ Indent: "ind_$",
47
+ } as const;
48
+
49
+ type TextStyleValue = (typeof TextStyle)[keyof typeof TextStyle];
50
+
51
+ export type Style =
52
+ | {
53
+ start: number;
54
+ len: number;
55
+ st: Exclude<TextStyleValue, typeof TextStyle.Indent>;
56
+ }
57
+ | {
58
+ start: number;
59
+ len: number;
60
+ st: typeof TextStyle.Indent;
61
+ indentSize?: number;
62
+ };
63
+
31
64
  export type Credentials = {
32
65
  imei: string;
33
66
  cookie: unknown;