PageRenderTime 54ms CodeModel.GetById 23ms RepoModel.GetById 1ms app.codeStats 0ms

/src/map/irc-bot.c

https://gitlab.com/pir8g33k/Hercules
C | 489 lines | 308 code | 87 blank | 94 comment | 93 complexity | 3ca6fac40d9d83d67fd38af843a0dc97 MD5 | raw file
Possible License(s): GPL-3.0
  1. // Copyright (c) Hercules Dev Team, licensed under GNU GPL.
  2. // See the LICENSE file
  3. // Base Author: shennetsind @ http://hercules.ws
  4. #define HERCULES_CORE
  5. #include "irc-bot.h"
  6. #include <stdio.h>
  7. #include <stdlib.h>
  8. #include <string.h>
  9. #include "clif.h"
  10. #include "map.h"
  11. #include "pc.h"
  12. #include "../common/cbasetypes.h"
  13. #include "../common/malloc.h"
  14. #include "../common/random.h"
  15. #include "../common/showmsg.h"
  16. #include "../common/socket.h"
  17. #include "../common/strlib.h"
  18. #include "../common/timer.h"
  19. //#define IRCBOT_DEBUG
  20. struct irc_bot_interface irc_bot_s;
  21. char send_string[IRC_MESSAGE_LENGTH];
  22. /**
  23. * Timer callback to (re-)connect to an IRC server
  24. * @see timer->do_timer
  25. */
  26. int irc_connect_timer(int tid, int64 tick, int id, intptr_t data) {
  27. struct hSockOpt opt;
  28. if( ircbot->isOn || ++ircbot->fails >= 3 )
  29. return 0;
  30. opt.silent = 1;
  31. opt.setTimeo = 0;
  32. ircbot->last_try = timer->gettick();
  33. if ((ircbot->fd = make_connection(ircbot->ip, clif->hChSys->irc_server_port, &opt)) > 0) {
  34. session[ircbot->fd]->func_parse = ircbot->parse;
  35. session[ircbot->fd]->flag.server = 1;
  36. timer->add(timer->gettick() + 3000, ircbot->identify_timer, 0, 0);
  37. ircbot->isOn = true;
  38. }
  39. return 0;
  40. }
  41. /**
  42. * Timer callback to send identification commands to an IRC server
  43. * @see timer->do_timer
  44. */
  45. int irc_identify_timer(int tid, int64 tick, int id, intptr_t data) {
  46. if( !ircbot->isOn )
  47. return 0;
  48. sprintf(send_string, "USER HerculesWS%d 8 * : Hercules IRC Bridge",rand()%777);
  49. ircbot->send(send_string);
  50. sprintf(send_string, "NICK %s", clif->hChSys->irc_nick);
  51. ircbot->send(send_string);
  52. timer->add(timer->gettick() + 3000, ircbot->join_timer, 0, 0);
  53. return 0;
  54. }
  55. /**
  56. * Timer callback to join channels (and optionally send NickServ commands)
  57. * @see timer->do_timer
  58. */
  59. int irc_join_timer(int tid, int64 tick, int id, intptr_t data) {
  60. if( !ircbot->isOn )
  61. return 0;
  62. if (clif->hChSys->irc_nick_pw[0] != '\0') {
  63. sprintf(send_string, "PRIVMSG NICKSERV : IDENTIFY %s", clif->hChSys->irc_nick_pw);
  64. ircbot->send(send_string);
  65. if (clif->hChSys->irc_use_ghost) {
  66. sprintf(send_string, "PRIVMSG NICKSERV : GHOST %s %s", clif->hChSys->irc_nick, clif->hChSys->irc_nick_pw);
  67. }
  68. }
  69. sprintf(send_string, "JOIN %s", clif->hChSys->irc_channel);
  70. ircbot->send(send_string);
  71. ircbot->isIn = true;
  72. return 0;
  73. }
  74. /**
  75. * Search the handler for a given IRC received command
  76. * @param function_name Name of the received IRC command
  77. * @return Function pointer to the command handler, NULL in case
  78. * of unhandled commands
  79. */
  80. struct irc_func* irc_func_search(char* function_name) {
  81. int i;
  82. for(i = 0; i < ircbot->funcs.size; i++) {
  83. if( strcmpi(ircbot->funcs.list[i]->name, function_name) == 0 ) {
  84. return ircbot->funcs.list[i];
  85. }
  86. }
  87. return NULL;
  88. }
  89. /**
  90. * Parser for the IRC server connection
  91. * @see do_sockets
  92. */
  93. int irc_parse(int fd) {
  94. char *parse_string = NULL, *str_safe = NULL;
  95. if (session[fd]->flag.eof) {
  96. do_close(fd);
  97. ircbot->fd = 0;
  98. ircbot->isOn = false;
  99. ircbot->isIn = false;
  100. ircbot->fails = 0;
  101. ircbot->ip = host2ip(clif->hChSys->irc_server);
  102. timer->add(timer->gettick() + 120000, ircbot->connect_timer, 0, 0);
  103. return 0;
  104. }
  105. if( !RFIFOREST(fd) )
  106. return 0;
  107. parse_string = (char*)RFIFOP(fd,0);
  108. parse_string[ RFIFOREST(fd) - 1 ] = '\0';
  109. parse_string = strtok_r(parse_string,"\r\n",&str_safe);
  110. while (parse_string != NULL) {
  111. ircbot->parse_sub(fd,parse_string);
  112. parse_string = strtok_r(NULL,"\r\n",&str_safe);
  113. }
  114. RFIFOSKIP(fd, RFIFOREST(fd));
  115. RFIFOFLUSH(fd);
  116. return 0;
  117. }
  118. /**
  119. * Parse the source from a received irc message
  120. * @param source Source string, as reported by the server
  121. * @param nick Pointer to a string where to return the nick (may not be NULL,
  122. * needs to be able to fit an IRC_NICK_LENGTH long string)
  123. * @param ident Pointer to a string where to return the ident (may not be
  124. * NULL, needs to be able to fit an IRC_IDENT_LENGTH long string)
  125. * @param host Pointer to a string where to return the hostname (may not be
  126. * NULL, needs to be able to fit an IRC_HOST_LENGTH long string)
  127. */
  128. void irc_parse_source(char *source, char *nick, char *ident, char *host) {
  129. int i, pos = 0;
  130. size_t len = strlen(source);
  131. unsigned char stage = 0;
  132. for(i = 0; i < len; i++) {
  133. if( stage == 0 && source[i] == '!' ) {
  134. safestrncpy(nick, &source[0], min(i + 1, IRC_NICK_LENGTH));
  135. pos = i+1;
  136. stage = 1;
  137. } else if( stage == 1 && source[i] == '@' ) {
  138. safestrncpy(ident, &source[pos], min(i - pos + 1, IRC_IDENT_LENGTH));
  139. safestrncpy(host, &source[i+1], min(len - i, IRC_HOST_LENGTH));
  140. break;
  141. }
  142. }
  143. }
  144. /**
  145. * Parse a received message from the irc server, and do the appropriate action
  146. * for the detected command
  147. * @param fd IRC server connection file descriptor
  148. * @param str Raw received message
  149. */
  150. void irc_parse_sub(int fd, char *str) {
  151. char source[180], command[60], buf1[IRC_MESSAGE_LENGTH], buf2[IRC_MESSAGE_LENGTH];
  152. char *target = buf1, *message = buf2;
  153. struct irc_func *func;
  154. source[0] = command[0] = buf1[0] = buf2[0] = '\0';
  155. if( str[0] == ':' )
  156. str++;
  157. if (sscanf(str, "%179s %59s %499s :%499[^\r\n]", source, command, buf1, buf2) == 3 && buf1[0] == ':') {
  158. // source command :message (i.e. QUIT)
  159. message = buf1+1;
  160. target = buf2;
  161. }
  162. if( command[0] == '\0' )
  163. return;
  164. if( !(func = ircbot->func_search(command)) && !(func = ircbot->func_search(source)) ) {
  165. #ifdef IRCBOT_DEBUG
  166. ShowWarning("Unknown command received %s from %s\n",command,source);
  167. #endif // IRCBOT_DEBUG
  168. return;
  169. }
  170. func->func(fd,command,source,target,message);
  171. }
  172. /**
  173. * Send a raw command to the irc server
  174. * @param str Command to send
  175. */
  176. void irc_send(char *str) {
  177. size_t len = strlen(str) + 2;
  178. if (len > IRC_MESSAGE_LENGTH-3)
  179. len = IRC_MESSAGE_LENGTH-3;
  180. WFIFOHEAD(ircbot->fd, len);
  181. snprintf((char*)WFIFOP(ircbot->fd,0),IRC_MESSAGE_LENGTH, "%s\r\n", str);
  182. WFIFOSET(ircbot->fd, len);
  183. }
  184. /**
  185. * Handler for the PING IRC command (send back a PONG)
  186. * @see irc_parse_sub
  187. */
  188. void irc_pong(int fd, char *cmd, char *source, char *target, char *msg) {
  189. sprintf(send_string, "PONG %s", cmd);
  190. ircbot->send(send_string);
  191. }
  192. /**
  193. * Handler for CTCP commands received via PRIVMSG
  194. * @see irc_privmsg
  195. */
  196. void irc_privmsg_ctcp(int fd, char *cmd, char *source, char *target, char *msg) {
  197. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  198. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  199. if( source[0] != '\0' )
  200. ircbot->parse_source(source,source_nick,source_ident,source_host);
  201. if( strcmpi(cmd,"ACTION") == 0 ) {
  202. if( ircbot->channel ) {
  203. snprintf(send_string, 150, "[ #%s ] * IRC.%s %s *",ircbot->channel->name,source_nick,msg);
  204. clif->chsys_msg2(ircbot->channel,send_string);
  205. }
  206. } else if( strcmpi(cmd,"ERRMSG") == 0 ) {
  207. // Ignore it
  208. } else if( strcmpi(cmd,"FINGER") == 0 ) {
  209. // Ignore it
  210. } else if( strcmpi(cmd,"PING") == 0 ) {
  211. sprintf(send_string, "NOTICE %s :\001PING %s\001",source_nick,msg);
  212. ircbot->send(send_string);
  213. } else if( strcmpi(cmd,"TIME") == 0 ) {
  214. time_t time_server; // variable for number of seconds (used with time() function)
  215. struct tm *datetime; // variable for time in structure ->tm_mday, ->tm_sec, ...
  216. char temp[CHAT_SIZE_MAX];
  217. memset(temp, '\0', sizeof(temp));
  218. time(&time_server); // get time in seconds since 1/1/1970
  219. datetime = localtime(&time_server); // convert seconds in structure
  220. // like sprintf, but only for date/time (Sunday, November 02 2003 15:12:52)
  221. strftime(temp, sizeof(temp)-1, msg_txt(230), datetime); // Server time (normal time): %A, %B %d %Y %X.
  222. sprintf(send_string, "NOTICE %s :\001TIME %s\001",source_nick,temp);
  223. ircbot->send(send_string);
  224. } else if( strcmpi(cmd,"VERSION") == 0 ) {
  225. sprintf(send_string, "NOTICE %s :\001VERSION Hercules.ws IRC Bridge\001",source_nick);
  226. ircbot->send(send_string);
  227. #ifdef IRCBOT_DEBUG
  228. } else {
  229. ShowWarning("Unknown CTCP command received %s (%s) from %s\n",cmd,msg,source);
  230. #endif // IRCBOT_DEBUG
  231. }
  232. }
  233. /**
  234. * Handler for the PRIVMSG IRC command (action depends on the message contents)
  235. * @see irc_parse_sub
  236. */
  237. void irc_privmsg(int fd, char *cmd, char *source, char *target, char *msg) {
  238. if( msg && *msg == '\001' && strlen(msg) > 2 && msg[strlen(msg)-1] == '\001' ) {
  239. // CTCP
  240. char command[IRC_MESSAGE_LENGTH], message[IRC_MESSAGE_LENGTH];
  241. command[0] = message[0] = '\0';
  242. sscanf(msg, "\001%499[^\001\r\n ] %499[^\r\n\001]\001", command, message);
  243. irc_privmsg_ctcp(fd, command, source, target, message);
  244. #ifdef IRCBOT_DEBUG
  245. } else if (strcmpi(target, clif->hChSys->irc_nick) == 0) {
  246. ShowDebug("irc_privmsg: Received message from %s: '%s'\n", source ? source : "(null)", msg);
  247. #endif // IRCBOT_DEBUG
  248. } else if (msg && strcmpi(target, clif->hChSys->irc_channel) == 0) {
  249. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  250. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  251. if( source[0] != '\0' )
  252. ircbot->parse_source(source,source_nick,source_ident,source_host);
  253. if( ircbot->channel ) {
  254. size_t padding_len = strlen(ircbot->channel->name) + strlen(source_nick) + 13;
  255. while (1) {
  256. snprintf(send_string, 150, "[ #%s ] IRC.%s : %s",ircbot->channel->name,source_nick,msg);
  257. clif->chsys_msg2(ircbot->channel,send_string);
  258. //break; // Uncomment this line to truncate long messages instead of posting them as multiple lines
  259. if (strlen(msg) <= 149 - padding_len)
  260. break;
  261. msg += 149 - padding_len;
  262. }
  263. }
  264. }
  265. }
  266. /**
  267. * Handler for the JOIN IRC command (notify an in-game channel of users joining
  268. * the IRC channel)
  269. * @see irc_parse_sub
  270. */
  271. void irc_userjoin(int fd, char *cmd, char *source, char *target, char *msg) {
  272. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  273. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  274. if( source[0] != '\0' )
  275. ircbot->parse_source(source,source_nick,source_ident,source_host);
  276. if( ircbot->channel ) {
  277. snprintf(send_string, 150, "[ #%s ] User IRC.%s joined the channel.",ircbot->channel->name,source_nick);
  278. clif->chsys_msg2(ircbot->channel,send_string);
  279. }
  280. }
  281. /**
  282. * Handler for the PART and QUIT IRC commands (notify an in-game channel of
  283. * users leaving the IRC channel)
  284. * @see irc_parse_sub
  285. */
  286. void irc_userleave(int fd, char *cmd, char *source, char *target, char *msg) {
  287. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  288. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  289. if( source[0] != '\0' )
  290. ircbot->parse_source(source,source_nick,source_ident,source_host);
  291. if( ircbot->channel ) {
  292. if (!strcmpi(cmd, "QUIT"))
  293. snprintf(send_string, 150, "[ #%s ] User IRC.%s left the channel. [Quit: %s]",ircbot->channel->name,source_nick,msg);
  294. else
  295. snprintf(send_string, 150, "[ #%s ] User IRC.%s left the channel. [%s]",ircbot->channel->name,source_nick,msg);
  296. clif->chsys_msg2(ircbot->channel,send_string);
  297. }
  298. }
  299. /**
  300. * Handler for the NICK IRC commands (notify an in-game channel of users
  301. * changing their name while in the IRC channel)
  302. * @see irc_parse_sub
  303. */
  304. void irc_usernick(int fd, char *cmd, char *source, char *target, char *msg) {
  305. char source_nick[IRC_NICK_LENGTH], source_ident[IRC_IDENT_LENGTH], source_host[IRC_HOST_LENGTH];
  306. source_nick[0] = source_ident[0] = source_host[0] = '\0';
  307. if( source[0] != '\0' )
  308. ircbot->parse_source(source,source_nick,source_ident,source_host);
  309. if( ircbot->channel ) {
  310. snprintf(send_string, 150, "[ #%s ] User IRC.%s is now known as IRC.%s",ircbot->channel->name,source_nick,msg);
  311. clif->chsys_msg2(ircbot->channel,send_string);
  312. }
  313. }
  314. /**
  315. * Relay a chat message to the irc channel the bot is connected to
  316. * @param name Sender's name
  317. * @param msg Message text
  318. */
  319. void irc_relay(char *name, const char *msg) {
  320. if( !ircbot->isIn )
  321. return;
  322. sprintf(send_string,"PRIVMSG %s :[ %s ] : %s", clif->hChSys->irc_channel, name, msg);
  323. ircbot->send(send_string);
  324. }
  325. /**
  326. * IRC bot initializer
  327. */
  328. void irc_bot_init(bool minimal) {
  329. /// Command handlers
  330. const struct irc_func irc_func_base[] = {
  331. { "PING" , ircbot->pong },
  332. { "PRIVMSG", ircbot->privmsg },
  333. { "JOIN", ircbot->userjoin },
  334. { "QUIT", ircbot->userleave },
  335. { "PART", ircbot->userleave },
  336. { "NICK", ircbot->usernick },
  337. };
  338. struct irc_func* function;
  339. int i;
  340. if (minimal)
  341. return;
  342. if (!clif->hChSys->irc)
  343. return;
  344. if (!(ircbot->ip = host2ip(clif->hChSys->irc_server))) {
  345. ShowError("Unable to resolve '%s' (irc server), disabling irc channel...\n", clif->hChSys->irc_server);
  346. clif->hChSys->irc = false;
  347. return;
  348. }
  349. ircbot->funcs.size = ARRAYLENGTH(irc_func_base);
  350. CREATE(ircbot->funcs.list,struct irc_func*,ircbot->funcs.size);
  351. for( i = 0; i < ircbot->funcs.size; i++ ) {
  352. CREATE(function, struct irc_func, 1);
  353. safestrncpy(function->name, irc_func_base[i].name, sizeof(function->name));
  354. function->func = irc_func_base[i].func;
  355. ircbot->funcs.list[i] = function;
  356. }
  357. ircbot->fails = 0;
  358. ircbot->fd = 0;
  359. ircbot->isIn = false;
  360. ircbot->isOn = false;
  361. timer->add_func_list(ircbot->connect_timer, "irc_connect_timer");
  362. timer->add(timer->gettick() + 7000, ircbot->connect_timer, 0, 0);
  363. }
  364. /**
  365. * IRC bot finalizer
  366. */
  367. void irc_bot_final(void) {
  368. int i;
  369. if (!clif->hChSys->irc)
  370. return;
  371. if( ircbot->isOn ) {
  372. ircbot->send("QUIT :Hercules is shutting down");
  373. do_close(ircbot->fd);
  374. }
  375. for( i = 0; i < ircbot->funcs.size; i++ ) {
  376. aFree(ircbot->funcs.list[i]);
  377. }
  378. aFree(ircbot->funcs.list);
  379. }
  380. /**
  381. * IRC bot interface defaults initializer
  382. */
  383. void ircbot_defaults(void) {
  384. ircbot = &irc_bot_s;
  385. ircbot->channel = NULL;
  386. ircbot->init = irc_bot_init;
  387. ircbot->final = irc_bot_final;
  388. ircbot->parse = irc_parse;
  389. ircbot->parse_sub = irc_parse_sub;
  390. ircbot->parse_source = irc_parse_source;
  391. ircbot->func_search = irc_func_search;
  392. ircbot->connect_timer = irc_connect_timer;
  393. ircbot->identify_timer = irc_identify_timer;
  394. ircbot->join_timer = irc_join_timer;
  395. ircbot->send = irc_send;
  396. ircbot->relay = irc_relay;
  397. ircbot->pong = irc_pong;
  398. ircbot->privmsg = irc_privmsg;
  399. ircbot->userjoin = irc_userjoin;
  400. ircbot->userleave = irc_userleave;
  401. ircbot->usernick = irc_usernick;
  402. }