IrcClient.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. /*\
  2. |*| Copyright 2015 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 Lesser 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 Lesser General Public License for more details.
  14. |*|
  15. |*| You should have received a copy of the GNU Lesser 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 <libircclient/libirc_rfcnumeric.h>
  21. #include "AvCaster.h"
  22. #include "IrcClient.h"
  23. #include "Trace/TraceIrcClient.h"
  24. String bitlbeehost = "localhost" ; // TODO: GUI support
  25. // String debianhost = "irc.debian.org" ; // TODO: GUI support
  26. String my_nick = "bill-auger" ; // TODO: GUI support
  27. String xmpp_channel = "#mychat" ; // TODO: GUI support
  28. String greeting = "" ; // TODO: GUI support
  29. /* IrcClient class methods */
  30. IrcClient::IrcClient(ValueTree servers_store) : Thread(APP::IRC_THREAD_NAME) ,
  31. serversStore(servers_store)
  32. {
  33. // create per server session instances and login
  34. int n_servers = this->serversStore.getNumChildren() ;
  35. for (int server_n = 0 ; server_n < n_servers ; ++server_n)
  36. {
  37. ValueTree server_store = this->serversStore.getChild(server_n) ;
  38. IrcServerInfo server_info = createSession(server_store) ;
  39. if (IsValidServerInfo(&server_info))
  40. this->servers.add(server_info) ; login(&server_info) ;
  41. }
  42. }
  43. IrcClient::~IrcClient()
  44. {
  45. IrcServerInfo* server = this->servers.begin() ;
  46. while (server != this->servers.end())
  47. {
  48. irc_cmd_quit (server->session , IRC::LOGOOUT_MSG) ;
  49. irc_disconnect (server->session) ;
  50. irc_destroy_session(server->session) ;
  51. ++server ;
  52. }
  53. }
  54. IrcServerInfo IrcClient::createSession(ValueTree server_store)
  55. {
  56. // assign shared server callbacks
  57. irc_callbacks_t callbacks ;
  58. memset(&callbacks , 0 , sizeof(callbacks)) ;
  59. callbacks.event_connect = OnConnect ;
  60. callbacks.event_channel = OnChannelMsg ;
  61. callbacks.event_join = OnJoin ;
  62. callbacks.event_part = OnPart ;
  63. callbacks.event_nick = OnNickChange ;
  64. callbacks.event_numeric = OnNumeric ;
  65. // create session
  66. String host = STRING(server_store[CONFIG::HOST_ID]) ;
  67. unsigned short port = int (server_store[CONFIG::PORT_ID]) ;
  68. IrcServerInfo server_info = { NULL , host , port , my_nick } ;
  69. server_info.session = irc_create_session(&callbacks) ;
  70. DEBUG_TRACE_CREATE_SESSION
  71. //irc_option_set(a_server.session , LIBIRC_OPTION_STRIPNICKS) ;
  72. //See LIBIRC_OPTION_SSL_NO_VERIFY for servers which use self-signed SSL certificates
  73. return server_info ;
  74. }
  75. void IrcClient::OnConnect(irc_session_t* session , const char* event , const char* origin ,
  76. const char** params , unsigned int count )
  77. {
  78. String host = origin ;
  79. String message = params[1] ;
  80. bool is_bitlbee = host == bitlbeehost &&
  81. message == IRC::BITLBEE_WELCOME_MSG + my_nick ;
  82. DEBUG_TRACE_CONNECTED
  83. // String OFTC_TLD = "oftc.net" ;
  84. // if (host.endsWith(IRC::OFTC_TLD)) AvCaster::RenameServer(debianhost , host) ;
  85. if (host.endsWith(IRC::OFTC_TLD)) AvCaster::UpdateIrcHost(IRC::OFTC_ALIAS_URIS , host) ;
  86. // display connected message
  87. AddClientChat(IRC::LOGGED_IN_MSG + host) ; AddServerChat(message) ;
  88. if (is_bitlbee)
  89. {
  90. // identify with bitlbee
  91. String my_pass = "123" ; // TODO: GUI and model support
  92. String identify_cmd = IRC::IDENTIFY_CMD + my_pass ;
  93. irc_cmd_msg(session , IRC::ROOT_CHANNEL , CHARSTAR(identify_cmd)) ;
  94. }
  95. else irc_cmd_join(session , CHARSTAR(xmpp_channel) , NULL) ;
  96. }
  97. /* handle nickserv
  98. static void event_notice (irc_session_t * session, const char * event,
  99. const char * origin, const char ** params, unsigned int count)
  100. {
  101. char buf[256];
  102. if ( !origin )
  103. return;
  104. if ( strcasecmp (origin, "nickserv") )
  105. return;
  106. if ( strstr (params[1], "This nick is not registered") == params[1] )
  107. {
  108. sprintf (buf, "REGISTER %s NOMAIL", gCfg.irc_nickserv_password);
  109. irc_cmd_msg(session, "nickserv", buf);
  110. }
  111. else if ( strstr (params[1], "This nickname is registered and protected") == params[1] )
  112. {
  113. sprintf (buf, "IDENTIFY %s", gCfg.irc_nickserv_password);
  114. irc_cmd_msg(session, "nickserv", buf);
  115. }
  116. else if ( strstr (params[1], "Password accepted - you are now recognized") == params[1] )
  117. printf ("Nickserv authentication succeed.");
  118. }
  119. */
  120. void IrcClient::OnChannelMsg(irc_session_t* session , const char* event , const char* origin ,
  121. const char** params , unsigned int count )
  122. {
  123. DEBUG_TRACE_CHAT_MSG_VB
  124. if (!origin || count != 2) return ;
  125. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  126. String user_id = String(origin) ;
  127. String nick = nickbuf ;
  128. String channel = CharPointer_UTF8(params[0]) ;
  129. String filtered_message = ProcessTextMeta(params[1]) ;
  130. StringArray processed_message = ProcessTimestamp(filtered_message) ;
  131. String timestamp = processed_message[0] ;
  132. String message = processed_message[1] ;
  133. bool is_root_user = user_id.startsWith(IRC::ROOT_USER + bitlbeehost) ;
  134. bool is_root_channel = channel == String (IRC::ROOT_CHANNEL ) ;
  135. bool is_xmpp_channel = channel == String (xmpp_channel ) ;
  136. bool is_login_blocked = message.endsWith (IRC::LOGIN_BLOCKED_MSG ) ;
  137. bool is_logged_in = message.endsWith (IRC::CONNECTED_MSG ) ||
  138. message.endsWith (IRC::KICKED_SELF_MSG ) ;
  139. DEBUG_TRACE_CHAT_MSG
  140. // handle login message from the bee
  141. if (is_root_user && is_root_channel)
  142. if (is_login_blocked) AddClientChat(IRC::SESSION_BLOCKED_MSG) ;
  143. else if (is_logged_in ) irc_cmd_join(session , CHARSTAR(xmpp_channel) , NULL) ;
  144. if (is_root_user || is_root_channel) return ;
  145. // handle peer messages
  146. AddUserChat(timestamp , nick , message) ;
  147. }
  148. void IrcClient::OnJoin(irc_session_t* session , const char* event , const char* origin ,
  149. const char** params , unsigned int count )
  150. {
  151. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  152. String nick = nickbuf ;
  153. String channel_name = params[0] ;
  154. irc_cmd_user_mode(session , "+i") ;
  155. DEBUG_TRACE_ONJOIN
  156. if (channel_name == IRC::ROOT_CHANNEL) return ;
  157. #ifndef HIDE_JOIN_PART_MESSAGES // TODO: GUI support
  158. AddClientChat(nick + " just joined channel " + channel_name) ;
  159. #endif // HIDE_JOIN_PART_MESSAGES
  160. if (nick == my_nick) irc_cmd_msg(session , params[0] , IRC::LOGIN_MSG ) ;
  161. else if (greeting.isNotEmpty()) irc_cmd_msg(session , params[0] , CHARSTAR(greeting)) ;
  162. // TODO: this maybe redundant
  163. irc_cmd_names(session , CHARSTAR(channel_name)) ;
  164. }
  165. void IrcClient::OnPart(irc_session_t* session , const char* event , const char* origin ,
  166. const char** params , unsigned int count )
  167. {
  168. char nickbuf[128] ; irc_target_get_nick(origin , nickbuf , sizeof(nickbuf)) ;
  169. String nick = nickbuf ;
  170. String channel_name = params[0] ;
  171. DEBUG_TRACE_ONPART
  172. #ifndef HIDE_JOIN_PART_MESSAGES
  173. AddClientChat(nick + " just parted channel " + channel_name) ;
  174. #endif // HIDE_JOIN_PART_MESSAGES
  175. // TODO: this maybe redundant
  176. irc_cmd_names(session , CHARSTAR(channel_name)) ;
  177. }
  178. void IrcClient::OnNickChange(irc_session_t* session , const char* event , const char* origin ,
  179. const char** params , unsigned int count )
  180. {
  181. String from_nick = origin ;
  182. String to_nick = params[0] ;
  183. // String channel_name = session. ; // TODO: may need to add session_ctx
  184. DEBUG_TRACE_NICK_CHANGE
  185. // DUMP_SERVER_PARAMS
  186. // irc_cmd_names(session , CHARSTAR(channel_name)) ;
  187. }
  188. void IrcClient::OnNumeric(irc_session_t* session , unsigned int event , const char* origin ,
  189. const char** params , unsigned int count )
  190. {
  191. DEBUG_TRACE_SERVER_MSG
  192. if (event == LIBIRC_RFC_RPL_NAMREPLY)
  193. {
  194. String host = origin ;
  195. String channel = params[2] ;
  196. StringArray nicks = StringArray::fromTokens(params[3] , false) ;
  197. HandleNicks(host , channel , nicks) ;
  198. }
  199. }
  200. bool IrcClient::IsValidServerInfo(IrcServerInfo* a_server_info)
  201. {
  202. DEBUG_TRACE_SERVER_INFO
  203. return a_server_info->session != 0 &&
  204. a_server_info->host.isNotEmpty() &&
  205. a_server_info->port > 0 &&
  206. a_server_info->nick.isNotEmpty() ;
  207. }
  208. bool IrcClient::IsSufficientVersion()
  209. {
  210. unsigned int major_version , minor_version ;
  211. irc_get_version(&major_version , &minor_version) ;
  212. return major_version >= IRC::MIN_MAJOR_VERSION &&
  213. minor_version >= IRC::MIN_MINOR_VERSION ;
  214. }
  215. void IrcClient::HandleNicks(String host , String channel , StringArray nicks)
  216. {
  217. if (host == bitlbeehost && channel == IRC::ROOT_CHANNEL) return ;
  218. nicks.removeEmptyStrings() ; nicks.removeString(IRC::ROOT_NICK) ;
  219. DEBUG_TRACE_NICKS
  220. #ifdef PREFIX_CHAT_NICKS
  221. AvCaster::UpdateChatNicks(host , channel , nicks) ;
  222. #else // PREFIX_CHAT_NICKS
  223. AvCaster::UpdateChatNicks(host , nicks) ;
  224. #endif // PREFIX_CHAT_NICKS
  225. }
  226. String IrcClient::ProcessTextMeta(const char* message)
  227. {
  228. // TODO: for now we just strip these - see docs for irc_color_convert_to_mirc()
  229. char* msg_cstr = irc_color_convert_from_mirc(message) ;
  230. String converted_msg = CharPointer_UTF8(msg_cstr) ; delete msg_cstr ;
  231. String filtered_msg = converted_msg.replace("[B]" , "").replace("[/B]" , "") // bold
  232. .replace("[I]" , "").replace("[/I]" , "") // italic
  233. .replace("[U]" , "").replace("[/U]" , "") // underline
  234. .replace("[/COLOR]" , "") ;
  235. while (filtered_msg.contains("[COLOR="))
  236. filtered_msg.replace(filtered_msg.fromFirstOccurrenceOf("[COLOR=" , true , false)
  237. .upToFirstOccurrenceOf("]" , true , false) , "") ;
  238. return filtered_msg ;
  239. }
  240. StringArray IrcClient::ProcessTimestamp(String message)
  241. {
  242. String timestamp = message.fromFirstOccurrenceOf("[" , true , false)
  243. .upToFirstOccurrenceOf("]" , true , false) ;
  244. String processed_msg = message.replace(timestamp , "") ;
  245. bool is_timestamp = timestamp.replaceCharacters("[:]" , "000")
  246. .containsOnly(APP::DIGITS) ;
  247. return (is_timestamp) ? StringArray::fromLines(timestamp + " \n" + processed_msg) :
  248. StringArray::fromLines("\n" + message) ;
  249. }
  250. void IrcClient::AddServerChat(String message)
  251. {
  252. AvCaster::AddChatLine(String::empty , GUI::SERVER_NICK , message) ;
  253. }
  254. void IrcClient::AddClientChat(String message)
  255. {
  256. AvCaster::AddChatLine(String::empty , GUI::CLIENT_NICK , message) ;
  257. }
  258. void IrcClient::AddUserChat(String prefix , String nick , String message)
  259. {
  260. AvCaster::AddChatLine(prefix , nick , message) ;
  261. }
  262. /* IrcClient instance methods */
  263. bool IrcClient::login(IrcServerInfo* a_server_info)
  264. {
  265. // NOTE: prefix 'server' with # for SSL
  266. irc_session_t* session = a_server_info->session ;
  267. const char* host = CHARSTAR(a_server_info->host ) ;
  268. unsigned short port = a_server_info->port ;
  269. const char* nick = CHARSTAR(a_server_info->nick ) ;
  270. // display login message
  271. AddClientChat(IRC::LOGGING_IN_MSG + a_server_info->host) ;
  272. bool is_err = irc_connect(session , host , port , 0 , nick , 0 , 0) ;
  273. DEBUG_TRACE_LOGIN
  274. return !is_err ;
  275. }
  276. void IrcClient::run()
  277. {
  278. #ifdef RUN_NETWORK_AS_PROC
  279. DEBUG_TRACE_THREAD_RUN_IN
  280. bool is_err = irc_run(this->session) ;
  281. DEBUG_TRACE_THREAD_RUN_OUT
  282. return ;
  283. #endif // RUN_NETWORK_AS_PROC
  284. if (threadShouldExit()) return ;
  285. for (IrcServerInfo* server = this->servers.begin() ; server != this->servers.end() ; ++server)
  286. if (!irc_is_connected(server->session)) { login(server) ; continue ; }
  287. struct timeval timeout ;
  288. timeout.tv_usec = 250000 ;
  289. timeout.tv_sec = 0 ;
  290. int maxfd = 0 ;
  291. fd_set in_set , out_set ;
  292. FD_ZERO(&in_set) ; FD_ZERO(&out_set) ;
  293. for (IrcServerInfo* server = this->servers.begin() ; server != this->servers.end() ; ++server)
  294. irc_add_select_descriptors(server->session , &in_set , &out_set , &maxfd) ;
  295. if (select(maxfd + 1 , &in_set , &out_set , 0 , &timeout) < 0) return ;
  296. for (IrcServerInfo* server = this->servers.begin() ; server != this->servers.end() ; ++server)
  297. if (irc_process_select_descriptors(server->session , &in_set , &out_set)) continue ;
  298. }
  299. void IrcClient::sendChat(String chat_msg)
  300. {
  301. IrcServerInfo* server = this->servers.begin() ;
  302. while (server != this->servers.end())
  303. irc_cmd_msg((server++)->session , CHARSTAR(xmpp_channel) , CHARSTAR(String(chat_msg))) ;
  304. AddUserChat(String::empty , my_nick , chat_msg) ;
  305. }
  306. #endif // DISABLE_CHAT