diff --git a/packages/moxxmpp/lib/src/events.dart b/packages/moxxmpp/lib/src/events.dart index 8b13013..8213794 100644 --- a/packages/moxxmpp/lib/src/events.dart +++ b/packages/moxxmpp/lib/src/events.dart @@ -74,6 +74,7 @@ class MessageEvent extends XmppEvent { this.funReplacement, this.funCancellation, this.messageRetraction, + this.messageCorrectionId, }); final StanzaError? error; final String body; @@ -95,6 +96,7 @@ class MessageEvent extends XmppEvent { final String? funCancellation; final bool encrypted; final MessageRetractionData? messageRetraction; + final String? messageCorrectionId; final Map other; } diff --git a/packages/moxxmpp/lib/src/managers/data.dart b/packages/moxxmpp/lib/src/managers/data.dart index ed7eb73..d4ffdbf 100644 --- a/packages/moxxmpp/lib/src/managers/data.dart +++ b/packages/moxxmpp/lib/src/managers/data.dart @@ -59,6 +59,8 @@ class StanzaHandlerData with _$StanzaHandlerData { // If non-null, then it indicates the origin Id of the message that should be // retracted MessageRetractionData? messageRetraction, + // If non-null, then the message is a correction for the specified stanza Id + String? lastMessageCorrectionSid, } ) = _StanzaHandlerData; } diff --git a/packages/moxxmpp/lib/src/managers/data.freezed.dart b/packages/moxxmpp/lib/src/managers/data.freezed.dart index fcf3da6..abbc16a 100644 --- a/packages/moxxmpp/lib/src/managers/data.freezed.dart +++ b/packages/moxxmpp/lib/src/managers/data.freezed.dart @@ -58,7 +58,8 @@ mixin _$StanzaHandlerData { throw _privateConstructorUsedError; // If non-null, then it indicates the origin Id of the message that should be // retracted MessageRetractionData? get messageRetraction => - throw _privateConstructorUsedError; + throw _privateConstructorUsedError; // If non-null, then the message is a correction for the specified stanza Id + String? get lastMessageCorrectionSid => throw _privateConstructorUsedError; @JsonKey(ignore: true) $StanzaHandlerDataCopyWith get copyWith => @@ -92,7 +93,8 @@ abstract class $StanzaHandlerDataCopyWith<$Res> { ExplicitEncryptionType? encryptionType, DelayedDelivery? delayedDelivery, Map other, - MessageRetractionData? messageRetraction}); + MessageRetractionData? messageRetraction, + String? lastMessageCorrectionSid}); } /// @nodoc @@ -128,6 +130,7 @@ class _$StanzaHandlerDataCopyWithImpl<$Res> Object? delayedDelivery = freezed, Object? other = freezed, Object? messageRetraction = freezed, + Object? lastMessageCorrectionSid = freezed, }) { return _then(_value.copyWith( done: done == freezed @@ -218,6 +221,10 @@ class _$StanzaHandlerDataCopyWithImpl<$Res> ? _value.messageRetraction : messageRetraction // ignore: cast_nullable_to_non_nullable as MessageRetractionData?, + lastMessageCorrectionSid: lastMessageCorrectionSid == freezed + ? _value.lastMessageCorrectionSid + : lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -251,7 +258,8 @@ abstract class _$$_StanzaHandlerDataCopyWith<$Res> ExplicitEncryptionType? encryptionType, DelayedDelivery? delayedDelivery, Map other, - MessageRetractionData? messageRetraction}); + MessageRetractionData? messageRetraction, + String? lastMessageCorrectionSid}); } /// @nodoc @@ -289,6 +297,7 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res> Object? delayedDelivery = freezed, Object? other = freezed, Object? messageRetraction = freezed, + Object? lastMessageCorrectionSid = freezed, }) { return _then(_$_StanzaHandlerData( done == freezed @@ -379,6 +388,10 @@ class __$$_StanzaHandlerDataCopyWithImpl<$Res> ? _value.messageRetraction : messageRetraction // ignore: cast_nullable_to_non_nullable as MessageRetractionData?, + lastMessageCorrectionSid: lastMessageCorrectionSid == freezed + ? _value.lastMessageCorrectionSid + : lastMessageCorrectionSid // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -404,7 +417,8 @@ class _$_StanzaHandlerData implements _StanzaHandlerData { this.encryptionType, this.delayedDelivery, final Map other = const {}, - this.messageRetraction}) + this.messageRetraction, + this.lastMessageCorrectionSid}) : _other = other; // Indicates to the runner that processing is now done. This means that all @@ -484,10 +498,13 @@ class _$_StanzaHandlerData implements _StanzaHandlerData { // retracted @override final MessageRetractionData? messageRetraction; +// If non-null, then the message is a correction for the specified stanza Id + @override + final String? lastMessageCorrectionSid; @override String toString() { - return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, stableId: $stableId, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction)'; + return 'StanzaHandlerData(done: $done, cancel: $cancel, cancelReason: $cancelReason, stanza: $stanza, retransmitted: $retransmitted, sims: $sims, sfs: $sfs, oob: $oob, stableId: $stableId, reply: $reply, chatState: $chatState, isCarbon: $isCarbon, deliveryReceiptRequested: $deliveryReceiptRequested, isMarkable: $isMarkable, fun: $fun, funReplacement: $funReplacement, funCancellation: $funCancellation, encrypted: $encrypted, encryptionType: $encryptionType, delayedDelivery: $delayedDelivery, other: $other, messageRetraction: $messageRetraction, lastMessageCorrectionSid: $lastMessageCorrectionSid)'; } @override @@ -525,7 +542,9 @@ class _$_StanzaHandlerData implements _StanzaHandlerData { .equals(other.delayedDelivery, delayedDelivery) && const DeepCollectionEquality().equals(other._other, this._other) && const DeepCollectionEquality() - .equals(other.messageRetraction, messageRetraction)); + .equals(other.messageRetraction, messageRetraction) && + const DeepCollectionEquality().equals( + other.lastMessageCorrectionSid, lastMessageCorrectionSid)); } @override @@ -552,7 +571,8 @@ class _$_StanzaHandlerData implements _StanzaHandlerData { const DeepCollectionEquality().hash(encryptionType), const DeepCollectionEquality().hash(delayedDelivery), const DeepCollectionEquality().hash(_other), - const DeepCollectionEquality().hash(messageRetraction) + const DeepCollectionEquality().hash(messageRetraction), + const DeepCollectionEquality().hash(lastMessageCorrectionSid) ]); @JsonKey(ignore: true) @@ -582,7 +602,8 @@ abstract class _StanzaHandlerData implements StanzaHandlerData { final ExplicitEncryptionType? encryptionType, final DelayedDelivery? delayedDelivery, final Map other, - final MessageRetractionData? messageRetraction}) = _$_StanzaHandlerData; + final MessageRetractionData? messageRetraction, + final String? lastMessageCorrectionSid}) = _$_StanzaHandlerData; @override // Indicates to the runner that processing is now done. This means that all // pre-processing is done and no other handlers should be consulted. @@ -635,6 +656,8 @@ abstract class _StanzaHandlerData implements StanzaHandlerData { @override // If non-null, then it indicates the origin Id of the message that should be // retracted MessageRetractionData? get messageRetraction; + @override // If non-null, then the message is a correction for the specified stanza Id + String? get lastMessageCorrectionSid; @override @JsonKey(ignore: true) _$$_StanzaHandlerDataCopyWith<_$_StanzaHandlerData> get copyWith => diff --git a/packages/moxxmpp/lib/src/managers/namespaces.dart b/packages/moxxmpp/lib/src/managers/namespaces.dart index 51a018e..d340848 100644 --- a/packages/moxxmpp/lib/src/managers/namespaces.dart +++ b/packages/moxxmpp/lib/src/managers/namespaces.dart @@ -25,3 +25,4 @@ const emeManager = 'org.moxxmpp.ememanager'; const cryptographicHashManager = 'org.moxxmpp.cryptographichashmanager'; const delayedDeliveryManager = 'org.moxxmpp.delayeddeliverymanager'; const messageRetractionManager = 'org.moxxmpp.messageretractionmanager'; +const lastMessageCorrectionManager = 'org.moxxmpp.lastmessagecorrectionmanager'; diff --git a/packages/moxxmpp/lib/src/message.dart b/packages/moxxmpp/lib/src/message.dart index 204a219..6e157e9 100644 --- a/packages/moxxmpp/lib/src/message.dart +++ b/packages/moxxmpp/lib/src/message.dart @@ -11,6 +11,7 @@ import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart'; import 'package:moxxmpp/src/xeps/xep_0066.dart'; import 'package:moxxmpp/src/xeps/xep_0085.dart'; import 'package:moxxmpp/src/xeps/xep_0184.dart'; +import 'package:moxxmpp/src/xeps/xep_0308.dart'; import 'package:moxxmpp/src/xeps/xep_0333.dart'; import 'package:moxxmpp/src/xeps/xep_0359.dart'; import 'package:moxxmpp/src/xeps/xep_0424.dart'; @@ -36,6 +37,7 @@ class MessageDetails { this.funCancellation, this.shouldEncrypt = false, this.messageRetraction, + this.lastMessageCorrectionId, }); final String to; final String? body; @@ -53,6 +55,7 @@ class MessageDetails { final String? funCancellation; final bool shouldEncrypt; final MessageRetractionData? messageRetraction; + final String? lastMessageCorrectionId; } class MessageManager extends XmppManagerBase { @@ -98,6 +101,7 @@ class MessageManager extends XmppManagerBase { funCancellation: state.funCancellation, encrypted: state.encrypted, messageRetraction: state.messageRetraction, + messageCorrectionId: state.lastMessageCorrectionSid, other: state.other, error: StanzaError.fromStanza(message), ),); @@ -249,6 +253,14 @@ class MessageManager extends XmppManagerBase { ); } } + + if (details.lastMessageCorrectionId != null) { + stanza.addChild( + makeLastMessageCorrectionEdit( + details.lastMessageCorrectionId!, + ), + ); + } getAttributes().sendStanza(stanza, awaitable: false); } diff --git a/packages/moxxmpp/lib/src/namespaces.dart b/packages/moxxmpp/lib/src/namespaces.dart index fb92e16..5b95a2d 100644 --- a/packages/moxxmpp/lib/src/namespaces.dart +++ b/packages/moxxmpp/lib/src/namespaces.dart @@ -76,6 +76,9 @@ const hashSha3512 = 'sha3-512'; const hashBlake2b256 = 'blake2b-256'; const hashBlake2b512 = 'blake2b-512'; +// XEP-0308 +const lmcXmlns = 'urn:xmpp:message-correct:0'; + // XEP-0333 const chatMarkersXmlns = 'urn:xmpp:chat-markers:0'; diff --git a/packages/moxxmpp/lib/src/xeps/xep_0308.dart b/packages/moxxmpp/lib/src/xeps/xep_0308.dart new file mode 100644 index 0000000..d58bddd --- /dev/null +++ b/packages/moxxmpp/lib/src/xeps/xep_0308.dart @@ -0,0 +1,52 @@ +import 'package:moxxmpp/src/managers/base.dart'; +import 'package:moxxmpp/src/managers/data.dart'; +import 'package:moxxmpp/src/managers/handlers.dart'; +import 'package:moxxmpp/src/managers/namespaces.dart'; +import 'package:moxxmpp/src/namespaces.dart'; +import 'package:moxxmpp/src/stanza.dart'; +import 'package:moxxmpp/src/stringxml.dart'; + +XMLNode makeLastMessageCorrectionEdit(String id) { + return XMLNode.xmlns( + tag: 'replace', + xmlns: lmcXmlns, + attributes: { + 'id': id, + }, + ); +} + +class LastMessageCorrectionManager extends XmppManagerBase { + @override + String getName() => 'LastMessageCorrectionManager'; + + @override + String getId() => lastMessageCorrectionManager; + + @override + List getDiscoFeatures() => [ lmcXmlns ]; + + @override + List getIncomingStanzaHandlers() => [ + StanzaHandler( + stanzaTag: 'message', + tagName: 'reply', + tagXmlns: replyXmlns, + callback: _onMessage, + // Before the message handler + priority: -99, + ) + ]; + + @override + Future isSupported() async => true; + + Future _onMessage(Stanza stanza, StanzaHandlerData state) async { + final edit = stanza.firstTag('replace', xmlns: lmcXmlns); + if (edit == null) return state; + + return state.copyWith( + lastMessageCorrectionSid: edit.attributes['id']! as String, + ); + } +}