IRC client framework (wrapper around libircclient library).
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

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. }