1 #import "JubCLIChatUI.h"
2 #import "JubChatClient.h"
3 #import "JubCLIColor.h"
4 #import "JubCLICommand.h"
9 BEGINCLICOMMAND(JubCLIReplyCommand, @":r", nil,
10 @"Sets the sender of the last incomming message as the default recipient")
12 if (_ui.lastIn == nil) {
13 [of_stdout writeLine: @"No message has been received yet"];
17 _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: _ui.lastIn];
18 [of_stdout writeFormat: @"Set sink to %@\n",
19 [_ui.lastIn.rosterItem.JID bareJID]];
23 BEGINCLICOMMAND(JubCLISetSinkCommand, @":s", @"<who>",
24 @"Selects <who> as the default recipient")
26 if ([parameters count] != 1) {
27 [of_stdout writeLine: @"Syntax: ':s <who>'"];
31 OFString *param = parameters[0];
32 XMPPContact *contact = _ui.client.contactManager.contacts[param];
35 [of_stdout writeFormat: @"Contact '%@' not found in your "
40 _ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: contact];
42 [of_stdout writeFormat: @"Set sink to %@\n", param];
46 BEGINCLICOMMAND(JubCLIMessageCommand, @":m", @"<who> <message>",
47 @"Sends a single message to <who>")
49 if ([parameters count] < 2) {
50 [of_stdout writeLine: @"Syntax: ':m <who> <message>'"];
54 XMPPContact *contact =
55 _ui.client.contactManager.contacts[parameters[0]];
58 [of_stdout writeFormat: @"Contact %@ not found in your "
59 @"roster\n", parameters[0]];
64 (JubCLIChatUI*)[_ui.client chatForContact: contact];
67 [parameters arrayByRemovingObject: [parameters firstObject]];
69 [chat send: [message componentsJoinedByString: @" "]];
73 BEGINCLICOMMAND(JubCLIPresenceCommand, @":t", @"<status> [<message>]",
74 @"Changes your presence")
76 if ([parameters count] < 1) {
78 @"Syntax: ':t <status> [<message>]'"];
82 OFString *show = parameters[0];
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"];
91 if ([parameters count] == 2) {
92 [_ui.client sendPresenceWithStatus: show];
96 OFString *message = [[parameters
97 arrayByRemovingObject: [parameters firstObject]]
98 componentsJoinedByString: @" "];
100 [_ui.client sendPresenceWithStatus: show
105 BEGINCLICOMMAND(JubCLIRosterCommand, @":roster", nil, @"Shows your roster")
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];
115 [of_stdout writeFormat: @"%@ <%@> (", name, key];
117 [of_stdout writeFormat: @"%@ (", key];
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")];
130 [of_stdout writeFormat: COL_ONLINE(@"online")];
132 [of_stdout writeString: @")\n"];
137 @implementation JubCLIUI
138 @synthesize client = _client;
139 @synthesize lastIn = _lastIn;
140 @synthesize sink = _sink;
142 - initWithClient: (JubChatClient*)client
147 _commands = [OFMutableDictionary new];
148 _client = [client retain];
149 _contactManager = client.contactManager;
150 [_contactManager addDelegate: self];
152 [self addCommand: [[[JubCLIReplyCommand alloc]
153 initWithCLIUI: self] autorelease]];
155 [self addCommand: [[[JubCLISetSinkCommand alloc]
156 initWithCLIUI: self] autorelease]];
158 [self addCommand: [[[JubCLIMessageCommand alloc]
159 initWithCLIUI: self] autorelease]];
161 [self addCommand: [[[JubCLIPresenceCommand alloc]
162 initWithCLIUI: self] autorelease]];
164 [self addCommand: [[[JubCLIRosterCommand alloc]
165 initWithCLIUI: self] autorelease]];
176 [_contactManager removeDelegate: self];
182 static JubCLIUI *completionData;
183 static void completionCallback(OFString *buf, OFList *lc)
185 if ([buf length] < 3)
188 if (![buf hasPrefix: @":s "] && ![buf hasPrefix: @":m "] &&
189 ![buf hasPrefix: @":t "])
192 if ([buf hasPrefix: @":t"]) {
193 OFString *options[] = {
202 for (int i = 0; i < 6; i++) {
203 if (![options[i] hasPrefix: buf])
205 [lc appendObject: options[i]];
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])
217 [lc appendObject: [command stringByAppendingString: key]];
221 - (void)startUIThread
223 completionData = self;
225 [[OFThread threadWithBlock: ^(void) {
227 Linenoise *reader = [Linenoise sharedLinenoise];
228 reader.multiline = true;
229 reader.completionCallback = completionCallback;
231 while ((line = [reader readInputWithPrompt: @"> "]) != nil) @autoreleasepool {
232 [self Jub_userInputWithStream: nil
235 if ([line length] != 0)
236 [reader addHistoryItem: line];
238 [self Jub_userInputWithStream: nil
246 - (void)client: (JubChatClient*)client
247 didChangePresence: (XMPPPresence*)presence
253 return [JubCLIChatUI class];
256 - (void)addCommand: (id<JubCLICommand>)command
258 [_commands setObject: command
259 forKey: command.command];
262 - (BOOL)Jub_userInputWithStream: (OFStream*)stream
263 line: (OFString*)line
264 exception: (OFException*)exception
266 if (line == nil || exception != nil)
267 [OFApplication terminate];
269 if ([line length] == 0)
272 if ([line characterAtIndex: 0] != ':') {
274 [of_stdout writeLine: @"No default sink selected, "
275 @"type `:h` for help"];
284 line = [line stringByDeletingTrailingWhitespaces];
286 OFArray *input= [line componentsSeparatedByString: @" "];
288 if ([input[0] isEqual: @":h"]) {
289 __block size_t longest = 0;
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]));
297 if (length > longest)
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]));
307 if (command.params == nil)
308 [of_stdout writeFormat: @"`%@`",
311 [of_stdout writeFormat: @"`%@ %@`",
312 command.command, command.params];
314 // This is NOT distributive due to integer arithmetic
315 size_t padding = (longest + 2)/8 - (length + 2)/8;
317 for (size_t i = 0; i <= padding; i++)
318 [of_stdout writeString: @"\t"];
320 [of_stdout writeFormat: @"- %@\n", command.help];
326 id<JubCLICommand> command = _commands[input[0]];
329 [command callWithParameters:
330 [input arrayByRemovingObject: [input firstObject]]];
335 [of_stdout writeLine: @"Invalid command, type `:h` for help"];
340 - (void)contact: (XMPPContact*)contact
341 didSendMessage: (XMPPMessage*)message
343 if (message.body == nil || ![message.type isEqual: @"chat"])
349 - (void)contact: (XMPPContact*)contact
350 didSendPresence: (XMPPPresence*)presence
352 [of_stdout writeFormat: @"\r"];
353 [of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];
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")];
366 [of_stdout writeFormat: COL_ONLINE(@"online")];
368 if (presence.status != nil)
369 [of_stdout writeFormat: @": %@", presence.status];
371 [of_stdout writeString: @"\n"];
373 [[Linenoise sharedLinenoise] refreshLine];