1/*
2 * Copyright (C) 2009 Nathan Ollerenshaw chrome@stupendous.net
3 *
4 * This library is free software; you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or (at your
7 * option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
12 * License for more details.
13 */
14
15#define IRCCLIENTVERSION "1.0"
16
17#import "IRCClientSession.h"
18#import "NSObject+DDExtensions.h"
19#import "IRCClientChannel.h"
20#include "string.h"
21
22static void onConnect(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
23static void onNick(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
24static void onQuit(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
25static void onJoinChannel(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
26static void onPartChannel(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
27static void onMode(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
28static void onUserMode(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
29static void onTopic(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
30static void onKick(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
31static void onChannelPrvmsg(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
32static void onPrivmsg(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
33static void onNotice(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
34static void onInvite(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
35static void onCtcpRequest(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
36static void onCtcpReply(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
37static void onCtcpAction(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
38static void onUnknownEvent(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
39static void onNumericEvent(irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count);
40
41@implementation IRCClientSession
42
43@synthesize delegate;
44@synthesize session;
45@synthesize version;
46@synthesize server;
47@synthesize port;
48@synthesize password;
49@synthesize nickname;
50@synthesize username;
51@synthesize realname;
52@synthesize channels;
53@synthesize encoding;
54
55-(id)init
56{
57    if ((self = [super init])) {
58		callbacks.event_connect = onConnect;
59		callbacks.event_nick = onNick;
60		callbacks.event_quit = onQuit;
61		callbacks.event_join = onJoinChannel;
62		callbacks.event_part = onPartChannel;
63		callbacks.event_mode = onMode;
64		callbacks.event_umode = onUserMode;
65		callbacks.event_topic = onTopic;
66		callbacks.event_kick = onKick;
67		callbacks.event_channel = onChannelPrvmsg;
68		callbacks.event_privmsg = onPrivmsg;
69		callbacks.event_notice = onNotice;
70		callbacks.event_invite = onInvite;
71		callbacks.event_ctcp_req = onCtcpRequest;
72		callbacks.event_ctcp_rep = onCtcpReply;
73		callbacks.event_ctcp_action = onCtcpAction;
74		callbacks.event_unknown = onUnknownEvent;
75		callbacks.event_numeric = onNumericEvent;
76		callbacks.event_dcc_chat_req = NULL;
77		callbacks.event_dcc_send_req = NULL;
78
79		session = irc_create_session(&callbacks);
80
81		if (!session) {
82			NSLog(@"Could not create irc_session.");
83			return nil;
84		}
85
86		irc_set_ctx(session, self);
87
88		unsigned int high, low;
89		irc_get_version (&high, &low);
90
91		[self setVersion:[NSString stringWithFormat:@"IRCClient Framework v%s (Nathan Ollerenshaw) - libirc v%d.%d (Georgy Yunaev)", IRCCLIENTVERSION, high, low]];
92
93		channels = [[[NSMutableDictionary alloc] init] retain];
94		encoding = NSASCIIStringEncoding;
95    }
96    return self;
97}
98
99-(void)dealloc
100{
101	if (irc_is_connected(session))
102		NSLog(@"Warning: IRC Session is not disconnected on dealloc");
103
104	irc_destroy_session(session);
105
106	[channels release];
107
108	[super dealloc];
109}
110
111- (int)connect;
112{
113	unsigned short sPort = [port intValue];
114
115	return irc_connect(session, [server cStringUsingEncoding:encoding], sPort, [password length] > 0 ? [password cStringUsingEncoding:encoding] : NULL , [nickname cStringUsingEncoding:encoding], [username cStringUsingEncoding:encoding], [realname cStringUsingEncoding:encoding]);
116}
117
118- (void)disconnect
119{
120	irc_disconnect(session);
121}
122
123- (bool)isConnected
124{
125	return irc_is_connected(session);
126}
127
128- (void)startThread
129{
130	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
131
132	irc_run(session);
133
134	[pool drain];
135}
136
137- (void)run
138{
139	if (thread) {
140		NSLog(@"Thread already running!");
141		return;
142	}
143
144	thread = [[NSThread alloc] initWithTarget:self selector:@selector(startThread) object:nil];
145	[thread retain];
146	[thread start];
147}
148
149- (int)sendRawWithFormat:(NSString *)format, ...
150{
151	va_list		ap;
152
153	va_start(ap, format);
154	NSString *line = [[NSString alloc] initWithFormat:format arguments:ap];
155	va_end(ap);
156
157	return irc_send_raw(session, [line cStringUsingEncoding:encoding]);
158}
159
160- (int)quit:(NSString *)reason
161{
162	return irc_cmd_quit(session, [reason cStringUsingEncoding:encoding]);
163}
164
165- (int)join:(NSString *)channel key:(NSString *)key
166{
167	NSLog(@"Joining %@", channel);
168
169	if (!key || ![key length] > 0)
170		return irc_cmd_join(session, [channel cStringUsingEncoding:encoding], NULL);
171
172	return irc_cmd_join(session, [channel cStringUsingEncoding:encoding], [key cStringUsingEncoding:encoding]);
173}
174
175- (int)list:(NSString *)channel
176{
177	return irc_cmd_list(session, [channel cStringUsingEncoding:encoding]);
178}
179
180- (int)userMode:(NSString *)mode
181{
182	return irc_cmd_user_mode(session, [mode cStringUsingEncoding:encoding]);
183}
184
185- (int)nick:(NSString *)newnick
186{
187	return irc_cmd_nick(session, [newnick cStringUsingEncoding:encoding]);
188}
189
190- (int)whois:(NSString *)nick
191{
192	return irc_cmd_whois(session, [nick cStringUsingEncoding:encoding]);
193}
194
195- (int)message:(NSString *)message to:(NSString *)target
196{
197	return irc_cmd_msg(session, [target cStringUsingEncoding:encoding], [message cStringUsingEncoding:encoding]);
198}
199
200- (int)action:(NSString *)action to:(NSString *)target
201{
202	return irc_cmd_me(session, [target cStringUsingEncoding:encoding], [action cStringUsingEncoding:encoding]);
203}
204
205- (int)notice:(NSString *)notice to:(NSString *)target
206{
207	return irc_cmd_notice(session, [target cStringUsingEncoding:encoding], [notice cStringUsingEncoding:encoding]);
208}
209
210- (int)ctcpRequest:(NSString *)request target:(NSString *)target
211{
212	return irc_cmd_ctcp_request(session, [target cStringUsingEncoding:encoding], [request cStringUsingEncoding:encoding]);
213}
214
215- (int)ctcpReply:(NSString *)reply target:(NSString *)target
216{
217	return irc_cmd_ctcp_reply(session, [target cStringUsingEncoding:encoding], [reply cStringUsingEncoding:encoding]);
218}
219
220
221
222@end
223
224NSString *
225getNickFromNickUserHost(NSString *nuh)
226{
227	NSArray *nuhArray = [nuh componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"!@"]];
228
229	if ([nuhArray count] == 3)
230		return [NSString stringWithString:[nuhArray objectAtIndex:0]];
231	else
232		return [NSString stringWithString:nuh];
233}
234
235NSString *
236getUserFromNickUserHost(NSString *nuh)
237{
238	NSArray *nuhArray = [nuh componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"!@"]];
239
240	if ([nuhArray count] == 3)
241		return [NSString stringWithString:[nuhArray objectAtIndex:1]];
242	else
243		return nil;
244}
245
246NSString *
247getHostFromNickUserHost(NSString *nuh)
248{
249	NSArray *nuhArray = [nuh componentsSeparatedByCharactersInSet:[NSCharacterSet characterSetWithCharactersInString:@"!@"]];
250
251	if ([nuhArray count] == 3)
252		return [NSString stringWithString:[nuhArray objectAtIndex:2]];
253	else
254		return nil;
255}
256
257/*!
258 * The "on_connect" event is triggered when the client successfully
259 * connects to the server, and could send commands to the server.
260 * No extra params supplied; \a params is 0.
261 */
262static void onConnect(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
263{
264	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
265
266	if ([[client delegate] respondsToSelector:@selector(onConnect)])
267		[[[client delegate] dd_invokeOnMainThread] onConnect];
268}
269
270/*!
271 * The "nick" event is triggered when the client receives a NICK message,
272 * meaning that someone (including you) on a channel with the client has
273 * changed their nickname.
274 *
275 * \param origin the person, who changes the nick. Note that it can be you!
276 * \param params[0] mandatory, contains the new nick.
277 */
278static void onNick(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
279{
280	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
281
282	NSString *nick = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
283	NSString *oldNick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
284
285	if ([[client nickname] compare:oldNick] == NSOrderedSame) {
286		[client setNickname:nick];
287	}
288
289	if ([[client delegate] respondsToSelector:@selector(onNick:oldNick:)])
290		[[[client delegate] dd_invokeOnMainThread] onNick:nick oldNick:oldNick];
291}
292
293/*!
294 * The "quit" event is triggered upon receipt of a QUIT message, which
295 * means that someone on a channel with the client has disconnected.
296 *
297 * \param origin the person, who is disconnected
298 * \param params[0] optional, contains the reason message (user-specified).
299 */
300static void onQuit(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
301{
302	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
303
304	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
305	NSString *reason = [NSString stringWithCString:params[0] encoding:[client encoding]];
306
307	if ([[client delegate] respondsToSelector:@selector(onQuit:reason:)])
308		[[[client delegate] dd_invokeOnMainThread] onQuit:nick reason:reason];
309}
310
311/*!
312 * The "join" event is triggered upon receipt of a JOIN message, which
313 * means that someone has entered a channel that the client is on.
314 *
315 * \param origin the person, who joins the channel. By comparing it with
316 *               your own nickname, you can check whether your JOIN
317 *               command succeed.
318 * \param params[0] mandatory, contains the channel name.
319 */
320static void onJoinChannel(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
321{
322	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
323	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
324	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
325
326	NSString *nickOnly = getNickFromNickUserHost(nick);
327
328	if ([[client nickname] compare:nickOnly] == NSOrderedSame) {
329		// We just joined a channel; allocate an IRCClientChannel object and send it
330		// to the main thread.
331
332		IRCClientChannel *newChannel = [[IRCClientChannel alloc] initWithName:channel];
333		[[client channels] setObject:newChannel forKey:channel];
334
335		if ([[client delegate] respondsToSelector:@selector(onJoinChannel:)])
336			[[[client delegate] dd_invokeOnMainThread] onJoinChannel:newChannel];
337	} else {
338		// Someone joined a channel we're on.
339
340		IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
341		[currentChannel onJoin:nick];
342	}
343}
344
345/*!
346 * The "part" event is triggered upon receipt of a PART message, which
347 * means that someone has left a channel that the client is on.
348 *
349 * \param origin the person, who leaves the channel. By comparing it with
350 *               your own nickname, you can check whether your PART
351 *               command succeed.
352 * \param params[0] mandatory, contains the channel name.
353 * \param params[1] optional, contains the reason message (user-defined).
354 */
355static void onPartChannel(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
356{
357	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
358
359	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
360	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
361	NSData *reason = nil;
362	IRCClientChannel *currentChannel = nil;
363
364	if (count > 1)
365		reason = [NSData dataWithBytes:params[1] length:strlen(params[1])];
366
367	if ([[client nickname] compare:nick] == NSOrderedSame) {
368		// We just left a channel; remove it from the channels dict.
369
370		currentChannel = [[client channels] objectForKey:channel];
371		[[client channels] removeObjectForKey:channel];
372	} else {
373		// Someone left a channel we're on.
374
375		currentChannel = [[client channels] objectForKey:channel];
376	}
377
378	[currentChannel onPart:nick reason:[[NSString alloc] initWithData:reason encoding:[currentChannel encoding]]];
379}
380
381/*!
382 * The "mode" event is triggered upon receipt of a channel MODE message,
383 * which means that someone on a channel with the client has changed the
384 * channel's parameters.
385 *
386 * \param origin the person, who changed the channel mode.
387 * \param params[0] mandatory, contains the channel name.
388 * \param params[1] mandatory, contains the changed channel mode, like
389 *        '+t', '-i' and so on.
390 * \param params[2] optional, contains the mode argument (for example, a
391 *      key for +k mode, or user who got the channel operator status for
392 *      +o mode)
393 */
394static void onMode(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
395{
396	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
397	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
398	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
399	NSString *mode = [NSString stringWithCString:params[1] encoding:NSASCIIStringEncoding];
400	NSString *modeParams = nil;
401
402	if (count > 2)
403		modeParams = [NSString stringWithCString:params[2] encoding:NSASCIIStringEncoding];
404
405	IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
406
407	[currentChannel onMode:mode params:modeParams nick:nick];
408}
409
410/*!
411 * The "umode" event is triggered upon receipt of a user MODE message,
412 * which means that your user mode has been changed.
413 *
414 * \param origin the person, who changed the channel mode.
415 * \param params[0] mandatory, contains the user changed mode, like
416 *        '+t', '-i' and so on.
417 */
418static void onUserMode(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
419{
420	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
421	NSString *mode = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
422
423	if ([[client delegate] respondsToSelector:@selector(onMode:)])
424		[[[client delegate] dd_invokeOnMainThread] onMode:mode];
425}
426
427/*!
428 * The "topic" event is triggered upon receipt of a TOPIC message, which
429 * means that someone on a channel with the client has changed the
430 * channel's topic.
431 *
432 * \param origin the person, who changes the channel topic.
433 * \param params[0] mandatory, contains the channel name.
434 * \param params[1] optional, contains the new topic.
435 */
436static void onTopic(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
437{
438	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
439	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
440	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
441	NSData *topic = nil;
442
443	if (count > 1)
444		topic = [NSData dataWithBytes:params[1] length:strlen(params[1])];
445
446	IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
447
448	[currentChannel onTopic:[[NSString alloc] initWithData:topic encoding:[currentChannel encoding]] nick:nick];
449}
450
451/*!
452 * The "kick" event is triggered upon receipt of a KICK message, which
453 * means that someone on a channel with the client (or possibly the
454 * client itself!) has been forcibly ejected.
455 *
456 * \param origin the person, who kicked the poor.
457 * \param params[0] mandatory, contains the channel name.
458 * \param params[1] optional, contains the nick of kicked person.
459 * \param params[2] optional, contains the kick text
460 */
461static void onKick(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
462{
463	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
464	NSString *byNick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
465	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
466	NSString *nick = nil;
467	NSData *reason = nil;
468
469	if (count > 1)
470		nick = [NSString stringWithCString:params[1] encoding:NSASCIIStringEncoding];
471
472	if (count > 2)
473		reason = [NSData dataWithBytes:params[2] length:strlen(params[2])];
474
475	if (nick == nil) {
476		// we got kicked
477		IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
478		[[client channels] removeObjectForKey:channel];
479
480		[currentChannel onKick:[client nickname] reason:[[NSString alloc] initWithData:reason encoding:[currentChannel encoding]] byNick:byNick];
481	} else {
482		// someone else got booted
483		IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
484
485		[currentChannel onKick:nick reason:[[NSString alloc] initWithData:reason encoding:[currentChannel encoding]] byNick:byNick];
486	}
487}
488
489/*!
490 * The "channel" event is triggered upon receipt of a PRIVMSG message
491 * to an entire channel, which means that someone on a channel with
492 * the client has said something aloud. Your own messages don't trigger
493 * PRIVMSG event.
494 *
495 * \param origin the person, who generates the message.
496 * \param params[0] mandatory, contains the channel name.
497 * \param params[1] optional, contains the message text
498 */
499static void onChannelPrvmsg(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
500{
501	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
502	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
503	NSString *channel = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
504	NSData *message = nil;
505
506	if (count > 1) {
507		message = [NSData dataWithBytes:params[1] length:strlen(params[1])];
508
509		IRCClientChannel *currentChannel = [[client channels] objectForKey:channel];
510
511		[currentChannel onPrivmsg:[[NSString alloc] initWithData:message encoding:[currentChannel encoding]] nick:nick];
512	}
513}
514
515/*!
516 * The "privmsg" event is triggered upon receipt of a PRIVMSG message
517 * which is addressed to one or more clients, which means that someone
518 * is sending the client a private message.
519 *
520 * \param origin the person, who generates the message.
521 * \param params[0] mandatory, contains your nick.
522 * \param params[1] optional, contains the message text
523 */
524static void onPrivmsg(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
525{
526	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
527	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
528	NSData *message = nil;
529
530	if (count > 1) {
531		message = [NSData dataWithBytes:params[1] length:strlen(params[1])];
532
533		if ([[client delegate] respondsToSelector:@selector(onPrivmsg:nick:)])
534			[[[client delegate] dd_invokeOnMainThread] onPrivmsg:message nick:nick];
535	}
536
537	// we eat privmsgs with no message
538}
539
540/*!
541 * The "notice" event is triggered upon receipt of a NOTICE message
542 * which means that someone has sent the client a public or private
543 * notice. According to RFC 1459, the only difference between NOTICE
544 * and PRIVMSG is that you should NEVER automatically reply to NOTICE
545 * messages. Unfortunately, this rule is frequently violated by IRC
546 * servers itself - for example, NICKSERV messages require reply, and
547 * are NOTICEs.
548 *
549 * \param origin the person, who generates the message.
550 * \param params[0] mandatory, contains the channel name.
551 * \param params[1] optional, contains the message text
552 */
553static void onNotice(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
554{
555	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
556	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
557	NSString *target = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
558	NSData *notice = nil;
559
560	IRCClientChannel *currentChannel = [[client channels] objectForKey:target];
561
562	if (count > 1) {
563		notice = [NSData dataWithBytes:params[1] length:strlen(params[1])];
564
565		if (currentChannel != nil) {
566			[currentChannel onNotice:[[NSString alloc] initWithData:notice encoding:[currentChannel encoding]] nick:nick];
567		} else {
568			if ([[client delegate] respondsToSelector:@selector(onNotice:nick:)])
569				[[[client delegate] dd_invokeOnMainThread] onNotice:notice nick:nick];
570		}
571	}
572
573	// we eat notices with no message
574}
575
576/*!
577 * The "invite" event is triggered upon receipt of an INVITE message,
578 * which means that someone is permitting the client's entry into a +i
579 * channel.
580 *
581 * \param origin the person, who INVITEs you.
582 * \param params[0] mandatory, contains your nick.
583 * \param params[1] mandatory, contains the channel name you're invited into.
584 *
585 * \sa irc_cmd_invite irc_cmd_chanmode_invite
586 */
587static void onInvite(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
588{
589	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
590	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
591	NSString *channel = [NSString stringWithCString:params[1] encoding:NSASCIIStringEncoding];
592
593	if ([[client delegate] respondsToSelector:@selector(onInvite:nick:)])
594		[[[client delegate] dd_invokeOnMainThread] onInvite:channel nick:nick];
595}
596
597/*!
598 * The "ctcp" event is triggered when the client receives the CTCP
599 * request. By default, the built-in CTCP request handler is used. The
600 * build-in handler automatically replies on most CTCP messages, so you
601 * will rarely need to override it.
602 *
603 * \param origin the person, who generates the message.
604 * \param params[0] mandatory, the complete CTCP message, including its
605 *                  arguments.
606 *
607 * Mirc generates PING, FINGER, VERSION, TIME and ACTION messages,
608 * check the source code of \c libirc_event_ctcp_internal function to
609 * see how to write your own CTCP request handler. Also you may find
610 * useful this question in FAQ: \ref faq4
611 */
612static void onCtcpRequest(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
613{
614	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
615	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
616
617	if ( origin )
618	{
619		char nickbuf[128];
620		irc_target_get_nick (origin, nickbuf, sizeof(nickbuf));
621
622		if ( strstr (params[0], "PING") == params[0] ) {
623			irc_cmd_ctcp_reply (session, nickbuf, params[0]);
624		}
625		else if ( !strcmp (params[0], "VERSION") )
626		{
627			irc_cmd_ctcp_reply (session, nickbuf, [[NSString stringWithFormat:@"VERSION %@", [client version]] UTF8String]);
628		}
629		else if ( !strcmp (params[0], "FINGER") )
630		{
631			irc_cmd_ctcp_reply (session, nickbuf, [[NSString stringWithFormat:@"FINGER %@ (%@) Idle 0 seconds", [client username], [client realname]] UTF8String]);
632		}
633		else if ( !strcmp (params[0], "TIME") )
634		{
635			irc_cmd_ctcp_reply(session, nickbuf, [[[NSDate dateWithTimeIntervalSinceNow:0] descriptionWithCalendarFormat:@"TIME %a %b %e %H:%M:%S %Z %Y" timeZone:nil locale:[[NSUserDefaults standardUserDefaults] dictionaryRepresentation]] UTF8String]);
636		} else {
637			if ([[client delegate] respondsToSelector:@selector(onCtcpRequest:type:nick:)]) {
638				NSString *requestString = [[NSString alloc] initWithData:[NSData dataWithBytes:params[0] length:strlen(params[0])] encoding:[client encoding]];
639
640				NSRange firstSpace = [requestString rangeOfString:@" "];
641
642				NSString *type = [requestString substringToIndex:firstSpace.location];
643				NSString *request = [requestString substringFromIndex:(firstSpace.location + 1)];
644
645				[[[client delegate] dd_invokeOnMainThread] onCtcpRequest:request type:type nick:nick];
646			}
647		}
648	}
649}
650
651/*!
652 * The "ctcp" event is triggered when the client receives the CTCP reply.
653 *
654 * \param origin the person, who generates the message.
655 * \param params[0] mandatory, the CTCP message itself with its arguments.
656 */
657static void onCtcpReply(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
658{
659	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
660
661	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
662	NSData *reply = [NSData dataWithBytes:params[0] length:strlen(params[0])];
663
664	if ([[client delegate] respondsToSelector:@selector(onCtcpReply:nick:)])
665		[[[client delegate] dd_invokeOnMainThread] onCtcpReply:reply nick:nick];
666}
667
668/*!
669 * The "action" event is triggered when the client receives the CTCP
670 * ACTION message. These messages usually looks like:\n
671 * \code
672 * [23:32:55] * Tim gonna sleep.
673 * \endcode
674 *
675 * \param origin the person, who generates the message.
676 * \param params[0] mandatory, the target of the message.
677 * \param params[1] mandatory, the ACTION message.
678 */
679static void onCtcpAction(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
680{
681	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
682
683	NSString *nick = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
684	NSString *target = [NSString stringWithCString:params[0] encoding:NSASCIIStringEncoding];
685	NSData *action = [NSData dataWithBytes:params[1] length:strlen(params[1])];
686
687	IRCClientChannel *currentChannel = [[client channels] objectForKey:target];
688
689	if (currentChannel) {
690		// An action on a channel we're on
691
692		[currentChannel onAction:[[NSString alloc] initWithData:action encoding:[currentChannel encoding]] nick:nick];
693	} else {
694		// An action in a private message
695
696		if ([[client delegate] respondsToSelector:@selector(onAction:nick:)])
697			[[[client delegate] dd_invokeOnMainThread] onAction:action nick:nick];
698	}
699}
700
701/*!
702 * The "unknown" event is triggered upon receipt of any number of
703 * unclassifiable miscellaneous messages, which aren't handled by the
704 * library.
705 */
706static void onUnknownEvent(irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count)
707{
708	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
709	NSString *eventString = [NSString stringWithCString:event encoding:NSASCIIStringEncoding];
710	NSString *sender = nil;
711
712	if (origin != NULL)
713		sender = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
714
715	NSMutableArray *paramsArray = [[NSMutableArray alloc] init];
716
717	for (unsigned int i = 0; i < count; i++)
718		[paramsArray addObject:[[NSString alloc] initWithData:[NSData dataWithBytes:params[i] length:strlen(params[i])] encoding:[client encoding]]];
719
720	if ([[client delegate] respondsToSelector:@selector(onUnknownEvent:origin:params:)])
721		[[[client delegate] dd_invokeOnMainThread] onUnknownEvent:eventString origin:sender params:paramsArray];
722}
723
724/*!
725 * The "numeric" event is triggered upon receipt of any numeric response
726 * from the server. There is a lot of such responses, see the full list
727 * here: \ref rfcnumbers.
728 *
729 * See the params in ::irc_eventcode_callback_t specification.
730 */
731static void onNumericEvent(irc_session_t * session, unsigned int event, const char * origin, const char ** params, unsigned int count)
732{
733	IRCClientSession *client = (IRCClientSession *) irc_get_ctx(session);
734	NSUInteger eventNumber = event;
735	NSString *originString = [NSString stringWithCString:origin encoding:NSASCIIStringEncoding];
736
737	NSMutableArray *paramsArray = [[NSMutableArray alloc] init];
738
739	for (unsigned int i = 0; i < count; i++)
740		[paramsArray addObject:[[NSString alloc] initWithData:[NSData dataWithBytes:params[i] length:strlen(params[i])] encoding:[client encoding]]];
741
742	if ([[client delegate] respondsToSelector:@selector(onNumericEvent:origin:params:)])
743		[[[client delegate] dd_invokeOnMainThread] onNumericEvent:eventNumber origin:originString params:paramsArray];
744}
745