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 : }
|