feat(xep): Implement the message sending callbacks
This commit is contained in:
parent
79d7e3ba64
commit
6f5de9c4dc
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:moxlib/moxlib.dart';
|
import 'package:moxlib/moxlib.dart';
|
||||||
import 'package:moxxmpp/src/events.dart';
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
@ -8,6 +9,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
import 'package:moxxmpp/src/xeps/staging/file_upload_notification.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
import 'package:moxxmpp/src/xeps/xep_0085.dart';
|
||||||
@ -26,6 +28,38 @@ import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
|||||||
import 'package:moxxmpp/src/xeps/xep_0449.dart';
|
import 'package:moxxmpp/src/xeps/xep_0449.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
import 'package:moxxmpp/src/xeps/xep_0461.dart';
|
||||||
|
|
||||||
|
class MessageBodyData {
|
||||||
|
const MessageBodyData(this.body);
|
||||||
|
|
||||||
|
/// The content of the <body /> element.
|
||||||
|
final String? body;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode(
|
||||||
|
tag: 'body',
|
||||||
|
text: body,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
if (extensions.get<ReplyData>() != null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
final data = extensions.get<MessageBodyData>();
|
||||||
|
return data != null ? [data.toXML()] : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageIdData {
|
||||||
|
const MessageIdData(this.id);
|
||||||
|
|
||||||
|
/// The id attribute of the stanza.
|
||||||
|
final String id;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef MessageSendingCallback = List<XMLNode> Function(TypedMap);
|
||||||
|
|
||||||
/// Data used to build a message stanza.
|
/// Data used to build a message stanza.
|
||||||
///
|
///
|
||||||
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
|
/// [setOOBFallbackBody] indicates, when using SFS, whether a OOB fallback should be
|
||||||
@ -79,7 +113,11 @@ class MessageDetails {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class MessageManager extends XmppManagerBase {
|
class MessageManager extends XmppManagerBase {
|
||||||
MessageManager() : super(messageManager);
|
MessageManager(this.messageSendingCallbacks) : super(messageManager);
|
||||||
|
|
||||||
|
/// A list of callbacks that are called when a message is sent in order to add
|
||||||
|
/// appropriate child elements.
|
||||||
|
final List<MessageSendingCallback> messageSendingCallbacks;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
List<StanzaHandler> getIncomingStanzaHandlers() => [
|
||||||
@ -145,6 +183,23 @@ class MessageManager extends XmppManagerBase {
|
|||||||
return state..done = true;
|
return state..done = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> sendMessage2(JID to, TypedMap extensions) async {
|
||||||
|
await getAttributes().sendStanza(
|
||||||
|
StanzaDetails(
|
||||||
|
Stanza.message(
|
||||||
|
to: to.toString(),
|
||||||
|
id: extensions.get<MessageIdData>()?.id,
|
||||||
|
type: 'chat',
|
||||||
|
children: messageSendingCallbacks
|
||||||
|
.map((c) => c(extensions))
|
||||||
|
.flattened
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
awaitable: false,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Send a message to to with the content body. If deliveryRequest is true, then
|
/// Send a message to to with the content body. If deliveryRequest is true, then
|
||||||
/// the message will also request a delivery receipt from the receiver.
|
/// the message will also request a delivery receipt from the receiver.
|
||||||
/// If id is non-null, then it will be the id of the message stanza.
|
/// If id is non-null, then it will be the id of the message stanza.
|
||||||
@ -217,9 +272,9 @@ class MessageManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.requestDeliveryReceipt) {
|
// if (details.requestDeliveryReceipt) {
|
||||||
stanza.addChild(makeMessageDeliveryRequest());
|
// stanza.addChild(makeMessageDeliveryRequest());
|
||||||
}
|
// }
|
||||||
if (details.requestChatMarkers) {
|
if (details.requestChatMarkers) {
|
||||||
stanza.addChild(makeChatMarkerMarkable());
|
stanza.addChild(makeChatMarkerMarkable());
|
||||||
}
|
}
|
||||||
@ -304,7 +359,7 @@ class MessageManager extends XmppManagerBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (details.messageReactions != null) {
|
if (details.messageReactions != null) {
|
||||||
stanza.addChild(details.messageReactions!.toXml());
|
stanza.addChild(details.messageReactions!.toXML());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (details.messageProcessingHints != null) {
|
if (details.messageProcessingHints != null) {
|
||||||
|
@ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
enum ChatState {
|
enum ChatState {
|
||||||
active,
|
active,
|
||||||
@ -52,6 +53,11 @@ enum ChatState {
|
|||||||
xmlns: chatStateXmlns,
|
xmlns: chatStateXmlns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<ChatState>();
|
||||||
|
return data != null ? [data.toXML()] : [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatStateManager extends XmppManagerBase {
|
class ChatStateManager extends XmppManagerBase {
|
||||||
|
@ -7,23 +7,42 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
class MessageDeliveryReceiptData {
|
class MessageDeliveryReceiptData {
|
||||||
const MessageDeliveryReceiptData(this.receiptRequested);
|
const MessageDeliveryReceiptData(this.receiptRequested);
|
||||||
|
|
||||||
/// Indicates whether a delivery receipt is requested or not.
|
/// Indicates whether a delivery receipt is requested or not.
|
||||||
final bool receiptRequested;
|
final bool receiptRequested;
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Merge those two functions into [MessageDeliveryReceiptData]
|
XMLNode toXML() {
|
||||||
XMLNode makeMessageDeliveryRequest() {
|
assert(
|
||||||
|
receiptRequested,
|
||||||
|
'This method makes little sense with receiptRequested == false',
|
||||||
|
);
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'request',
|
tag: 'request',
|
||||||
xmlns: deliveryXmlns,
|
xmlns: deliveryXmlns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
XMLNode makeMessageDeliveryResponse(String id) {
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<MessageDeliveryReceiptData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageDeliveryReceivedData {
|
||||||
|
const MessageDeliveryReceivedData(this.id);
|
||||||
|
|
||||||
|
/// The stanza id of the message we received.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'received',
|
tag: 'received',
|
||||||
xmlns: deliveryXmlns,
|
xmlns: deliveryXmlns,
|
||||||
@ -31,6 +50,12 @@ XMLNode makeMessageDeliveryResponse(String id) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<MessageDeliveryReceivedData>();
|
||||||
|
return data != null ? [data.toXML()] : [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class MessageDeliveryReceiptManager extends XmppManagerBase {
|
class MessageDeliveryReceiptManager extends XmppManagerBase {
|
||||||
MessageDeliveryReceiptManager() : super(messageDeliveryReceiptManager);
|
MessageDeliveryReceiptManager() : super(messageDeliveryReceiptManager);
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
class LastMessageCorrectionData {
|
class LastMessageCorrectionData {
|
||||||
const LastMessageCorrectionData(this.id);
|
const LastMessageCorrectionData(this.id);
|
||||||
@ -21,6 +22,15 @@ class LastMessageCorrectionData {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<LastMessageCorrectionData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class LastMessageCorrectionManager extends XmppManagerBase {
|
class LastMessageCorrectionManager extends XmppManagerBase {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
enum MessageProcessingHint {
|
enum MessageProcessingHint {
|
||||||
noPermanentStore,
|
noPermanentStore,
|
||||||
@ -45,4 +46,13 @@ enum MessageProcessingHint {
|
|||||||
xmlns: messageProcessingHintsXmlns,
|
xmlns: messageProcessingHintsXmlns,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<MessageProcessingHint>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,32 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
|
/// Representation of a <stanza-id /> element.
|
||||||
|
class StanzaId {
|
||||||
|
const StanzaId(
|
||||||
|
this.id,
|
||||||
|
this.by,
|
||||||
|
);
|
||||||
|
|
||||||
|
/// The unique stanza id.
|
||||||
|
final String id;
|
||||||
|
|
||||||
|
/// The JID the id was generated by.
|
||||||
|
final JID by;
|
||||||
|
|
||||||
|
XMLNode toXML() {
|
||||||
|
return XMLNode.xmlns(
|
||||||
|
tag: 'stanza-id',
|
||||||
|
xmlns: stableIdXmlns,
|
||||||
|
attributes: {
|
||||||
|
'id': id,
|
||||||
|
'by': by.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class StableIdData {
|
class StableIdData {
|
||||||
const StableIdData(this.originId, this.stanzaIds);
|
const StableIdData(this.originId, this.stanzaIds);
|
||||||
@ -27,30 +53,22 @@ class StableIdData {
|
|||||||
attributes: {'id': originId!},
|
attributes: {'id': originId!},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<XMLNode> toXML() {
|
||||||
|
return [
|
||||||
|
if (originId != null)
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'origin-id',
|
||||||
|
xmlns: stableIdXmlns,
|
||||||
|
attributes: {'id': originId!},
|
||||||
|
),
|
||||||
|
if (stanzaIds != null) ...stanzaIds!.map((s) => s.toXML()),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Representation of a <stanza-id /> element.
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
class StanzaId {
|
final data = extensions.get<StableIdData>();
|
||||||
const StanzaId(
|
return data != null ? data.toXML() : [];
|
||||||
this.id,
|
|
||||||
this.by,
|
|
||||||
);
|
|
||||||
|
|
||||||
/// The unique stanza id.
|
|
||||||
final String id;
|
|
||||||
|
|
||||||
/// The JID the id was generated by.
|
|
||||||
final JID by;
|
|
||||||
|
|
||||||
XMLNode toXml() {
|
|
||||||
return XMLNode.xmlns(
|
|
||||||
tag: 'stanza-id',
|
|
||||||
xmlns: stableIdXmlns,
|
|
||||||
attributes: {
|
|
||||||
'id': id,
|
|
||||||
'by': by.toString(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,11 +4,48 @@ import 'package:moxxmpp/src/managers/handlers.dart';
|
|||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
class MessageRetractionData {
|
class MessageRetractionData {
|
||||||
MessageRetractionData(this.id, this.fallback);
|
MessageRetractionData(this.id, this.fallback);
|
||||||
|
|
||||||
|
/// A potential fallback message to set the body to when retracting.
|
||||||
final String? fallback;
|
final String? fallback;
|
||||||
|
|
||||||
|
/// The id of the message that is retracted.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<MessageRetractionData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'apply-to',
|
||||||
|
xmlns: fasteningXmlns,
|
||||||
|
attributes: <String, String>{
|
||||||
|
'id': data.id,
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'retract',
|
||||||
|
xmlns: messageRetractionXmlns,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data.fallback != null)
|
||||||
|
XMLNode(
|
||||||
|
tag: 'body',
|
||||||
|
text: data.fallback,
|
||||||
|
),
|
||||||
|
if (data.fallback != null)
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'fallback',
|
||||||
|
xmlns: fallbackIndicationXmlns,
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageRetractionManager extends XmppManagerBase {
|
class MessageRetractionManager extends XmppManagerBase {
|
||||||
|
@ -5,13 +5,14 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
class MessageReactions {
|
class MessageReactions {
|
||||||
const MessageReactions(this.messageId, this.emojis);
|
const MessageReactions(this.messageId, this.emojis);
|
||||||
final String messageId;
|
final String messageId;
|
||||||
final List<String> emojis;
|
final List<String> emojis;
|
||||||
|
|
||||||
XMLNode toXml() {
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'reactions',
|
tag: 'reactions',
|
||||||
xmlns: messageReactionsXmlns,
|
xmlns: messageReactionsXmlns,
|
||||||
@ -26,6 +27,15 @@ class MessageReactions {
|
|||||||
}).toList(),
|
}).toList(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<MessageReactions>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
data.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MessageReactionsManager extends XmppManagerBase {
|
class MessageReactionsManager extends XmppManagerBase {
|
||||||
|
@ -6,6 +6,8 @@ import 'package:moxxmpp/src/managers/namespaces.dart';
|
|||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
import 'package:moxxmpp/src/xeps/xep_0066.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
import 'package:moxxmpp/src/xeps/xep_0446.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
import 'package:moxxmpp/src/xeps/xep_0448.dart';
|
||||||
|
|
||||||
@ -71,7 +73,11 @@ List<StatelessFileSharingSource> processStatelessFileSharingSources(
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StatelessFileSharingData {
|
class StatelessFileSharingData {
|
||||||
const StatelessFileSharingData(this.metadata, this.sources);
|
const StatelessFileSharingData(
|
||||||
|
this.metadata,
|
||||||
|
this.sources, {
|
||||||
|
this.includeOOBFallback = false,
|
||||||
|
});
|
||||||
|
|
||||||
/// Parse [node] as a StatelessFileSharingData element.
|
/// Parse [node] as a StatelessFileSharingData element.
|
||||||
factory StatelessFileSharingData.fromXML(XMLNode node) {
|
factory StatelessFileSharingData.fromXML(XMLNode node) {
|
||||||
@ -88,6 +94,10 @@ class StatelessFileSharingData {
|
|||||||
final FileMetadataData metadata;
|
final FileMetadataData metadata;
|
||||||
final List<StatelessFileSharingSource> sources;
|
final List<StatelessFileSharingSource> sources;
|
||||||
|
|
||||||
|
/// Flag indicating whether an OOB fallback should be set. The value is only
|
||||||
|
/// relevant in the context of the messageSendingCallback.
|
||||||
|
final bool includeOOBFallback;
|
||||||
|
|
||||||
XMLNode toXML() {
|
XMLNode toXML() {
|
||||||
return XMLNode.xmlns(
|
return XMLNode.xmlns(
|
||||||
tag: 'file-sharing',
|
tag: 'file-sharing',
|
||||||
@ -109,6 +119,26 @@ class StatelessFileSharingData {
|
|||||||
source is StatelessFileSharingUrlSource,
|
source is StatelessFileSharingUrlSource,
|
||||||
) as StatelessFileSharingUrlSource?;
|
) as StatelessFileSharingUrlSource?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<StatelessFileSharingData>();
|
||||||
|
if (data == null) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Unknown): Consider all sources?
|
||||||
|
final source = data.sources.first;
|
||||||
|
OOBData? oob;
|
||||||
|
if (source is StatelessFileSharingUrlSource && data.includeOOBFallback) {
|
||||||
|
// SFS recommends OOB as a fallback
|
||||||
|
oob = OOBData(source.url, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
data.toXML(),
|
||||||
|
if (oob != null) oob.toXML(),
|
||||||
|
];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SFSManager extends XmppManagerBase {
|
class SFSManager extends XmppManagerBase {
|
||||||
@ -122,7 +152,7 @@ class SFSManager extends XmppManagerBase {
|
|||||||
tagXmlns: sfsXmlns,
|
tagXmlns: sfsXmlns,
|
||||||
callback: _onMessage,
|
callback: _onMessage,
|
||||||
// Before the message handler
|
// Before the message handler
|
||||||
priority: -99,
|
priority: -98,
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ import 'package:moxxmpp/src/rfcs/rfc_4790.dart';
|
|||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
import 'package:moxxmpp/src/types/result.dart';
|
import 'package:moxxmpp/src/types/result.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
import 'package:moxxmpp/src/xeps/xep_0060/xep_0060.dart';
|
||||||
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
import 'package:moxxmpp/src/xeps/xep_0300.dart';
|
||||||
@ -228,10 +229,29 @@ class StickerPack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StickersData {
|
class StickersData {
|
||||||
const StickersData(this.stickerPackId);
|
const StickersData(this.stickerPackId, this.sticker);
|
||||||
|
|
||||||
/// The id of the sticker pack the referenced sticker is from.
|
/// The id of the sticker pack the referenced sticker is from.
|
||||||
final String stickerPackId;
|
final String stickerPackId;
|
||||||
|
|
||||||
|
/// The metadata of the sticker.
|
||||||
|
final StatelessFileSharingData sticker;
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<StickersData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'sticker',
|
||||||
|
xmlns: stickersXmlns,
|
||||||
|
attributes: {
|
||||||
|
'pack': data.stickerPackId,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
data.sticker.toXML(),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class StickersManager extends XmppManagerBase {
|
class StickersManager extends XmppManagerBase {
|
||||||
@ -258,7 +278,10 @@ class StickersManager extends XmppManagerBase {
|
|||||||
final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!;
|
final sticker = stanza.firstTag('sticker', xmlns: stickersXmlns)!;
|
||||||
return state
|
return state
|
||||||
..extensions.set(
|
..extensions.set(
|
||||||
StickersData(sticker.attributes['pack']! as String),
|
StickersData(
|
||||||
|
sticker.attributes['pack']! as String,
|
||||||
|
state.extensions.get<StatelessFileSharingData>()!,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,24 +1,36 @@
|
|||||||
import 'package:meta/meta.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/managers/data.dart';
|
import 'package:moxxmpp/src/managers/data.dart';
|
||||||
import 'package:moxxmpp/src/managers/handlers.dart';
|
import 'package:moxxmpp/src/managers/handlers.dart';
|
||||||
import 'package:moxxmpp/src/managers/namespaces.dart';
|
import 'package:moxxmpp/src/managers/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/namespaces.dart';
|
import 'package:moxxmpp/src/namespaces.dart';
|
||||||
import 'package:moxxmpp/src/stanza.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
|
|
||||||
/// Data summarizing the XEP-0461 data.
|
/// A reply to a message.
|
||||||
class ReplyData {
|
class ReplyData {
|
||||||
const ReplyData({
|
const ReplyData(
|
||||||
required this.id,
|
this.id, {
|
||||||
this.to,
|
this.body,
|
||||||
|
this.jid,
|
||||||
this.start,
|
this.start,
|
||||||
this.end,
|
this.end,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The bare JID to whom the reply applies to
|
ReplyData.fromQuoteData(
|
||||||
final String? to;
|
this.id,
|
||||||
|
QuoteData quote, {
|
||||||
|
this.jid,
|
||||||
|
}) : body = quote.body,
|
||||||
|
start = 0,
|
||||||
|
end = quote.fallbackLength;
|
||||||
|
|
||||||
/// The stanza ID of the message that is replied to
|
/// The JID of the entity whose message we are replying to.
|
||||||
|
final JID? jid;
|
||||||
|
|
||||||
|
/// The id of the message that is replied to. What id to use depends on what kind
|
||||||
|
/// of message you want to reply to.
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
/// The start of the fallback body (inclusive)
|
/// The start of the fallback body (inclusive)
|
||||||
@ -27,18 +39,59 @@ class ReplyData {
|
|||||||
/// The end of the fallback body (exclusive)
|
/// The end of the fallback body (exclusive)
|
||||||
final int? end;
|
final int? end;
|
||||||
|
|
||||||
|
/// The body of the message.
|
||||||
|
final String? body;
|
||||||
|
|
||||||
/// Applies the metadata to the received body [body] in order to remove the fallback.
|
/// Applies the metadata to the received body [body] in order to remove the fallback.
|
||||||
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
|
/// If either [ReplyData.start] or [ReplyData.end] are null, then body is returned as
|
||||||
/// is.
|
/// is.
|
||||||
String removeFallback(String body) {
|
String? get withoutFallback {
|
||||||
|
if (body == null) return null;
|
||||||
if (start == null || end == null) return body;
|
if (start == null || end == null) return body;
|
||||||
|
|
||||||
return body.replaceRange(start!, end, '');
|
return body!.replaceRange(start!, end, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<XMLNode> messageSendingCallback(TypedMap extensions) {
|
||||||
|
final data = extensions.get<ReplyData>();
|
||||||
|
return data != null
|
||||||
|
? [
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'reply',
|
||||||
|
xmlns: replyXmlns,
|
||||||
|
attributes: {
|
||||||
|
// The to attribute is optional
|
||||||
|
if (data.jid != null) 'to': data.jid!.toString(),
|
||||||
|
|
||||||
|
'id': data.id,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
if (data.body != null)
|
||||||
|
XMLNode(
|
||||||
|
tag: 'body',
|
||||||
|
text: data.body,
|
||||||
|
),
|
||||||
|
if (data.body != null)
|
||||||
|
XMLNode.xmlns(
|
||||||
|
tag: 'fallback',
|
||||||
|
xmlns: fallbackXmlns,
|
||||||
|
attributes: {'for': replyXmlns},
|
||||||
|
children: [
|
||||||
|
XMLNode(
|
||||||
|
tag: 'body',
|
||||||
|
attributes: {
|
||||||
|
'start': data.start!.toString(),
|
||||||
|
'end': data.end!.toString(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
]
|
||||||
|
: [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Internal class describing how to build a message with a quote fallback body.
|
/// Internal class describing how to build a message with a quote fallback body.
|
||||||
@visibleForTesting
|
|
||||||
class QuoteData {
|
class QuoteData {
|
||||||
const QuoteData(this.body, this.fallbackLength);
|
const QuoteData(this.body, this.fallbackLength);
|
||||||
|
|
||||||
@ -90,8 +143,8 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
StanzaHandlerData state,
|
StanzaHandlerData state,
|
||||||
) async {
|
) async {
|
||||||
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
final reply = stanza.firstTag('reply', xmlns: replyXmlns)!;
|
||||||
final id = reply.attributes['id']! as String;
|
|
||||||
final to = reply.attributes['to'] as String?;
|
final to = reply.attributes['to'] as String?;
|
||||||
|
final jid = to != null ? JID.fromString(to) : null;
|
||||||
int? start;
|
int? start;
|
||||||
int? end;
|
int? end;
|
||||||
|
|
||||||
@ -106,10 +159,11 @@ class MessageRepliesManager extends XmppManagerBase {
|
|||||||
return state
|
return state
|
||||||
..extensions.set(
|
..extensions.set(
|
||||||
ReplyData(
|
ReplyData(
|
||||||
id: id,
|
reply.attributes['id']! as String,
|
||||||
to: to,
|
jid: jid,
|
||||||
start: start,
|
start: start,
|
||||||
end: end,
|
end: end,
|
||||||
|
body: stanza.firstTag('body')?.innerText(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:moxxmpp/src/connection.dart';
|
import 'package:moxxmpp/src/connection.dart';
|
||||||
import 'package:moxxmpp/src/connectivity.dart';
|
import 'package:moxxmpp/src/connectivity.dart';
|
||||||
|
import 'package:moxxmpp/src/events.dart';
|
||||||
import 'package:moxxmpp/src/handlers/client.dart';
|
import 'package:moxxmpp/src/handlers/client.dart';
|
||||||
import 'package:moxxmpp/src/jid.dart';
|
import 'package:moxxmpp/src/jid.dart';
|
||||||
import 'package:moxxmpp/src/managers/attributes.dart';
|
import 'package:moxxmpp/src/managers/attributes.dart';
|
||||||
import 'package:moxxmpp/src/managers/base.dart';
|
import 'package:moxxmpp/src/managers/base.dart';
|
||||||
import 'package:moxxmpp/src/reconnect.dart';
|
import 'package:moxxmpp/src/reconnect.dart';
|
||||||
import 'package:moxxmpp/src/settings.dart';
|
import 'package:moxxmpp/src/settings.dart';
|
||||||
import 'package:moxxmpp/src/socket.dart';
|
import 'package:moxxmpp/src/stanza.dart';
|
||||||
import 'package:moxxmpp/src/stringxml.dart';
|
import 'package:moxxmpp/src/stringxml.dart';
|
||||||
|
|
||||||
import '../helpers/xmpp.dart';
|
import '../helpers/xmpp.dart';
|
||||||
@ -15,15 +16,15 @@ import '../helpers/xmpp.dart';
|
|||||||
/// This class allows registering managers for easier testing.
|
/// This class allows registering managers for easier testing.
|
||||||
class TestingManagerHolder {
|
class TestingManagerHolder {
|
||||||
TestingManagerHolder({
|
TestingManagerHolder({
|
||||||
BaseSocketWrapper? socket,
|
StubTCPSocket? stubSocket,
|
||||||
}) : _socket = socket ?? StubTCPSocket([]);
|
}) : socket = stubSocket ?? StubTCPSocket([]);
|
||||||
|
|
||||||
final BaseSocketWrapper _socket;
|
final StubTCPSocket socket;
|
||||||
|
|
||||||
final Map<String, XmppManagerBase> _managers = {};
|
final Map<String, XmppManagerBase> _managers = {};
|
||||||
|
|
||||||
// The amount of stanzas sent
|
/// A list of events that were triggered.
|
||||||
int sentStanzas = 0;
|
final List<XmppEvent> sentEvents = List.empty(growable: true);
|
||||||
|
|
||||||
static final JID jid = JID.fromString('testuser@example.org/abc123');
|
static final JID jid = JID.fromString('testuser@example.org/abc123');
|
||||||
static final ConnectionSettings settings = ConnectionSettings(
|
static final ConnectionSettings settings = ConnectionSettings(
|
||||||
@ -31,15 +32,9 @@ class TestingManagerHolder {
|
|||||||
password: 'abc123',
|
password: 'abc123',
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<XMLNode> _sendStanza(
|
Future<XMLNode?> _sendStanza(StanzaDetails details) async {
|
||||||
stanza, {
|
socket.write(details.stanza.toXml());
|
||||||
bool addId = true,
|
return null;
|
||||||
bool awaitable = true,
|
|
||||||
bool encrypted = false,
|
|
||||||
bool forceEncryption = false,
|
|
||||||
}) async {
|
|
||||||
sentStanzas++;
|
|
||||||
return XMLNode.fromString('<iq />');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
T? _getManagerById<T extends XmppManagerBase>(String id) {
|
T? _getManagerById<T extends XmppManagerBase>(String id) {
|
||||||
@ -54,12 +49,12 @@ class TestingManagerHolder {
|
|||||||
TestingReconnectionPolicy(),
|
TestingReconnectionPolicy(),
|
||||||
AlwaysConnectedConnectivityManager(),
|
AlwaysConnectedConnectivityManager(),
|
||||||
ClientToServerNegotiator(),
|
ClientToServerNegotiator(),
|
||||||
_socket,
|
socket,
|
||||||
),
|
),
|
||||||
getConnectionSettings: () => settings,
|
getConnectionSettings: () => settings,
|
||||||
sendNonza: (_) {},
|
sendNonza: (_) {},
|
||||||
sendEvent: (_) {},
|
sendEvent: sentEvents.add,
|
||||||
getSocket: () => _socket,
|
getSocket: () => socket,
|
||||||
getNegotiatorById: getNegotiatorNullStub,
|
getNegotiatorById: getNegotiatorNullStub,
|
||||||
getFullJID: () => jid,
|
getFullJID: () => jid,
|
||||||
getManagerById: _getManagerById,
|
getManagerById: _getManagerById,
|
||||||
|
@ -140,7 +140,7 @@ void main() {
|
|||||||
// Query Alice's device
|
// Query Alice's device
|
||||||
final result = await dm.discoInfoQuery(aliceJid);
|
final result = await dm.discoInfoQuery(aliceJid);
|
||||||
expect(result.isType<DiscoError>(), false);
|
expect(result.isType<DiscoError>(), false);
|
||||||
expect(tm.sentStanzas, 0);
|
expect(tm.socket.getState(), 0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -168,7 +168,6 @@ void main() {
|
|||||||
PubSubManager(),
|
PubSubManager(),
|
||||||
DiscoManager([]),
|
DiscoManager([]),
|
||||||
PresenceManager(),
|
PresenceManager(),
|
||||||
MessageManager(),
|
|
||||||
RosterManager(TestingRosterStateManager(null, [])),
|
RosterManager(TestingRosterStateManager(null, [])),
|
||||||
]);
|
]);
|
||||||
await connection.registerFeatureNegotiators([
|
await connection.registerFeatureNegotiators([
|
||||||
|
@ -1,7 +1,14 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../helpers/logging.dart';
|
||||||
|
import '../helpers/manager.dart';
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
initLogger();
|
||||||
|
|
||||||
test('Test parsing a large sticker pack', () {
|
test('Test parsing a large sticker pack', () {
|
||||||
// Example sticker pack based on the "miho" sticker pack by Movim
|
// Example sticker pack based on the "miho" sticker pack by Movim
|
||||||
final rawPack = XMLNode.fromString('''
|
final rawPack = XMLNode.fromString('''
|
||||||
@ -225,4 +232,183 @@ void main() {
|
|||||||
|
|
||||||
expect(pack.stickers.length, 16);
|
expect(pack.stickers.length, 16);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('Test sending a sticker', () async {
|
||||||
|
final manager = MessageManager([
|
||||||
|
StatelessFileSharingData.messageSendingCallback,
|
||||||
|
StickersData.messageSendingCallback,
|
||||||
|
]);
|
||||||
|
final holder = TestingManagerHolder(
|
||||||
|
stubSocket: StubTCPSocket([
|
||||||
|
StanzaExpectation(
|
||||||
|
// Example taken from https://xmpp.org/extensions/xep-0449.html#send
|
||||||
|
// - Replaced <dimensions /> with <width /> and <height />
|
||||||
|
'''
|
||||||
|
<message to="user@example.org" type="chat">
|
||||||
|
<sticker xmlns='urn:xmpp:stickers:0' pack='EpRv28DHHzFrE4zd+xaNpVb4' />
|
||||||
|
<file-sharing xmlns='urn:xmpp:sfs:0'>
|
||||||
|
<file xmlns='urn:xmpp:file:metadata:0'>
|
||||||
|
<media-type>image/png</media-type>
|
||||||
|
<desc>😘</desc>
|
||||||
|
<size>67016</size>
|
||||||
|
<width>512</width>
|
||||||
|
<height>512</height>
|
||||||
|
<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=</hash>
|
||||||
|
</file>
|
||||||
|
<sources>
|
||||||
|
<url-data xmlns='http://jabber.org/protocol/url-data' target='https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png' />
|
||||||
|
</sources>
|
||||||
|
</file-sharing>
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
'',
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
);
|
||||||
|
await holder.register(manager);
|
||||||
|
|
||||||
|
await manager.sendMessage2(
|
||||||
|
JID.fromString('user@example.org'),
|
||||||
|
TypedMap()
|
||||||
|
..set(
|
||||||
|
StickersData(
|
||||||
|
'EpRv28DHHzFrE4zd+xaNpVb4',
|
||||||
|
StatelessFileSharingData(
|
||||||
|
const FileMetadataData(
|
||||||
|
mediaType: 'image/png',
|
||||||
|
desc: '😘',
|
||||||
|
size: 67016,
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
hashes: {
|
||||||
|
HashFunction.sha256:
|
||||||
|
'gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=',
|
||||||
|
},
|
||||||
|
thumbnails: [],
|
||||||
|
),
|
||||||
|
[
|
||||||
|
StatelessFileSharingUrlSource(
|
||||||
|
'https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 1));
|
||||||
|
|
||||||
|
expect(holder.socket.getState(), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test receiving a sticker', () async {
|
||||||
|
final fakeSocket = StubTCPSocket(
|
||||||
|
[
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||||
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||||
|
<optional/>
|
||||||
|
</session>
|
||||||
|
<csi xmlns="urn:xmpp:csi:0"/>
|
||||||
|
<sm xmlns="urn:xmpp:sm:3"/>
|
||||||
|
</stream:features>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||||
|
ignoreId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
fakeSocket,
|
||||||
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
);
|
||||||
|
await conn.registerManagers([
|
||||||
|
MessageManager([]),
|
||||||
|
SFSManager(),
|
||||||
|
StickersManager(),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
]);
|
||||||
|
await conn.connect(
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
waitUntilLogin: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
MessageEvent? messageEvent;
|
||||||
|
conn.asBroadcastStream().listen((event) {
|
||||||
|
if (event is MessageEvent) {
|
||||||
|
messageEvent = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the fake message
|
||||||
|
fakeSocket.injectRawXml(
|
||||||
|
'''
|
||||||
|
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||||
|
<sticker xmlns='urn:xmpp:stickers:0' pack='EpRv28DHHzFrE4zd+xaNpVb4' />
|
||||||
|
<file-sharing xmlns='urn:xmpp:sfs:0'>
|
||||||
|
<file xmlns='urn:xmpp:file:metadata:0'>
|
||||||
|
<media-type>image/png</media-type>
|
||||||
|
<desc>😘</desc>
|
||||||
|
<size>67016</size>
|
||||||
|
<width>512</width>
|
||||||
|
<height>512</height>
|
||||||
|
<hash xmlns='urn:xmpp:hashes:2' algo='sha-256'>gw+6xdCgOcvCYSKuQNrXH33lV9NMzuDf/s0huByCDsY=</hash>
|
||||||
|
</file>
|
||||||
|
<sources>
|
||||||
|
<url-data xmlns='http://jabber.org/protocol/url-data' target='https://download.montague.lit/51078299-d071-46e1-b6d3-3de4a8ab67d6/sticker_marsey_kiss.png' />
|
||||||
|
</sources>
|
||||||
|
</file-sharing>
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
expect(messageEvent?.stickerPackId, 'EpRv28DHHzFrE4zd+xaNpVb4');
|
||||||
|
expect(messageEvent?.sfs!.metadata.desc, '😘');
|
||||||
|
expect(
|
||||||
|
messageEvent?.sfs!.sources.first is StatelessFileSharingUrlSource,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
import 'package:moxxmpp/moxxmpp.dart';
|
import 'package:moxxmpp/moxxmpp.dart';
|
||||||
|
import 'package:moxxmpp/src/util/typed_map.dart';
|
||||||
import 'package:test/test.dart';
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
import '../helpers/xmpp.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
test('Test building a singleline quote', () {
|
test('Test building a singleline quote', () {
|
||||||
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
|
final quote = QuoteData.fromBodies('Hallo Welt', 'Hello Earth!');
|
||||||
@ -20,28 +23,250 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('Applying a singleline quote', () {
|
test('Applying a singleline quote', () {
|
||||||
const body = '> Hallo Welt\nHello right back!';
|
|
||||||
const reply = ReplyData(
|
const reply = ReplyData(
|
||||||
to: '',
|
'',
|
||||||
id: '',
|
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 13,
|
end: 13,
|
||||||
|
body: '> Hallo Welt\nHello right back!',
|
||||||
);
|
);
|
||||||
|
|
||||||
final bodyWithoutFallback = reply.removeFallback(body);
|
expect(reply.withoutFallback, 'Hello right back!');
|
||||||
expect(bodyWithoutFallback, 'Hello right back!');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Applying a multiline quote', () {
|
test('Applying a multiline quote', () {
|
||||||
const body = "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!";
|
|
||||||
const reply = ReplyData(
|
const reply = ReplyData(
|
||||||
to: '',
|
'',
|
||||||
id: '',
|
|
||||||
start: 0,
|
start: 0,
|
||||||
end: 28,
|
end: 28,
|
||||||
|
body: "> Hallo Welt\n> How are you?\nI'm fine.\nThank you!",
|
||||||
);
|
);
|
||||||
|
|
||||||
final bodyWithoutFallback = reply.removeFallback(body);
|
expect(reply.withoutFallback, "I'm fine.\nThank you!");
|
||||||
expect(bodyWithoutFallback, "I'm fine.\nThank you!");
|
});
|
||||||
|
|
||||||
|
test('Test calling the message sending callback', () {
|
||||||
|
final result = ReplyData.messageSendingCallback(
|
||||||
|
TypedMap()
|
||||||
|
..set(
|
||||||
|
ReplyData.fromQuoteData(
|
||||||
|
'some-random-id',
|
||||||
|
QuoteData.fromBodies(
|
||||||
|
'Hello world',
|
||||||
|
'How are you doing?',
|
||||||
|
),
|
||||||
|
jid: JID.fromString('quoted-user@example.org'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final reply = result.firstWhere((e) => e.tag == 'reply');
|
||||||
|
final body = result.firstWhere((e) => e.tag == 'body');
|
||||||
|
final fallback = result.firstWhere((e) => e.tag == 'fallback');
|
||||||
|
|
||||||
|
expect(reply.attributes['to'], 'quoted-user@example.org');
|
||||||
|
expect(body.innerText(), '> Hello world\nHow are you doing?');
|
||||||
|
expect(fallback.children.first.attributes['start'], '0');
|
||||||
|
expect(fallback.children.first.attributes['end'], '14');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test parsing a reply without fallback', () async {
|
||||||
|
final fakeSocket = StubTCPSocket(
|
||||||
|
[
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||||
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||||
|
<optional/>
|
||||||
|
</session>
|
||||||
|
<csi xmlns="urn:xmpp:csi:0"/>
|
||||||
|
<sm xmlns="urn:xmpp:sm:3"/>
|
||||||
|
</stream:features>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||||
|
ignoreId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
fakeSocket,
|
||||||
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
);
|
||||||
|
await conn.registerManagers([
|
||||||
|
MessageManager([]),
|
||||||
|
MessageRepliesManager(),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
]);
|
||||||
|
await conn.connect(
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
waitUntilLogin: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
MessageEvent? messageEvent;
|
||||||
|
conn.asBroadcastStream().listen((event) {
|
||||||
|
if (event is MessageEvent) {
|
||||||
|
messageEvent = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the fake message
|
||||||
|
fakeSocket.injectRawXml(
|
||||||
|
'''
|
||||||
|
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||||
|
<body>Great idea!</body>
|
||||||
|
<reply to='anna@example.com/tablet' id='message-id1' xmlns='urn:xmpp:reply:0' />
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
final reply = messageEvent!.reply!;
|
||||||
|
expect(reply.withoutFallback, 'Great idea!');
|
||||||
|
expect(reply.id, 'message-id1');
|
||||||
|
expect(reply.jid, JID.fromString('anna@example.com/tablet'));
|
||||||
|
expect(reply.start, null);
|
||||||
|
expect(reply.end, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Test parsing a reply with a fallback', () async {
|
||||||
|
final fakeSocket = StubTCPSocket(
|
||||||
|
[
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<mechanisms xmlns="urn:ietf:params:xml:ns:xmpp-sasl">
|
||||||
|
<mechanism>PLAIN</mechanism>
|
||||||
|
</mechanisms>
|
||||||
|
</stream:features>''',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>AHBvbHlub21kaXZpc2lvbgBhYWFh</auth>",
|
||||||
|
'<success xmlns="urn:ietf:params:xml:ns:xmpp-sasl" />',
|
||||||
|
),
|
||||||
|
StringExpectation(
|
||||||
|
"<stream:stream xmlns='jabber:client' version='1.0' xmlns:stream='http://etherx.jabber.org/streams' to='test.server' from='polynomdivision@test.server' xml:lang='en'>",
|
||||||
|
'''
|
||||||
|
<stream:stream
|
||||||
|
xmlns="jabber:client"
|
||||||
|
version="1.0"
|
||||||
|
xmlns:stream="http://etherx.jabber.org/streams"
|
||||||
|
from="test.server"
|
||||||
|
xml:lang="en">
|
||||||
|
<stream:features xmlns="http://etherx.jabber.org/streams">
|
||||||
|
<bind xmlns="urn:ietf:params:xml:ns:xmpp-bind">
|
||||||
|
<required/>
|
||||||
|
</bind>
|
||||||
|
<session xmlns="urn:ietf:params:xml:ns:xmpp-session">
|
||||||
|
<optional/>
|
||||||
|
</session>
|
||||||
|
<csi xmlns="urn:xmpp:csi:0"/>
|
||||||
|
<sm xmlns="urn:xmpp:sm:3"/>
|
||||||
|
</stream:features>
|
||||||
|
''',
|
||||||
|
),
|
||||||
|
StanzaExpectation(
|
||||||
|
'<iq xmlns="jabber:client" type="set" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"/></iq>',
|
||||||
|
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||||
|
ignoreId: true,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final conn = XmppConnection(
|
||||||
|
TestingReconnectionPolicy(),
|
||||||
|
AlwaysConnectedConnectivityManager(),
|
||||||
|
ClientToServerNegotiator(),
|
||||||
|
fakeSocket,
|
||||||
|
)..connectionSettings = ConnectionSettings(
|
||||||
|
jid: JID.fromString('polynomdivision@test.server'),
|
||||||
|
password: 'aaaa',
|
||||||
|
);
|
||||||
|
await conn.registerManagers([
|
||||||
|
MessageManager([]),
|
||||||
|
MessageRepliesManager(),
|
||||||
|
]);
|
||||||
|
await conn.registerFeatureNegotiators([
|
||||||
|
SaslPlainNegotiator(),
|
||||||
|
ResourceBindingNegotiator(),
|
||||||
|
]);
|
||||||
|
await conn.connect(
|
||||||
|
shouldReconnect: false,
|
||||||
|
enableReconnectOnSuccess: false,
|
||||||
|
waitUntilLogin: true,
|
||||||
|
);
|
||||||
|
|
||||||
|
MessageEvent? messageEvent;
|
||||||
|
conn.asBroadcastStream().listen((event) {
|
||||||
|
if (event is MessageEvent) {
|
||||||
|
messageEvent = event;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send the fake message
|
||||||
|
fakeSocket.injectRawXml(
|
||||||
|
'''
|
||||||
|
<message id="aaaaaaaaa" from="user@example.org" to="polynomdivision@test.server/abc123" type="chat">
|
||||||
|
<body>> Anna wrote:\n> We should bake a cake\nGreat idea!</body>
|
||||||
|
<reply to='anna@example.com/laptop' id='message-id1' xmlns='urn:xmpp:reply:0' />
|
||||||
|
<fallback xmlns='urn:xmpp:feature-fallback:0' for='urn:xmpp:reply:0'>
|
||||||
|
<body start="0" end="38" />
|
||||||
|
</fallback>
|
||||||
|
</message>
|
||||||
|
''',
|
||||||
|
);
|
||||||
|
|
||||||
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
final reply = messageEvent!.reply!;
|
||||||
|
expect(reply.withoutFallback, 'Great idea!');
|
||||||
|
expect(reply.id, 'message-id1');
|
||||||
|
expect(reply.jid, JID.fromString('anna@example.com/laptop'));
|
||||||
|
expect(reply.start, 0);
|
||||||
|
expect(reply.end, 38);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user