Implementation
types.Message parseMessage(RoomMessage message) {
RoomVirtualItem? virtualItem = message.virtualItem();
if (virtualItem != null) {
switch (virtualItem.eventType()) {
case 'ReadMarker':
return const types.SystemMessage(
metadata: {'type': '_read_marker'},
id: 'read-marker',
text: 'read-until-here',
);
// should not return null, before we can keep track of index in diff receiver
default:
return types.UnsupportedMessage(
author: const types.User(id: 'virtual'),
id: UniqueKey().toString(),
metadata: {
'itemType': 'virtual',
'eventType': virtualItem.eventType(),
},
);
}
}
// If not virtual item, it should be event item
RoomEventItem eventItem = message.eventItem().expect(
'room msg should have event item',
);
EventSendState? eventState;
if (eventItem.sendState() != null) {
eventState = eventItem.sendState();
}
String eventType = eventItem.eventType();
String sender = eventItem.sender();
bool isEditable = eventItem.isEditable();
bool wasEdited = eventItem.wasEdited();
final author = types.User(id: sender, firstName: simplifyUserId(sender));
int createdAt = eventItem.originServerTs(); // in milliseconds
String uniqueId = message.uniqueId();
String? eventId = eventItem.eventId();
String? inReplyTo = eventItem.inReplyTo();
// user read receipts for timeline event item
Map<String, int> receipts = {};
for (final userId in asDartStringList(eventItem.readUsers())) {
final ts = eventItem.receiptTs(userId);
if (ts != null) receipts[userId] = ts;
}
// state event
switch (eventType) {
case 'm.policy.rule.room':
case 'm.policy.rule.server':
case 'm.policy.rule.user':
case 'm.room.aliases':
case 'm.room.avatar':
case 'm.room.canonical_alias':
case 'm.room.create':
case 'm.room.encryption':
case 'm.room.guest.access':
case 'm.room.history_visibility':
case 'm.room.join.rules':
case 'm.room.name':
case 'm.room.pinned_events':
case 'm.room.power_levels':
case 'm.room.server_acl':
case 'm.room.third_party_invite':
case 'm.room.tombstone':
case 'm.room.topic':
case 'm.space.child':
case 'm.space.parent':
return types.CustomMessage(
author: author,
createdAt: createdAt,
id: uniqueId,
remoteId: eventId,
metadata: {
'itemType': 'event',
'eventType': eventType,
'body': eventItem.msgContent()?.body(),
'eventState': eventItem.sendState(),
'receipts': receipts,
},
);
}
// message event
switch (eventType) {
case 'm.call.answer':
case 'm.call.candidates':
case 'm.call.hangup':
case 'm.call.invite':
break;
case 'm.reaction':
case 'm.room.encrypted':
final metadata = {
'itemType': 'event',
'eventType': eventType,
'eventState': eventState,
'receipts': receipts,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
return types.CustomMessage(
remoteId: eventId,
author: author,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
);
case 'm.room.redaction':
final metadata = {
'itemType': 'event',
'eventType': eventType,
'eventState': eventState,
'receipts': receipts,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
return types.CustomMessage(
remoteId: eventId,
author: author,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
);
case 'm.room.member':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
String? formattedBody = msgContent.formattedBody();
String body = msgContent.body(); // always exists
return types.CustomMessage(
author: author,
createdAt: createdAt,
id: uniqueId,
remoteId: eventId,
metadata: {
'itemType': 'event',
'eventType': eventType,
'msgType': eventItem.msgType(),
'body': formattedBody ?? body,
'eventState': eventState,
'receipts': receipts,
},
);
}
break;
case 'm.room.message':
Map<String, dynamic> reactions = {};
for (final key in asDartStringList(eventItem.reactionKeys())) {
final records = eventItem.reactionRecords(key);
if (records != null) reactions[key] = records.toList();
}
String? msgType = eventItem.msgType();
switch (msgType) {
case 'm.audio':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'base64': '',
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
final source = msgContent.source().expect(
'msg content of m.audio should have media source',
);
return types.AudioMessage(
author: author,
createdAt: createdAt,
remoteId: eventId,
duration: Duration(seconds: msgContent.duration() ?? 0),
id: uniqueId,
metadata: metadata,
mimeType: msgContent.mimetype(),
name: msgContent.body(),
size: msgContent.size() ?? 0,
uri: source.url(),
);
}
break;
case 'm.emote':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
String? formattedBody = msgContent.formattedBody();
String body = msgContent.body(); // always exists
Map<String, dynamic> metadata = {
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
// check whether string only contains emoji(s).
'enlargeEmoji': isOnlyEmojis(body),
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
return types.TextMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
text: formattedBody ?? body,
);
}
break;
case 'm.file':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
final source = msgContent.source().expect(
'msg content of m.file should have media source',
);
return types.FileMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
mimeType: msgContent.mimetype(),
name: msgContent.body(),
size: msgContent.size() ?? 0,
uri: source.url(),
);
}
break;
case 'm.image':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
final source = msgContent.source().expect(
'msg content of m.image should have media source',
);
return types.ImageMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
height: msgContent.height()?.toDouble(),
id: uniqueId,
metadata: metadata,
name: msgContent.body(),
size: msgContent.size() ?? 0,
uri: source.url(),
width: msgContent.width()?.toDouble(),
);
}
break;
case 'm.location':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'itemType': 'event',
'eventType': eventType,
'msgType': msgType,
'body': msgContent.body(),
'geoUri': msgContent.geoUri(),
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
final thumbnailSource = msgContent.thumbnailSource();
if (thumbnailSource != null) {
metadata['thumbnailSource'] = thumbnailSource.url();
}
final thumbnailInfo = msgContent.thumbnailInfo();
final mimetype = thumbnailInfo?.mimetype();
final size = thumbnailInfo?.size();
final width = thumbnailInfo?.width();
final height = thumbnailInfo?.height();
if (mimetype != null) {
metadata['thumbnailMimetype'] = mimetype;
}
if (size != null) {
metadata['thumbnailSize'] = size;
}
if (width != null) {
metadata['thumbnailWidth'] = width;
}
if (height != null) {
metadata['thumbnailHeight'] = height;
}
return types.CustomMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
);
}
break;
case 'm.notice':
case 'm.server_notice':
case 'm.text':
final body = prepareMsg(eventItem.msgContent());
Map<String, dynamic> metadata = {
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
// check whether string only contains emoji(s).
'enlargeEmoji': isOnlyEmojis(body),
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
return types.TextMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
text: body,
);
case 'm.video':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'base64': '',
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
final source = msgContent.source().expect(
'msg content of m.video should have media source',
);
return types.VideoMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
name: msgContent.body(),
size: msgContent.size() ?? 0,
uri: source.url(),
);
}
break;
case 'm.key.verification.request':
break;
}
break;
case 'm.sticker':
Map<String, dynamic> receipts = {};
for (final userId in asDartStringList(eventItem.readUsers())) {
final ts = eventItem.receiptTs(userId);
if (ts != null) receipts[userId] = ts;
}
Map<String, dynamic> reactions = {};
for (final key in asDartStringList(eventItem.reactionKeys())) {
final records = eventItem.reactionRecords(key);
if (records != null) reactions[key] = records.toList();
}
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
Map<String, dynamic> metadata = {
'itemType': 'event',
'eventType': eventType,
'name': msgContent.body(),
'size': msgContent.size() ?? 0,
'width': msgContent.width()?.toDouble(),
'height': msgContent.height()?.toDouble(),
'base64': '',
'eventState': eventState,
'receipts': receipts,
'was_edited': wasEdited,
'isEditable': isEditable,
};
if (inReplyTo != null) {
metadata['repliedTo'] = inReplyTo;
}
if (reactions.isNotEmpty) {
metadata['reactions'] = reactions;
}
return types.CustomMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: metadata,
);
}
break;
case 'm.poll.start':
MsgContent? msgContent = eventItem.msgContent();
if (msgContent != null) {
String body = msgContent.body();
return types.CustomMessage(
author: author,
remoteId: eventId,
createdAt: createdAt,
id: uniqueId,
metadata: {
'itemType': 'event',
'eventType': eventType,
'msgType': eventItem.msgType(),
'body': body,
'was_edited': wasEdited,
'isEditable': isEditable,
'eventState': eventState,
'receipts': receipts,
},
);
}
break;
}
return types.UnsupportedMessage(
author: const types.User(id: 'virtual'),
remoteId: eventId,
id: UniqueKey().toString(),
metadata: const {'itemType': 'virtual'},
);
}