Compare commits
21 Commits
b92e825bc1
...
1e7279e23b
Author | SHA1 | Date | |
---|---|---|---|
1e7279e23b | |||
|
b2724aba0c | ||
|
d3742ea156 | ||
|
8b00e85167 | ||
|
04dfc6d2ac | ||
|
9e70e802ef | ||
|
3ebd9b86ec | ||
|
a873edb9ec | ||
|
e6bd6d05cd | ||
|
b7d53b8f47 | ||
|
217c3ac236 | ||
|
51bca6c25d | ||
|
8728166a4d | ||
|
1f1321b269 | ||
|
66195f66fa | ||
|
70fdfaf16d | ||
|
cd73f89e63 | ||
|
05c41d3185 | ||
|
64a8de6caa | ||
|
68809469f6 | ||
|
762cf1c77a |
80
examples_dart/bin/muc_client.dart
Normal file
80
examples_dart/bin/muc_client.dart
Normal file
@ -0,0 +1,80 @@
|
||||
import 'package:cli_repl/cli_repl.dart';
|
||||
import 'package:example_dart/arguments.dart';
|
||||
import 'package:example_dart/socket.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
// Set up logging
|
||||
Logger.root.level = Level.ALL;
|
||||
Logger.root.onRecord.listen((record) {
|
||||
// ignore: avoid_print
|
||||
print(
|
||||
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
|
||||
);
|
||||
});
|
||||
|
||||
final parser = ArgumentParser()
|
||||
..parser.addOption('muc', help: 'The MUC to send messages to')
|
||||
..parser.addOption('nick', help: 'The nickname with which to join the MUC');
|
||||
final options = parser.handleArguments(args);
|
||||
if (options == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Connect
|
||||
final muc = JID.fromString(options['muc']! as String).toBare();
|
||||
final nick = options['nick']! as String;
|
||||
final connection = XmppConnection(
|
||||
TestingReconnectionPolicy(),
|
||||
AlwaysConnectedConnectivityManager(),
|
||||
ClientToServerNegotiator(),
|
||||
ExampleTCPSocketWrapper(parser.srvRecord),
|
||||
)..connectionSettings = parser.connectionSettings;
|
||||
|
||||
// Register the managers and negotiators
|
||||
await connection.registerManagers([
|
||||
PresenceManager(),
|
||||
DiscoManager([]),
|
||||
PubSubManager(),
|
||||
MessageManager(),
|
||||
MUCManager(),
|
||||
]);
|
||||
await connection.registerFeatureNegotiators([
|
||||
SaslPlainNegotiator(),
|
||||
ResourceBindingNegotiator(),
|
||||
StartTlsNegotiator(),
|
||||
SaslScramNegotiator(10, '', '', ScramHashType.sha1),
|
||||
]);
|
||||
|
||||
// Connect
|
||||
Logger.root.info('Connecting...');
|
||||
final result =
|
||||
await connection.connect(shouldReconnect: false, waitUntilLogin: true);
|
||||
if (!result.isType<bool>()) {
|
||||
Logger.root.severe('Authentication failed!');
|
||||
return;
|
||||
}
|
||||
Logger.root.info('Connected.');
|
||||
|
||||
// Join room
|
||||
await connection.getManagerById<MUCManager>(mucManager)!.joinRoom(muc, nick);
|
||||
|
||||
final repl = Repl(prompt: '> ');
|
||||
await for (final line in repl.runAsync()) {
|
||||
await connection
|
||||
.getManagerById<MessageManager>(messageManager)!
|
||||
.sendMessage(
|
||||
muc,
|
||||
TypedMap<StanzaHandlerExtension>.fromList([
|
||||
MessageBodyData(line),
|
||||
]),
|
||||
type: 'groupchat');
|
||||
}
|
||||
|
||||
// Leave room
|
||||
await connection.getManagerById<MUCManager>(mucManager)!.leaveRoom(muc);
|
||||
|
||||
// Disconnect
|
||||
await connection.disconnect();
|
||||
}
|
@ -47,6 +47,9 @@ export 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/helpers.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0045/xep_0045.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0054.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0060/errors.dart';
|
||||
export 'package:moxxmpp/src/xeps/xep_0060/helpers.dart';
|
||||
|
@ -33,3 +33,4 @@ const stickersManager = 'org.moxxmpp.stickersmanager';
|
||||
const entityCapabilitiesManager = 'org.moxxmpp.entitycapabilities';
|
||||
const messageProcessingHintManager = 'org.moxxmpp.messageprocessinghint';
|
||||
const occupantIdManager = 'org.moxxmpp.occupantidmanager';
|
||||
const mucManager = 'org.moxxmpp.mucmanager';
|
||||
|
@ -103,14 +103,15 @@ class MessageManager extends XmppManagerBase {
|
||||
/// data for building the message.
|
||||
Future<void> sendMessage(
|
||||
JID to,
|
||||
TypedMap<StanzaHandlerExtension> extensions,
|
||||
) async {
|
||||
TypedMap<StanzaHandlerExtension> extensions, {
|
||||
String type = 'chat',
|
||||
}) async {
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.message(
|
||||
to: to.toString(),
|
||||
id: extensions.get<MessageIdData>()?.id,
|
||||
type: 'chat',
|
||||
type: type,
|
||||
children: _messageSendingCallbacks
|
||||
.map((c) => c(extensions))
|
||||
.flattened
|
||||
|
@ -21,6 +21,9 @@ const discoItemsXmlns = 'http://jabber.org/protocol/disco#items';
|
||||
// XEP-0033
|
||||
const extendedAddressingXmlns = 'http://jabber.org/protocol/address';
|
||||
|
||||
// XEP-0045
|
||||
const mucXmlns = 'http://jabber.org/protocol/muc';
|
||||
|
||||
// XEP-0054
|
||||
const vCardTempXmlns = 'vcard-temp';
|
||||
const vCardTempUpdate = 'vcard-temp:x:update';
|
||||
|
19
packages/moxxmpp/lib/src/xeps/xep_0045/errors.dart
Normal file
19
packages/moxxmpp/lib/src/xeps/xep_0045/errors.dart
Normal file
@ -0,0 +1,19 @@
|
||||
/// Represents an error related to Multi-User Chat (MUC).
|
||||
abstract class MUCError {}
|
||||
|
||||
/// Error indicating an invalid (non-supported) stanza received while going
|
||||
/// through normal operation/flow of an MUC.
|
||||
class InvalidStanzaFormat extends MUCError {}
|
||||
|
||||
/// Represents an error indicating an abnormal condition while parsing
|
||||
/// the DiscoInfo response stanza in Multi-User Chat (MUC).
|
||||
class InvalidDiscoInfoResponse extends MUCError {}
|
||||
|
||||
/// Returned when no nickname was specified from the client side while trying to
|
||||
/// perform some actions on the MUC, such as joining the room.
|
||||
class NoNicknameSpecified extends MUCError {}
|
||||
|
||||
/// This error occurs when a user attempts to perform an action that requires
|
||||
/// them to be a member of a room, but they are not currently joined to
|
||||
/// that room.
|
||||
class RoomNotJoinedError extends MUCError {}
|
43
packages/moxxmpp/lib/src/xeps/xep_0045/types.dart
Normal file
43
packages/moxxmpp/lib/src/xeps/xep_0045/types.dart
Normal file
@ -0,0 +1,43 @@
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
|
||||
class RoomInformation {
|
||||
/// Represents information about a Multi-User Chat (MUC) room.
|
||||
RoomInformation({
|
||||
required this.jid,
|
||||
required this.features,
|
||||
required this.name,
|
||||
});
|
||||
|
||||
/// Constructs a [RoomInformation] object from a [DiscoInfo] object.
|
||||
/// The [DiscoInfo] object contains the necessary information to populate
|
||||
/// the [RoomInformation] fields.
|
||||
factory RoomInformation.fromDiscoInfo({
|
||||
required DiscoInfo discoInfo,
|
||||
}) =>
|
||||
RoomInformation(
|
||||
jid: discoInfo.jid!,
|
||||
features: discoInfo.features,
|
||||
name: discoInfo.identities
|
||||
.firstWhere((i) => i.category == 'conference')
|
||||
.name!,
|
||||
);
|
||||
|
||||
/// The JID of the Multi-User Chat (MUC) room.
|
||||
final JID jid;
|
||||
|
||||
/// A list of features supported by the Multi-User Chat (MUC) room.
|
||||
final List<String> features;
|
||||
|
||||
/// The name or title of the Multi-User Chat (MUC) room.
|
||||
final String name;
|
||||
}
|
||||
|
||||
class RoomState {
|
||||
RoomState({
|
||||
required this.roomJid,
|
||||
this.nick,
|
||||
});
|
||||
final JID roomJid;
|
||||
String? nick;
|
||||
}
|
112
packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart
Normal file
112
packages/moxxmpp/lib/src/xeps/xep_0045/xep_0045.dart
Normal file
@ -0,0 +1,112 @@
|
||||
import 'package:moxlib/moxlib.dart';
|
||||
import 'package:moxxmpp/src/jid.dart';
|
||||
import 'package:moxxmpp/src/managers/base.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';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/types.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0030/xep_0030.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/errors.dart';
|
||||
import 'package:moxxmpp/src/xeps/xep_0045/types.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
|
||||
class MUCManager extends XmppManagerBase {
|
||||
MUCManager() : super(mucManager);
|
||||
|
||||
@override
|
||||
Future<bool> isSupported() async => true;
|
||||
|
||||
/// Map full JID to RoomState
|
||||
final Map<JID, RoomState> _mucRoomCache = {};
|
||||
|
||||
/// Cache lock
|
||||
final Lock _cacheLock = Lock();
|
||||
|
||||
/// Queries the information of a Multi-User Chat room.
|
||||
///
|
||||
/// Retrieves the information about the specified MUC room by performing a
|
||||
/// disco info query. Returns a [Result] with the [RoomInformation] on success
|
||||
/// or an appropriate [MUCError] on failure.
|
||||
Future<Result<RoomInformation, MUCError>> queryRoomInformation(
|
||||
JID roomJID,
|
||||
) async {
|
||||
final result = await getAttributes()
|
||||
.getManagerById<DiscoManager>(discoManager)!
|
||||
.discoInfoQuery(roomJID);
|
||||
if (result.isType<StanzaError>()) {
|
||||
return Result(InvalidStanzaFormat());
|
||||
}
|
||||
try {
|
||||
final roomInformation = RoomInformation.fromDiscoInfo(
|
||||
discoInfo: result.get<DiscoInfo>(),
|
||||
);
|
||||
return Result(roomInformation);
|
||||
} catch (e) {
|
||||
return Result(InvalidDiscoInfoResponse);
|
||||
}
|
||||
}
|
||||
|
||||
/// Joins a Multi-User Chat room.
|
||||
///
|
||||
/// Joins the specified MUC room using the provided nickname. Sends a presence
|
||||
/// stanza with the appropriate attributes to join the room. Returns a [Result]
|
||||
/// with a boolean value indicating success or failure, or an [MUCError]
|
||||
/// if applicable.
|
||||
Future<Result<bool, MUCError>> joinRoom(
|
||||
JID roomJid,
|
||||
String nick,
|
||||
) async {
|
||||
if (nick.isEmpty) {
|
||||
return Result(NoNicknameSpecified());
|
||||
}
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
to: roomJid.withResource(nick).toString(),
|
||||
children: [
|
||||
XMLNode.xmlns(
|
||||
tag: 'x',
|
||||
xmlns: mucXmlns,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
await _cacheLock.synchronized(
|
||||
() {
|
||||
_mucRoomCache[roomJid] = RoomState(roomJid: roomJid, nick: nick);
|
||||
},
|
||||
);
|
||||
return const Result(true);
|
||||
}
|
||||
|
||||
/// Leaves a Multi-User Chat room.
|
||||
///
|
||||
/// Leaves the specified MUC room by sending an 'unavailable' presence stanza.
|
||||
/// Removes the corresponding room entry from the cache. Returns a [Result]
|
||||
/// with a boolean value indicating success or failure, or an [MUCError]
|
||||
/// if applicable.
|
||||
Future<Result<bool, MUCError>> leaveRoom(
|
||||
JID roomJid,
|
||||
) async {
|
||||
final nick = await _cacheLock.synchronized(() {
|
||||
final nick = _mucRoomCache[roomJid]?.nick;
|
||||
_mucRoomCache.remove(roomJid);
|
||||
return nick;
|
||||
});
|
||||
if (nick == null) {
|
||||
return Result(RoomNotJoinedError);
|
||||
}
|
||||
await getAttributes().sendStanza(
|
||||
StanzaDetails(
|
||||
Stanza.presence(
|
||||
to: roomJid.withResource(nick).toString(),
|
||||
type: 'unavailable',
|
||||
),
|
||||
),
|
||||
);
|
||||
return const Result(true);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user