feat: Set m.mention field when sending text event

This commit is contained in:
Christian Kußowski 2025-09-12 11:35:32 +02:00
parent 77fb2b29e7
commit 98dd75d822
No known key found for this signature in database
GPG Key ID: E067ECD60F1A0652
4 changed files with 89 additions and 2 deletions

View File

@ -710,6 +710,7 @@ class Room {
String? threadRootEventId,
String? threadLastEventId,
StringBuffer? commandStdout,
bool addMentions = true,
}) {
if (parseCommands) {
return client.parseAndRunCommand(
@ -727,6 +728,41 @@ class Room {
'msgtype': msgtype,
'body': message,
};
if (addMentions) {
var potentialMentions = message
.split('@')
.map(
(text) => text.startsWith('[')
? '@${text.split(']').first}]'
: '@${text.split(RegExp(r'\s+')).first}',
)
.toList()
..removeAt(0);
final hasRoomMention = potentialMentions.remove('@room');
potentialMentions = potentialMentions
.map(
(mention) =>
mention.isValidMatrixId ? mention : getMention(mention),
)
.nonNulls
.toSet() // Deduplicate
.toList()
..remove(client.userID); // We should never mention ourself.
// https://spec.matrix.org/v1.7/client-server-api/#mentioning-the-replied-to-user
if (inReplyTo != null) potentialMentions.add(inReplyTo.senderId);
if (hasRoomMention || potentialMentions.isNotEmpty) {
event['m.mentions'] = {
if (hasRoomMention) 'room': true,
if (potentialMentions.isNotEmpty) 'user_ids': potentialMentions,
};
}
}
if (parseMarkdown) {
final html = markdown(
event['body'],

View File

@ -243,6 +243,9 @@ void main() {
expect(sent, {
'msgtype': 'm.text',
'body': '> <@test:fakeServer.notExisting> reply\n\nreply',
'm.mentions': {
'user_ids': ['@test:fakeServer.notExisting'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!1234:fakeServer.notExisting/\$parent_event">In reply to</a> <a href="https://matrix.to/#/@test:fakeServer.notExisting">@test:fakeServer.notExisting</a><br>reply</blockquote></mx-reply>reply',

View File

@ -2960,7 +2960,7 @@ void main() async {
'm.mentions': {
'user_ids': ['@alice:matrix.org'],
'room': false,
}
},
},
'event_id': '\$143273582443PhrSn:example.org',
'origin_server_ts': 1432735824653,

View File

@ -1038,6 +1038,36 @@ void main() {
});
});
test('sendEvent with room mention', () async {
FakeMatrixApi.calledEndpoints.clear();
final resp = await room.sendTextEvent(
'Hello world @room',
txid: 'testtxid',
addMentions: true,
);
expect(resp?.startsWith('\$event'), true);
final entry = FakeMatrixApi.calledEndpoints.entries
.firstWhere((p) => p.key.contains('/send/m.room.message/'));
final content = json.decode(entry.value.first);
expect(content['m.mentions'], {'room': true});
});
test('sendEvent with user mention', () async {
FakeMatrixApi.calledEndpoints.clear();
final resp = await room.sendTextEvent(
'Hello world @[Alice Margatroid]',
addMentions: true,
txid: 'testtxid',
);
expect(resp?.startsWith('\$event'), true);
final entry = FakeMatrixApi.calledEndpoints.entries
.firstWhere((p) => p.key.contains('/send/m.room.message/'));
final content = json.decode(entry.value.first);
expect(content['m.mentions'], {
'user_ids': ['@alice:matrix.org'],
});
});
test('send edit', () async {
FakeMatrixApi.calledEndpoints.clear();
final dynamic resp = await room.sendTextEvent(
@ -1089,6 +1119,9 @@ void main() {
expect(content, {
'body': '> <@alice:example.org> Blah\n\nHello world',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>Blah</blockquote></mx-reply>Hello world',
@ -1125,6 +1158,9 @@ void main() {
'body':
'> <@alice:example.org> <b>Blah</b>\n> beep\n\nHello world\nfox',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>&lt;b&gt;Blah&lt;&#47;b&gt;<br>beep</blockquote></mx-reply>Hello world<br/>fox',
@ -1162,6 +1198,9 @@ void main() {
expect(content, {
'body': '> <@alice:example.org> plaintext meow\n\nHello world',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>meow</blockquote></mx-reply>Hello world',
@ -1197,6 +1236,9 @@ void main() {
expect(content, {
'body': '> <@alice:example.org> Hey @\u{200b}room\n\nHello world',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>Hey @room</blockquote></mx-reply>Hello world',
@ -1214,6 +1256,9 @@ void main() {
'content': {
'body': '> <@alice:example.org> Hey\n\nHello world',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>Hey</blockquote></mx-reply>Hello world',
@ -1238,6 +1283,9 @@ void main() {
expect(content, {
'body': '> <@alice:example.org> Hello world\n\nFox',
'msgtype': 'm.text',
'm.mentions': {
'user_ids': ['@alice:example.org'],
},
'format': 'org.matrix.custom.html',
'formatted_body':
'<mx-reply><blockquote><a href="https://matrix.to/#/!localpart:server.abc/\$replyEvent">In reply to</a> <a href="https://matrix.to/#/@alice:example.org">@alice:example.org</a><br>Hello world</blockquote></mx-reply>Fox',
@ -1296,7 +1344,7 @@ void main() {
test('sendFileEvent', () async {
final testFile = MatrixFile(bytes: Uint8List(0), name: 'file.jpeg');
final resp = await room.sendFileEvent(testFile, txid: 'testtxid');
expect(resp.toString(), '\$event10');
expect(resp.toString(), '\$event12');
});
test('pushRuleState', () async {