]> cgit.babelmonkeys.de Git - jubjub.git/blob - src/gui/cli/JubCLIUI.m
3cf59c2b05b96b5e5fb6a57ce528c4b77c7c6672
[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 @implementation JubCLIUI
138 @synthesize client = _client;
139 @synthesize lastIn = _lastIn;
140 @synthesize sink = _sink;
141
142 - initWithClient: (JubChatClient*)client
143 {
144         self = [super init];
145
146         @try {
147                 _commands =  [OFMutableDictionary new];
148                 _client = [client retain];
149                 _contactManager = client.contactManager;
150                 [_contactManager addDelegate: self];
151
152                 [self addCommand: [[[JubCLIReplyCommand alloc]
153                                        initWithCLIUI: self] autorelease]];
154
155                 [self addCommand: [[[JubCLISetSinkCommand alloc]
156                                        initWithCLIUI: self] autorelease]];
157
158                 [self addCommand: [[[JubCLIMessageCommand alloc]
159                                        initWithCLIUI: self] autorelease]];
160
161                 [self addCommand: [[[JubCLIPresenceCommand alloc]
162                                        initWithCLIUI: self] autorelease]];
163
164                 [self addCommand: [[[JubCLIRosterCommand alloc]
165                                        initWithCLIUI: self] autorelease]];
166         } @catch (id e) {
167                 [self release];
168                 @throw e;
169         }
170
171         return self;
172 }
173
174 - (void)dealloc
175 {
176         [_contactManager removeDelegate: self];
177         [_client release];
178         [_commands release];
179         [super dealloc];
180 }
181
182 static JubCLIUI *completionData;
183 static void completionCallback(OFString *buf, OFList *lc)
184 {
185         if ([buf length] < 3)
186                 return;
187
188         if (![buf hasPrefix: @":s "] && ![buf hasPrefix: @":m "] &&
189             ![buf hasPrefix: @":t "])
190                 return;
191
192         if ([buf hasPrefix: @":t"]) {
193                 OFString *options[] = {
194                         @":t available",
195                         @":t away",
196                         @":t dnd",
197                         @":t xa",
198                         @":t chat",
199                         @":t unavailable"
200                 };
201
202                 for (int i = 0; i < 6; i++) {
203                         if (![options[i] hasPrefix: buf])
204                                 continue;
205                         [lc appendObject: options[i]];
206                 }
207
208                 return;
209         }
210
211         OFString *command = [buf substringWithRange: of_range(0, 3)];
212         OFString *query = [buf substringWithRange: of_range(3, [buf length]-3)];
213         OFDictionary *contacts = completionData.client.contactManager.contacts;
214         for (OFString *key in contacts) {
215                 if (![key  hasPrefix: query])
216                         continue;
217                 [lc appendObject: [command stringByAppendingString: key]];
218         }
219 }
220
221 - (void)startUIThread
222 {
223         completionData = self;
224
225         [[OFThread threadWithBlock: ^(void) {
226                 OFString *line;
227                 Linenoise *reader = [Linenoise sharedLinenoise];
228                 reader.multiline = true;
229                 reader.completionCallback = completionCallback;
230
231                 while ((line = [reader readInputWithPrompt: @"> "]) != nil) @autoreleasepool {
232                         [self Jub_userInputWithStream: nil
233                                                  line: line
234                                             exception: nil];
235                         if ([line length] != 0)
236                                 [reader addHistoryItem: line];
237                 }
238                 [self Jub_userInputWithStream: nil
239                                          line: nil
240                                     exception: nil];
241
242                 return nil;
243         }] start];
244 }
245
246 -      (void)client: (JubChatClient*)client
247   didChangePresence: (XMPPPresence*)presence
248 {
249 }
250
251 - (Class)chatUIClass
252 {
253         return [JubCLIChatUI class];
254 }
255
256 - (void)addCommand: (id<JubCLICommand>)command
257 {
258         [_commands setObject: command
259                       forKey: command.command];
260 }
261
262 - (BOOL)Jub_userInputWithStream: (OFStream*)stream
263                            line: (OFString*)line
264                       exception: (OFException*)exception
265 {
266         if (line == nil || exception != nil)
267                 [OFApplication terminate];
268
269         if ([line length] == 0)
270                 return YES;
271
272         if ([line characterAtIndex: 0] != ':') {
273                 if (_sink == nil) {
274                         [of_stdout writeLine: @"No default sink selected, "
275                             @"type `:h` for help"];
276                         return YES;
277                 }
278
279                 [_sink send: line];
280
281                 return YES;
282         }
283
284         line = [line stringByDeletingTrailingWhitespaces];
285
286         OFArray *input= [line componentsSeparatedByString: @" "];
287
288         if ([input[0] isEqual: @":h"]) {
289                 __block size_t longest = 0;
290
291                 [_commands enumerateKeysAndObjectsUsingBlock:
292                     ^(OFString *key, id<JubCLICommand> command, bool *stop) {
293                         size_t length = [command.command length] +
294                             (command.params == nil ? 0 :
295                                 (1 + [command.params length]));
296
297                         if (length > longest)
298                                 longest = length;
299                 }];
300
301                 for (OFString *key in [[_commands allKeys] sortedArray]) {
302                         id<JubCLICommand> command = _commands[key];
303                         size_t length = [command.command length] +
304                             (command.params == nil ? 0 :
305                                 (1 + [command.params length]));
306
307                         if (command.params == nil)
308                                 [of_stdout writeFormat: @"`%@`",
309                                     command.command];
310                         else
311                                 [of_stdout writeFormat: @"`%@ %@`",
312                                     command.command, command.params];
313
314                         // This is NOT distributive due to integer arithmetic
315                         size_t padding = (longest + 2)/8 - (length + 2)/8;
316
317                         for (size_t i = 0; i <= padding; i++)
318                                 [of_stdout writeString: @"\t"];
319
320                         [of_stdout writeFormat: @"- %@\n", command.help];
321                 };
322
323                 return YES;
324         }
325
326         id<JubCLICommand> command = _commands[input[0]];
327
328         if (command) {
329                 [command callWithParameters:
330                     [input arrayByRemovingObject: [input firstObject]]];
331
332                 return YES;
333         }
334
335         [of_stdout writeLine: @"Invalid command, type `:h` for help"];
336
337         return YES;
338 }
339
340 -  (void)contact: (XMPPContact*)contact
341   didSendMessage: (XMPPMessage*)message
342 {
343         if (message.body == nil || ![message.type isEqual: @"chat"])
344                 return;
345
346         _lastIn =  contact;
347 }
348
349 -   (void)contact: (XMPPContact*)contact
350   didSendPresence: (XMPPPresence*)presence
351 {
352         [of_stdout writeFormat: @"\r"];
353         [of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];
354
355         if ([presence.type isEqual: @"unavailable"])
356                 [of_stdout writeFormat: COL_OFFLINE(@"offline")];
357         else if ([presence.show isEqual: @"chat"])
358                 [of_stdout writeFormat: COL_CHAT(@"free for chat")];
359         else if ([presence.show isEqual: @"away"])
360                 [of_stdout writeFormat: COL_AWAY(@"away")];
361         else if ([presence.show isEqual: @"xa"])
362                 [of_stdout writeFormat: COL_XA(@"extended away")];
363         else if ([presence.show isEqual: @"dnd"])
364                 [of_stdout writeFormat: COL_DND(@"do not disturb")];
365         else
366                 [of_stdout writeFormat: COL_ONLINE(@"online")];
367
368         if (presence.status != nil)
369                 [of_stdout writeFormat: @": %@", presence.status];
370
371         [of_stdout writeString: @"\n"];
372
373         [[Linenoise sharedLinenoise] refreshLine];
374 }
375 @end