#import "JubCLICommand.h"
#import "JubCLIUI.h"
+#include <string.h>
+
BEGINCLICOMMAND(JubCLIReplyCommand, @":r", nil,
@"Sets the sender of the last incomming message as the default recipient")
{
}
ENDCLICOMMAND
+BEGINCLICOMMAND(JubCLISubsCommand, @":subs",
+ @"[list | ack <who> | nak <who>]",
+ @"Lists, acknowledges or denies subscription requests")
+{
+ if ([parameters count] < 1) {
+ [of_stdout writeLine: @"Syntax: ':subs "
+ @"[list | ack <who> | nak <who>]'"];
+ return;
+ }
+
+ OFString *action = parameters[0];
+ if (![action isEqual: @"list"] && ([parameters count] != 2)) {
+ [of_stdout writeLine: @"Syntax: ':subs "
+ @"[list | ack <who> | nak <who>]'"];
+ return;
+ }
+
+ if ([action isEqual: @"list"]) {
+ for (OFString *request in _ui.subRequests)
+ [of_stdout writeLine: request];
+ return;
+ }
+
+ XMPPJID *JID = [XMPPJID JIDWithString: parameters[1]];
+
+ if ([action isEqual: @"ack"])
+ [_ui.client.contactManager sendSubscribedToJID: JID];
+ else if ([action isEqual: @"nak"])
+ [_ui.client.contactManager sendUnsubscribedToJID: JID];
+
+ [_ui.subRequests removeObject: parameters[1]];
+}
+ENDCLICOMMAND
+
@implementation JubCLIUI
@synthesize client = _client;
@synthesize lastIn = _lastIn;
@synthesize sink = _sink;
+@synthesize subRequests = _subRequests;
- initWithClient: (JubChatClient*)client
{
@try {
_commands = [OFMutableDictionary new];
+ _subRequests = [OFMutableSet new];
_client = [client retain];
_contactManager = client.contactManager;
[_contactManager addDelegate: self];
[self addCommand: [[[JubCLIRosterCommand alloc]
initWithCLIUI: self] autorelease]];
+ [self addCommand: [[[JubCLISubsCommand alloc]
+ initWithCLIUI: self] autorelease]];
} @catch (id e) {
[self release];
@throw e;
{
[_contactManager removeDelegate: self];
[_client release];
+ [_subRequests release];
[_commands release];
[super dealloc];
}
+static JubCLIUI *completionData;
+static void completionCallback(OFString *buf, OFList *lc)
+{
+ if ([buf length] < 3)
+ return;
+
+ if (![buf hasPrefix: @":s "] && ![buf hasPrefix: @":m "] &&
+ ![buf hasPrefix: @":t "])
+ return;
+
+ if ([buf hasPrefix: @":t"]) {
+ OFString *options[] = {
+ @":t available",
+ @":t away",
+ @":t dnd",
+ @":t xa",
+ @":t chat",
+ @":t unavailable"
+ };
+
+ for (int i = 0; i < 6; i++) {
+ if (![options[i] hasPrefix: buf])
+ continue;
+ [lc appendObject: options[i]];
+ }
+
+ return;
+ }
+
+ OFString *command = [buf substringWithRange: of_range(0, 3)];
+ OFString *query = [buf substringWithRange: of_range(3, [buf length]-3)];
+ OFDictionary *contacts = completionData.client.contactManager.contacts;
+ for (OFString *key in contacts) {
+ if (![key hasPrefix: query])
+ continue;
+ [lc appendObject: [command stringByAppendingString: key]];
+ }
+}
+
- (void)startUIThread
{
- [of_stdin asyncReadLineWithTarget: self
- selector: @selector(Jub_userInputWithStream:
- line:exception:)];
+ completionData = self;
+
+ [[OFThread threadWithBlock: ^(void) {
+ OFString *line;
+ Linenoise *reader = [Linenoise sharedLinenoise];
+ reader.multiline = true;
+ reader.completionCallback = completionCallback;
+
+ while ((line = [reader readInputWithPrompt: @"> "]) != nil) @autoreleasepool {
+ [self Jub_userInputWithStream: nil
+ line: line
+ exception: nil];
+ if ([line length] != 0)
+ [reader addHistoryItem: line];
+ }
+ [self Jub_userInputWithStream: nil
+ line: nil
+ exception: nil];
+
+ return nil;
+ }] start];
}
- (void)client: (JubChatClient*)client
forKey: command.command];
}
-- (BOOL)Jub_userInputWithStream: (OFStream*)stream
+- (bool)Jub_userInputWithStream: (OFStream*)stream
line: (OFString*)line
exception: (OFException*)exception
{
- if (line == nil)
+ if (line == nil || exception != nil)
[OFApplication terminate];
if ([line length] == 0)
- return YES;
+ return true;
if ([line characterAtIndex: 0] != ':') {
- if (_sink == nil)
+ if (_sink == nil) {
[of_stdout writeLine: @"No default sink selected, "
@"type `:h` for help"];
+ return true;
+ }
[_sink send: line];
- return YES;
+ return true;
}
line = [line stringByDeletingTrailingWhitespaces];
[of_stdout writeFormat: @"- %@\n", command.help];
};
- return YES;
+ return true;
}
id<JubCLICommand> command = _commands[input[0]];
[command callWithParameters:
[input arrayByRemovingObject: [input firstObject]]];
- return YES;
+ return true;
}
[of_stdout writeLine: @"Invalid command, type `:h` for help"];
- return YES;
+ return true;
+}
+
+- (void)contactManager: (XMPPContactManager*)manager
+ didAddContact: (XMPPContact*)contact
+{
+ XMPPRosterItem *rosterItem = contact.rosterItem;
+ OFString *subscription = rosterItem.subscription;
+ OFString *bareJID = [rosterItem.JID bareJID];
+
+ if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
+ [_subRequests removeObject: bareJID];
+}
+
+- (void)contactManager: (XMPPContactManager*)manager
+ didReceiveSubscriptionRequest: (XMPPPresence*)presence
+{
+ OFString *bareJID = [presence.from bareJID];
+ [_subRequests addObject: bareJID];
+ [of_stdout writeFormat: @"\r" BOLD("%@") @" send a request to "
+ @"subscribe to your presence\r\n"
+ @"Use the :subs command to answer it\n",
+ bareJID];
+ [[Linenoise sharedLinenoise] refreshLine];
+}
+
+- (void)contact: (XMPPContact*)contact
+ willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem
+{
+ OFString *subscription = rosterItem.subscription;
+ OFString *bareJID = [rosterItem.JID bareJID];
+
+ if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
+ [_subRequests removeObject: bareJID];
}
- (void)contact: (XMPPContact*)contact
- (void)contact: (XMPPContact*)contact
didSendPresence: (XMPPPresence*)presence
{
+ [of_stdout writeFormat: @"\r"];
[of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];
if ([presence.type isEqual: @"unavailable"])
[of_stdout writeFormat: @": %@", presence.status];
[of_stdout writeString: @"\n"];
+
+ [[Linenoise sharedLinenoise] refreshLine];
}
@end