LCOV - code coverage report
Current view: top level - lib/src/utils - commands_extension.dart (source / functions) Hit Total Coverage
Test: merged.info Lines: 140 156 89.7 %
Date: 2024-09-04 20:26:16 Functions: 0 0 -

          Line data    Source code
       1             : /*
       2             :  *   Famedly Matrix SDK
       3             :  *   Copyright (C) 2021 Famedly GmbH
       4             :  *
       5             :  *   This program is free software: you can redistribute it and/or modify
       6             :  *   it under the terms of the GNU Affero General Public License as
       7             :  *   published by the Free Software Foundation, either version 3 of the
       8             :  *   License, or (at your option) any later version.
       9             :  *
      10             :  *   This program is distributed in the hope that it will be useful,
      11             :  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
      12             :  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
      13             :  *   GNU Affero General Public License for more details.
      14             :  *
      15             :  *   You should have received a copy of the GNU Affero General Public License
      16             :  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
      17             :  */
      18             : 
      19             : import 'dart:async';
      20             : import 'dart:convert';
      21             : 
      22             : import 'package:matrix/matrix.dart';
      23             : 
      24             : extension CommandsClientExtension on Client {
      25             :   /// Add a command to the command handler. `command` is its name, and `callback` is the
      26             :   /// callback to invoke
      27          39 :   void addCommand(
      28             :       String command, FutureOr<String?> Function(CommandArgs) callback) {
      29         117 :     commands[command.toLowerCase()] = callback;
      30             :   }
      31             : 
      32             :   /// Parse and execute a string, `msg` is the input. Optionally `inReplyTo` is the event being
      33             :   /// replied to and `editEventId` is the eventId of the event being replied to
      34           5 :   Future<String?> parseAndRunCommand(
      35             :     Room room,
      36             :     String msg, {
      37             :     Event? inReplyTo,
      38             :     String? editEventId,
      39             :     String? txid,
      40             :     String? threadRootEventId,
      41             :     String? threadLastEventId,
      42             :   }) async {
      43           5 :     final args = CommandArgs(
      44             :       inReplyTo: inReplyTo,
      45             :       editEventId: editEventId,
      46             :       msg: '',
      47             :       room: room,
      48             :       txid: txid,
      49             :       threadRootEventId: threadRootEventId,
      50             :       threadLastEventId: threadLastEventId,
      51             :     );
      52           5 :     if (!msg.startsWith('/')) {
      53          10 :       final sendCommand = commands['send'];
      54             :       if (sendCommand != null) {
      55           5 :         args.msg = msg;
      56           5 :         return await sendCommand(args);
      57             :       }
      58             :       return null;
      59             :     }
      60             :     // remove the /
      61           1 :     msg = msg.substring(1);
      62             :     var command = msg;
      63           1 :     if (msg.contains(' ')) {
      64           1 :       final idx = msg.indexOf(' ');
      65           2 :       command = msg.substring(0, idx).toLowerCase();
      66           3 :       args.msg = msg.substring(idx + 1);
      67             :     } else {
      68           1 :       command = msg.toLowerCase();
      69             :     }
      70           2 :     final commandOp = commands[command];
      71             :     if (commandOp != null) {
      72           1 :       return await commandOp(args);
      73             :     }
      74           3 :     if (msg.startsWith('/') && commands.containsKey('send')) {
      75             :       // re-set to include the "command"
      76           2 :       final sendCommand = commands['send'];
      77             :       if (sendCommand != null) {
      78           1 :         args.msg = msg;
      79           1 :         return await sendCommand(args);
      80             :       }
      81             :     }
      82             :     return null;
      83             :   }
      84             : 
      85             :   /// Unregister all commands
      86           0 :   void unregisterAllCommands() {
      87           0 :     commands.clear();
      88             :   }
      89             : 
      90             :   /// Register all default commands
      91          39 :   void registerDefaultCommands() {
      92          44 :     addCommand('send', (CommandArgs args) async {
      93          10 :       return await args.room.sendTextEvent(
      94           5 :         args.msg,
      95           5 :         inReplyTo: args.inReplyTo,
      96           5 :         editEventId: args.editEventId,
      97             :         parseCommands: false,
      98           5 :         txid: args.txid,
      99           5 :         threadRootEventId: args.threadRootEventId,
     100           5 :         threadLastEventId: args.threadLastEventId,
     101             :       );
     102             :     });
     103          40 :     addCommand('me', (CommandArgs args) async {
     104           2 :       return await args.room.sendTextEvent(
     105           1 :         args.msg,
     106           1 :         inReplyTo: args.inReplyTo,
     107           1 :         editEventId: args.editEventId,
     108             :         msgtype: MessageTypes.Emote,
     109             :         parseCommands: false,
     110           1 :         txid: args.txid,
     111           1 :         threadRootEventId: args.threadRootEventId,
     112           1 :         threadLastEventId: args.threadLastEventId,
     113             :       );
     114             :     });
     115          40 :     addCommand('dm', (CommandArgs args) async {
     116           2 :       final parts = args.msg.split(' ');
     117           3 :       return await args.room.client.startDirectChat(
     118           1 :         parts.first,
     119           3 :         enableEncryption: !parts.any((part) => part == '--no-encryption'),
     120             :       );
     121             :     });
     122          40 :     addCommand('create', (CommandArgs args) async {
     123           2 :       final parts = args.msg.split(' ');
     124           3 :       return await args.room.client.createGroupChat(
     125           3 :         enableEncryption: !parts.any((part) => part == '--no-encryption'),
     126             :       );
     127             :     });
     128          40 :     addCommand('plain', (CommandArgs args) async {
     129           2 :       return await args.room.sendTextEvent(
     130           1 :         args.msg,
     131           1 :         inReplyTo: args.inReplyTo,
     132           1 :         editEventId: args.editEventId,
     133             :         parseMarkdown: false,
     134             :         parseCommands: false,
     135           1 :         txid: args.txid,
     136           1 :         threadRootEventId: args.threadRootEventId,
     137           1 :         threadLastEventId: args.threadLastEventId,
     138             :       );
     139             :     });
     140          40 :     addCommand('html', (CommandArgs args) async {
     141           1 :       final event = <String, dynamic>{
     142             :         'msgtype': 'm.text',
     143           1 :         'body': args.msg,
     144             :         'format': 'org.matrix.custom.html',
     145           1 :         'formatted_body': args.msg,
     146             :       };
     147           2 :       return await args.room.sendEvent(
     148             :         event,
     149           1 :         inReplyTo: args.inReplyTo,
     150           1 :         editEventId: args.editEventId,
     151           1 :         txid: args.txid,
     152             :       );
     153             :     });
     154          40 :     addCommand('react', (CommandArgs args) async {
     155           1 :       final inReplyTo = args.inReplyTo;
     156             :       if (inReplyTo == null) {
     157             :         return null;
     158             :       }
     159           4 :       return await args.room.sendReaction(inReplyTo.eventId, args.msg);
     160             :     });
     161          40 :     addCommand('join', (CommandArgs args) async {
     162           4 :       await args.room.client.joinRoom(args.msg);
     163             :       return null;
     164             :     });
     165          40 :     addCommand('leave', (CommandArgs args) async {
     166           2 :       await args.room.leave();
     167             :       return '';
     168             :     });
     169          40 :     addCommand('op', (CommandArgs args) async {
     170           2 :       final parts = args.msg.split(' ');
     171           1 :       if (parts.isEmpty) {
     172             :         return null;
     173             :       }
     174             :       int? pl;
     175           2 :       if (parts.length >= 2) {
     176           2 :         pl = int.tryParse(parts[1]);
     177             :       }
     178           1 :       final mxid = parts.first;
     179           2 :       return await args.room.setPower(mxid, pl ?? 50);
     180             :     });
     181          40 :     addCommand('kick', (CommandArgs args) async {
     182           2 :       final parts = args.msg.split(' ');
     183           3 :       await args.room.kick(parts.first);
     184             :       return '';
     185             :     });
     186          40 :     addCommand('ban', (CommandArgs args) async {
     187           2 :       final parts = args.msg.split(' ');
     188           3 :       await args.room.ban(parts.first);
     189             :       return '';
     190             :     });
     191          40 :     addCommand('unban', (CommandArgs args) async {
     192           2 :       final parts = args.msg.split(' ');
     193           3 :       await args.room.unban(parts.first);
     194             :       return '';
     195             :     });
     196          40 :     addCommand('invite', (CommandArgs args) async {
     197           2 :       final parts = args.msg.split(' ');
     198           3 :       await args.room.invite(parts.first);
     199             :       return '';
     200             :     });
     201          40 :     addCommand('myroomnick', (CommandArgs args) async {
     202           1 :       final currentEventJson = args.room
     203           4 :               .getState(EventTypes.RoomMember, args.room.client.userID!)
     204           1 :               ?.content
     205           1 :               .copy() ??
     206           0 :           {};
     207           2 :       currentEventJson['displayname'] = args.msg;
     208           3 :       return await args.room.client.setRoomStateWithKey(
     209           2 :         args.room.id,
     210             :         EventTypes.RoomMember,
     211           3 :         args.room.client.userID!,
     212             :         currentEventJson,
     213             :       );
     214             :     });
     215          40 :     addCommand('myroomavatar', (CommandArgs args) async {
     216           1 :       final currentEventJson = args.room
     217           4 :               .getState(EventTypes.RoomMember, args.room.client.userID!)
     218           1 :               ?.content
     219           1 :               .copy() ??
     220           0 :           {};
     221           2 :       currentEventJson['avatar_url'] = args.msg;
     222           3 :       return await args.room.client.setRoomStateWithKey(
     223           2 :         args.room.id,
     224             :         EventTypes.RoomMember,
     225           3 :         args.room.client.userID!,
     226             :         currentEventJson,
     227             :       );
     228             :     });
     229          40 :     addCommand('discardsession', (CommandArgs args) async {
     230           2 :       await encryption?.keyManager
     231           3 :           .clearOrUseOutboundGroupSession(args.room.id, wipe: true);
     232             :       return '';
     233             :     });
     234          40 :     addCommand('clearcache', (CommandArgs args) async {
     235           1 :       await clearCache();
     236             :       return '';
     237             :     });
     238          40 :     addCommand('markasdm', (CommandArgs args) async {
     239           1 :       final mxid = args.msg;
     240           1 :       if (!mxid.isValidMatrixId) {
     241           0 :         throw Exception('You must enter a valid mxid when using /maskasdm');
     242             :       }
     243           2 :       if (await args.room.requestUser(mxid, requestProfile: false) == null) {
     244           0 :         throw Exception('User $mxid is not in this room');
     245             :       }
     246           3 :       await args.room.addToDirectChat(args.msg);
     247             :       return;
     248             :     });
     249          40 :     addCommand('markasgroup', (CommandArgs args) async {
     250           2 :       await args.room.removeFromDirectChat();
     251             :       return;
     252             :     });
     253          40 :     addCommand('hug', (CommandArgs args) async {
     254           1 :       final content = CuteEventContent.hug;
     255           2 :       return await args.room.sendEvent(
     256             :         content,
     257           1 :         inReplyTo: args.inReplyTo,
     258           1 :         editEventId: args.editEventId,
     259           1 :         txid: args.txid,
     260             :       );
     261             :     });
     262          40 :     addCommand('googly', (CommandArgs args) async {
     263           1 :       final content = CuteEventContent.googlyEyes;
     264           2 :       return await args.room.sendEvent(
     265             :         content,
     266           1 :         inReplyTo: args.inReplyTo,
     267           1 :         editEventId: args.editEventId,
     268           1 :         txid: args.txid,
     269             :       );
     270             :     });
     271          40 :     addCommand('cuddle', (CommandArgs args) async {
     272           1 :       final content = CuteEventContent.cuddle;
     273           2 :       return await args.room.sendEvent(
     274             :         content,
     275           1 :         inReplyTo: args.inReplyTo,
     276           1 :         editEventId: args.editEventId,
     277           1 :         txid: args.txid,
     278             :       );
     279             :     });
     280          39 :     addCommand('sendRaw', (args) async {
     281           0 :       await args.room.sendEvent(
     282           0 :         jsonDecode(args.msg),
     283           0 :         inReplyTo: args.inReplyTo,
     284           0 :         txid: args.txid,
     285             :       );
     286             :       return null;
     287             :     });
     288          39 :     addCommand('ignore', (args) async {
     289           0 :       final mxid = args.msg;
     290           0 :       if (mxid.isEmpty) {
     291             :         throw 'Please provide a User ID';
     292             :       }
     293           0 :       await ignoreUser(mxid);
     294             :       return null;
     295             :     });
     296          39 :     addCommand('unignore', (args) async {
     297           0 :       final mxid = args.msg;
     298           0 :       if (mxid.isEmpty) {
     299             :         throw 'Please provide a User ID';
     300             :       }
     301           0 :       await unignoreUser(mxid);
     302             :       return null;
     303             :     });
     304             :   }
     305             : }
     306             : 
     307             : class CommandArgs {
     308             :   String msg;
     309             :   String? editEventId;
     310             :   Event? inReplyTo;
     311             :   Room room;
     312             :   String? txid;
     313             :   String? threadRootEventId;
     314             :   String? threadLastEventId;
     315             : 
     316           5 :   CommandArgs(
     317             :       {required this.msg,
     318             :       this.editEventId,
     319             :       this.inReplyTo,
     320             :       required this.room,
     321             :       this.txid,
     322             :       this.threadRootEventId,
     323             :       this.threadLastEventId});
     324             : }

Generated by: LCOV version 1.14