]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/cli/JubCLIUI.m
Adapt to ObjFW changes
[jubjub.git] / src / gui / cli / JubCLIUI.m
1 #import "JubCLIChatUI.h"
2 #import "JubChatClient.h"
3 #import "JubCLIColor.h"
4 #import "JubCLICommand.h"
5 #import "JubCLIUI.h"
6
7 #include <string.h>
8
9 BEGINCLICOMMAND(JubCLIReplyCommand, @":r", nil,
10     @"Sets the sender of the last incomming message as the default recipient")
11 {
12         if (_ui.lastIn == nil) {
13                 [of_stdout writeLine: @"No message has been received yet"];
14                 return;
15         }
16
17         _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: _ui.lastIn];
18         [of_stdout writeFormat: @"Set sink to %@\n",
19             [_ui.lastIn.rosterItem.JID bareJID]];
20 }
21 ENDCLICOMMAND
22
23 BEGINCLICOMMAND(JubCLISetSinkCommand, @":s", @"<who>",
24     @"Selects <who> as the default recipient")
25 {
26         if ([parameters count] != 1) {
27                 [of_stdout writeLine: @"Syntax: ':s <who>'"];
28                 return;
29         }
30
31         OFString *param = parameters[0];
32         XMPPContact *contact = _ui.client.contactManager.contacts[param];
33
34         if (contact == nil) {
35                 [of_stdout writeFormat: @"Contact '%@' not found in your "
36                     @"roster\n", param];
37                 return;
38         }
39
40         _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: contact];
41
42         [of_stdout writeFormat: @"Set sink to %@\n", param];
43 }
44 ENDCLICOMMAND
45
46 BEGINCLICOMMAND(JubCLIMessageCommand, @":m", @"<who> <message>",
47     @"Sends a single message to <who>")
48 {
49         if ([parameters count] < 2) {
50                 [of_stdout writeLine: @"Syntax: ':m <who> <message>'"];
51                 return;
52         }
53
54         XMPPContact *contact =
55             _ui.client.contactManager.contacts[parameters[0]];
56
57         if (contact == nil) {
58                 [of_stdout writeFormat: @"Contact %@ not found in your "
59                     @"roster\n", parameters[0]];
60                 return;
61         }
62
63         JubCLIChatUI *chat =
64             (JubCLIChatUI*)[_ui.client chatForContact: contact];
65
66         OFArray *message =
67             [parameters arrayByRemovingObject: [parameters firstObject]];
68
69         [chat send: [message componentsJoinedByString: @" "]];
70 }
71 ENDCLICOMMAND
72
73 BEGINCLICOMMAND(JubCLIPresenceCommand, @":t", @"<status> [<message>]",
74     @"Changes your presence")
75 {
76         if ([parameters count] < 1) {
77                 [of_stdout writeLine:
78                     @"Syntax: ':t <status> [<message>]'"];
79                 return;
80         }
81
82         OFString *show = parameters[0];
83
84         if (![@[ @"available", @"away", @"dnd", @"xa", @"chat", @"unavailable" ]
85             containsObject: show]) {
86                 [of_stdout writeLine: @"<status> must be one of: "
87                     @"available, away, dnd, xa, chat, unavailable"];
88                 return;
89         }
90
91         if ([parameters count] == 2) {
92                 [_ui.client sendPresenceWithStatus: show];
93                 return;
94         }
95
96         OFString *message = [[parameters
97             arrayByRemovingObject: [parameters firstObject]]
98                 componentsJoinedByString: @" "];
99
100         [_ui.client sendPresenceWithStatus: show
101                                       text: message];
102 }
103 ENDCLICOMMAND
104
105 BEGINCLICOMMAND(JubCLIRosterCommand, @":roster", nil, @"Shows your roster")
106 {
107         OFDictionary *contacts = _ui.client.contactManager.contacts;
108         for (OFString *key in contacts) {
109                 XMPPContact *contact = contacts[key];
110                 OFString *name = contact.rosterItem.name;
111                 XMPPPresence *presence =
112                     [[[contact.presences allObjects] sortedArray] firstObject];
113
114                 if (name != nil)
115                         [of_stdout writeFormat: @"%@ <%@> (", name, key];
116                 else
117                         [of_stdout writeFormat: @"%@ (", key];
118
119                 if (presence == nil)
120                         [of_stdout writeFormat: COL_OFFLINE(@"offline")];
121                 else if ([presence.show isEqual: @"chat"])
122                         [of_stdout writeFormat: COL_CHAT(@"free for chat")];
123                 else if ([presence.show isEqual: @"away"])
124                         [of_stdout writeFormat: COL_AWAY(@"away")];
125                 else if ([presence.show isEqual: @"xa"])
126                         [of_stdout writeFormat: COL_XA(@"extended away")];
127                 else if ([presence.show isEqual: @"dnd"])
128                         [of_stdout writeFormat: COL_DND(@"do not disturb")];
129                 else
130                         [of_stdout writeFormat: COL_ONLINE(@"online")];
131
132                 [of_stdout writeString: @")\n"];
133         }
134 }
135 ENDCLICOMMAND
136
137 BEGINCLICOMMAND(JubCLISubsCommand, @":subs",
138     @"[list | ack <who> | nak <who>]",
139     @"Lists, acknowledges or denies subscription requests")
140 {
141         if ([parameters count] < 1) {
142                 [of_stdout writeLine: @"Syntax: ':subs "
143                     @"[list | ack <who> | nak <who>]'"];
144                 return;
145         }
146
147         OFString *action = parameters[0];
148         if (![action isEqual: @"list"] && ([parameters count] != 2)) {
149                 [of_stdout writeLine: @"Syntax: ':subs "
150                     @"[list | ack <who> | nak <who>]'"];
151                 return;
152         }
153
154         if ([action isEqual: @"list"]) {
155                 for (OFString *request in _ui.subRequests)
156                         [of_stdout writeLine: request];
157                 return;
158         }
159
160         XMPPJID *JID = [XMPPJID JIDWithString: parameters[1]];
161
162         if ([action isEqual: @"ack"])
163                 [_ui.client.contactManager sendSubscribedToJID: JID];
164         else if ([action isEqual: @"nak"])
165                 [_ui.client.contactManager sendUnsubscribedToJID: JID];
166
167         [_ui.subRequests removeObject: parameters[1]];
168 }
169 ENDCLICOMMAND
170
171 @implementation JubCLIUI
172 @synthesize client = _client;
173 @synthesize lastIn = _lastIn;
174 @synthesize sink = _sink;
175 @synthesize subRequests = _subRequests;
176
177 - initWithClient: (JubChatClient*)client
178 {
179         self = [super init];
180
181         @try {
182                 _commands =  [OFMutableDictionary new];
183                 _subRequests =  [OFMutableSet new];
184                 _client = [client retain];
185                 _contactManager = client.contactManager;
186                 [_contactManager addDelegate: self];
187
188                 [self addCommand: [[[JubCLIReplyCommand alloc]
189                                        initWithCLIUI: self] autorelease]];
190
191                 [self addCommand: [[[JubCLISetSinkCommand alloc]
192                                        initWithCLIUI: self] autorelease]];
193
194                 [self addCommand: [[[JubCLIMessageCommand alloc]
195                                        initWithCLIUI: self] autorelease]];
196
197                 [self addCommand: [[[JubCLIPresenceCommand alloc]
198                                        initWithCLIUI: self] autorelease]];
199
200                 [self addCommand: [[[JubCLIRosterCommand alloc]
201                                        initWithCLIUI: self] autorelease]];
202                 [self addCommand: [[[JubCLISubsCommand alloc]
203                                        initWithCLIUI: self] autorelease]];
204         } @catch (id e) {
205                 [self release];
206                 @throw e;
207         }
208
209         return self;
210 }
211
212 - (void)dealloc
213 {
214         [_contactManager removeDelegate: self];
215         [_client release];
216         [_subRequests release];
217         [_commands release];
218         [super dealloc];
219 }
220
221 static JubCLIUI *completionData;
222 static void completionCallback(OFString *buf, OFList *lc)
223 {
224         if ([buf length] < 3)
225                 return;
226
227         if (![buf hasPrefix: @":s "] && ![buf hasPrefix: @":m "] &&
228             ![buf hasPrefix: @":t "])
229                 return;
230
231         if ([buf hasPrefix: @":t"]) {
232                 OFString *options[] = {
233                         @":t available",
234                         @":t away",
235                         @":t dnd",
236                         @":t xa",
237                         @":t chat",
238                         @":t unavailable"
239                 };
240
241                 for (int i = 0; i < 6; i++) {
242                         if (![options[i] hasPrefix: buf])
243                                 continue;
244                         [lc appendObject: options[i]];
245                 }
246
247                 return;
248         }
249
250         OFString *command = [buf substringWithRange: of_range(0, 3)];
251         OFString *query = [buf substringWithRange: of_range(3, [buf length]-3)];
252         OFDictionary *contacts = completionData.client.contactManager.contacts;
253         for (OFString *key in contacts) {
254                 if (![key  hasPrefix: query])
255                         continue;
256                 [lc appendObject: [command stringByAppendingString: key]];
257         }
258 }
259
260 - (void)startUIThread
261 {
262         completionData = self;
263
264         [[OFThread threadWithThreadBlock: ^(void) {
265                 OFString *line;
266                 Linenoise *reader = [Linenoise sharedLinenoise];
267                 reader.multiline = true;
268                 reader.completionCallback = completionCallback;
269
270                 while ((line = [reader readInputWithPrompt: @"> "]) != nil) @autoreleasepool {
271                         [self Jub_userInputWithStream: nil
272                                                  line: line
273                                             exception: nil];
274                         if ([line length] != 0)
275                                 [reader addHistoryItem: line];
276                 }
277                 [self Jub_userInputWithStream: nil
278                                          line: nil
279                                     exception: nil];
280
281                 return nil;
282         }] start];
283 }
284
285 -      (void)client: (JubChatClient*)client
286   didChangePresence: (XMPPPresence*)presence
287 {
288 }
289
290 - (Class)chatUIClass
291 {
292         return [JubCLIChatUI class];
293 }
294
295 - (void)addCommand: (id<JubCLICommand>)command
296 {
297         [_commands setObject: command
298                       forKey: command.command];
299 }
300
301 - (bool)Jub_userInputWithStream: (OFStream*)stream
302                            line: (OFString*)line
303                       exception: (OFException*)exception
304 {
305         if (line == nil || exception != nil)
306                 [OFApplication terminate];
307
308         if ([line length] == 0)
309                 return true;
310
311         if ([line characterAtIndex: 0] != ':') {
312                 if (_sink == nil) {
313                         [of_stdout writeLine: @"No default sink selected, "
314                             @"type `:h` for help"];
315                         return true;
316                 }
317
318                 [_sink send: line];
319
320                 return true;
321         }
322
323         line = [line stringByDeletingTrailingWhitespaces];
324
325         OFArray *input= [line componentsSeparatedByString: @" "];
326
327         if ([input[0] isEqual: @":h"]) {
328                 __block size_t longest = 0;
329
330                 [_commands enumerateKeysAndObjectsUsingBlock:
331                     ^(OFString *key, id<JubCLICommand> command, bool *stop) {
332                         size_t length = [command.command length] +
333                             (command.params == nil ? 0 :
334                                 (1 + [command.params length]));
335
336                         if (length > longest)
337                                 longest = length;
338                 }];
339
340                 for (OFString *key in [[_commands allKeys] sortedArray]) {
341                         id<JubCLICommand> command = _commands[key];
342                         size_t length = [command.command length] +
343                             (command.params == nil ? 0 :
344                                 (1 + [command.params length]));
345
346                         if (command.params == nil)
347                                 [of_stdout writeFormat: @"`%@`",
348                                     command.command];
349                         else
350                                 [of_stdout writeFormat: @"`%@ %@`",
351                                     command.command, command.params];
352
353                         // This is NOT distributive due to integer arithmetic
354                         size_t padding = (longest + 2)/8 - (length + 2)/8;
355
356                         for (size_t i = 0; i <= padding; i++)
357                                 [of_stdout writeString: @"\t"];
358
359                         [of_stdout writeFormat: @"- %@\n", command.help];
360                 };
361
362                 return true;
363         }
364
365         id<JubCLICommand> command = _commands[input[0]];
366
367         if (command) {
368                 [command callWithParameters:
369                     [input arrayByRemovingObject: [input firstObject]]];
370
371                 return true;
372         }
373
374         [of_stdout writeLine: @"Invalid command, type `:h` for help"];
375
376         return true;
377 }
378
379 - (void)contactManager: (XMPPContactManager*)manager
380          didAddContact: (XMPPContact*)contact
381 {
382         XMPPRosterItem *rosterItem = contact.rosterItem;
383         OFString *subscription = rosterItem.subscription;
384         OFString *bareJID = [rosterItem.JID bareJID];
385
386         if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
387                 [_subRequests removeObject: bareJID];
388 }
389
390 -          (void)contactManager: (XMPPContactManager*)manager
391   didReceiveSubscriptionRequest: (XMPPPresence*)presence
392 {
393         OFString *bareJID = [presence.from bareJID];
394         [_subRequests addObject: bareJID];
395         [of_stdout writeFormat: @"\r" BOLD("%@") @" send a request to "
396             @"subscribe to your presence\r\n"
397             @"Use the :subs command to answer it\n",
398             bareJID];
399         [[Linenoise sharedLinenoise] refreshLine];
400 }
401
402 -            (void)contact: (XMPPContact*)contact
403   willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem
404 {
405         OFString *subscription = rosterItem.subscription;
406         OFString *bareJID = [rosterItem.JID bareJID];
407
408         if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
409                 [_subRequests removeObject: bareJID];
410 }
411
412 -  (void)contact: (XMPPContact*)contact
413   didSendMessage: (XMPPMessage*)message
414 {
415         if (message.body == nil || ![message.type isEqual: @"chat"])
416                 return;
417
418         _lastIn =  contact;
419 }
420
421 -   (void)contact: (XMPPContact*)contact
422   didSendPresence: (XMPPPresence*)presence
423 {
424         [of_stdout writeFormat: @"\r"];
425         [of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];
426
427         if ([presence.type isEqual: @"unavailable"])
428                 [of_stdout writeFormat: COL_OFFLINE(@"offline")];
429         else if ([presence.show isEqual: @"chat"])
430                 [of_stdout writeFormat: COL_CHAT(@"free for chat")];
431         else if ([presence.show isEqual: @"away"])
432                 [of_stdout writeFormat: COL_AWAY(@"away")];
433         else if ([presence.show isEqual: @"xa"])
434                 [of_stdout writeFormat: COL_XA(@"extended away")];
435         else if ([presence.show isEqual: @"dnd"])
436                 [of_stdout writeFormat: COL_DND(@"do not disturb")];
437         else
438                 [of_stdout writeFormat: COL_ONLINE(@"online")];
439
440         if (presence.status != nil)
441                 [of_stdout writeFormat: @": %@", presence.status];
442
443         [of_stdout writeString: @"\n"];
444
445         [[Linenoise sharedLinenoise] refreshLine];
446 }
447 @end