LCOV - code coverage report
Current view: top level - lib/src/utils - commands_extension.dart (source / functions) Coverage Total Hit
Test: merged.info Lines: 89.7 % 156 140
Test Date: 2025-01-14 13:39:53 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 2.0-1