Merge pull request #1937 from TheOneWithTheBraid/braid/command-runner
feat: improve commands_extension
This commit is contained in:
commit
24a0cfb9a8
|
|
@ -98,7 +98,7 @@ class Client extends MatrixApi {
|
||||||
DateTime? _accessTokenExpiresAt;
|
DateTime? _accessTokenExpiresAt;
|
||||||
|
|
||||||
// For CommandsClientExtension
|
// For CommandsClientExtension
|
||||||
final Map<String, FutureOr<String?> Function(CommandArgs)> commands = {};
|
final Map<String, CommandExecutionCallback> commands = {};
|
||||||
final Filter syncFilter;
|
final Filter syncFilter;
|
||||||
|
|
||||||
final NativeImplementations nativeImplementations;
|
final NativeImplementations nativeImplementations;
|
||||||
|
|
|
||||||
|
|
@ -625,6 +625,7 @@ class Room {
|
||||||
String msgtype = MessageTypes.Text,
|
String msgtype = MessageTypes.Text,
|
||||||
String? threadRootEventId,
|
String? threadRootEventId,
|
||||||
String? threadLastEventId,
|
String? threadLastEventId,
|
||||||
|
StringBuffer? commandStdout,
|
||||||
}) {
|
}) {
|
||||||
if (parseCommands) {
|
if (parseCommands) {
|
||||||
return client.parseAndRunCommand(
|
return client.parseAndRunCommand(
|
||||||
|
|
@ -635,6 +636,7 @@ class Room {
|
||||||
txid: txid,
|
txid: txid,
|
||||||
threadRootEventId: threadRootEventId,
|
threadRootEventId: threadRootEventId,
|
||||||
threadLastEventId: threadLastEventId,
|
threadLastEventId: threadLastEventId,
|
||||||
|
stdout: commandStdout,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
final event = <String, dynamic>{
|
final event = <String, dynamic>{
|
||||||
|
|
|
||||||
|
|
@ -21,31 +21,45 @@ import 'dart:convert';
|
||||||
|
|
||||||
import 'package:matrix/matrix.dart';
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
/// callback taking [CommandArgs] as input and a [StringBuffer] as standard output
|
||||||
|
/// optionally returns an event ID as in the [Room.sendEvent] syntax.
|
||||||
|
/// a [CommandException] should be thrown if the specified arguments are considered invalid
|
||||||
|
typedef CommandExecutionCallback = FutureOr<String?> Function(
|
||||||
|
CommandArgs,
|
||||||
|
StringBuffer? stdout,
|
||||||
|
);
|
||||||
|
|
||||||
extension CommandsClientExtension on Client {
|
extension CommandsClientExtension on Client {
|
||||||
/// Add a command to the command handler. `command` is its name, and `callback` is the
|
/// Add a command to the command handler. `command` is its name, and `callback` is the
|
||||||
/// callback to invoke
|
/// callback to invoke
|
||||||
void addCommand(
|
void addCommand(String command, CommandExecutionCallback callback) {
|
||||||
String command,
|
|
||||||
FutureOr<String?> Function(CommandArgs) callback,
|
|
||||||
) {
|
|
||||||
commands[command.toLowerCase()] = callback;
|
commands[command.toLowerCase()] = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parse and execute a string, `msg` is the input. Optionally `inReplyTo` is the event being
|
/// Parse and execute a command on Client level
|
||||||
/// replied to and `editEventId` is the eventId of the event being replied to
|
/// - `room`: a [Room] to run the command on. Can be null unless you execute a command strictly requiring a [Room] to run on
|
||||||
|
/// - `msg`: the complete input to process
|
||||||
|
/// - `inReplyTo`: an optional [Event] the command is supposed to reply to
|
||||||
|
/// - `editEventId`: an optional event ID the command is supposed to edit
|
||||||
|
/// - `txid`: an optional transaction ID
|
||||||
|
/// - `threadRootEventId`: an optional root event ID of a thread the command is supposed to run on
|
||||||
|
/// - `threadLastEventId`: an optional most recent event ID of a thread the command is supposed to run on
|
||||||
|
/// - `stdout`: an optional [StringBuffer] the command can write output to. This is meant as tiny implementation of https://en.wikipedia.org/wiki/Standard_streams in order to process advanced command output to the matrix client. See [DefaultCommandOutput] for a rough idea.
|
||||||
Future<String?> parseAndRunCommand(
|
Future<String?> parseAndRunCommand(
|
||||||
Room room,
|
Room? room,
|
||||||
String msg, {
|
String msg, {
|
||||||
Event? inReplyTo,
|
Event? inReplyTo,
|
||||||
String? editEventId,
|
String? editEventId,
|
||||||
String? txid,
|
String? txid,
|
||||||
String? threadRootEventId,
|
String? threadRootEventId,
|
||||||
String? threadLastEventId,
|
String? threadLastEventId,
|
||||||
|
StringBuffer? stdout,
|
||||||
}) async {
|
}) async {
|
||||||
final args = CommandArgs(
|
final args = CommandArgs(
|
||||||
inReplyTo: inReplyTo,
|
inReplyTo: inReplyTo,
|
||||||
editEventId: editEventId,
|
editEventId: editEventId,
|
||||||
msg: '',
|
msg: '',
|
||||||
|
client: this,
|
||||||
room: room,
|
room: room,
|
||||||
txid: txid,
|
txid: txid,
|
||||||
threadRootEventId: threadRootEventId,
|
threadRootEventId: threadRootEventId,
|
||||||
|
|
@ -55,7 +69,7 @@ extension CommandsClientExtension on Client {
|
||||||
final sendCommand = commands['send'];
|
final sendCommand = commands['send'];
|
||||||
if (sendCommand != null) {
|
if (sendCommand != null) {
|
||||||
args.msg = msg;
|
args.msg = msg;
|
||||||
return await sendCommand(args);
|
return await sendCommand(args, stdout);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -71,14 +85,14 @@ extension CommandsClientExtension on Client {
|
||||||
}
|
}
|
||||||
final commandOp = commands[command];
|
final commandOp = commands[command];
|
||||||
if (commandOp != null) {
|
if (commandOp != null) {
|
||||||
return await commandOp(args);
|
return await commandOp(args, stdout);
|
||||||
}
|
}
|
||||||
if (msg.startsWith('/') && commands.containsKey('send')) {
|
if (msg.startsWith('/') && commands.containsKey('send')) {
|
||||||
// re-set to include the "command"
|
// re-set to include the "command"
|
||||||
final sendCommand = commands['send'];
|
final sendCommand = commands['send'];
|
||||||
if (sendCommand != null) {
|
if (sendCommand != null) {
|
||||||
args.msg = msg;
|
args.msg = msg;
|
||||||
return await sendCommand(args);
|
return await sendCommand(args, stdout);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -91,8 +105,12 @@ extension CommandsClientExtension on Client {
|
||||||
|
|
||||||
/// Register all default commands
|
/// Register all default commands
|
||||||
void registerDefaultCommands() {
|
void registerDefaultCommands() {
|
||||||
addCommand('send', (CommandArgs args) async {
|
addCommand('send', (args, stdout) async {
|
||||||
return await args.room.sendTextEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendTextEvent(
|
||||||
args.msg,
|
args.msg,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
|
|
@ -102,8 +120,12 @@ extension CommandsClientExtension on Client {
|
||||||
threadLastEventId: args.threadLastEventId,
|
threadLastEventId: args.threadLastEventId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('me', (CommandArgs args) async {
|
addCommand('me', (args, stdout) async {
|
||||||
return await args.room.sendTextEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendTextEvent(
|
||||||
args.msg,
|
args.msg,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
|
|
@ -114,21 +136,44 @@ extension CommandsClientExtension on Client {
|
||||||
threadLastEventId: args.threadLastEventId,
|
threadLastEventId: args.threadLastEventId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('dm', (CommandArgs args) async {
|
addCommand('dm', (args, stdout) async {
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
return await args.room.client.startDirectChat(
|
final mxid = parts.first;
|
||||||
parts.first,
|
if (!mxid.isValidMatrixId) {
|
||||||
|
throw CommandException('You must enter a valid mxid when using /dm');
|
||||||
|
}
|
||||||
|
|
||||||
|
final roomId = await args.client.startDirectChat(
|
||||||
|
mxid,
|
||||||
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
||||||
);
|
);
|
||||||
});
|
stdout?.write(
|
||||||
addCommand('create', (CommandArgs args) async {
|
DefaultCommandOutput(
|
||||||
final parts = args.msg.split(' ');
|
rooms: [roomId],
|
||||||
return await args.room.client.createGroupChat(
|
users: [mxid],
|
||||||
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
).toString(),
|
||||||
);
|
);
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('plain', (CommandArgs args) async {
|
addCommand('create', (args, stdout) async {
|
||||||
return await args.room.sendTextEvent(
|
final groupName = args.msg.replaceFirst('--no-encryption', '').trim();
|
||||||
|
|
||||||
|
final parts = args.msg.split(' ');
|
||||||
|
|
||||||
|
final roomId = await args.client.createGroupChat(
|
||||||
|
groupName: groupName.isNotEmpty ? groupName : null,
|
||||||
|
enableEncryption: !parts.any((part) => part == '--no-encryption'),
|
||||||
|
waitForSync: false,
|
||||||
|
);
|
||||||
|
stdout?.write(DefaultCommandOutput(rooms: [roomId]).toString());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
addCommand('plain', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendTextEvent(
|
||||||
args.msg,
|
args.msg,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
|
|
@ -139,168 +184,280 @@ extension CommandsClientExtension on Client {
|
||||||
threadLastEventId: args.threadLastEventId,
|
threadLastEventId: args.threadLastEventId,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('html', (CommandArgs args) async {
|
addCommand('html', (args, stdout) async {
|
||||||
final event = <String, dynamic>{
|
final event = <String, dynamic>{
|
||||||
'msgtype': 'm.text',
|
'msgtype': 'm.text',
|
||||||
'body': args.msg,
|
'body': args.msg,
|
||||||
'format': 'org.matrix.custom.html',
|
'format': 'org.matrix.custom.html',
|
||||||
'formatted_body': args.msg,
|
'formatted_body': args.msg,
|
||||||
};
|
};
|
||||||
return await args.room.sendEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendEvent(
|
||||||
event,
|
event,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
txid: args.txid,
|
txid: args.txid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('react', (CommandArgs args) async {
|
addCommand('react', (args, stdout) async {
|
||||||
final inReplyTo = args.inReplyTo;
|
final inReplyTo = args.inReplyTo;
|
||||||
if (inReplyTo == null) {
|
if (inReplyTo == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return await args.room.sendReaction(inReplyTo.eventId, args.msg);
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
final parts = args.msg.split(' ');
|
||||||
|
final reaction = parts.first.trim();
|
||||||
|
if (reaction.isEmpty) {
|
||||||
|
throw CommandException('You must provide a reaction when using /react');
|
||||||
|
}
|
||||||
|
return await room.sendReaction(inReplyTo.eventId, reaction);
|
||||||
});
|
});
|
||||||
addCommand('join', (CommandArgs args) async {
|
addCommand('join', (args, stdout) async {
|
||||||
await args.room.client.joinRoom(args.msg);
|
final roomId = await args.client.joinRoom(args.msg);
|
||||||
|
stdout?.write(DefaultCommandOutput(rooms: [roomId]).toString());
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('leave', (CommandArgs args) async {
|
addCommand('leave', (args, stdout) async {
|
||||||
await args.room.leave();
|
final room = args.room;
|
||||||
return '';
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
await room.leave();
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('op', (CommandArgs args) async {
|
addCommand('op', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
if (parts.isEmpty) {
|
if (parts.isEmpty || !parts.first.isValidMatrixId) {
|
||||||
return null;
|
throw CommandException('You must enter a valid mxid when using /op');
|
||||||
}
|
}
|
||||||
int? pl;
|
int? pl;
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
pl = int.tryParse(parts[1]);
|
pl = int.tryParse(parts[1]);
|
||||||
|
if (pl == null) {
|
||||||
|
throw CommandException(
|
||||||
|
'Invalid power level ${parts[1]} when using /op',
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
final mxid = parts.first;
|
final mxid = parts.first;
|
||||||
return await args.room.setPower(mxid, pl ?? 50);
|
return await room.setPower(mxid, pl ?? 50);
|
||||||
});
|
});
|
||||||
addCommand('kick', (CommandArgs args) async {
|
addCommand('kick', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
await args.room.kick(parts.first);
|
final mxid = parts.first;
|
||||||
return '';
|
if (!mxid.isValidMatrixId) {
|
||||||
|
throw CommandException('You must enter a valid mxid when using /kick');
|
||||||
|
}
|
||||||
|
await room.kick(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('ban', (CommandArgs args) async {
|
addCommand('ban', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
await args.room.ban(parts.first);
|
final mxid = parts.first;
|
||||||
return '';
|
if (!mxid.isValidMatrixId) {
|
||||||
|
throw CommandException('You must enter a valid mxid when using /ban');
|
||||||
|
}
|
||||||
|
await room.ban(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('unban', (CommandArgs args) async {
|
addCommand('unban', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
await args.room.unban(parts.first);
|
final mxid = parts.first;
|
||||||
return '';
|
if (!mxid.isValidMatrixId) {
|
||||||
|
throw CommandException('You must enter a valid mxid when using /unban');
|
||||||
|
}
|
||||||
|
await room.unban(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('invite', (CommandArgs args) async {
|
addCommand('invite', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
|
||||||
final parts = args.msg.split(' ');
|
final parts = args.msg.split(' ');
|
||||||
await args.room.invite(parts.first);
|
final mxid = parts.first;
|
||||||
return '';
|
if (!mxid.isValidMatrixId) {
|
||||||
|
throw CommandException(
|
||||||
|
'You must enter a valid mxid when using /invite',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await room.invite(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('myroomnick', (CommandArgs args) async {
|
addCommand('myroomnick', (args, stdout) async {
|
||||||
final currentEventJson = args.room
|
final room = args.room;
|
||||||
.getState(EventTypes.RoomMember, args.room.client.userID!)
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentEventJson = room
|
||||||
|
.getState(EventTypes.RoomMember, args.client.userID!)
|
||||||
?.content
|
?.content
|
||||||
.copy() ??
|
.copy() ??
|
||||||
{};
|
{};
|
||||||
currentEventJson['displayname'] = args.msg;
|
currentEventJson['displayname'] = args.msg;
|
||||||
return await args.room.client.setRoomStateWithKey(
|
|
||||||
args.room.id,
|
return await args.client.setRoomStateWithKey(
|
||||||
|
room.id,
|
||||||
EventTypes.RoomMember,
|
EventTypes.RoomMember,
|
||||||
args.room.client.userID!,
|
args.client.userID!,
|
||||||
currentEventJson,
|
currentEventJson,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('myroomavatar', (CommandArgs args) async {
|
addCommand('myroomavatar', (args, stdout) async {
|
||||||
final currentEventJson = args.room
|
final room = args.room;
|
||||||
.getState(EventTypes.RoomMember, args.room.client.userID!)
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentEventJson = room
|
||||||
|
.getState(EventTypes.RoomMember, args.client.userID!)
|
||||||
?.content
|
?.content
|
||||||
.copy() ??
|
.copy() ??
|
||||||
{};
|
{};
|
||||||
currentEventJson['avatar_url'] = args.msg;
|
currentEventJson['avatar_url'] = args.msg;
|
||||||
return await args.room.client.setRoomStateWithKey(
|
|
||||||
args.room.id,
|
return await args.client.setRoomStateWithKey(
|
||||||
|
room.id,
|
||||||
EventTypes.RoomMember,
|
EventTypes.RoomMember,
|
||||||
args.room.client.userID!,
|
args.client.userID!,
|
||||||
currentEventJson,
|
currentEventJson,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('discardsession', (CommandArgs args) async {
|
addCommand('discardsession', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
await encryption?.keyManager
|
await encryption?.keyManager
|
||||||
.clearOrUseOutboundGroupSession(args.room.id, wipe: true);
|
.clearOrUseOutboundGroupSession(room.id, wipe: true);
|
||||||
return '';
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('clearcache', (CommandArgs args) async {
|
addCommand('clearcache', (args, stdout) async {
|
||||||
await clearCache();
|
await clearCache();
|
||||||
return '';
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('markasdm', (CommandArgs args) async {
|
addCommand('markasdm', (args, stdout) async {
|
||||||
final mxid = args.msg;
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
|
||||||
|
final mxid = args.msg.split(' ').first;
|
||||||
if (!mxid.isValidMatrixId) {
|
if (!mxid.isValidMatrixId) {
|
||||||
throw Exception('You must enter a valid mxid when using /maskasdm');
|
throw CommandException(
|
||||||
|
'You must enter a valid mxid when using /maskasdm',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if (await args.room.requestUser(mxid, requestProfile: false) == null) {
|
if (await room.requestUser(mxid, requestProfile: false) == null) {
|
||||||
throw Exception('User $mxid is not in this room');
|
throw CommandException('User $mxid is not in this room');
|
||||||
}
|
}
|
||||||
await args.room.addToDirectChat(args.msg);
|
await room.addToDirectChat(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
addCommand('markasgroup', (args, stdout) async {
|
||||||
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
|
||||||
|
await room.removeFromDirectChat();
|
||||||
return;
|
return;
|
||||||
});
|
});
|
||||||
addCommand('markasgroup', (CommandArgs args) async {
|
addCommand('hug', (args, stdout) async {
|
||||||
await args.room.removeFromDirectChat();
|
|
||||||
return;
|
|
||||||
});
|
|
||||||
addCommand('hug', (CommandArgs args) async {
|
|
||||||
final content = CuteEventContent.hug;
|
final content = CuteEventContent.hug;
|
||||||
return await args.room.sendEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendEvent(
|
||||||
content,
|
content,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
txid: args.txid,
|
txid: args.txid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('googly', (CommandArgs args) async {
|
addCommand('googly', (args, stdout) async {
|
||||||
final content = CuteEventContent.googlyEyes;
|
final content = CuteEventContent.googlyEyes;
|
||||||
return await args.room.sendEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendEvent(
|
||||||
content,
|
content,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
txid: args.txid,
|
txid: args.txid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('cuddle', (CommandArgs args) async {
|
addCommand('cuddle', (args, stdout) async {
|
||||||
final content = CuteEventContent.cuddle;
|
final content = CuteEventContent.cuddle;
|
||||||
return await args.room.sendEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendEvent(
|
||||||
content,
|
content,
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
editEventId: args.editEventId,
|
editEventId: args.editEventId,
|
||||||
txid: args.txid,
|
txid: args.txid,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
addCommand('sendRaw', (args) async {
|
addCommand('sendRaw', (args, stdout) async {
|
||||||
await args.room.sendEvent(
|
final room = args.room;
|
||||||
|
if (room == null) {
|
||||||
|
throw RoomCommandException();
|
||||||
|
}
|
||||||
|
return await room.sendEvent(
|
||||||
jsonDecode(args.msg),
|
jsonDecode(args.msg),
|
||||||
inReplyTo: args.inReplyTo,
|
inReplyTo: args.inReplyTo,
|
||||||
txid: args.txid,
|
txid: args.txid,
|
||||||
);
|
);
|
||||||
return null;
|
|
||||||
});
|
});
|
||||||
addCommand('ignore', (args) async {
|
addCommand('ignore', (args, stdout) async {
|
||||||
final mxid = args.msg;
|
final mxid = args.msg;
|
||||||
if (mxid.isEmpty) {
|
if (mxid.isEmpty) {
|
||||||
throw 'Please provide a User ID';
|
throw CommandException('Please provide a User ID');
|
||||||
}
|
}
|
||||||
await ignoreUser(mxid);
|
await ignoreUser(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
addCommand('unignore', (args) async {
|
addCommand('unignore', (args, stdout) async {
|
||||||
final mxid = args.msg;
|
final mxid = args.msg;
|
||||||
if (mxid.isEmpty) {
|
if (mxid.isEmpty) {
|
||||||
throw 'Please provide a User ID';
|
throw CommandException('Please provide a User ID');
|
||||||
}
|
}
|
||||||
await unignoreUser(mxid);
|
await unignoreUser(mxid);
|
||||||
|
stdout?.write(DefaultCommandOutput(users: [mxid]).toString());
|
||||||
return null;
|
return null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -310,7 +467,8 @@ class CommandArgs {
|
||||||
String msg;
|
String msg;
|
||||||
String? editEventId;
|
String? editEventId;
|
||||||
Event? inReplyTo;
|
Event? inReplyTo;
|
||||||
Room room;
|
Client client;
|
||||||
|
Room? room;
|
||||||
String? txid;
|
String? txid;
|
||||||
String? threadRootEventId;
|
String? threadRootEventId;
|
||||||
String? threadLastEventId;
|
String? threadLastEventId;
|
||||||
|
|
@ -319,9 +477,98 @@ class CommandArgs {
|
||||||
required this.msg,
|
required this.msg,
|
||||||
this.editEventId,
|
this.editEventId,
|
||||||
this.inReplyTo,
|
this.inReplyTo,
|
||||||
required this.room,
|
required this.client,
|
||||||
|
this.room,
|
||||||
this.txid,
|
this.txid,
|
||||||
this.threadRootEventId,
|
this.threadRootEventId,
|
||||||
this.threadLastEventId,
|
this.threadLastEventId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class CommandException implements Exception {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const CommandException(this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '${super.toString()}: $message';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RoomCommandException extends CommandException {
|
||||||
|
const RoomCommandException() : super('This command must run on a room');
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper class for normalized command output
|
||||||
|
///
|
||||||
|
/// This class can be used to provide a default, processable output of commands
|
||||||
|
/// containing some generic data.
|
||||||
|
///
|
||||||
|
/// NOTE: Please be careful whether to include event IDs into the output.
|
||||||
|
///
|
||||||
|
/// If your command actually sends an event to a room, please do not include
|
||||||
|
/// the event ID here. The default behavior of the [Room.sendTextEvent] is to
|
||||||
|
/// return the event ID of the just sent event. The [DefaultCommandOutput.events]
|
||||||
|
/// field is not supposed to replace/duplicate this behavior.
|
||||||
|
///
|
||||||
|
/// But if your command performs an action such as search, highlight or anything
|
||||||
|
/// your matrix client should display different than adding an event to the
|
||||||
|
/// [Timeline], you can include the event IDs related to the command output here.
|
||||||
|
class DefaultCommandOutput {
|
||||||
|
static const format = 'com.famedly.default_command_output';
|
||||||
|
final List<String>? rooms;
|
||||||
|
final List<String>? events;
|
||||||
|
final List<String>? users;
|
||||||
|
final List<String>? messages;
|
||||||
|
final Map<String, Object?>? custom;
|
||||||
|
|
||||||
|
const DefaultCommandOutput({
|
||||||
|
this.rooms,
|
||||||
|
this.events,
|
||||||
|
this.users,
|
||||||
|
this.messages,
|
||||||
|
this.custom,
|
||||||
|
});
|
||||||
|
|
||||||
|
static DefaultCommandOutput? fromStdout(String stdout) {
|
||||||
|
final Object? json = jsonDecode(stdout);
|
||||||
|
if (json is! Map<String, Object?>) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (json['format'] != format) return null;
|
||||||
|
return DefaultCommandOutput(
|
||||||
|
rooms: json['rooms'] == null
|
||||||
|
? null
|
||||||
|
: List<String>.from(json['rooms'] as Iterable),
|
||||||
|
events: json['events'] == null
|
||||||
|
? null
|
||||||
|
: List<String>.from(json['events'] as Iterable),
|
||||||
|
users: json['users'] == null
|
||||||
|
? null
|
||||||
|
: List<String>.from(json['users'] as Iterable),
|
||||||
|
messages: json['messages'] == null
|
||||||
|
? null
|
||||||
|
: List<String>.from(json['messages'] as Iterable),
|
||||||
|
custom: json['custom'] == null
|
||||||
|
? null
|
||||||
|
: Map<String, Object?>.from(json['custom'] as Map),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, Object?> toJson() {
|
||||||
|
return {
|
||||||
|
'format': format,
|
||||||
|
if (rooms != null) 'rooms': rooms,
|
||||||
|
if (events != null) 'events': events,
|
||||||
|
if (users != null) 'users': users,
|
||||||
|
if (messages != null) 'messages': messages,
|
||||||
|
...?custom,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return jsonEncode(toJson());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -395,7 +395,7 @@ void main() {
|
||||||
await room.sendTextEvent('/dm @alice:example.com --no-encryption');
|
await room.sendTextEvent('/dm @alice:example.com --no-encryption');
|
||||||
expect(
|
expect(
|
||||||
json.decode(
|
json.decode(
|
||||||
FakeMatrixApi.calledEndpoints['/client/v3/createRoom']?.first,
|
FakeMatrixApi.calledEndpoints['/client/v3/createRoom']?.last,
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
'invite': ['@alice:example.com'],
|
'invite': ['@alice:example.com'],
|
||||||
|
|
@ -406,12 +406,15 @@ void main() {
|
||||||
|
|
||||||
test('create', () async {
|
test('create', () async {
|
||||||
FakeMatrixApi.calledEndpoints.clear();
|
FakeMatrixApi.calledEndpoints.clear();
|
||||||
await room.sendTextEvent('/create @alice:example.com --no-encryption');
|
await room.sendTextEvent('/create New room --no-encryption');
|
||||||
expect(
|
expect(
|
||||||
json.decode(
|
json.decode(
|
||||||
FakeMatrixApi.calledEndpoints['/client/v3/createRoom']?.first,
|
FakeMatrixApi.calledEndpoints['/client/v3/createRoom']?.last,
|
||||||
),
|
),
|
||||||
{'preset': 'private_chat'},
|
{
|
||||||
|
'name': 'New room',
|
||||||
|
'preset': 'private_chat',
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -527,6 +530,22 @@ void main() {
|
||||||
expect(sent, CuteEventContent.cuddle);
|
expect(sent, CuteEventContent.cuddle);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('client - clearcache', () async {
|
||||||
|
await client.parseAndRunCommand(null, '/clearcache');
|
||||||
|
expect(client.prevBatch, null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('client - missing room - discardsession', () async {
|
||||||
|
Object? error;
|
||||||
|
try {
|
||||||
|
await client.parseAndRunCommand(null, '/discardsession');
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error is RoomCommandException, isTrue);
|
||||||
|
});
|
||||||
|
|
||||||
test('dispose client', () async {
|
test('dispose client', () async {
|
||||||
await client.dispose(closeDatabase: true);
|
await client.dispose(closeDatabase: true);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue