Files
moxxy/lib/xmpp/message.dart

214 lines
6.0 KiB
Dart

import 'package:moxxyv2/shared/helpers.dart';
import 'package:moxxyv2/xmpp/events.dart';
import 'package:moxxyv2/xmpp/jid.dart';
import 'package:moxxyv2/xmpp/managers/base.dart';
import 'package:moxxyv2/xmpp/managers/data.dart';
import 'package:moxxyv2/xmpp/managers/handlers.dart';
import 'package:moxxyv2/xmpp/managers/namespaces.dart';
import 'package:moxxyv2/xmpp/namespaces.dart';
import 'package:moxxyv2/xmpp/stanza.dart';
import 'package:moxxyv2/xmpp/stringxml.dart';
import 'package:moxxyv2/xmpp/xeps/staging/file_upload_notification.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0066.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0085.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0184.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0333.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0359.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0446.dart';
import 'package:moxxyv2/xmpp/xeps/xep_0447.dart';
class MessageDetails {
const MessageDetails({
required this.to,
this.body,
this.requestDeliveryReceipt = false,
this.requestChatMarkers = true,
this.id,
this.originId,
this.quoteBody,
this.quoteId,
this.quoteFrom,
this.chatState,
this.sfs,
this.fun,
this.funReplacement,
this.funCancellation,
this.shouldEncrypt = false,
});
final String to;
final String? body;
final bool requestDeliveryReceipt;
final bool requestChatMarkers;
final String? id;
final String? originId;
final String? quoteBody;
final String? quoteId;
final String? quoteFrom;
final ChatState? chatState;
final StatelessFileSharingData? sfs;
final FileMetadataData? fun;
final String? funReplacement;
final String? funCancellation;
final bool shouldEncrypt;
}
class MessageManager extends XmppManagerBase {
@override
String getId() => messageManager;
@override
String getName() => 'MessageManager';
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: 'message',
callback: _onMessage,
priority: -100,
)
];
@override
Future<bool> isSupported() async => true;
Future<StanzaHandlerData> _onMessage(Stanza _, StanzaHandlerData state) async {
final message = state.stanza;
final body = message.firstTag('body');
getAttributes().sendEvent(MessageEvent(
body: body != null ? body.innerText() : '',
fromJid: JID.fromString(message.attributes['from']! as String),
toJid: JID.fromString(message.attributes['to']! as String),
sid: message.attributes['id']! as String,
stanzaId: state.stableId ?? const StableStanzaId(),
isCarbon: state.isCarbon,
deliveryReceiptRequested: state.deliveryReceiptRequested,
isMarkable: state.isMarkable,
type: message.attributes['type'] as String?,
oob: state.oob,
sfs: state.sfs,
sims: state.sims,
reply: state.reply,
chatState: state.chatState,
fun: state.fun,
funReplacement: state.funReplacement,
funCancellation: state.funCancellation,
encrypted: state.encrypted,
other: state.other,
),);
return state.copyWith(done: true);
}
/// 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.
/// If id is non-null, then it will be the id of the message stanza.
/// element to this id. If originId is non-null, then it will create an "origin-id"
/// child in the message stanza and set its id to originId.
void sendMessage(MessageDetails details) {
final stanza = Stanza.message(
to: details.to,
type: 'chat',
id: details.id,
children: [],
);
if (details.quoteBody != null) {
final fallback = '&gt; ${details.quoteBody!}';
stanza
..addChild(
XMLNode(tag: 'body', text: '$fallback\n${details.body}'),
)
..addChild(
XMLNode.xmlns(
tag: 'reply',
xmlns: replyXmlns,
attributes: {
'to': details.quoteFrom!,
'id': details.quoteId!
},
),
)
..addChild(
XMLNode.xmlns(
tag: 'fallback',
xmlns: fallbackXmlns,
attributes: {
'for': replyXmlns
},
children: [
XMLNode(
tag: 'body',
attributes: <String, String>{
'start': '0',
'end': '${fallback.length}'
},
)
],
),
);
} else {
var body = details.body;
if (details.sfs != null) {
body = details.sfs!.url;
}
stanza.addChild(
XMLNode(tag: 'body', text: body),
);
}
if (details.requestDeliveryReceipt) {
stanza.addChild(makeMessageDeliveryRequest());
}
if (details.requestChatMarkers) {
stanza.addChild(makeChatMarkerMarkable());
}
if (details.originId != null) {
stanza.addChild(makeOriginIdElement(details.originId!));
}
if (details.sfs != null) {
stanza
..addChild(details.sfs!.toXML())
// SFS recommends OOB as a fallback
..addChild(constructOOBNode(OOBData(url: details.sfs!.url)),);
}
if (details.chatState != null) {
stanza.addChild(
// TODO(Unknown): Move this into xep_0085.dart
XMLNode.xmlns(tag: chatStateToString(details.chatState!), xmlns: chatStateXmlns),
);
}
if (details.fun != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'file-upload',
xmlns: fileUploadNotificationXmlns,
children: [
details.fun!.toXML(),
],
),
);
}
if (details.funReplacement != null) {
stanza.addChild(
XMLNode.xmlns(
tag: 'replaces',
xmlns: fileUploadNotificationXmlns,
attributes: <String, String>{
'id': details.funReplacement!,
},
),
);
}
getAttributes().sendStanza(stanza, awaitable: false);
}
}