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

Generated by: LCOV version 1.14