/* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ import 'dart:async'; import '../../matrix.dart'; extension CommandsClientExtension on Client { /// Add a command to the command handler. `command` is its name, and `callback` is the /// callback to invoke void addCommand( String command, FutureOr Function(CommandArgs) callback) { commands[command.toLowerCase()] = callback; } /// Parse and execute a string, `msg` is the input. Optionally `inReplyTo` is the event being /// replied to and `editEventId` is the eventId of the event being replied to Future parseAndRunCommand(Room room, String msg, {Event? inReplyTo, String? editEventId, String? txid}) async { final args = CommandArgs( inReplyTo: inReplyTo, editEventId: editEventId, msg: '', room: room, txid: txid, ); if (!msg.startsWith('/')) { final sendCommand = commands['send']; if (sendCommand != null) { args.msg = msg; return await sendCommand(args); } return null; } // remove the / msg = msg.substring(1); var command = msg; if (msg.contains(' ')) { final idx = msg.indexOf(' '); command = msg.substring(0, idx).toLowerCase(); args.msg = msg.substring(idx + 1); } else { command = msg.toLowerCase(); } final commandOp = commands[command]; if (commandOp != null) { return await commandOp(args); } if (msg.startsWith('/') && commands.containsKey('send')) { // re-set to include the "command" final sendCommand = commands['send']; if (sendCommand != null) { args.msg = msg; return await sendCommand(args); } } return null; } /// Unregister all commands void unregisterAllCommands() { commands.clear(); } /// Register all default commands void registerDefaultCommands() { addCommand('send', (CommandArgs args) async { return await args.room.sendTextEvent( args.msg, inReplyTo: args.inReplyTo, editEventId: args.editEventId, parseCommands: false, txid: args.txid, ); }); addCommand('me', (CommandArgs args) async { return await args.room.sendTextEvent( args.msg, inReplyTo: args.inReplyTo, editEventId: args.editEventId, msgtype: MessageTypes.Emote, parseCommands: false, txid: args.txid, ); }); addCommand('dm', (CommandArgs args) async { final parts = args.msg.split(' '); return await args.room.client.startDirectChat( parts.first, enableEncryption: !parts.any((part) => part == '--no-encryption'), ); }); addCommand('create', (CommandArgs args) async { final parts = args.msg.split(' '); return await args.room.client.createGroupChat( enableEncryption: !parts.any((part) => part == '--no-encryption'), ); }); addCommand('plain', (CommandArgs args) async { return await args.room.sendTextEvent( args.msg, inReplyTo: args.inReplyTo, editEventId: args.editEventId, parseMarkdown: false, parseCommands: false, txid: args.txid, ); }); addCommand('html', (CommandArgs args) async { final event = { 'msgtype': 'm.text', 'body': args.msg, 'format': 'org.matrix.custom.html', 'formatted_body': args.msg, }; return await args.room.sendEvent( event, inReplyTo: args.inReplyTo, editEventId: args.editEventId, txid: args.txid, ); }); addCommand('react', (CommandArgs args) async { final inReplyTo = args.inReplyTo; if (inReplyTo == null) { return null; } return await args.room.sendReaction(inReplyTo.eventId, args.msg); }); addCommand('join', (CommandArgs args) async { await args.room.client.joinRoom(args.msg); return null; }); addCommand('leave', (CommandArgs args) async { await args.room.leave(); return ''; }); addCommand('op', (CommandArgs args) async { final parts = args.msg.split(' '); if (parts.isEmpty) { return null; } int? pl; if (parts.length >= 2) { pl = int.tryParse(parts[1]); } final mxid = parts.first; return await args.room.setPower(mxid, pl ?? 50); }); addCommand('kick', (CommandArgs args) async { final parts = args.msg.split(' '); await args.room.kick(parts.first); return ''; }); addCommand('ban', (CommandArgs args) async { final parts = args.msg.split(' '); await args.room.ban(parts.first); return ''; }); addCommand('unban', (CommandArgs args) async { final parts = args.msg.split(' '); await args.room.unban(parts.first); return ''; }); addCommand('invite', (CommandArgs args) async { final parts = args.msg.split(' '); await args.room.invite(parts.first); return ''; }); addCommand('myroomnick', (CommandArgs args) async { final currentEventJson = args.room .getState(EventTypes.RoomMember, args.room.client.userID!) ?.content .copy() ?? {}; currentEventJson['displayname'] = args.msg; return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, args.room.client.userID!, currentEventJson, ); }); addCommand('myroomavatar', (CommandArgs args) async { final currentEventJson = args.room .getState(EventTypes.RoomMember, args.room.client.userID!) ?.content .copy() ?? {}; currentEventJson['avatar_url'] = args.msg; return await args.room.client.setRoomStateWithKey( args.room.id, EventTypes.RoomMember, args.room.client.userID!, currentEventJson, ); }); addCommand('discardsession', (CommandArgs args) async { await encryption?.keyManager .clearOrUseOutboundGroupSession(args.room.id, wipe: true); return ''; }); addCommand('clearcache', (CommandArgs args) async { await clearCache(); return ''; }); } } class CommandArgs { String msg; String? editEventId; Event? inReplyTo; Room room; String? txid; CommandArgs( {required this.msg, this.editEventId, this.inReplyTo, required this.room, this.txid}); }