IRC client framework (wrapper around libircclient library).
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

IRCClientSession.m 33KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013
  1. //
  2. // IRCClientSession.m
  3. //
  4. // Modified IRCClient Copyright 2015-2021 Said Achmiz.
  5. // Original IRCClient Copyright 2009 Nathan Ollerenshaw.
  6. // libircclient Copyright 2004-2009 Georgy Yunaev.
  7. //
  8. // See LICENSE and README.md for more info.
  9. /********************************/
  10. #pragma mark Defines and includes
  11. /********************************/
  12. #define IRCCLIENTVERSION "2.0a5"
  13. #import "IRCClientSession.h"
  14. #import "IRCClientChannel.h"
  15. #import "IRCClientChannel_Private.h"
  16. #import "NSArray+SA_NSArrayExtensions.h"
  17. #import "NSData+SA_NSDataExtensions.h"
  18. #import "NSString+SA_NSStringExtensions.h"
  19. #import "NSRange-Conventional.h"
  20. /********************************************/
  21. #pragma mark - Callback function declarations
  22. /********************************************/
  23. static void onEvent (irc_session_t *session, const char *event, const char *origin, const char **params, unsigned int count);
  24. static void onNumericEvent (irc_session_t *session, unsigned int event, const char *origin, const char **params, unsigned int count);
  25. static void onDCCChatRequest(irc_session_t *session, const char *nick, const char *addr, irc_dcc_t dccid);
  26. static void onDCCSendRequest(irc_session_t *session, const char *nick, const char *addr, const char *filename, size_t size, irc_dcc_t dccid);
  27. static NSDictionary* ircNumericCodeList;
  28. /***************************************************/
  29. #pragma mark - IRCClientSession class implementation
  30. /***************************************************/
  31. @implementation IRCClientSession {
  32. irc_callbacks_t _callbacks;
  33. irc_session_t *_irc_session;
  34. NSMutableDictionary <NSData *, IRCClientChannel *> *_channels;
  35. }
  36. /******************************/
  37. #pragma mark - Custom accessors
  38. /******************************/
  39. -(NSDictionary <NSData *, IRCClientChannel *> *) channels {
  40. return [_channels copy];
  41. }
  42. -(bool) isConnected {
  43. return irc_is_connected(_irc_session);
  44. }
  45. +(NSDictionary *) ircNumericCodes {
  46. if (ircNumericCodeList == nil)
  47. [IRCClientSession loadNumericCodes];
  48. return ircNumericCodeList;
  49. }
  50. /********************************************/
  51. #pragma mark - Initializers & factory methods
  52. /********************************************/
  53. +(instancetype) session {
  54. return [self new];
  55. }
  56. /*************************************/
  57. #pragma mark - Useful helper functions
  58. /*************************************/
  59. +(NSData *) nickFromNickUserHost:(NSData *)nickUserHost {
  60. if (nickUserHost == nil)
  61. return nil;
  62. NSRange rangeOfNickUserSeparator = [nickUserHost rangeOfData:[NSData dataFromCString:"!"]
  63. options:(NSDataSearchOptions) 0
  64. range:NSRangeMake(0, nickUserHost.length)];
  65. return (rangeOfNickUserSeparator.location == NSNotFound
  66. ? nickUserHost
  67. : [nickUserHost subdataWithRange:NSRangeMake(0, rangeOfNickUserSeparator.location)]);
  68. }
  69. +(NSData *) userFromNickUserHost:(NSData *)nickUserHost {
  70. if (nickUserHost == nil)
  71. return nil;
  72. NSRange rangeOfNickUserSeparator = [nickUserHost rangeOfData:[NSData dataFromCString:"!"]
  73. options:(NSDataSearchOptions) 0
  74. range:NSRangeMake(0, nickUserHost.length)];
  75. NSRange rangeOfUserHostSeparator = [nickUserHost rangeOfData:[NSData dataFromCString:"@"]
  76. options:(NSDataSearchOptions) 0
  77. range:NSRangeMake(0, nickUserHost.length)];
  78. return (( rangeOfNickUserSeparator.location == NSNotFound
  79. || rangeOfUserHostSeparator.location == NSNotFound)
  80. ? [NSData data]
  81. : [nickUserHost subdataWithRange:NSRangeMake(rangeOfNickUserSeparator.location + 1,
  82. rangeOfUserHostSeparator.location - (rangeOfNickUserSeparator.location + 1))]);
  83. }
  84. +(NSData *) hostFromNickUserHost:(NSData *)nickUserHost {
  85. if (nickUserHost == nil)
  86. return nil;
  87. NSRange rangeOfUserHostSeparator = [nickUserHost rangeOfData:[NSData dataFromCString:"@"]
  88. options:(NSDataSearchOptions) 0
  89. range:NSRangeMake(0, nickUserHost.length)];
  90. return (rangeOfUserHostSeparator.location == NSNotFound
  91. ? [NSData data]
  92. : [nickUserHost subdataWithRange:NSRangeMake(rangeOfUserHostSeparator.location + 1,
  93. nickUserHost.length - (rangeOfUserHostSeparator.location + 1))]);
  94. }
  95. /***************************/
  96. #pragma mark - Class methods
  97. /***************************/
  98. -(instancetype) init {
  99. if (!(self = [super init]))
  100. return nil;
  101. _callbacks.event_connect = onEvent;
  102. _callbacks.event_ping = onEvent;
  103. _callbacks.event_nick = onEvent;
  104. _callbacks.event_quit = onEvent;
  105. _callbacks.event_join = onEvent;
  106. _callbacks.event_part = onEvent;
  107. _callbacks.event_mode = onEvent;
  108. _callbacks.event_umode = onEvent;
  109. _callbacks.event_topic = onEvent;
  110. _callbacks.event_kick = onEvent;
  111. _callbacks.event_error = onEvent;
  112. _callbacks.event_channel = onEvent;
  113. _callbacks.event_privmsg = onEvent;
  114. _callbacks.event_server_msg = onEvent;
  115. _callbacks.event_notice = onEvent;
  116. _callbacks.event_channel_notice = onEvent;
  117. _callbacks.event_server_notice = onEvent;
  118. _callbacks.event_invite = onEvent;
  119. _callbacks.event_ctcp_req = onEvent;
  120. _callbacks.event_ctcp_rep = onEvent;
  121. _callbacks.event_ctcp_action = onEvent;
  122. _callbacks.event_unknown = onEvent;
  123. _callbacks.event_numeric = onNumericEvent;
  124. _callbacks.event_dcc_chat_req = onDCCChatRequest;
  125. _callbacks.event_dcc_send_req = onDCCSendRequest;
  126. _irc_session = irc_create_session(&_callbacks);
  127. if (!_irc_session) {
  128. NSLog(@"Could not create irc_session.");
  129. return nil;
  130. }
  131. // Strip server info from nicks.
  132. // irc_option_set(_irc_session, LIBIRC_OPTION_STRIPNICKS);
  133. // Set debug mode.
  134. // irc_option_set(_irc_session, LIBIRC_OPTION_DEBUG);
  135. irc_set_ctx(_irc_session, (__bridge void *)(self));
  136. unsigned int high, low;
  137. irc_get_version (&high, &low);
  138. _version = [[NSString stringWithFormat:@"IRCClient Framework v%s (Said Achmiz) - libirc v%d.%d (Georgy Yunaev)",
  139. IRCCLIENTVERSION,
  140. high,
  141. low] dataAsUTF8];
  142. _channels = [NSMutableDictionary dictionary];
  143. _encoding = NSUTF8StringEncoding;
  144. _userInfo = [NSMutableDictionary dictionary];
  145. return self;
  146. }
  147. -(void) dealloc {
  148. if (irc_is_connected(_irc_session)) {
  149. NSLog(@"Warning: IRC Session is not disconnected on dealloc");
  150. }
  151. irc_destroy_session(_irc_session);
  152. }
  153. +(void) loadNumericCodes {
  154. NSString* numericCodeListPath = [[NSBundle bundleForClass:[self class]] pathForResource:@"IRC_Numerics"
  155. ofType:@"plist"];
  156. ircNumericCodeList = [NSDictionary dictionaryWithContentsOfFile:numericCodeListPath];
  157. if (ircNumericCodeList) {
  158. NSLog(@"IRC numeric codes list loaded successfully.\n");
  159. } else {
  160. NSLog(@"Could not load IRC numeric codes list!\n");
  161. }
  162. }
  163. -(int) connect {
  164. return irc_connect(_irc_session,
  165. _server.terminatedCString,
  166. (unsigned short) _port,
  167. (_password.length > 0 ? _password.terminatedCString : NULL),
  168. _nickname.terminatedCString,
  169. _username.terminatedCString,
  170. _realname.terminatedCString);
  171. }
  172. -(void) disconnect {
  173. irc_disconnect(_irc_session);
  174. }
  175. -(void) run {
  176. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  177. @autoreleasepool {
  178. irc_run(_irc_session);
  179. }
  180. [_delegate disconnected:self];
  181. });
  182. }
  183. -(int) setNickname:(NSData *)nickname
  184. username:(NSData *)username
  185. realname:(NSData *)realname {
  186. if (self.isConnected) {
  187. return 0;
  188. } else {
  189. _nickname = nickname;
  190. _username = username;
  191. _realname = realname;
  192. return 1;
  193. }
  194. }
  195. /**************************/
  196. #pragma mark - IRC commands
  197. /**************************/
  198. -(int) sendRaw:(NSData *)message {
  199. return irc_send_raw(_irc_session,
  200. message.terminatedCString);
  201. }
  202. - (int) quit:(NSData *)reason {
  203. return irc_send_raw(_irc_session,
  204. "QUIT :%s",
  205. (reason
  206. ? reason.terminatedCString
  207. : "quit"));
  208. }
  209. -(int) join:(NSData *)channel
  210. key:(NSData *)key {
  211. if (!channel || channel.length == 0)
  212. return LIBIRC_ERR_STATE;
  213. if (key && key.length > 0)
  214. return irc_send_raw(_irc_session,
  215. "JOIN %s :%s",
  216. channel.terminatedCString,
  217. key.terminatedCString);
  218. else
  219. return irc_send_raw(_irc_session,
  220. "JOIN %s",
  221. channel.terminatedCString);
  222. }
  223. -(int) names:(NSData *)channel {
  224. if (channel)
  225. return irc_send_raw(_irc_session,
  226. "NAMES %s",
  227. channel.terminatedCString);
  228. else
  229. return irc_send_raw(_irc_session,
  230. "NAMES");
  231. }
  232. -(int) list:(NSData *)channel {
  233. if (channel)
  234. return irc_send_raw(_irc_session,
  235. "LIST %s",
  236. channel.terminatedCString);
  237. else
  238. return irc_send_raw(_irc_session,
  239. "LIST");
  240. }
  241. -(int) userMode:(NSData *)mode {
  242. if (mode)
  243. return irc_send_raw(_irc_session,
  244. "MODE %s %s",
  245. _nickname.terminatedCString,
  246. mode.terminatedCString);
  247. else
  248. return irc_send_raw(_irc_session,
  249. "MODE %s",
  250. _nickname.terminatedCString);
  251. }
  252. -(int) nick:(NSData *)newnick {
  253. if (!newnick || newnick.length == 0)
  254. return LIBIRC_ERR_INVAL;
  255. return irc_send_raw(_irc_session,
  256. "NICK %s",
  257. newnick.terminatedCString);
  258. }
  259. -(int) who:(NSData *)nickmask {
  260. if (!nickmask || nickmask.length == 0)
  261. return LIBIRC_ERR_INVAL;
  262. return irc_send_raw(_irc_session,
  263. "WHO %s",
  264. nickmask.terminatedCString);
  265. }
  266. -(int) whois:(NSData *)nick {
  267. if (!nick || nick.length == 0)
  268. return LIBIRC_ERR_INVAL;
  269. return irc_send_raw(_irc_session,
  270. "WHOIS %s",
  271. nick.terminatedCString);
  272. }
  273. -(int) message:(NSData *)message
  274. to:(NSData *)target {
  275. if ( !target || target.length == 0
  276. || !message || message.length == 0)
  277. return LIBIRC_ERR_STATE;
  278. return irc_send_raw(_irc_session,
  279. "PRIVMSG %s :%s",
  280. target.terminatedCString,
  281. irc_color_convert_to_mirc(message.terminatedCString));
  282. }
  283. -(int) action:(NSData *)action
  284. to:(NSData *)target {
  285. if ( !target || target.length == 0
  286. || !action || action.length == 0)
  287. return LIBIRC_ERR_STATE;
  288. return irc_send_raw(_irc_session,
  289. "PRIVMSG %s :\x01" "ACTION %s\x01",
  290. target.terminatedCString,
  291. irc_color_convert_to_mirc(action.terminatedCString));
  292. }
  293. -(int) notice:(NSData *)notice
  294. to:(NSData *)target {
  295. if ( !target || target.length == 0
  296. || !notice || notice.length == 0)
  297. return LIBIRC_ERR_STATE;
  298. return irc_send_raw(_irc_session,
  299. "NOTICE %s :%s",
  300. target.terminatedCString,
  301. notice.terminatedCString);
  302. }
  303. -(int) ctcpRequest:(NSData *)request
  304. target:(NSData *)target {
  305. if ( !target || target.length == 0
  306. || !request || request.length == 0)
  307. return LIBIRC_ERR_STATE;
  308. return irc_send_raw(_irc_session,
  309. "PRIVMSG %s :\x01%s\x01",
  310. target.terminatedCString,
  311. request.terminatedCString);
  312. }
  313. -(int) ctcpReply:(NSData *)reply
  314. target:(NSData *)target {
  315. if ( !target || target.length == 0
  316. || !reply || reply.length == 0)
  317. return LIBIRC_ERR_STATE;
  318. return irc_send_raw(_irc_session,
  319. "NOTICE %s :\x01%s\x01",
  320. target.terminatedCString,
  321. reply.terminatedCString);
  322. }
  323. /********************************/
  324. #pragma mark - IRC event handlers
  325. /********************************/
  326. -(void) ircEventReceived:(const char *)event
  327. from:(const char *)origin
  328. withParams:(const char **)params
  329. count:(unsigned int)count {
  330. typedef enum : NSUInteger {
  331. SA_IRC_ParseColorCodes,
  332. SA_IRC_StripColorCodes,
  333. SA_IRC_IgnoreColorCodes,
  334. } SA_IRC_ColorCodeHandling;
  335. // TODO: Support setting this somehow...
  336. SA_IRC_ColorCodeHandling whatAboutColors = SA_IRC_ParseColorCodes;
  337. NSMutableArray <NSData *> *params_array;
  338. if ((whatAboutColors != SA_IRC_IgnoreColorCodes)
  339. && ( !strcmp(event, "PRIVMSG")
  340. || !strcmp(event, "CHANMSG")
  341. || !strcmp(event, "SERVMSG")
  342. || !strcmp(event, "PRIVNOTICE")
  343. || !strcmp(event, "CHANNOTICE")
  344. || !strcmp(event, "SERVNOTICE")
  345. || !strcmp(event, "CTCP_ACTION")
  346. )) {
  347. char* (*process_color_codes) (const char *) = (whatAboutColors == SA_IRC_ParseColorCodes
  348. ? irc_color_convert_from_mirc
  349. : irc_color_strip_from_mirc);
  350. params_array = [NSMutableArray arrayWithCapacity:count];
  351. for (NSUInteger i = 0; i < count; i++) {
  352. [params_array addObject:[NSData dataFromCString:(*process_color_codes)(params[i])]];
  353. }
  354. } else {
  355. params_array = (NSMutableArray *) [NSArray arrayOfCStringData:params
  356. count:count];
  357. }
  358. NSData *origin_data = origin ? [NSData dataFromCString:origin] : nil;
  359. NSData *param_0_data = (count > 0
  360. ? params_array[0]
  361. : nil);
  362. NSData *param_1_data = (count > 1
  363. ? params_array[1]
  364. : nil);
  365. NSData *param_2_data = (count > 2
  366. ? params_array[2]
  367. : nil);
  368. if (!strcmp(event, "CONNECT")) {
  369. /*!
  370. * The ‘on_connect’ event is triggered when the client successfully
  371. * connects to the server, and could send commands to the server.
  372. * No extra params supplied; \a params is 0.
  373. */
  374. [_delegate connectionSucceeded:self];
  375. } else if (!strcmp(event, "PING")) {
  376. // TODO: the part about LIBIRC_OPTION_PING_PASSTHROUGH seems to be a lie??
  377. // But see also LIBIRC_OPTION_IGNORE_PING???
  378. /*!
  379. * The ‘ping’ event is triggered when the client receives a PING message.
  380. * It is only generated if the LIBIRC_OPTION_PING_PASSTHROUGH option is set;
  381. * otherwise, the library responds to PING messages automatically.
  382. *
  383. * \param origin the person, who generated the ping.
  384. * \param params[0] mandatory, contains who knows what.
  385. */
  386. if ([_delegate respondsToSelector:@selector(ping:from:session:)]) {
  387. [_delegate ping:param_0_data
  388. from:origin_data
  389. session:self];
  390. }
  391. } else if (!strcmp(event, "NICK")) {
  392. /*!
  393. * The ‘nick’ event is triggered when the client receives a NICK message,
  394. * meaning that someone (including you) on a channel with the client has
  395. * changed their nickname.
  396. *
  397. * \param origin The person who changed their nick. Note that it can be you!
  398. * \param params[0] Mandatory; contains the new nick.
  399. */
  400. [self nickChangedFrom:origin_data
  401. to:param_0_data];
  402. } else if (!strcmp(event, "QUIT")) {
  403. /*!
  404. * The ‘quit’ event is triggered upon receipt of a QUIT message, which
  405. * means that someone on a channel with the client has disconnected.
  406. *
  407. * \param origin The person who is disconnected.
  408. * \param params[0] Optional; contains the reason message (user-specified).
  409. */
  410. [_delegate userQuit:origin_data
  411. withReason:param_0_data
  412. session:self];
  413. } else if (!strcmp(event, "JOIN")) {
  414. /*!
  415. * The ‘join’ event is triggered upon receipt of a JOIN message, which
  416. * means that someone has entered a channel that the client is on.
  417. *
  418. * \param origin The person who joined the channel. By comparing it with
  419. * your own nickname, you can check whether your JOIN
  420. * command succeed.
  421. * \param params[0] Mandatory; contains the channel name.
  422. */
  423. [self userJoined:origin_data
  424. channel:param_0_data];
  425. } else if (!strcmp(event, "PART")) {
  426. /*!
  427. * The ‘part’ event is triggered upon receipt of a PART message, which
  428. * means that someone has left a channel that the client is on.
  429. *
  430. * \param Origin The person who left the channel. By comparing it with
  431. * your own nickname, you can check whether your PART
  432. * command succeed.
  433. * \param params[0] Mandatory; contains the channel name.
  434. * \param params[1] Optional; contains the reason message (user-defined).
  435. */
  436. [self userParted:origin_data
  437. channel:param_0_data
  438. withReason:param_1_data];
  439. } else if (!strcmp(event, "MODE")) {
  440. /*!
  441. * The ‘mode’ event is triggered upon receipt of a channel MODE message,
  442. * which means that someone on a channel with the client has changed the
  443. * channel’s parameters.
  444. *
  445. * \param origin The person who changed the channel mode.
  446. * \param params[0] Mandatory; contains the channel name.
  447. * \param params[1] Mandatory; contains the changed channel mode, like
  448. * ‘+t’, ‘-i’, and so on.
  449. * \param params[2] Optional; contains the mode argument (for example, a
  450. * key for +k mode, or user who got channel operator status for
  451. * +o mode)
  452. */
  453. IRCClientChannel *channel = _channels[param_0_data];
  454. [channel modeSet:param_1_data
  455. withParams:param_2_data
  456. by:origin_data];
  457. } else if (!strcmp(event, "UMODE")) {
  458. /*!
  459. * The ‘umode’ event is triggered upon receipt of a user MODE message,
  460. * which means that your user mode has been changed.
  461. *
  462. * \param origin The person who changed the user mode.
  463. * \param params[0] Mandatory; contains the user changed mode, like
  464. * ‘+t’, ‘-i’ and so on.
  465. */
  466. // TODO: keep track of the user's mode on the connection?
  467. [_delegate modeSet:param_0_data
  468. by:origin_data
  469. session:self];
  470. } else if (!strcmp(event, "TOPIC")) {
  471. /*!
  472. * The ‘topic’ event is triggered upon receipt of a TOPIC message, which
  473. * means that someone on a channel with the client has changed the
  474. * channel’s topic.
  475. *
  476. * \param origin The person who changes the channel topic.
  477. * \param params[0] Mandatory; contains the channel name.
  478. * \param params[1] Optional; contains the new topic.
  479. */
  480. IRCClientChannel *channel = _channels[param_0_data];
  481. [channel topicSet:param_1_data
  482. by:origin_data];
  483. } else if (!strcmp(event, "KICK")) {
  484. /*!
  485. * The ‘kick’ event is triggered upon receipt of a KICK message, which
  486. * means that someone on a channel with the client (or possibly the
  487. * client itself!) has been forcibly ejected.
  488. *
  489. * \param origin The person who kicked the poor victim.
  490. * \param params[0] Mandatory; contains the channel name.
  491. * \param params[1] Optional; contains the nick of kicked person.
  492. * \param params[2] Optional; contains the kick text.
  493. */
  494. [self userKicked:param_1_data
  495. fromChannel:param_0_data
  496. by:origin_data
  497. withReason:param_2_data];
  498. } else if (!strcmp(event, "ERROR")) {
  499. /*!
  500. * The ‘error’ event is triggered upon receipt of an ERROR message, which
  501. * (when sent to clients) usually means the client has been disconnected.
  502. *
  503. * \param origin the person, who generates the message.
  504. * \param params optional, contains who knows what.
  505. */
  506. [_delegate errorReceived:param_0_data
  507. session:self];
  508. } else if (!strcmp(event, "INVITE")) {
  509. /*!
  510. * The ‘invite’ event is triggered upon receipt of an INVITE message,
  511. * which means that someone is permitting the client’s entry into a +i
  512. * channel.
  513. *
  514. * \param origin The person who INVITEd you.
  515. * \param params[0] Mandatory; contains your nick.
  516. * \param params[1] Mandatory; contains the channel name you’re invited into.
  517. *
  518. * \sa irc_cmd_invite irc_cmd_chanmode_invite
  519. */
  520. [_delegate invitedToChannel:param_1_data
  521. by:origin_data
  522. session:self];
  523. } else if (!strcmp(event, "PRIVMSG")) {
  524. /*!
  525. * The ‘privmsg’ event is triggered upon receipt of a PRIVMSG message
  526. * which is addressed to one or more clients, which means that someone
  527. * is sending the client a private message.
  528. *
  529. * \param origin The person who generated the message.
  530. * \param params[0] Mandatory; contains your nick.
  531. * \param params[1] Optional; contains the message text.
  532. */
  533. [_delegate privateMessageReceived:param_1_data
  534. fromUser:origin_data
  535. session:self];
  536. } else if (!strcmp(event, "CHANMSG")) {
  537. /*!
  538. * The ‘chanmsg’ event is triggered upon receipt of a PRIVMSG message
  539. * to an entire channel, which means that someone on a channel with
  540. * the client has said something aloud. Your own messages don’t trigger
  541. * PRIVMSG event.
  542. *
  543. * \param origin The person who generated the message.
  544. * \param params[0] Mandatory; contains the channel name.
  545. * \param params[1] Optional; contains the message text.
  546. */
  547. IRCClientChannel *channel = _channels[param_0_data];
  548. [channel messageSent:param_1_data
  549. byUser:origin_data];
  550. } else if (!strcmp(event, "SERVMSG")) {
  551. /*!
  552. * The ‘servmsg’ event is triggered upon receipt of a PRIVMSG message
  553. * which is addressed to no one in particular, but it sent to the client
  554. * anyway.
  555. *
  556. * \param origin The person who generated the message.
  557. * \param params Optional; contains who knows what.
  558. */
  559. [_delegate serverMessageReceivedFrom:origin_data
  560. params:params_array
  561. session:self];
  562. } else if (!strcmp(event, "PRIVNOTICE")) {
  563. /*!
  564. * The ‘notice’ event is triggered upon receipt of a NOTICE message
  565. * which means that someone has sent the client a public or private
  566. * notice. According to RFC 1459, the only difference between NOTICE
  567. * and PRIVMSG is that you should NEVER automatically reply to NOTICE
  568. * messages. Unfortunately, this rule is frequently violated by IRC
  569. * servers itself - for example, NICKSERV messages require reply, and
  570. * are NOTICEs.
  571. *
  572. * \param origin The person who generated the message.
  573. * \param params[0] Mandatory; contains your nick.
  574. * \param params[1] Optional; contains the message text.
  575. */
  576. [_delegate privateNoticeReceived:param_1_data
  577. fromUser:origin_data
  578. session:self];
  579. } else if (!strcmp(event, "CHANNOTICE")) {
  580. /*!
  581. * The ‘notice’ event is triggered upon receipt of a NOTICE message
  582. * which means that someone has sent the client a public or private
  583. * notice. According to RFC 1459, the only difference between NOTICE
  584. * and PRIVMSG is that you should NEVER automatically reply to NOTICE
  585. * messages. Unfortunately, this rule is frequently violated by IRC
  586. * servers itself - for example, NICKSERV messages require reply, and
  587. * are NOTICEs.
  588. *
  589. * \param origin The person who generated the message.
  590. * \param params[0] Mandatory; contains the target channel name.
  591. * \param params[1] Optional; contains the message text.
  592. */
  593. IRCClientChannel *channel = _channels[param_0_data];
  594. [channel noticeSent:param_1_data
  595. byUser:origin_data];
  596. } else if (!strcmp(event, "SERVNOTICE")) {
  597. /*!
  598. * The ‘server_notice’ event is triggered upon receipt of a NOTICE
  599. * message which means that the server has sent the client a notice.
  600. * This notice is not necessarily addressed to the client’s nick
  601. * (for example, AUTH notices, sent before the client’s nick is known).
  602. * According to RFC 1459, the only difference between NOTICE
  603. * and PRIVMSG is that you should NEVER automatically reply to NOTICE
  604. * messages. Unfortunately, this rule is frequently violated by IRC
  605. * servers itself - for example, NICKSERV messages require reply, and
  606. * are NOTICEs.
  607. *
  608. * \param origin The person who generated the message.
  609. * \param params Optional; contains who knows what.
  610. */
  611. [_delegate serverNoticeReceivedFrom:origin_data
  612. params:params_array
  613. session:self];
  614. } else if (!strcmp(event, "CTCP_REQ")) {
  615. /*!
  616. * The ‘ctcp’ event is triggered when the client receives the CTCP
  617. * request. By default, the built-in CTCP request handler is used. The
  618. * build-in handler automatically replies on most CTCP messages, so you
  619. * will rarely need to override it.
  620. *
  621. * \param origin The person who generated the message.
  622. * \param params[0] Mandatory; contains the complete CTCP message, including
  623. * its arguments.
  624. *
  625. * Mirc generates PING, FINGER, VERSION, TIME and ACTION messages,
  626. * check the source code of \c libirc_event_ctcp_internal function to
  627. * see how to write your own CTCP request handler. Also you may find
  628. * useful this question in FAQ: \ref faq4
  629. */
  630. [self CTCPRequestReceived:param_0_data
  631. fromUser:origin_data];
  632. } else if (!strcmp(event, "CTCP_REPL")) {
  633. /*!
  634. * The ‘ctcp’ event is triggered when the client receives the CTCP reply.
  635. *
  636. * \param origin The person who generated the message.
  637. * \param params[0] Mandatory; the CTCP message itself with its arguments.
  638. */
  639. if ([_delegate respondsToSelector:@selector(CTCPReplyReceived:fromUser:session:)]) {
  640. [_delegate CTCPReplyReceived:param_0_data
  641. fromUser:origin_data
  642. session:self];
  643. }
  644. } else if (!strcmp(event, "CTCP_ACTION")) {
  645. /*!
  646. * The ‘action’ event is triggered when the client receives the CTCP
  647. * ACTION message. These messages usually looks like:\n
  648. * \code
  649. * [23:32:55] * Tim gonna sleep.
  650. * \endcode
  651. *
  652. * \param origin The person who generated the message.
  653. * \param params[0] Mandatory; the target of the message.
  654. * \param params[1] Mandatory; the ACTION message.
  655. */
  656. IRCClientChannel* channel = _channels[param_0_data];
  657. if (channel != nil) {
  658. // An action on a channel we’re on.
  659. [channel actionPerformed:param_1_data
  660. byUser:origin_data];
  661. } else {
  662. // An action in a private message.
  663. [_delegate privateCTCPActionReceived:param_1_data
  664. fromUser:origin_data
  665. session:self];
  666. }
  667. } else {
  668. /*!
  669. * The ‘unknown’ event is triggered upon receipt of any number of
  670. * unclassifiable miscellaneous messages, which aren’t handled by the
  671. * library.
  672. */
  673. if ([_delegate respondsToSelector:@selector(unknownEventReceived:from:params:session:)]) {
  674. [_delegate unknownEventReceived:[NSData dataFromCString:event]
  675. from:origin_data
  676. params:params_array
  677. session:self];
  678. }
  679. }
  680. }
  681. -(void) numericEventReceived:(NSUInteger)event
  682. from:(NSData *)origin
  683. params:(NSArray *)params {
  684. if ([_delegate respondsToSelector:@selector(numericEventReceived:from:params:session:)]) {
  685. [_delegate numericEventReceived:event
  686. from:origin
  687. params:params
  688. session:self];
  689. }
  690. }
  691. /******************************************/
  692. #pragma mark - Event handler helper methods
  693. /******************************************/
  694. -(void) nickChangedFrom:(NSData *)oldNick
  695. to:(NSData *)newNick {
  696. NSData* oldNickOnly = [IRCClientSession nickFromNickUserHost:oldNick];
  697. if ([_nickname isEqualToData:oldNickOnly]) {
  698. _nickname = newNick;
  699. [_delegate nickChangedFrom:oldNickOnly
  700. to:newNick
  701. own:YES
  702. session:self];
  703. } else {
  704. [_delegate nickChangedFrom:oldNickOnly
  705. to:newNick
  706. own:NO
  707. session:self];
  708. }
  709. }
  710. -(void) userJoined:(NSData *)nick
  711. channel:(NSData *)channelName {
  712. NSData* nickOnly = [IRCClientSession nickFromNickUserHost:nick];
  713. if ([_nickname isEqualToData:nickOnly]) {
  714. // We just joined a channel; allocate an IRCClientChannel object and
  715. // add it to our channels list.
  716. IRCClientChannel* newChannel = [[IRCClientChannel alloc] initWithName:channelName
  717. andIRCSession:_irc_session];
  718. _channels[channelName] = newChannel;
  719. [_delegate joinedNewChannel:newChannel
  720. session:self];
  721. } else {
  722. // Someone joined a channel we’re on.
  723. IRCClientChannel* channel = _channels[channelName];
  724. [channel userJoined:nick];
  725. }
  726. }
  727. -(void) userParted:(NSData *)nick
  728. channel:(NSData *)channelName
  729. withReason:(NSData *)reason {
  730. IRCClientChannel* channel = _channels[channelName];
  731. NSData* nickOnly = [IRCClientSession nickFromNickUserHost:nick];
  732. if ([_nickname isEqualToData:nickOnly]) {
  733. // We just left a channel; remove it from the channels dict.
  734. [_channels removeObjectForKey:channelName];
  735. [channel userParted:nick
  736. withReason:reason
  737. us:YES];
  738. } else {
  739. [channel userParted:nick
  740. withReason:reason
  741. us:NO];
  742. }
  743. }
  744. -(void) userKicked:(NSData *)nick
  745. fromChannel:(NSData *)channelName
  746. by:(NSData *)byNick
  747. withReason:(NSData *)reason {
  748. IRCClientChannel* channel = _channels[channelName];
  749. if (nick == nil) {
  750. // we got kicked from a channel we’re on :(
  751. [_channels removeObjectForKey:channelName];
  752. [channel userKicked:_nickname
  753. withReason:reason
  754. by:byNick
  755. us:YES];
  756. } else {
  757. // Someone else got booted from a channel we’re on.
  758. [channel userKicked:nick
  759. withReason:reason
  760. by:byNick
  761. us:NO];
  762. }
  763. }
  764. /*****************************************/
  765. #pragma mark - CTCP request handler helper
  766. /*****************************************/
  767. -(void) CTCPRequestReceived:(NSData *)request
  768. fromUser:(NSData *)nick {
  769. NSData *nickOnly = [IRCClientSession nickFromNickUserHost:nick];
  770. if (!strncmp(request.terminatedCString, "PING", 4)) {
  771. [self ctcpReply:request
  772. target:nickOnly];
  773. } else if (!strcmp(request.terminatedCString, "VERSION")) {
  774. const char *versionFormat = "VERSION %s";
  775. NSMutableData* versionReply = [NSMutableData dataWithLength:(strlen(versionFormat) + (_version.length - 2))];
  776. sprintf(versionReply.mutableBytes,
  777. versionFormat,
  778. _version.terminatedCString);
  779. [self ctcpReply:versionReply
  780. target:nickOnly];
  781. } else if (!strcmp(request.terminatedCString, "FINGER")) {
  782. const char *fingerFormat = "FINGER %s (%s) Idle 0 seconds)";
  783. NSMutableData* fingerReply = [NSMutableData dataWithLength:(strlen(fingerFormat) + (_username.length - 2) + (_realname.length - 2))];
  784. sprintf(fingerReply.mutableBytes,
  785. fingerFormat,
  786. _username.terminatedCString,
  787. _realname.terminatedCString);
  788. [self ctcpReply:fingerReply
  789. target:nickOnly];
  790. } else if (!strcmp(request.terminatedCString, "TIME")) {
  791. time_t current_time;
  792. char timestamp[40];
  793. struct tm *time_info;
  794. time(&current_time);
  795. time_info = localtime(&current_time);
  796. strftime(timestamp, 40, "TIME %a %b %e %H:%M:%S %Z %Y", time_info);
  797. [self ctcpReply:[NSData dataFromCString:timestamp]
  798. target:nickOnly];
  799. } else {
  800. if ([_delegate respondsToSelector:@selector(CTCPRequestReceived:ofType:fromUser:session:)]) {
  801. NSRange rangeOfFirstSpace = [request rangeOfData:[NSData dataFromCString:" "]
  802. options:(NSDataSearchOptions) 0
  803. range:NSRangeMake(0, request.length)];
  804. NSRange rangeOfSecondSpace = (rangeOfFirstSpace.location != NSNotFound
  805. ? [request rangeOfData:[NSData dataFromCString:" "]
  806. options:(NSDataSearchOptions) 0
  807. range:NSRangeMake(rangeOfFirstSpace.location + 1,
  808. request.length - (rangeOfFirstSpace.location + 1))]
  809. : NSRangeMake(NSNotFound, 0));
  810. NSData *requestTypeData = (rangeOfFirstSpace.location != NSNotFound
  811. ? [request subdataWithRange:NSRangeMake(0, rangeOfFirstSpace.location)]
  812. : request);
  813. NSData *requestBodyData = (rangeOfSecondSpace.location != NSNotFound
  814. ? [request subdataWithRange:NSRangeMake(rangeOfFirstSpace.location + 1,
  815. rangeOfSecondSpace.location - (rangeOfFirstSpace.location + 1))]
  816. : nil);
  817. [_delegate CTCPRequestReceived:requestBodyData
  818. ofType:requestTypeData
  819. fromUser:nick
  820. session:self];
  821. }
  822. }
  823. }
  824. @end
  825. /***********************************************/
  826. #pragma mark - Callback function implementations
  827. /***********************************************/
  828. static void onEvent(irc_session_t *session,
  829. const char *event,
  830. const char *origin,
  831. const char **params,
  832. unsigned int count) {
  833. @autoreleasepool {
  834. [(__bridge IRCClientSession *) irc_get_ctx(session) ircEventReceived:event
  835. from:origin
  836. withParams:params
  837. count:count];
  838. }
  839. }
  840. /*!
  841. * The ‘numeric’ event is triggered upon receipt of any numeric response
  842. * from the server. There is a lot of such responses, see the full list
  843. * here: \ref rfcnumbers.
  844. *
  845. * \param session the session, which generates an event
  846. * \param event the numeric code of the event. Useful in case you use a
  847. * single event handler for several events simultaneously.
  848. * \param origin the originator of the event. See the note below.
  849. * \param params a list of event params. Depending on the event nature, it
  850. * could have zero or more params. The actual number of params
  851. * is specified in count. None of the params can be NULL, but
  852. * ‘params’ pointer itself could be NULL for some events.
  853. * \param count the total number of params supplied.
  854. */
  855. static void onNumericEvent(irc_session_t *session,
  856. unsigned int event,
  857. const char *origin,
  858. const char **params,
  859. unsigned int count) {
  860. @autoreleasepool {
  861. [(__bridge IRCClientSession *) irc_get_ctx(session) numericEventReceived:event
  862. from:[NSData dataFromCString:origin]
  863. params:[NSArray arrayOfCStringData:params
  864. count:count]];
  865. }
  866. }
  867. /*!
  868. * The ‘dcc chat’ event is triggered when someone requests a DCC CHAT from
  869. * you.
  870. *
  871. * \param session the session, which generates an event
  872. * \param nick the person who requested DCC CHAT with you.
  873. * \param addr the person's IP address in decimal-dot notation.
  874. * \param dccid an id associated with this request. Use it in calls to
  875. * irc_dcc_accept() or irc_dcc_decline().
  876. */
  877. static void onDCCChatRequest(irc_session_t *session,
  878. const char *nick,
  879. const char *addr,
  880. irc_dcc_t dccid) {
  881. // TODO: figure out what to do here???
  882. }
  883. /*!
  884. * The ‘dcc send’ event is triggered when someone wants to send a file
  885. * to you via DCC SEND request.
  886. *
  887. * \param session the session, which generates an event
  888. * \param nick the person who requested DCC SEND to you.
  889. * \param addr the person's IP address in decimal-dot notation.
  890. * \param filename the sent filename.
  891. * \param size the filename size.
  892. * \param dccid an id associated with this request. Use it in calls to
  893. * irc_dcc_accept() or irc_dcc_decline().
  894. */
  895. static void onDCCSendRequest(irc_session_t *session,
  896. const char *nick,
  897. const char *addr,
  898. const char *filename,
  899. size_t size,
  900. irc_dcc_t dccid) {
  901. // TODO: figure out what to do here???
  902. }