Skip to content

Commit 799f03f

Browse files
authored
fix: add attachments support (#304)
1 parent 49d4aad commit 799f03f

File tree

10 files changed

+318
-17
lines changed

10 files changed

+318
-17
lines changed

packages/common/src/adapter.interface.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,10 @@ export interface AdapterInterface<
301301
* @param message
302302
* @param options Options for creating the container.
303303
*/
304-
createContainer: (message: Message, options: CreateContainerOptions) => C;
304+
createContainer: (
305+
message: Message,
306+
options: CreateContainerOptions,
307+
) => Promise<C>;
305308

306309
/**
307310
* Authorizes the request.
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
/**
2+
* Add more attachment types as needed.
3+
*/
4+
export enum AttachmentType {
5+
/**
6+
* An image attachment.
7+
*/
8+
Image = "image",
9+
/**
10+
* A video attachment.
11+
*/
12+
Video = "video",
13+
/**
14+
* An audio attachment.
15+
*/
16+
Audio = "audio",
17+
/**
18+
* A file attachment.
19+
*/
20+
File = "file",
21+
/**
22+
* A user recorded voice attachment.
23+
*/
24+
Voice = "voice",
25+
/**
26+
* A sticker attachment.
27+
*/
28+
Sticker = "sticker",
29+
/**
30+
* A contact attachment.
31+
*/
32+
Contact = "contact",
33+
/**
34+
* A location attachment.
35+
*/
36+
Location = "location",
37+
/**
38+
* A poll attachment.
39+
*/
40+
Poll = "poll",
41+
/**
42+
* A quiz attachment.
43+
*/
44+
Quiz = "quiz",
45+
/**
46+
* A button attachment.
47+
*/
48+
Button = "button",
49+
}
50+
51+
export type BaseAttachment = {
52+
type: AttachmentType;
53+
};
54+
55+
export type ImageAttachment = BaseAttachment & {
56+
type: AttachmentType.Image;
57+
url: string;
58+
};
59+
60+
export type VideoAttachment = BaseAttachment & {
61+
type: AttachmentType.Video;
62+
url: string;
63+
thumbnail?: string;
64+
};
65+
66+
export type AudioAttachment = BaseAttachment & {
67+
type: AttachmentType.Audio;
68+
url: string;
69+
duration?: number;
70+
};
71+
72+
export type FileAttachment = BaseAttachment & {
73+
type: AttachmentType.File;
74+
url: string;
75+
size?: number;
76+
mimeType?: string;
77+
};
78+
79+
export type VoiceAttachment = BaseAttachment & {
80+
type: AttachmentType.Voice;
81+
url: string;
82+
duration: number;
83+
};
84+
85+
export type StickerAttachment = BaseAttachment & {
86+
type: AttachmentType.Sticker;
87+
url: string;
88+
};
89+
90+
// Data-based attachments
91+
export type ContactAttachment = BaseAttachment & {
92+
type: AttachmentType.Contact;
93+
data: {
94+
name: string;
95+
phoneNumber: string;
96+
email?: string;
97+
};
98+
};
99+
100+
export type LocationAttachment = BaseAttachment & {
101+
type: AttachmentType.Location;
102+
data: {
103+
latitude: number;
104+
longitude: number;
105+
address?: string;
106+
};
107+
};
108+
109+
export type PollAttachment = BaseAttachment & {
110+
type: AttachmentType.Poll;
111+
data: {
112+
question: string;
113+
options: {
114+
text: string;
115+
votes: number;
116+
}[];
117+
multipleChoice?: boolean;
118+
endTime?: string;
119+
};
120+
};
121+
122+
export type QuizAttachment = BaseAttachment & {
123+
type: AttachmentType.Quiz;
124+
data: {
125+
question: string;
126+
options: string[];
127+
correctAnswer: number;
128+
explanation?: string;
129+
};
130+
};
131+
132+
export type ButtonAttachment = BaseAttachment & {
133+
type: AttachmentType.Button;
134+
data: {
135+
text: string;
136+
url?: string;
137+
action?: string;
138+
payload?: Record<string, unknown>;
139+
};
140+
};
141+
142+
// Union type of all possible attachments
143+
export type Attachment =
144+
| ImageAttachment
145+
| VideoAttachment
146+
| AudioAttachment
147+
| FileAttachment
148+
| VoiceAttachment
149+
| StickerAttachment
150+
| ContactAttachment
151+
| LocationAttachment
152+
| PollAttachment
153+
| QuizAttachment
154+
| ButtonAttachment;

packages/common/src/container.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type React from "react";
2+
import { Attachment } from "./attachment.interface";
23

34
export enum ContainerType {
45
ROOT = "ROOT",
@@ -46,4 +47,9 @@ export interface Container<
4647
* For example, in `Telegram`, if user type `@bot_name`, the bot will be mentioned.
4748
*/
4849
hasBeenMentioned: boolean;
50+
51+
/**
52+
* Attachments that are sent with the message.
53+
*/
54+
attachments: Attachment[];
4955
}

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from "./router.interface";
1010
export * from "./storage.interface";
1111
export * from "./page.interface";
1212
export * from "./compiler.interface";
13+
export * from "./attachment.interface";

packages/common/src/page.interface.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { ReactNode } from "react";
2+
import { Attachment } from "./attachment.interface";
23
import { RouteInfoFile } from "./router.interface";
34
import { StorageClientInterface } from "./storage.interface";
45

@@ -65,6 +66,11 @@ export interface PageProps {
6566
* The chatroom id of the current chatroom.
6667
*/
6768
chatroomId: string;
69+
70+
/**
71+
* Attachments that are sent with the message.
72+
*/
73+
attachments: Attachment[];
6874
}
6975

7076
export interface ErrorPageProps {

packages/core/src/components/builder/componentBuilder.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ describe("should be able to build component", () => {
2020
type: ContainerType.ROOT,
2121
hasBeenMentioned: false,
2222
isInGroup: false,
23+
attachments: [],
2324
},
2425
{},
2526
);
@@ -39,6 +40,7 @@ describe("should be able to build component", () => {
3940
type: ContainerType.ROOT,
4041
hasBeenMentioned: false,
4142
isInGroup: false,
43+
attachments: [],
4244
},
4345
{},
4446
);
@@ -55,6 +57,7 @@ describe("should be able to build component", () => {
5557
type: ContainerType.ROOT,
5658
hasBeenMentioned: false,
5759
isInGroup: false,
60+
attachments: [],
5861
},
5962
{},
6063
);
@@ -75,6 +78,7 @@ describe("should be able to build component", () => {
7578
type: ContainerType.ROOT,
7679
hasBeenMentioned: false,
7780
isInGroup: false,
81+
attachments: [],
7882
},
7983
{},
8084
),
@@ -94,6 +98,7 @@ describe("should be able to build component", () => {
9498
type: ContainerType.ROOT,
9599
hasBeenMentioned: false,
96100
isInGroup: false,
101+
attachments: [],
97102
},
98103
{},
99104
),
@@ -113,6 +118,7 @@ describe("should be able to build component", () => {
113118
type: ContainerType.ROOT,
114119
hasBeenMentioned: false,
115120
isInGroup: false,
121+
attachments: [],
116122
},
117123
{},
118124
),
@@ -133,6 +139,7 @@ describe("should be able to build component", () => {
133139
type: ContainerType.ROOT,
134140
hasBeenMentioned: false,
135141
isInGroup: false,
142+
attachments: [],
136143
},
137144
{},
138145
),
@@ -149,6 +156,7 @@ describe("should be able to build component", () => {
149156
type: ContainerType.ROOT,
150157
hasBeenMentioned: false,
151158
isInGroup: false,
159+
attachments: [],
152160
},
153161
{},
154162
),

packages/core/src/core/core.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ describe.skip("Reconciler(Suspendable)", () => {
102102
children: [],
103103
hasBeenMentioned: false,
104104
isInGroup: false,
105+
attachments: [],
105106
});
106107

107108
expect(mockAdapter.messages).toHaveLength(0);
@@ -124,6 +125,7 @@ describe.skip("Reconciler(Suspendable)", () => {
124125
message: undefined,
125126
hasBeenMentioned: false,
126127
isInGroup: false,
128+
attachments: [],
127129
});
128130

129131
await new Promise((resolve) => setTimeout(resolve, 1000));

packages/core/src/core/core.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -189,15 +189,15 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
189189
return container;
190190
},
191191
clientRedirectTo: async (message, path, options) => {
192-
const container = this.adapter.createContainer(message, {
192+
const container = await this.adapter.createContainer(message, {
193193
renderNewMessage: options.renderNewMessage ?? true,
194194
userId: options.userId,
195195
});
196196
await this.redirect(container, { route: path }, options);
197197
return container;
198198
},
199199
reload: async (message: BaseMessage, options: ReloadOptions) => {
200-
const container = this.adapter.createContainer(message, {
200+
const container = await this.adapter.createContainer(message, {
201201
renderNewMessage: options.shouldRenderNewMessage ?? false,
202202
});
203203
// get the current route
@@ -283,10 +283,17 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
283283
}
284284

285285
async handleMessageUpdate(request: Request, message: BaseMessage) {
286-
await this.adapter.authorize(request);
287-
await this.adapter.handleMessageUpdate(message);
288-
this.updateLastCommitUpdateTime();
289-
return this.waitForMessageToBeSent();
286+
try {
287+
await this.adapter.authorize(request);
288+
await this.adapter.handleMessageUpdate(message);
289+
this.updateLastCommitUpdateTime();
290+
return this.waitForMessageToBeSent();
291+
} catch (e) {
292+
if (e instanceof SkipError) {
293+
return;
294+
}
295+
throw e;
296+
}
290297
}
291298

292299
async redirect(container: T, routeOrObject: any, options?: RedirectOptions) {
@@ -504,6 +511,7 @@ export class Core<T extends Container<BaseChatroomInfo, BaseMessage>>
504511
hasBeenMentioned: container.hasBeenMentioned,
505512
messageId: container.chatroomInfo.messageId?.toString() as any,
506513
chatroomId: container.chatroomInfo.id.toString() as any,
514+
attachments: container.attachments,
507515
...this.element.props,
508516
...oldProps,
509517
storage: {

packages/telegram-adapter/src/adapter.spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import { Menu } from "@rx-lab/common";
2+
import { AuthorizationError } from "@rx-lab/errors";
23
import * as TelegramBot from "node-telegram-bot-api";
34
import { TGContainer, TelegramAdapter } from "./adapter";
45
import { renderElement } from "./renderer";
56
import { DEFAULT_ROOT_PATH } from "./types";
6-
import { AuthorizationError } from "@rx-lab/errors";
77

88
jest.mock("node-telegram-bot-api");
99
jest.mock("./callbackParser");
@@ -34,6 +34,7 @@ describe("TelegramAdapter", () => {
3434
hasUpdated: false,
3535
hasBeenMentioned: false,
3636
isInGroup: false,
37+
attachments: [],
3738
};
3839

3940
const result = await adapter.adapt(container as any, false);
@@ -52,6 +53,7 @@ describe("TelegramAdapter", () => {
5253
hasUpdated: true,
5354
hasBeenMentioned: false,
5455
isInGroup: false,
56+
attachments: [],
5557
};
5658

5759
const result = await adapter.adapt(container as any, false);
@@ -73,6 +75,7 @@ describe("TelegramAdapter", () => {
7375
hasUpdated: true,
7476
hasBeenMentioned: false,
7577
isInGroup: false,
78+
attachments: [],
7679
};
7780

7881
await adapter.adapt(container as any, false);
@@ -129,6 +132,7 @@ describe("TelegramAdapter", () => {
129132
hasUpdated: true,
130133
hasBeenMentioned: false,
131134
isInGroup: false,
135+
attachments: [],
132136
};
133137

134138
const result = await adapter.adapt(container as any, false);

0 commit comments

Comments
 (0)