Skip to content

Commit 0dedc4c

Browse files
committed
refactor: enhance message content types and schemas
Updated message content types in the core and federation SDK to better categorize message types into text, file, and location. Introduced new schemas for message content, including detailed structures for file information and new content for edits. This refactor improves type safety and clarity in message handling across the application.
1 parent 5f43238 commit 0dedc4c

File tree

3 files changed

+203
-87
lines changed

3 files changed

+203
-87
lines changed

packages/core/src/events/m.room.message.ts

Lines changed: 71 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,72 @@
11
import { type EventBase, createEventBase } from './eventBase';
22
import { createEventWithId } from './utils/createSignedEvent';
33

4-
type MessageType =
5-
| 'm.text'
6-
| 'm.emote'
7-
| 'm.notice'
8-
| 'm.image'
9-
| 'm.file'
10-
| 'm.audio'
11-
| 'm.video'
12-
| 'm.location';
4+
export type TextMessageType = 'm.text' | 'm.emote' | 'm.notice';
5+
export type FileMessageType = 'm.image' | 'm.file' | 'm.audio' | 'm.video';
6+
export type LocationMessageType = 'm.location';
7+
export type MessageType =
8+
| TextMessageType
9+
| FileMessageType
10+
| LocationMessageType;
11+
12+
// Base message content
13+
type BaseMessageContent = {
14+
body: string;
15+
'm.mentions'?: Record<string, any>;
16+
format?: string;
17+
formatted_body?: string;
18+
'm.relates_to'?: MessageRelation;
19+
};
20+
21+
// Text message content
22+
export type TextMessageContent = BaseMessageContent & {
23+
msgtype: TextMessageType;
24+
};
25+
26+
// File message content
27+
export type FileMessageContent = BaseMessageContent & {
28+
msgtype: FileMessageType;
29+
url: string;
30+
info?: {
31+
size?: number;
32+
mimetype?: string;
33+
w?: number;
34+
h?: number;
35+
duration?: number;
36+
thumbnail_url?: string;
37+
thumbnail_info?: {
38+
w?: number;
39+
h?: number;
40+
mimetype?: string;
41+
size?: number;
42+
};
43+
};
44+
};
45+
46+
// Location message content
47+
export type LocationMessageContent = BaseMessageContent & {
48+
msgtype: LocationMessageType;
49+
geo_uri: string;
50+
};
51+
52+
// New content for edits
53+
type NewContent =
54+
| Pick<TextMessageContent, 'body' | 'msgtype' | 'format' | 'formatted_body'>
55+
| Pick<FileMessageContent, 'body' | 'msgtype' | 'url' | 'info'>
56+
| Pick<LocationMessageContent, 'body' | 'msgtype' | 'geo_uri'>;
1357

1458
declare module './eventBase' {
1559
interface Events {
1660
'm.room.message': {
1761
unsigned: {
1862
age_ts: number;
1963
};
20-
content: {
21-
body: string;
22-
msgtype: MessageType;
23-
'm.mentions'?: Record<string, any>;
24-
format?: string;
25-
formatted_body?: string;
26-
'm.relates_to'?: MessageRelation;
27-
'm.new_content'?: {
28-
body: string;
29-
msgtype: MessageType;
30-
format?: string;
31-
formatted_body?: string;
32-
};
64+
content: (
65+
| TextMessageContent
66+
| FileMessageContent
67+
| LocationMessageContent
68+
) & {
69+
'm.new_content'?: NewContent;
3370
};
3471
};
3572
}
@@ -67,19 +104,12 @@ export const isRoomMessageEvent = (
67104

68105
export interface RoomMessageEvent extends EventBase {
69106
type: 'm.room.message';
70-
content: {
71-
body: string;
72-
msgtype: MessageType;
73-
'm.mentions'?: Record<string, any>;
74-
format?: string;
75-
formatted_body?: string;
76-
'm.relates_to'?: MessageRelation;
77-
'm.new_content'?: {
78-
body: string;
79-
msgtype: MessageType;
80-
format?: string;
81-
formatted_body?: string;
82-
};
107+
content: (
108+
| TextMessageContent
109+
| FileMessageContent
110+
| LocationMessageContent
111+
) & {
112+
'm.new_content'?: NewContent;
83113
};
84114
unsigned: {
85115
age: number;
@@ -110,19 +140,12 @@ export const roomMessageEvent = ({
110140
prev_events: string[];
111141
depth: number;
112142
unsigned?: RoomMessageEvent['unsigned'];
113-
content: {
114-
body: string;
115-
msgtype: MessageType;
116-
'm.mentions'?: Record<string, any>;
117-
format?: string;
118-
formatted_body?: string;
119-
'm.relates_to'?: MessageRelation;
120-
'm.new_content'?: {
121-
body: string;
122-
msgtype: MessageType;
123-
format?: string;
124-
formatted_body?: string;
125-
};
143+
content: (
144+
| TextMessageContent
145+
| FileMessageContent
146+
| LocationMessageContent
147+
) & {
148+
'm.new_content'?: NewContent;
126149
};
127150
origin?: string;
128151
ts?: number;

packages/federation-sdk/src/services/message.service.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,27 @@ import { FederationService } from './federation.service';
2626
import { RoomService } from './room.service';
2727
import { StateService } from './state.service';
2828

29+
// File message content type
30+
export type FileMessageContent = {
31+
body: string;
32+
msgtype: 'm.image' | 'm.file' | 'm.video' | 'm.audio';
33+
url: string;
34+
info?: {
35+
size?: number;
36+
mimetype?: string;
37+
w?: number;
38+
h?: number;
39+
duration?: number;
40+
thumbnail_url?: string;
41+
thumbnail_info?: {
42+
w?: number;
43+
h?: number;
44+
mimetype?: string;
45+
size?: number;
46+
};
47+
};
48+
};
49+
2950
@singleton()
3051
export class MessageService {
3152
private readonly logger = createLogger('MessageService');
@@ -121,25 +142,7 @@ export class MessageService {
121142

122143
async sendFileMessage(
123144
roomId: string,
124-
content: {
125-
body: string;
126-
msgtype: 'm.image' | 'm.file' | 'm.video' | 'm.audio';
127-
url: string;
128-
info?: {
129-
size?: number;
130-
mimetype?: string;
131-
w?: number;
132-
h?: number;
133-
duration?: number;
134-
thumbnail_url?: string;
135-
thumbnail_info?: {
136-
w?: number;
137-
h?: number;
138-
mimetype?: string;
139-
size?: number;
140-
};
141-
};
142-
},
145+
content: FileMessageContent,
143146
senderUserId: string,
144147
): Promise<PersistentEventBase> {
145148
const roomVersion = await this.stateService.getRoomVersion(roomId);

packages/room/src/types/v3-11.ts

Lines changed: 110 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -338,10 +338,21 @@ export type PduRoomNameEventContent = z.infer<
338338
typeof PduRoomNameEventContentSchema
339339
>;
340340

341-
export const PduMessageEventContentSchema = z.object({
341+
// Base message content schema
342+
const BaseMessageContentSchema = z.object({
342343
body: z.string().describe('The body of the message.'),
343-
// TODO: add more types
344-
msgtype: z.enum(['m.text', 'm.image']).describe('The type of the message.'),
344+
msgtype: z
345+
.enum([
346+
'm.text',
347+
'm.image',
348+
'm.file',
349+
'm.audio',
350+
'm.video',
351+
'm.emote',
352+
'm.notice',
353+
'm.location',
354+
])
355+
.describe('The type of the message.'),
345356
// Optional fields for message edits and relations aka threads
346357
'm.relates_to': z
347358
.object({
@@ -368,23 +379,6 @@ export const PduMessageEventContentSchema = z.object({
368379
})
369380
.optional()
370381
.describe('Relation information for edits, replies, reactions, etc.'),
371-
'm.new_content': z
372-
.object({
373-
body: z.string().describe('The new body of the message for edits.'),
374-
msgtype: z
375-
.enum(['m.text', 'm.image'])
376-
.describe('The type of the new message content.'),
377-
format: z
378-
.enum(['org.matrix.custom.html'])
379-
.describe('The format of the message content.')
380-
.optional(),
381-
formatted_body: z
382-
.string()
383-
.describe('The formatted body of the message.')
384-
.optional(),
385-
})
386-
.optional()
387-
.describe('The new content for message edits.'),
388382
format: z
389383
.enum(['org.matrix.custom.html'])
390384
.describe('The format of the message content.')
@@ -395,6 +389,102 @@ export const PduMessageEventContentSchema = z.object({
395389
.optional(),
396390
});
397391

392+
// File info schema
393+
const FileInfoSchema = z.object({
394+
size: z.number().describe('The size of the file in bytes.').optional(),
395+
mimetype: z.string().describe('The MIME type of the file.').optional(),
396+
w: z.number().describe('The width of the image/video in pixels.').optional(),
397+
h: z.number().describe('The height of the image/video in pixels.').optional(),
398+
duration: z
399+
.number()
400+
.describe('The duration of the audio/video in milliseconds.')
401+
.optional(),
402+
thumbnail_url: z
403+
.string()
404+
.describe('The URL of the thumbnail image.')
405+
.optional(),
406+
thumbnail_info: z
407+
.object({
408+
w: z
409+
.number()
410+
.describe('The width of the thumbnail in pixels.')
411+
.optional(),
412+
h: z
413+
.number()
414+
.describe('The height of the thumbnail in pixels.')
415+
.optional(),
416+
mimetype: z
417+
.string()
418+
.describe('The MIME type of the thumbnail.')
419+
.optional(),
420+
size: z
421+
.number()
422+
.describe('The size of the thumbnail in bytes.')
423+
.optional(),
424+
})
425+
.describe('Information about the thumbnail.')
426+
.optional(),
427+
});
428+
429+
// Text message content (m.text, m.emote, m.notice)
430+
const TextMessageContentSchema = BaseMessageContentSchema.extend({
431+
msgtype: z.enum(['m.text', 'm.emote', 'm.notice']),
432+
});
433+
434+
// File message content (m.image, m.file, m.audio, m.video)
435+
const FileMessageContentSchema = BaseMessageContentSchema.extend({
436+
msgtype: z.enum(['m.image', 'm.file', 'm.audio', 'm.video']),
437+
url: z.string().describe('The URL of the file.'),
438+
info: FileInfoSchema.describe('Information about the file.').optional(),
439+
});
440+
441+
// Location message content (m.location)
442+
const LocationMessageContentSchema = BaseMessageContentSchema.extend({
443+
msgtype: z.literal('m.location'),
444+
geo_uri: z.string().describe('The geo URI of the location.'),
445+
// Additional location fields can be added here
446+
});
447+
448+
// New content schema for edits
449+
const NewContentSchema = z.discriminatedUnion('msgtype', [
450+
TextMessageContentSchema.pick({
451+
body: true,
452+
msgtype: true,
453+
format: true,
454+
formatted_body: true,
455+
}),
456+
FileMessageContentSchema.pick({
457+
body: true,
458+
msgtype: true,
459+
url: true,
460+
info: true,
461+
}),
462+
LocationMessageContentSchema.pick({
463+
body: true,
464+
msgtype: true,
465+
geo_uri: true,
466+
}),
467+
]);
468+
469+
// Main message content schema using discriminated union
470+
export const PduMessageEventContentSchema = z.union([
471+
TextMessageContentSchema.extend({
472+
'm.new_content': NewContentSchema.optional().describe(
473+
'The new content for message edits.',
474+
),
475+
}),
476+
FileMessageContentSchema.extend({
477+
'm.new_content': NewContentSchema.optional().describe(
478+
'The new content for message edits.',
479+
),
480+
}),
481+
LocationMessageContentSchema.extend({
482+
'm.new_content': NewContentSchema.optional().describe(
483+
'The new content for message edits.',
484+
),
485+
}),
486+
]);
487+
398488
export type PduMessageEventContent = z.infer<
399489
typeof PduMessageEventContentSchema
400490
>;

0 commit comments

Comments
 (0)