@bruch/max-client 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.
Files changed (174) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +168 -0
  3. package/UPSTREAM.md +10 -0
  4. package/dist/api/account.d.ts +24 -0
  5. package/dist/api/account.d.ts.map +1 -0
  6. package/dist/api/auth.d.ts +57 -0
  7. package/dist/api/auth.d.ts.map +1 -0
  8. package/dist/api/bots.d.ts +8 -0
  9. package/dist/api/bots.d.ts.map +1 -0
  10. package/dist/api/chats.d.ts +41 -0
  11. package/dist/api/chats.d.ts.map +1 -0
  12. package/dist/api/facade.d.ts +19 -0
  13. package/dist/api/facade.d.ts.map +1 -0
  14. package/dist/api/index.d.ts +10 -0
  15. package/dist/api/index.d.ts.map +1 -0
  16. package/dist/api/messages.d.ts +35 -0
  17. package/dist/api/messages.d.ts.map +1 -0
  18. package/dist/api/response.d.ts +7 -0
  19. package/dist/api/response.d.ts.map +1 -0
  20. package/dist/api/runtime.d.ts +19 -0
  21. package/dist/api/runtime.d.ts.map +1 -0
  22. package/dist/api/uploads.d.ts +23 -0
  23. package/dist/api/uploads.d.ts.map +1 -0
  24. package/dist/api/users.d.ts +25 -0
  25. package/dist/api/users.d.ts.map +1 -0
  26. package/dist/app.d.ts +55 -0
  27. package/dist/app.d.ts.map +1 -0
  28. package/dist/auth/flows.d.ts +27 -0
  29. package/dist/auth/flows.d.ts.map +1 -0
  30. package/dist/auth/index.d.ts +3 -0
  31. package/dist/auth/index.d.ts.map +1 -0
  32. package/dist/auth/index.js +109 -0
  33. package/dist/auth/index.js.map +11 -0
  34. package/dist/auth/providers.d.ts +11 -0
  35. package/dist/auth/providers.d.ts.map +1 -0
  36. package/dist/client.d.ts +139 -0
  37. package/dist/client.d.ts.map +1 -0
  38. package/dist/config.d.ts +80 -0
  39. package/dist/config.d.ts.map +1 -0
  40. package/dist/connection/connection.d.ts +25 -0
  41. package/dist/connection/connection.d.ts.map +1 -0
  42. package/dist/connection/index.d.ts +4 -0
  43. package/dist/connection/index.d.ts.map +1 -0
  44. package/dist/connection/pending.d.ts +9 -0
  45. package/dist/connection/pending.d.ts.map +1 -0
  46. package/dist/connection/readers.d.ts +17 -0
  47. package/dist/connection/readers.d.ts.map +1 -0
  48. package/dist/dispatch/dispatcher.d.ts +16 -0
  49. package/dist/dispatch/dispatcher.d.ts.map +1 -0
  50. package/dist/dispatch/index.d.ts +3 -0
  51. package/dist/dispatch/index.d.ts.map +1 -0
  52. package/dist/dispatch/router.d.ts +64 -0
  53. package/dist/dispatch/router.d.ts.map +1 -0
  54. package/dist/errors.d.ts +24 -0
  55. package/dist/errors.d.ts.map +1 -0
  56. package/dist/files/file.d.ts +29 -0
  57. package/dist/files/file.d.ts.map +1 -0
  58. package/dist/files/index.d.ts +2 -0
  59. package/dist/files/index.d.ts.map +1 -0
  60. package/dist/files/index.js +95 -0
  61. package/dist/files/index.js.map +10 -0
  62. package/dist/formatting/index.d.ts +2 -0
  63. package/dist/formatting/index.d.ts.map +1 -0
  64. package/dist/formatting/markdown.d.ts +13 -0
  65. package/dist/formatting/markdown.d.ts.map +1 -0
  66. package/dist/index.d.ts +15 -0
  67. package/dist/index.d.ts.map +1 -0
  68. package/dist/index.js +3967 -0
  69. package/dist/index.js.map +48 -0
  70. package/dist/logging.d.ts +10 -0
  71. package/dist/logging.d.ts.map +1 -0
  72. package/dist/protocol/compression.d.ts +8 -0
  73. package/dist/protocol/compression.d.ts.map +1 -0
  74. package/dist/protocol/enums.d.ts +175 -0
  75. package/dist/protocol/enums.d.ts.map +1 -0
  76. package/dist/protocol/index.d.ts +6 -0
  77. package/dist/protocol/index.d.ts.map +1 -0
  78. package/dist/protocol/index.js +438 -0
  79. package/dist/protocol/index.js.map +13 -0
  80. package/dist/protocol/tcp.d.ts +35 -0
  81. package/dist/protocol/tcp.d.ts.map +1 -0
  82. package/dist/protocol/types.d.ts +38 -0
  83. package/dist/protocol/types.d.ts.map +1 -0
  84. package/dist/protocol/websocket.d.ts +7 -0
  85. package/dist/protocol/websocket.d.ts.map +1 -0
  86. package/dist/session/index.d.ts +3 -0
  87. package/dist/session/index.d.ts.map +1 -0
  88. package/dist/session/index.js +179 -0
  89. package/dist/session/index.js.map +12 -0
  90. package/dist/session/store.d.ts +18 -0
  91. package/dist/session/store.d.ts.map +1 -0
  92. package/dist/session/types.d.ts +29 -0
  93. package/dist/session/types.d.ts.map +1 -0
  94. package/dist/telemetry/index.d.ts +4 -0
  95. package/dist/telemetry/index.d.ts.map +1 -0
  96. package/dist/telemetry/index.js +831 -0
  97. package/dist/telemetry/index.js.map +17 -0
  98. package/dist/telemetry/navigation.d.ts +41 -0
  99. package/dist/telemetry/navigation.d.ts.map +1 -0
  100. package/dist/telemetry/payloads.d.ts +30 -0
  101. package/dist/telemetry/payloads.d.ts.map +1 -0
  102. package/dist/telemetry/service.d.ts +53 -0
  103. package/dist/telemetry/service.d.ts.map +1 -0
  104. package/dist/transport/base.d.ts +18 -0
  105. package/dist/transport/base.d.ts.map +1 -0
  106. package/dist/transport/index.d.ts +4 -0
  107. package/dist/transport/index.d.ts.map +1 -0
  108. package/dist/transport/tcp.d.ts +28 -0
  109. package/dist/transport/tcp.d.ts.map +1 -0
  110. package/dist/transport/websocket.d.ts +14 -0
  111. package/dist/transport/websocket.d.ts.map +1 -0
  112. package/dist/types/attachments.d.ts +18 -0
  113. package/dist/types/attachments.d.ts.map +1 -0
  114. package/dist/types/index.d.ts +4 -0
  115. package/dist/types/index.d.ts.map +1 -0
  116. package/dist/types/index.js +376 -0
  117. package/dist/types/index.js.map +12 -0
  118. package/dist/types/models.d.ts +210 -0
  119. package/dist/types/models.d.ts.map +1 -0
  120. package/dist/types/utils.d.ts +10 -0
  121. package/dist/types/utils.d.ts.map +1 -0
  122. package/package.json +95 -0
  123. package/src/api/account.ts +93 -0
  124. package/src/api/auth.ts +312 -0
  125. package/src/api/bots.ts +21 -0
  126. package/src/api/chats.ts +234 -0
  127. package/src/api/facade.ts +28 -0
  128. package/src/api/index.ts +9 -0
  129. package/src/api/messages.ts +230 -0
  130. package/src/api/response.ts +24 -0
  131. package/src/api/runtime.ts +27 -0
  132. package/src/api/uploads.ts +91 -0
  133. package/src/api/users.ts +68 -0
  134. package/src/app.ts +271 -0
  135. package/src/auth/flows.ts +103 -0
  136. package/src/auth/index.ts +2 -0
  137. package/src/auth/providers.ts +30 -0
  138. package/src/client.ts +506 -0
  139. package/src/config.ts +161 -0
  140. package/src/connection/connection.ts +93 -0
  141. package/src/connection/index.ts +3 -0
  142. package/src/connection/pending.ts +49 -0
  143. package/src/connection/readers.ts +33 -0
  144. package/src/dispatch/dispatcher.ts +194 -0
  145. package/src/dispatch/index.ts +2 -0
  146. package/src/dispatch/router.ts +149 -0
  147. package/src/errors.ts +41 -0
  148. package/src/files/file.ts +92 -0
  149. package/src/files/index.ts +1 -0
  150. package/src/formatting/index.ts +1 -0
  151. package/src/formatting/markdown.ts +123 -0
  152. package/src/index.ts +15 -0
  153. package/src/logging.ts +67 -0
  154. package/src/protocol/compression.ts +69 -0
  155. package/src/protocol/enums.ts +175 -0
  156. package/src/protocol/index.ts +5 -0
  157. package/src/protocol/tcp.ts +139 -0
  158. package/src/protocol/types.ts +45 -0
  159. package/src/protocol/websocket.ts +47 -0
  160. package/src/session/index.ts +2 -0
  161. package/src/session/store.ts +154 -0
  162. package/src/session/types.ts +45 -0
  163. package/src/telemetry/index.ts +3 -0
  164. package/src/telemetry/navigation.ts +160 -0
  165. package/src/telemetry/payloads.ts +84 -0
  166. package/src/telemetry/service.ts +194 -0
  167. package/src/transport/base.ts +43 -0
  168. package/src/transport/index.ts +3 -0
  169. package/src/transport/tcp.ts +296 -0
  170. package/src/transport/websocket.ts +63 -0
  171. package/src/types/attachments.ts +24 -0
  172. package/src/types/index.ts +3 -0
  173. package/src/types/models.ts +461 -0
  174. package/src/types/utils.ts +45 -0
@@ -0,0 +1,461 @@
1
+ import type { Attachment } from "./attachments.ts";
2
+ import { parseAttachment } from "./attachments.ts";
3
+ import {
4
+ asArray,
5
+ asNumber,
6
+ asRecord,
7
+ asString,
8
+ optionalMaxId,
9
+ optionalRecord,
10
+ toMaxId,
11
+ type MaxId,
12
+ } from "./utils.ts";
13
+
14
+ export const ChatType = {
15
+ DIALOG: "DIALOG",
16
+ CHAT: "CHAT",
17
+ CHANNEL: "CHANNEL",
18
+ } as const;
19
+
20
+ export interface ReactionCounter {
21
+ count: number;
22
+ reaction: string;
23
+ }
24
+
25
+ export interface ReactionInfo {
26
+ totalCount: number;
27
+ counters: ReactionCounter[];
28
+ yourReaction: string | null;
29
+ }
30
+
31
+ export interface ReadState {
32
+ unread: number;
33
+ mark: number;
34
+ }
35
+
36
+ export interface MessageActions {
37
+ sendMessage(options: SendMessageOptions): Promise<Message | null>;
38
+ forwardMessage(options: ForwardMessageOptions): Promise<Message | null>;
39
+ editMessage(options: EditMessageOptions): Promise<Message>;
40
+ deleteMessage(options: { chatId: MaxId; messageIds: MaxId[]; forMe: boolean }): Promise<boolean>;
41
+ pinMessage(options: { chatId: MaxId; messageId: MaxId; notifyPin: boolean }): Promise<boolean>;
42
+ readMessage(messageId: MaxId, chatId: MaxId): Promise<ReadState>;
43
+ addReaction(chatId: MaxId, messageId: MaxId, reaction: string): Promise<ReactionInfo | null>;
44
+ removeReaction(chatId: MaxId, messageId: MaxId): Promise<ReactionInfo | null>;
45
+ getReactions(chatId: MaxId, messageIds: MaxId[]): Promise<Map<MaxId, ReactionInfo> | null>;
46
+ getMessages(chatId: MaxId, messageIds: MaxId[]): Promise<Message[]>;
47
+ fetchHistory(options: HistoryOptions): Promise<Message[] | null>;
48
+ }
49
+
50
+ export interface ChatActions {
51
+ leaveGroup(chatId: MaxId): Promise<void>;
52
+ leaveChannel(chatId: MaxId): Promise<void>;
53
+ deleteChat(chatId: MaxId, forMe: boolean): Promise<boolean>;
54
+ inviteUsersToGroup(chatId: MaxId, userIds: MaxId[]): Promise<Chat | null>;
55
+ removeUsersFromGroup(chatId: MaxId, userIds: MaxId[]): Promise<Chat | null>;
56
+ }
57
+
58
+ export interface UserActions {
59
+ addContact(contactId: MaxId): Promise<User>;
60
+ removeContact(contactId: MaxId): Promise<boolean>;
61
+ getChatId(firstUserId: MaxId, secondUserId: MaxId): MaxId;
62
+ }
63
+
64
+ export interface SendMessageOptions {
65
+ chatId: MaxId;
66
+ text: string;
67
+ replyTo?: MaxId | null;
68
+ attachments?: readonly unknown[] | null;
69
+ notify?: boolean;
70
+ }
71
+
72
+ export interface ForwardMessageOptions {
73
+ chatId: MaxId;
74
+ messageId: MaxId;
75
+ sourceChatId?: MaxId | null;
76
+ notify?: boolean;
77
+ }
78
+
79
+ export interface EditMessageOptions {
80
+ chatId: MaxId;
81
+ messageId: MaxId;
82
+ text: string;
83
+ attachments?: readonly unknown[] | null;
84
+ }
85
+
86
+ export interface HistoryOptions {
87
+ chatId: MaxId;
88
+ forward?: number;
89
+ backward?: number;
90
+ backwardTime?: number;
91
+ forwardTime?: number;
92
+ from?: number;
93
+ itemType?: string;
94
+ getChat?: boolean;
95
+ getMessages?: boolean;
96
+ interactive?: boolean;
97
+ }
98
+
99
+ export class Message {
100
+ readonly id: MaxId;
101
+ chatId: MaxId | null;
102
+ readonly sender: MaxId | null;
103
+ readonly text: string;
104
+ readonly time: number;
105
+ readonly type: string;
106
+ readonly cid: MaxId | null;
107
+ readonly attaches: Attachment[];
108
+ readonly stats: Record<string, unknown> | null;
109
+ readonly status: string | null;
110
+ readonly reactionInfo: ReactionInfo | null;
111
+ readonly options: number | Record<string, unknown> | null;
112
+ readonly previousMessageId: MaxId | null;
113
+ readonly ttl: boolean | null;
114
+ readonly unread: number | null;
115
+ readonly mark: number | null;
116
+ readonly elements: Record<string, unknown>[];
117
+ readonly raw: Readonly<Record<string, unknown>>;
118
+ private actions: MessageActions | null = null;
119
+
120
+ private constructor(payload: Record<string, unknown>) {
121
+ this.id = toMaxId(payload.id, "message.id");
122
+ this.chatId = optionalMaxId(payload.chatId, "message.chatId");
123
+ this.sender = optionalMaxId(payload.sender, "message.sender");
124
+ this.text = asString(payload.text);
125
+ this.time = asNumber(payload.time);
126
+ this.type = asString(payload.type);
127
+ this.cid = optionalMaxId(payload.cid, "message.cid");
128
+ this.attaches = asArray(payload.attaches).map(parseAttachment);
129
+ this.stats = optionalRecord(payload.stats);
130
+ this.status = typeof payload.status === "string" ? payload.status : null;
131
+ this.reactionInfo = parseReactionInfo(payload.reactionInfo);
132
+ this.options =
133
+ typeof payload.options === "number" || optionalRecord(payload.options) !== null
134
+ ? (payload.options as number | Record<string, unknown>)
135
+ : null;
136
+ this.previousMessageId = optionalMaxId(payload.prevMessageId, "message.prevMessageId");
137
+ this.ttl = typeof payload.ttl === "boolean" ? payload.ttl : null;
138
+ this.unread =
139
+ payload.unread === null || payload.unread === undefined ? null : asNumber(payload.unread);
140
+ this.mark = payload.mark === null || payload.mark === undefined ? null : asNumber(payload.mark);
141
+ this.elements = asArray(payload.elements).map((item) => asRecord(item, "element"));
142
+ this.raw = Object.freeze({ ...payload });
143
+ }
144
+
145
+ static fromPayload(value: unknown): Message {
146
+ return new Message(asRecord(value, "message"));
147
+ }
148
+
149
+ bind(actions: MessageActions): this {
150
+ this.actions = actions;
151
+ return this;
152
+ }
153
+
154
+ async reply(
155
+ text: string,
156
+ options: Omit<SendMessageOptions, "chatId" | "text" | "replyTo"> = {},
157
+ ): Promise<Message | null> {
158
+ return await this.requireActions().sendMessage({
159
+ ...options,
160
+ chatId: this.requireChatId(),
161
+ text,
162
+ replyTo: this.id,
163
+ });
164
+ }
165
+
166
+ async answer(
167
+ text: string,
168
+ options: Omit<SendMessageOptions, "chatId" | "text"> = {},
169
+ ): Promise<Message | null> {
170
+ return await this.requireActions().sendMessage({
171
+ ...options,
172
+ chatId: this.requireChatId(),
173
+ text,
174
+ });
175
+ }
176
+
177
+ async forward(chatId: MaxId, notify = true): Promise<Message | null> {
178
+ return await this.requireActions().forwardMessage({
179
+ chatId,
180
+ messageId: this.id,
181
+ sourceChatId: this.requireChatId(),
182
+ notify,
183
+ });
184
+ }
185
+
186
+ async edit(text: string, attachments?: readonly unknown[] | null): Promise<Message> {
187
+ return await this.requireActions().editMessage({
188
+ chatId: this.requireChatId(),
189
+ messageId: this.id,
190
+ text,
191
+ ...(attachments !== undefined ? { attachments } : {}),
192
+ });
193
+ }
194
+
195
+ async pin(notifyPin = true): Promise<boolean> {
196
+ return await this.requireActions().pinMessage({
197
+ chatId: this.requireChatId(),
198
+ messageId: this.id,
199
+ notifyPin,
200
+ });
201
+ }
202
+
203
+ async delete(forMe = false): Promise<boolean> {
204
+ return await this.requireActions().deleteMessage({
205
+ chatId: this.requireChatId(),
206
+ messageIds: [this.id],
207
+ forMe,
208
+ });
209
+ }
210
+
211
+ async read(): Promise<ReadState> {
212
+ return await this.requireActions().readMessage(this.id, this.requireChatId());
213
+ }
214
+
215
+ async react(reaction: string): Promise<ReactionInfo | null> {
216
+ return await this.requireActions().addReaction(this.requireChatId(), this.id, reaction);
217
+ }
218
+
219
+ async unreact(): Promise<ReactionInfo | null> {
220
+ return await this.requireActions().removeReaction(this.requireChatId(), this.id);
221
+ }
222
+
223
+ async getReactions(): Promise<ReactionInfo | null> {
224
+ return (
225
+ (await this.requireActions().getReactions(this.requireChatId(), [this.id]))?.get(this.id) ??
226
+ null
227
+ );
228
+ }
229
+
230
+ private requireActions(): MessageActions {
231
+ if (!this.actions) throw new Error("Message is not bound to a client");
232
+ return this.actions;
233
+ }
234
+
235
+ private requireChatId(): MaxId {
236
+ if (this.chatId === null) throw new Error("Message has no chatId");
237
+ return this.chatId;
238
+ }
239
+ }
240
+
241
+ export class Chat {
242
+ readonly id: MaxId;
243
+ readonly type: string;
244
+ readonly status: string;
245
+ readonly owner: MaxId;
246
+ readonly participants: Map<MaxId, number>;
247
+ readonly title: string | null;
248
+ readonly lastMessage: Message | null;
249
+ readonly pinnedMessage: Message | null;
250
+ readonly lastEventTime: number;
251
+ readonly link: string | null;
252
+ readonly description: string | null;
253
+ readonly raw: Readonly<Record<string, unknown>>;
254
+ private messageActions: MessageActions | null = null;
255
+ private chatActions: ChatActions | null = null;
256
+
257
+ private constructor(payload: Record<string, unknown>) {
258
+ this.id = toMaxId(payload.id, "chat.id");
259
+ this.type = asString(payload.type);
260
+ this.status = asString(payload.status);
261
+ this.owner = optionalMaxId(payload.owner, "chat.owner") ?? 0n;
262
+ this.participants = parseParticipants(payload.participants);
263
+ this.title = typeof payload.title === "string" ? payload.title : null;
264
+ this.lastMessage = payload.lastMessage ? Message.fromPayload(payload.lastMessage) : null;
265
+ this.pinnedMessage = payload.pinnedMessage ? Message.fromPayload(payload.pinnedMessage) : null;
266
+ this.lastEventTime = asNumber(payload.lastEventTime);
267
+ this.link = typeof payload.link === "string" ? payload.link : null;
268
+ this.description = typeof payload.description === "string" ? payload.description : null;
269
+ this.raw = Object.freeze({ ...payload });
270
+ }
271
+
272
+ static fromPayload(value: unknown): Chat {
273
+ return new Chat(asRecord(value, "chat"));
274
+ }
275
+
276
+ bind(messageActions: MessageActions, chatActions: ChatActions): this {
277
+ this.messageActions = messageActions;
278
+ this.chatActions = chatActions;
279
+ this.lastMessage?.bind(messageActions);
280
+ this.pinnedMessage?.bind(messageActions);
281
+ return this;
282
+ }
283
+
284
+ async answer(
285
+ text: string,
286
+ options: Omit<SendMessageOptions, "chatId" | "text"> = {},
287
+ ): Promise<Message | null> {
288
+ return await this.requireMessageActions().sendMessage({ ...options, chatId: this.id, text });
289
+ }
290
+
291
+ async history(options: Omit<HistoryOptions, "chatId"> = {}): Promise<Message[] | null> {
292
+ return await this.requireMessageActions().fetchHistory({ ...options, chatId: this.id });
293
+ }
294
+
295
+ async getMessage(messageId: MaxId): Promise<Message | null> {
296
+ return (await this.requireMessageActions().getMessages(this.id, [messageId]))[0] ?? null;
297
+ }
298
+
299
+ async getMessages(messageIds: MaxId[]): Promise<Message[]> {
300
+ return await this.requireMessageActions().getMessages(this.id, messageIds);
301
+ }
302
+
303
+ async leave(): Promise<void> {
304
+ const actions = this.requireChatActions();
305
+ if (this.type === ChatType.CHANNEL) await actions.leaveChannel(this.id);
306
+ else if (this.type === ChatType.CHAT) await actions.leaveGroup(this.id);
307
+ else throw new Error("Cannot leave a dialog");
308
+ }
309
+
310
+ async delete(forMe = true): Promise<boolean> {
311
+ return await this.requireChatActions().deleteChat(this.id, forMe);
312
+ }
313
+
314
+ async invite(userIds: MaxId[]): Promise<Chat | null> {
315
+ return await this.requireChatActions().inviteUsersToGroup(this.id, userIds);
316
+ }
317
+
318
+ async removeUsers(userIds: MaxId[]): Promise<Chat | null> {
319
+ return await this.requireChatActions().removeUsersFromGroup(this.id, userIds);
320
+ }
321
+
322
+ private requireMessageActions(): MessageActions {
323
+ if (!this.messageActions) throw new Error("Chat is not bound to a client");
324
+ return this.messageActions;
325
+ }
326
+ private requireChatActions(): ChatActions {
327
+ if (!this.chatActions) throw new Error("Chat is not bound to a client");
328
+ return this.chatActions;
329
+ }
330
+ }
331
+
332
+ export class User {
333
+ readonly id: MaxId;
334
+ readonly names: ReadonlyArray<{ firstName: string; lastName: string | null; name?: string }>;
335
+ readonly phone: MaxId | null;
336
+ readonly status: string | null;
337
+ readonly description: string | null;
338
+ readonly gender: string | number | null;
339
+ readonly link: string | null;
340
+ readonly webApp: Record<string, unknown> | string | null;
341
+ readonly raw: Readonly<Record<string, unknown>>;
342
+ private actions: UserActions | null = null;
343
+
344
+ private constructor(payload: Record<string, unknown>) {
345
+ this.id = toMaxId(payload.id, "user.id");
346
+ this.names = asArray(payload.names).map((item) => {
347
+ const name = asRecord(item, "name");
348
+ return {
349
+ firstName: asString(name.firstName),
350
+ lastName: typeof name.lastName === "string" ? name.lastName : null,
351
+ ...(typeof name.name === "string" ? { name: name.name } : {}),
352
+ };
353
+ });
354
+ this.phone = optionalMaxId(payload.phone, "user.phone");
355
+ this.status = typeof payload.status === "string" ? payload.status : null;
356
+ this.description = typeof payload.description === "string" ? payload.description : null;
357
+ this.gender =
358
+ typeof payload.gender === "string" || typeof payload.gender === "number"
359
+ ? payload.gender
360
+ : null;
361
+ this.link = typeof payload.link === "string" ? payload.link : null;
362
+ this.webApp =
363
+ typeof payload.webApp === "string" ? payload.webApp : optionalRecord(payload.webApp);
364
+ this.raw = Object.freeze({ ...payload });
365
+ }
366
+
367
+ static fromPayload(value: unknown): User {
368
+ return new User(asRecord(value, "user"));
369
+ }
370
+ bind(actions: UserActions): this {
371
+ this.actions = actions;
372
+ return this;
373
+ }
374
+ async addContact(): Promise<User> {
375
+ return await this.requireActions().addContact(this.id);
376
+ }
377
+ async removeContact(): Promise<boolean> {
378
+ return await this.requireActions().removeContact(this.id);
379
+ }
380
+ getChatId(userId: MaxId): MaxId {
381
+ return this.requireActions().getChatId(userId, this.id);
382
+ }
383
+ private requireActions(): UserActions {
384
+ if (!this.actions) throw new Error("User is not bound to a client");
385
+ return this.actions;
386
+ }
387
+ }
388
+
389
+ export class Profile {
390
+ readonly contact: User;
391
+ readonly profileOptions: number[] | null;
392
+ readonly raw: Readonly<Record<string, unknown>>;
393
+ private constructor(payload: Record<string, unknown>) {
394
+ this.contact = User.fromPayload(payload.contact);
395
+ const rawOptions = payload.profileOptions ?? payload.options;
396
+ this.profileOptions = Array.isArray(rawOptions)
397
+ ? rawOptions.map((item) => asNumber(item))
398
+ : null;
399
+ this.raw = Object.freeze({ ...payload });
400
+ }
401
+ static fromPayload(value: unknown): Profile {
402
+ return new Profile(asRecord(value, "profile"));
403
+ }
404
+ }
405
+
406
+ export interface MessageDeleteEvent {
407
+ chatId: MaxId;
408
+ messageIds: MaxId[];
409
+ chat?: Chat | null;
410
+ message?: Message | null;
411
+ ttl?: boolean;
412
+ }
413
+ export interface MessageReadEvent {
414
+ chatId: MaxId;
415
+ userId: MaxId | null;
416
+ mark: number;
417
+ }
418
+ export interface TypingEvent {
419
+ chatId: MaxId;
420
+ userId: MaxId;
421
+ typing: boolean;
422
+ }
423
+ export interface PresenceEvent {
424
+ userId: MaxId;
425
+ online: boolean;
426
+ lastSeen: number | null;
427
+ raw: Readonly<Record<string, unknown>>;
428
+ }
429
+ export interface ReactionUpdateEvent {
430
+ chatId: MaxId;
431
+ messageId: MaxId;
432
+ reactionInfo: ReactionInfo | null;
433
+ }
434
+
435
+ export function parseReactionInfo(value: unknown): ReactionInfo | null {
436
+ if (value === null || value === undefined) return null;
437
+ const payload = asRecord(value, "reactionInfo");
438
+ return {
439
+ totalCount: asNumber(payload.totalCount),
440
+ counters: asArray(payload.counters).map((item) => {
441
+ const counter = asRecord(item, "reactionCounter");
442
+ return { count: asNumber(counter.count), reaction: asString(counter.reaction) };
443
+ }),
444
+ yourReaction: typeof payload.yourReaction === "string" ? payload.yourReaction : null,
445
+ };
446
+ }
447
+
448
+ export function parseReadState(value: unknown): ReadState {
449
+ const payload = asRecord(value, "readState");
450
+ return { unread: asNumber(payload.unread), mark: asNumber(payload.mark) };
451
+ }
452
+
453
+ function parseParticipants(value: unknown): Map<MaxId, number> {
454
+ const result = new Map<MaxId, number>();
455
+ if (value instanceof Map) {
456
+ for (const [id, role] of value) result.set(toMaxId(id), asNumber(role));
457
+ } else if (value !== null && typeof value === "object") {
458
+ for (const [id, role] of Object.entries(value)) result.set(toMaxId(id), asNumber(role));
459
+ }
460
+ return result;
461
+ }
@@ -0,0 +1,45 @@
1
+ export type MaxId = bigint;
2
+
3
+ export function toMaxId(value: unknown, field = "id"): MaxId {
4
+ if (typeof value === "bigint") return value;
5
+ if (typeof value === "string" && /^-?\d+$/.test(value)) return BigInt(value);
6
+ if (typeof value === "number" && Number.isSafeInteger(value)) return BigInt(value);
7
+ throw new TypeError(`${field} must be a lossless integer`);
8
+ }
9
+
10
+ export function optionalMaxId(value: unknown, field = "id"): MaxId | null {
11
+ return value === null || value === undefined ? null : toMaxId(value, field);
12
+ }
13
+
14
+ export function asRecord(value: unknown, field = "payload"): Record<string, unknown> {
15
+ if (value === null || typeof value !== "object" || Array.isArray(value)) {
16
+ throw new TypeError(`${field} must be an object`);
17
+ }
18
+ return value as Record<string, unknown>;
19
+ }
20
+
21
+ export function optionalRecord(value: unknown): Record<string, unknown> | null {
22
+ return value !== null && typeof value === "object" && !Array.isArray(value)
23
+ ? (value as Record<string, unknown>)
24
+ : null;
25
+ }
26
+
27
+ export function asString(value: unknown, fallback = ""): string {
28
+ return typeof value === "string" ? value : fallback;
29
+ }
30
+
31
+ export function asNumber(value: unknown, fallback = 0): number {
32
+ if (typeof value === "bigint") {
33
+ const number = Number(value);
34
+ return Number.isSafeInteger(number) ? number : fallback;
35
+ }
36
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
37
+ }
38
+
39
+ export function asBoolean(value: unknown, fallback = false): boolean {
40
+ return typeof value === "boolean" ? value : fallback;
41
+ }
42
+
43
+ export function asArray(value: unknown): unknown[] {
44
+ return Array.isArray(value) ? value : [];
45
+ }