IrcClient.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442
  1. /*\
  2. |*| Copyright 2015-2016 bill-auger <https://github.com/bill-auger/av-caster/issues>
  3. |*|
  4. |*| This file is part of the AvCaster program.
  5. |*|
  6. |*| AvCaster is free software: you can redistribute it and/or modify
  7. |*| it under the terms of the GNU General Public License version 3
  8. |*| as published by the Free Software Foundation.
  9. |*|
  10. |*| AvCaster is distributed in the hope that it will be useful,
  11. |*| but WITHOUT ANY WARRANTY; without even the implied warranty of
  12. |*| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  13. |*| GNU General Public License for more details.
  14. |*|
  15. |*| You should have received a copy of the GNU General Public License
  16. |*| along with AvCaster. If not, see <http://www.gnu.org/licenses/>.
  17. \*/
  18. #include "IrcClient.h"
  19. #ifndef DISABLE_CHAT
  20. #include <libirc_rfcnumeric.h>
  21. #include "AvCaster.h"
  22. #include "IrcClient.h"
  23. #include "../Trace/TraceIrcClient.h"
  24. /* IrcClient class variables */
  25. ValueTree IrcClient::NetworkStore ; // IrcClient()
  26. irc_callbacks_t IrcClient::ServerCallbacks ; // IrcClient()
  27. StringArray IrcClient::Nicks ; // HandleNicks()
  28. /* IrcClient public instance methods */
  29. void IrcClient::configure(bool should_create_session)
  30. {
  31. // disconnect and destroy existing network connection
  32. destroySession() ;
  33. // validate and create network session (and login asynchronously)
  34. if (should_create_session) createSession() ;
  35. }
  36. /* IrcClient private class methods */
  37. IrcClient::IrcClient(ValueTree network_store) : Thread(APP::IRC_THREAD_NAME)
  38. {
  39. // validate and initialize
  40. if (ServerCallbacks.event_connect != OnConnect ||
  41. ServerCallbacks.event_channel != OnChannelMsg ||
  42. ServerCallbacks.event_join != OnJoin ||
  43. ServerCallbacks.event_part != OnPart ||
  44. ServerCallbacks.event_nick != OnNickChange ||
  45. ServerCallbacks.event_numeric != OnNumeric )
  46. {
  47. // assign server callbacks (NOTE: there are many more)
  48. memset(&ServerCallbacks , 0 , sizeof(ServerCallbacks)) ;
  49. ServerCallbacks.event_connect = OnConnect ;
  50. ServerCallbacks.event_channel = OnChannelMsg ;
  51. ServerCallbacks.event_join = OnJoin ;
  52. ServerCallbacks.event_part = OnPart ;
  53. ServerCallbacks.event_nick = OnNickChange ;
  54. ServerCallbacks.event_numeric = OnNumeric ;
  55. }
  56. NetworkStore = network_store ;
  57. this->session = nullptr ; configure(true) ;
  58. }
  59. IrcClient::~IrcClient() { destroySession() ; NetworkStore = ValueTree::invalid ; }
  60. void IrcClient::OnConnect(irc_session_t* session , const char* event , const char* origin ,
  61. const char** params , unsigned int count )
  62. {
  63. String host = origin ;
  64. String message = params[1] ;
  65. bool is_bitlbee = message.startsWith(IRC::BITLBEE_WELCOME_MSG) ;
  66. String channel = STRING(NetworkStore[CONFIG::CHANNEL_ID]) ;
  67. DEBUG_TRACE_CONNECTED
  68. // set actual connected host for this network
  69. AvCaster::SetValue(CONFIG::HOST_ID , host) ;
  70. // display connected message
  71. AddClientChat(IRC::LOGGED_IN_MSG + host) ; AddServerChat(message) ;
  72. if (is_bitlbee)
  73. {
  74. // identify with bitlbee
  75. String identify_cmd = IRC::BITLBEE_IDENTIFY_CMD + "this->pass" ;
  76. irc_cmd_msg(session , IRC::BITLBEE_ROOT_CHANNEL , CHARSTAR(identify_cmd)) ;
  77. //irc_cmd_join(session , IRC::BITLBEE_XMPP_CHANNEL , NULL) ; // FIXME: when to join?
  78. }
  79. else irc_cmd_join(session , CHARSTAR(channel) , NULL) ;
  80. // reset retries counter
  81. SetRetries(IRC::MAX_N_RETRIES) ;
  82. }
  83. /* TODO: handle nickserv
  84. static void event_notice (irc_session_t * session, const char * event,
  85. const char * origin, const char ** params, unsigned int count)
  86. {
  87. char buf[256];
  88. if ( !origin )
  89. return;
  90. if ( strcasecmp (origin, "nickserv") )
  91. return;
  92. if ( strstr (params[1], "This nick is not registered") == params[1] )
  93. {
  94. sprintf (buf, "REGISTER %s NOMAIL", gCfg.irc_nickserv_password);
  95. irc_cmd_msg(session, "nickserv", buf);
  96. }
  97. else if ( strstr (params[1], "This nickname is registered and protected") == params[1] )
  98. {
  99. sprintf (buf, "IDENTIFY %s", gCfg.irc_nickserv_password);
  100. irc_cmd_msg(session, "nickserv", buf);
  101. }
  102. else if ( strstr (params[1], "Password accepted - you are now recognized") == params[1] )
  103. printf ("Nickserv authentication succeed.");
  104. }
  105. */
  106. void IrcClient::OnChannelMsg(irc_session_t* session , const char* event , const char* origin ,
  107. const char** params , unsigned int count )
  108. {
  109. DEBUG_TRACE_CHAT_MSG_VB
  110. if (!origin || count != 2) return ;
  111. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  112. String user_id = String(origin ) ;
  113. String nick = String(nickbuf) ;
  114. String channel = CharPointer_UTF8(params[0]) ;
  115. String filtered_message = ProcessTextMeta (params[1]) ;
  116. StringArray processed_message = ProcessTimestamp(filtered_message) ;
  117. String timestamp = processed_message[0].trim() ;
  118. String message = processed_message[1].trim() ;
  119. bool is_root_user = IRC::BITLBEE_ROOT_USERS.contains(user_id) ;
  120. bool is_root_channel = channel == String(IRC::BITLBEE_ROOT_CHANNEL ) ;
  121. bool is_xmpp_channel = channel == String(IRC::BITLBEE_XMPP_CHANNEL ) ;
  122. bool is_login_blocked = message.endsWith (IRC::BITLBEE_LOGIN_BLOCKED_MSG) ;
  123. bool has_kicked_self = message.endsWith (IRC::BITLBEE_KICKED_SELF_MSG ) ;
  124. bool is_logged_in = message.endsWith (IRC::BITLBEE_CONNECTED_MSG ) ||
  125. has_kicked_self ;
  126. DEBUG_TRACE_CHAT_MSG
  127. // handle login confirmation from the bee
  128. if (is_root_user && is_root_channel)
  129. if (is_login_blocked) AddClientChat(IRC::BITLBEE_SESSION_BLOCKED_MSG) ;
  130. else if (is_logged_in ) irc_cmd_join(session , IRC::BITLBEE_XMPP_CHANNEL , NULL) ;
  131. if (is_root_user || is_root_channel) return ;
  132. // handle peer messages
  133. AddUserChat(timestamp , nick , message) ;
  134. }
  135. void IrcClient::OnJoin(irc_session_t* session , const char* event , const char* origin ,
  136. const char** params , unsigned int count )
  137. {
  138. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  139. String nick = nickbuf ;
  140. String channel = params[0] ;
  141. String network = STRING(NetworkStore[CONFIG::NETWORK_ID ]) ;
  142. String this_nick = STRING(NetworkStore[CONFIG::NICK_ID ]) ;
  143. String peer_greeting = STRING(NetworkStore[CONFIG::GREETING_ID ]) ;
  144. bool should_show_joinparts = bool (NetworkStore[CONFIG::JOINPARTS_ID]) ;
  145. bool is_local_nick = nick == this_nick ;
  146. String greeting = (is_local_nick) ? IRC::LOGIN_GREETING : peer_greeting ;
  147. irc_cmd_user_mode(session , "+i") ;
  148. DEBUG_TRACE_ONJOIN
  149. // if (channel_name == IRC::BITLBEE_ROOT_CHANNEL) return ; // FIXME: probably redundant now
  150. if (should_show_joinparts && !is_local_nick)
  151. AddClientChat(nick + " just joined " + network + " channel " + channel) ;
  152. #ifndef SUPRESS_GREETING_MESSAGES
  153. irc_cmd_msg(session , CHARSTAR(channel) , CHARSTAR(greeting)) ;
  154. #endif // SUPRESS_GREETING_MESSAGES
  155. // irc_cmd_names(session , CHARSTAR(channel_name)) ; // FIXME: this maybe redundant
  156. }
  157. void IrcClient::OnPart(irc_session_t* session , const char* event , const char* origin ,
  158. const char** params , unsigned int count )
  159. {
  160. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  161. String nick = nickbuf ;
  162. String channel = params[0] ;
  163. String network = STRING(NetworkStore[CONFIG::NETWORK_ID ]) ;
  164. String this_nick = STRING(NetworkStore[CONFIG::NICK_ID ]) ;
  165. bool should_show_joinparts = bool (NetworkStore[CONFIG::JOINPARTS_ID]) ;
  166. bool is_local_nick = nick == this_nick ;
  167. DEBUG_TRACE_ONPART
  168. if (should_show_joinparts && !is_local_nick)
  169. AddClientChat(nick + " just parted " + network + " channel " + channel) ;
  170. // irc_cmd_names(session , CHARSTAR(channel_name)) ; // FIXME: this maybe redundant
  171. }
  172. void IrcClient::OnNickChange(irc_session_t* session , const char* event , const char* origin ,
  173. const char** params , unsigned int count )
  174. {
  175. String from_nick = origin ;
  176. String to_nick = params[0] ;
  177. String network = STRING(NetworkStore[CONFIG::NETWORK_ID]) ;
  178. String channel = STRING(NetworkStore[CONFIG::CHANNEL_ID]) ;
  179. DEBUG_TRACE_NICK_CHANGE
  180. // DUMP_SERVER_PARAMS // TODO: nick changes nyi
  181. // irc_cmd_names(session , CHARSTAR(channel_name)) ; // FIXME: this maybe redundant
  182. }
  183. void IrcClient::OnNumeric(irc_session_t* session , unsigned int event , const char* origin ,
  184. const char** params , unsigned int count )
  185. {
  186. String nicks = params[3] ;
  187. DEBUG_TRACE_SERVER_EVENT
  188. if (event == LIBIRC_RFC_RPL_NAMREPLY ) HandleNicks(nicks) ;
  189. else if (event == LIBIRC_RFC_RPL_ENDOFNAMES) UpdateNicks() ;
  190. }
  191. bool IrcClient::IsSufficientVersion()
  192. {
  193. unsigned int major_version , minor_version ;
  194. irc_get_version(&major_version , &minor_version) ;
  195. return major_version >= IRC::MIN_MAJOR_VERSION &&
  196. minor_version >= IRC::MIN_MINOR_VERSION ;
  197. }
  198. void IrcClient::SetRetries(int n_retries)
  199. {
  200. NetworkStore.setProperty(CONFIG::RETRIES_ID , n_retries , nullptr) ;
  201. }
  202. void IrcClient::HandleNicks(String nicks)
  203. {
  204. String network = STRING(NetworkStore[CONFIG::NETWORK_ID]) ;
  205. String channel = STRING(NetworkStore[CONFIG::CHANNEL_ID]) ;
  206. if (IRC::BITLBEE_HOSTS.contains(network) && channel == IRC::BITLBEE_ROOT_CHANNEL) return ;
  207. Nicks.addTokens (nicks , false) ;
  208. Nicks.removeEmptyStrings() ;
  209. Nicks.removeString (IRC::BITLBEE_ROOT_NICK) ;
  210. DEBUG_TRACE_NICKS
  211. }
  212. void IrcClient::UpdateNicks()
  213. {
  214. #ifdef MOCK_CHAT_NICKS
  215. Nicks.clear() ; int n_nicks = 100 ;
  216. while (n_nicks--) Nicks.add("MockNick" + String(n_nicks)) ;
  217. #endif // MOCK_CHAT_NICKS
  218. AvCaster::UpdateChatters(Nicks) ; Nicks.clear() ;
  219. }
  220. String IrcClient::ProcessTextMeta(const char* message)
  221. {
  222. // filter formatting markup tokens
  223. // TODO: for now we just strip these
  224. // WHITE , BLACK , DARKBLUE , DARKGREEN , RED , BROWN , PURPLE , OLIVE
  225. // YELLOW , GREEN , TEAL , CYAN , BLUE , MAGENTA , DARKGRAY , LIGHTGRAY
  226. // e.g. "The tree[U]s[/U] are [COLOR=GREEN/BLACK]green[/COLOR]"
  227. char* msg_cstr = irc_color_convert_from_mirc(message) ;
  228. String converted_msg = CharPointer_UTF8(msg_cstr) ; delete msg_cstr ;
  229. String filtered_msg = converted_msg.replace(IRC::BOLD_BEGIN , "").replace(IRC::BOLD_END , "")
  230. .replace(IRC::ITALIC_BEGIN , "").replace(IRC::ITALIC_END , "")
  231. .replace(IRC::ULINE_BEGIN , "").replace(IRC::ULINE_END , "")
  232. .replace(IRC::COLOR_END , "") ;
  233. while (filtered_msg.contains(IRC::COLOR_BEGIN))
  234. filtered_msg.replace(filtered_msg.fromFirstOccurrenceOf(IRC::COLOR_BEGIN , true , false)
  235. .upToFirstOccurrenceOf("]" , true , false) , "") ;
  236. return filtered_msg ;
  237. }
  238. StringArray IrcClient::ProcessTimestamp(String message)
  239. {
  240. String timestamp = message .fromFirstOccurrenceOf("[" , true , false)
  241. .upToFirstOccurrenceOf("]" , true , false) ;
  242. String processed_msg = message .replace(timestamp , "") ;
  243. bool is_timestamp = timestamp.isNotEmpty() &&
  244. timestamp.replaceCharacters("[:]" , "000")
  245. .containsOnly(APP::DIGITS) ;
  246. return (is_timestamp) ? StringArray::fromLines(timestamp + "\n" + processed_msg) :
  247. StringArray::fromLines( "\n" + message ) ;
  248. }
  249. void IrcClient::AddServerChat(String message)
  250. {
  251. AvCaster::AddChatLine(String::empty , GUI::SERVER_NICK , message) ;
  252. }
  253. void IrcClient::AddClientChat(String message)
  254. {
  255. AvCaster::AddChatLine(String::empty , GUI::CLIENT_NICK , message) ;
  256. }
  257. void IrcClient::AddUserChat(String prefix , String nick , String message)
  258. {
  259. AvCaster::AddChatLine(prefix , nick , message) ;
  260. }
  261. /* IrcClient private instance methods */
  262. void IrcClient::createSession()
  263. {
  264. if (!NetworkStore.isValid()) return ;
  265. // create network session (and login asynchronously)
  266. this->session = irc_create_session(&ServerCallbacks) ;
  267. String network = STRING(NetworkStore[CONFIG::NETWORK_ID ]) ;
  268. unsigned short port = int (NetworkStore[CONFIG::PORT_ID ]) ;
  269. String nick = STRING(NetworkStore[CONFIG::NICK_ID ]) ;
  270. String pass = STRING(NetworkStore[CONFIG::PASS_ID ]) ;
  271. String channel = STRING(NetworkStore[CONFIG::CHANNEL_ID ]) ;
  272. String greeting = STRING(NetworkStore[CONFIG::GREETING_ID]) ;
  273. bool is_valid_session = this->session != 0 ;
  274. bool is_valid_network = network.isNotEmpty() ;
  275. bool is_valid_port = IRC::PORT_RANGE.contains(uint16(port)) ;
  276. bool is_valid_nick = nick .isNotEmpty() ;
  277. bool is_valid_channel = channel.isNotEmpty() ;
  278. // NOTE: text editors are implicitly sanitized in Config::Config() -> MainContent->configureTextEditor()
  279. // NOTE: irc_option_set(session , LIBIRC_OPTION_STRIPNICKS) ;
  280. // NOTE: See LIBIRC_OPTION_SSL_NO_VERIFY for servers which use self-signed SSL certificates
  281. DEBUG_TRACE_CREATE_SESSION
  282. // set retries counter or desroy invalid session
  283. if (is_valid_session && is_valid_network && is_valid_port &&
  284. is_valid_nick && is_valid_channel ) SetRetries(IRC::MAX_N_RETRIES) ;
  285. else if (is_valid_session) destroySession() ;
  286. }
  287. void IrcClient::destroySession()
  288. {
  289. if (this->session == nullptr) return ;
  290. // display login message and clear peers list
  291. if (irc_is_connected(this->session))
  292. {
  293. DEBUG_TRACE_LOGOUT
  294. AddClientChat(IRC::LOGGING_OUT_MSG + network) ;
  295. Nicks.clear() ; UpdateNicks() ;
  296. }
  297. // disconnect and destroy network session
  298. #ifndef SUPRESS_GREETING_MESSAGES
  299. irc_cmd_quit (this->session , CHARSTAR(IRC::LOGOUT_GREETING)) ;
  300. #else // SUPRESS_GREETING_MESSAGES
  301. irc_disconnect (this->session) ;
  302. #endif // SUPRESS_GREETING_MESSAGES
  303. irc_destroy_session(this->session) ;
  304. this->session = nullptr ;
  305. }
  306. void IrcClient::run()
  307. {
  308. if (this->session == nullptr) return ;
  309. if (irc_is_connected(this->session))
  310. {
  311. // pump IRC client
  312. struct timeval timeout ;
  313. timeout.tv_usec = 250000 ;
  314. timeout.tv_sec = 0 ;
  315. int maxfd = 0 ;
  316. fd_set in_set , out_set ;
  317. FD_ZERO(&in_set) ; FD_ZERO(&out_set) ;
  318. irc_add_select_descriptors(this->session , &in_set , &out_set , &maxfd) ;
  319. if (select(maxfd + 1 , &in_set , &out_set , 0 , &timeout) < 0 ||
  320. irc_process_select_descriptors(this->session , &in_set , &out_set)) return ;
  321. }
  322. else login() ;
  323. }
  324. bool IrcClient::login()
  325. {
  326. String network = STRING(NetworkStore[CONFIG::NETWORK_ID]) ;
  327. unsigned short port = int (NetworkStore[CONFIG::PORT_ID ]) ;
  328. String nick = STRING(NetworkStore[CONFIG::NICK_ID ]) ;
  329. int n_retries = int (NetworkStore[CONFIG::RETRIES_ID]) ;
  330. DEBUG_TRACE_LOGIN_FAILED
  331. // fail after nRetries connection attempts
  332. if (n_retries != IRC::STATE_FAILED) SetRetries(--n_retries) ;
  333. if (n_retries == IRC::STATE_FAILED) return true ;
  334. // display login message
  335. AddClientChat(IRC::LOGGING_IN_MSG + network) ;
  336. bool is_err = irc_connect(this->session , CHARSTAR(network) , port ,
  337. nullptr , CHARSTAR(nick ) , nullptr , nullptr) ;
  338. DEBUG_TRACE_LOGIN
  339. return !is_err ;
  340. }
  341. void IrcClient::sendChat(String chat_message)
  342. {
  343. String channel = STRING(NetworkStore[CONFIG::CHANNEL_ID]) ;
  344. irc_cmd_msg(this->session , CHARSTAR(channel) , CHARSTAR(chat_message)) ;
  345. }
  346. #endif // DISABLE_CHAT