The easy to use and full featured Irc Bot everyone is talking about!
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IRC.cs 25KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Linq.Expressions;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using System.Net;
  9. using System.Net.NetworkInformation;
  10. using System.Net.Sockets;
  11. using Combot.IRCServices.Messaging;
  12. using Combot.IRCServices.Commanding;
  13. using Combot.IRCServices.TCP;
  14. namespace Combot.IRCServices
  15. {
  16. public partial class IRC
  17. {
  18. public List<Channel> Channels = new List<Channel>();
  19. public Messages Message;
  20. public Commands Command;
  21. public event Action ConnectEvent;
  22. public event Action DisconnectEvent;
  23. public event Action<TCPError> TCPErrorEvent;
  24. public string Nickname;
  25. public Dictionary<string, PrivilegeMode> PrivilegeMapping = new Dictionary<string, PrivilegeMode>() { { "+", PrivilegeMode.v }, { "%", PrivilegeMode.h }, { "@", PrivilegeMode.o }, { "&", PrivilegeMode.a }, { "~", PrivilegeMode.q }, { "!", PrivilegeMode.q } };
  26. private int ReadTimeout;
  27. private int AllowedFailedReads;
  28. private int MessageSendDelay;
  29. private Thread TCPReader;
  30. private Thread KeepAlive;
  31. private DateTime LastMessageSend;
  32. private event Action<string> TCPMessageEvent;
  33. private readonly TCPInterface _TCP;
  34. private readonly ReaderWriterLockSlim ChannelRWLock;
  35. public IRC(int maxMessageLength, int messageSendDelay = 0, int readTimeout = 5000, int allowedFailedReads = 0)
  36. {
  37. Nickname = string.Empty;
  38. ChannelRWLock = new ReaderWriterLockSlim();
  39. ReadTimeout = readTimeout;
  40. AllowedFailedReads = allowedFailedReads;
  41. LastMessageSend = DateTime.Now;
  42. MessageSendDelay = messageSendDelay;
  43. _TCP = new TCPInterface();
  44. Message = new Messages(this);
  45. Command = new Commands(this, maxMessageLength, messageSendDelay);
  46. TCPMessageEvent += Message.ParseTCPMessage;
  47. _TCP.TCPConnectionEvent += HandleTCPConnection;
  48. _TCP.TCPErrorEvent += HandleTCPError;
  49. Message.ErrorMessageEvent += HandleErrorMessage;
  50. Message.PingEvent += HandlePing;
  51. Message.ServerReplyEvent += HandleReply;
  52. Message.ChannelModeChangeEvent += HandleChannelModeChange;
  53. Message.UserModeChangeEvent += HandleUserModeChange;
  54. Message.NickChangeEvent += HandleNickChange;
  55. Message.JoinChannelEvent += HandleJoin;
  56. Message.PartChannelEvent += HandlePart;
  57. Message.KickEvent += HandleKick;
  58. Message.QuitEvent += HandleQuit;
  59. }
  60. /// <summary>
  61. /// Starts a TCP connection to the specified host.
  62. /// </summary>
  63. /// <param name="IP">The IP address of the host.</param>
  64. /// <param name="port">The port for the tcp connection.</param>
  65. /// <param name="readTimeout">The timeout for read operations in milliseconds.</param>
  66. /// <param name="allowedFailedCount">Number of times a read can fail before disconnecting.</param>
  67. /// <returns></returns>
  68. public bool Connect(IPAddress IP, int port)
  69. {
  70. bool result = false;
  71. if (!_TCP.Connected)
  72. {
  73. result = _TCP.Connect(IP, port, ReadTimeout, AllowedFailedReads);
  74. if (result)
  75. {
  76. TCPReader = new Thread(ReadTCPMessages);
  77. TCPReader.IsBackground = true;
  78. TCPReader.Start();
  79. KeepAlive = new Thread(() => CheckConnection(IP, port));
  80. KeepAlive.IsBackground = true;
  81. KeepAlive.Start();
  82. if (ConnectEvent != null)
  83. {
  84. ConnectEvent();
  85. }
  86. }
  87. }
  88. return result;
  89. }
  90. /// <summary>
  91. /// Disconencts from the active TCP connection.
  92. /// </summary>
  93. /// <returns></returns>
  94. public void Disconnect()
  95. {
  96. if (_TCP.Connected)
  97. {
  98. _TCP.Disconnect();
  99. }
  100. if (KeepAlive.IsAlive)
  101. {
  102. KeepAlive.Join();
  103. }
  104. if (TCPReader.IsAlive)
  105. {
  106. TCPReader.Join();
  107. }
  108. ChannelRWLock.EnterWriteLock();
  109. Channels = new List<Channel>();
  110. ChannelRWLock.ExitWriteLock();
  111. if (DisconnectEvent != null)
  112. {
  113. DisconnectEvent();
  114. }
  115. }
  116. /// <summary>
  117. /// Logs in the specified nick using their Username and Realname.
  118. /// </summary>
  119. /// <param name="serverName">The server's name.</param>
  120. /// <param name="nick">The nick information for the login.</param>
  121. public void Login(string serverName, Nick nick)
  122. {
  123. Nickname = nick.Nickname;
  124. Command.SendNick(nick.Nickname);
  125. Command.SendUser(nick.Username, nick.Host, serverName, nick.Realname);
  126. }
  127. /// <summary>
  128. /// Parses a given mode and parameter string to generate a channel mode list.
  129. /// </summary>
  130. /// <param name="modeString">The mode string that contains the mode info.</param>
  131. /// <param name="parameterString">The parameter string that is associated with the mode info.</param>
  132. /// <returns></returns>
  133. public List<ChannelModeInfo> ParseChannelModeString(string modeString, string parameterString)
  134. {
  135. string[] modeArgs = parameterString.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  136. char[] modeInfo = modeString.ToCharArray();
  137. bool set = true;
  138. int argIndex = 0;
  139. List<ChannelModeInfo> modeInfos = new List<ChannelModeInfo>();
  140. foreach (char mode in modeInfo)
  141. {
  142. if (mode.Equals('-'))
  143. {
  144. set = false;
  145. }
  146. else if (mode.Equals('+'))
  147. {
  148. set = true;
  149. }
  150. else
  151. {
  152. ChannelModeInfo newMode = new ChannelModeInfo();
  153. newMode.Set = set;
  154. ChannelMode foundMode;
  155. bool validMode = Enum.TryParse(mode.ToString(), false, out foundMode);
  156. if (validMode)
  157. {
  158. newMode.Mode = foundMode;
  159. if (modeArgs.GetUpperBound(0) >= argIndex)
  160. {
  161. switch (newMode.Mode)
  162. {
  163. case ChannelMode.k:
  164. case ChannelMode.l:
  165. case ChannelMode.b:
  166. case ChannelMode.e:
  167. case ChannelMode.I:
  168. case ChannelMode.v:
  169. case ChannelMode.h:
  170. case ChannelMode.o:
  171. case ChannelMode.a:
  172. case ChannelMode.q:
  173. newMode.Parameter = modeArgs[argIndex];
  174. argIndex++;
  175. break;
  176. default:
  177. newMode.Parameter = string.Empty;
  178. break;
  179. }
  180. }
  181. else
  182. {
  183. newMode.Parameter = string.Empty;
  184. }
  185. modeInfos.Add(newMode);
  186. }
  187. }
  188. }
  189. return modeInfos;
  190. }
  191. public List<UserModeInfo> ParseUserModeString(string modeString)
  192. {
  193. List<UserModeInfo> userModes = new List<UserModeInfo>();
  194. bool set = true;
  195. char[] modeArr = modeString.ToCharArray();
  196. for (int i = 1; i <= modeArr.GetUpperBound(0); i++)
  197. {
  198. UserModeInfo newMode = new UserModeInfo();
  199. if (modeArr[i].Equals('-'))
  200. {
  201. set = false;
  202. }
  203. else if (modeArr[i].Equals('+'))
  204. {
  205. set = true;
  206. }
  207. else if (modeArr[i].Equals('*'))
  208. {
  209. newMode.Mode = UserMode.o;
  210. newMode.Set = set;
  211. userModes.Add(newMode);
  212. }
  213. else
  214. {
  215. UserMode foundMode;
  216. bool validMode = Enum.TryParse(modeArr[i].ToString(), false, out foundMode);
  217. if (validMode)
  218. {
  219. newMode.Mode = foundMode;
  220. newMode.Set = set;
  221. userModes.Add(newMode);
  222. }
  223. }
  224. }
  225. return userModes;
  226. }
  227. private void ReadTCPMessages()
  228. {
  229. while (_TCP.Connected)
  230. {
  231. string response = ReadTCPMessage();
  232. if (TCPMessageEvent != null && response != null && response != string.Empty)
  233. {
  234. TCPMessageEvent(response);
  235. }
  236. }
  237. }
  238. private string ReadTCPMessage()
  239. {
  240. if (_TCP.Connected)
  241. {
  242. return _TCP.Read();
  243. }
  244. return null;
  245. }
  246. internal void SendTCPMessage(string message)
  247. {
  248. if (_TCP.Connected)
  249. {
  250. TimeSpan sinceLastMessage = (DateTime.Now - LastMessageSend);
  251. if (sinceLastMessage.TotalMilliseconds < MessageSendDelay)
  252. {
  253. Thread.Sleep((int)(MessageSendDelay - sinceLastMessage.TotalMilliseconds));
  254. }
  255. LastMessageSend = DateTime.Now;
  256. string replaceWith = string.Empty;
  257. string parsedMessage = message.Replace("\r\n", replaceWith).Replace("\n", replaceWith).Replace("\r", replaceWith);
  258. _TCP.Write(parsedMessage);
  259. }
  260. }
  261. private void CheckConnection(IPAddress IP, int port)
  262. {
  263. int diconnectCount = 0;
  264. bool disconnectActivated = false;
  265. while (_TCP.Connected)
  266. {
  267. Thread.Sleep(5000);
  268. bool stillConnected = NetworkInterface.GetIsNetworkAvailable();
  269. if (stillConnected)
  270. {
  271. Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
  272. try
  273. {
  274. s.Connect(IP, port);
  275. }
  276. catch
  277. {
  278. stillConnected = false;
  279. }
  280. }
  281. if (!stillConnected)
  282. {
  283. diconnectCount++;
  284. }
  285. else
  286. {
  287. diconnectCount = 0;
  288. }
  289. if (diconnectCount >= 5 && !disconnectActivated)
  290. {
  291. disconnectActivated = true;
  292. Task.Run(() =>
  293. {
  294. Disconnect();
  295. });
  296. }
  297. }
  298. }
  299. /// <summary>
  300. /// Responds with PONG on a PING with the specified arguments.
  301. /// </summary>
  302. /// <param name="sender"></param>
  303. /// <param name="e"></param>
  304. private void HandlePing(object sender, PingInfo e)
  305. {
  306. Command.SendPong(e.Message);
  307. }
  308. private void HandleTCPConnection(int e)
  309. {
  310. if (DisconnectEvent != null)
  311. {
  312. DisconnectEvent();
  313. }
  314. }
  315. private void HandleTCPError(TCPError e)
  316. {
  317. if (TCPErrorEvent != null)
  318. {
  319. TCPErrorEvent(e);
  320. }
  321. }
  322. private void HandleErrorMessage(object sender, ErrorMessage e)
  323. {
  324. Disconnect();
  325. }
  326. private void HandleReply(object sender, IReply e)
  327. {
  328. if (e.GetType() == typeof(ServerReplyMessage))
  329. {
  330. ServerReplyMessage msg = (ServerReplyMessage)e;
  331. switch (msg.ReplyCode)
  332. {
  333. // If we get a WHO response, we parse and add the nicks to the specified channel if they are not there already.
  334. case IRCReplyCode.RPL_WHOREPLY:
  335. ChannelRWLock.EnterWriteLock();
  336. string[] msgSplit = msg.Message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  337. if (msgSplit.GetUpperBound(0) > 0)
  338. {
  339. string target = msgSplit[0];
  340. if (target.StartsWith("&") || target.StartsWith("#"))
  341. {
  342. if (msgSplit.GetUpperBound(0) >= 7)
  343. {
  344. string nickname = msgSplit[4];
  345. string realname = msgSplit[7];
  346. string username = msgSplit[1];
  347. string host = msgSplit[2];
  348. string modeString = msgSplit[5];
  349. Channel channel = Channels.Find(chan => chan.Name == target);
  350. if (channel != null)
  351. {
  352. Nick nick = channel.GetNick(nickname);
  353. bool nickFound = true;
  354. if (nick == null)
  355. {
  356. nickFound = false;
  357. nick = new Nick();
  358. }
  359. nick.Nickname = nickname;
  360. nick.Host = host;
  361. nick.Realname = realname;
  362. nick.Username = username;
  363. nick.Modes = new List<UserMode>();
  364. nick.Privileges = new List<PrivilegeMode>();
  365. char[] modeArr = modeString.ToCharArray();
  366. for (int i = 1; i <= modeArr.GetUpperBound(0); i++)
  367. {
  368. if (PrivilegeMapping.ContainsKey(modeArr[i].ToString()))
  369. {
  370. nick.Privileges.Add(PrivilegeMapping[modeArr[i].ToString()]);
  371. }
  372. else if (modeArr[i].ToString() == "*")
  373. {
  374. nick.Modes.Add(UserMode.o);
  375. }
  376. else
  377. {
  378. UserMode foundMode;
  379. bool valid = Enum.TryParse(modeArr[i].ToString(), false, out foundMode);
  380. if (valid)
  381. {
  382. nick.Modes.Add(foundMode);
  383. }
  384. }
  385. }
  386. if (!nickFound)
  387. {
  388. channel.AddNick(nick);
  389. }
  390. }
  391. }
  392. }
  393. }
  394. ChannelRWLock.ExitWriteLock();
  395. break;
  396. // On a topic reply, update the channel's topic
  397. case IRCReplyCode.RPL_TOPIC:
  398. ChannelRWLock.EnterWriteLock();
  399. string[] topicSplit = msg.Message.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  400. if (topicSplit.GetUpperBound(0) > 0)
  401. {
  402. string topicChan = topicSplit[0];
  403. Channel topicChannel = Channels.Find(chan => chan.Name == topicChan);
  404. if (topicChannel != null)
  405. {
  406. topicChannel.Topic = topicSplit[1].Remove(0, 1);
  407. }
  408. }
  409. ChannelRWLock.ExitWriteLock();
  410. break;
  411. default:
  412. break;
  413. }
  414. }
  415. else
  416. {
  417. ServerErrorMessage msg = (ServerErrorMessage)e;
  418. }
  419. }
  420. /// <summary>
  421. /// Update a channel's mode.
  422. /// </summary>
  423. /// <param name="sender"></param>
  424. /// <param name="e"></param>
  425. private void HandleChannelModeChange(object sender, ChannelModeChangeInfo e)
  426. {
  427. ChannelRWLock.EnterWriteLock();
  428. Channel channel = Channels.Find(chan => chan.Name == e.Channel);
  429. if (channel != null)
  430. {
  431. foreach (ChannelModeInfo mode in e.Modes)
  432. {
  433. switch (mode.Mode)
  434. {
  435. case ChannelMode.v:
  436. case ChannelMode.h:
  437. case ChannelMode.o:
  438. case ChannelMode.a:
  439. case ChannelMode.q:
  440. Nick changedNick = channel.GetNick(mode.Parameter);
  441. if (changedNick != null)
  442. {
  443. if (mode.Set)
  444. {
  445. changedNick.AddPrivilege((PrivilegeMode) Enum.Parse(typeof (PrivilegeMode), mode.Mode.ToString()));
  446. }
  447. else
  448. {
  449. changedNick.RemovePrivilege((PrivilegeMode) Enum.Parse(typeof (PrivilegeMode), mode.Mode.ToString()));
  450. }
  451. }
  452. break;
  453. case ChannelMode.b:
  454. if (mode.Set)
  455. {
  456. channel.AddBan(mode.Parameter);
  457. }
  458. else
  459. {
  460. channel.RemoveBan(mode.Parameter);
  461. }
  462. break;
  463. case ChannelMode.k:
  464. if (mode.Set)
  465. {
  466. channel.AddMode(mode.Mode);
  467. channel.Key = mode.Parameter;
  468. }
  469. else
  470. {
  471. channel.RemoveMode(mode.Mode);
  472. channel.Key = string.Empty;
  473. }
  474. break;
  475. default:
  476. if (mode.Set)
  477. {
  478. channel.AddMode(mode.Mode);
  479. }
  480. else
  481. {
  482. channel.RemoveMode(mode.Mode);
  483. }
  484. break;
  485. }
  486. }
  487. Command.SendWho(channel.Name);
  488. }
  489. ChannelRWLock.ExitWriteLock();
  490. }
  491. /// <summary>
  492. /// Update a nick's mode.
  493. /// </summary>
  494. /// <param name="sender"></param>
  495. /// <param name="e"></param>
  496. private void HandleUserModeChange(object sender, UserModeChangeInfo e)
  497. {
  498. ChannelRWLock.EnterWriteLock();
  499. for (int i = 0; i < Channels.Count; i++)
  500. {
  501. Nick changedNick = Channels[i].GetNick(e.Nick.Nickname);
  502. if (changedNick != null)
  503. {
  504. foreach (UserModeInfo mode in e.Modes)
  505. {
  506. if (mode.Set)
  507. {
  508. changedNick.AddMode(mode.Mode);
  509. }
  510. else
  511. {
  512. changedNick.RemoveMode(mode.Mode);
  513. }
  514. }
  515. }
  516. }
  517. ChannelRWLock.ExitWriteLock();
  518. }
  519. /// <summary>
  520. /// Update a nick to use their new nickname.
  521. /// </summary>
  522. /// <param name="sender"></param>
  523. /// <param name="e"></param>
  524. private void HandleNickChange(object sender, NickChangeInfo e)
  525. {
  526. ChannelRWLock.EnterWriteLock();
  527. for (int i = 0; i < Channels.Count; i++)
  528. {
  529. Nick newNick = Channels[i].GetNick(e.OldNick.Nickname);
  530. if (newNick != null)
  531. {
  532. if (e.OldNick.Nickname == Nickname)
  533. {
  534. Nickname = e.NewNick.Nickname;
  535. }
  536. newNick.Nickname = e.NewNick.Nickname;
  537. }
  538. }
  539. ChannelRWLock.ExitWriteLock();
  540. }
  541. /// <summary>
  542. /// Add a nick to a channel on join.
  543. /// </summary>
  544. /// <param name="sender"></param>
  545. /// <param name="e"></param>
  546. private void HandleJoin(object sender, JoinChannelInfo e)
  547. {
  548. ChannelRWLock.EnterWriteLock();
  549. Channel channel = Channels.Find(chan => chan.Name == e.Channel);
  550. if (channel != null)
  551. {
  552. channel.AddNick(e.Nick);
  553. }
  554. else
  555. {
  556. Channel newChannel = new Channel();
  557. newChannel.Name = e.Channel;
  558. newChannel.Nicks.Add(e.Nick);
  559. Channels.Add(newChannel);
  560. Command.SendWho(newChannel.Name);
  561. }
  562. ChannelRWLock.ExitWriteLock();
  563. }
  564. /// <summary>
  565. /// Remove a nick from a channel on part.
  566. /// </summary>
  567. /// <param name="sender"></param>
  568. /// <param name="e"></param>
  569. private void HandlePart(object sender, PartChannelInfo e)
  570. {
  571. ChannelRWLock.EnterWriteLock();
  572. Channel channel = Channels.Find(chan => chan.Name == e.Channel);
  573. if (channel != null)
  574. {
  575. if (e.Nick.Nickname == Nickname)
  576. {
  577. Channels.Remove(channel);
  578. }
  579. else
  580. {
  581. channel.RemoveNick(e.Nick.Nickname);
  582. }
  583. }
  584. ChannelRWLock.ExitWriteLock();
  585. }
  586. /// <summary>
  587. /// Remove a nick from a channel on kick.
  588. /// </summary>
  589. /// <param name="sender"></param>
  590. /// <param name="e"></param>
  591. private void HandleKick(object sender, KickInfo e)
  592. {
  593. ChannelRWLock.EnterWriteLock();
  594. Channel channel = Channels.Find(chan => chan.Name == e.Channel);
  595. if (channel != null)
  596. {
  597. if (e.KickedNick.Nickname == Nickname)
  598. {
  599. Channels.Remove(channel);
  600. }
  601. else
  602. {
  603. channel.RemoveNick(e.KickedNick.Nickname);
  604. }
  605. }
  606. ChannelRWLock.ExitWriteLock();
  607. }
  608. /// <summary>
  609. /// Remove a nick from all channels on quit.
  610. /// </summary>
  611. /// <param name="sender"></param>
  612. /// <param name="e"></param>
  613. private void HandleQuit(object sender, QuitInfo e)
  614. {
  615. ChannelRWLock.EnterWriteLock();
  616. for (int i = 0; i < Channels.Count; i++)
  617. {
  618. Channels[i].RemoveNick(e.Nick.Nickname);
  619. }
  620. ChannelRWLock.ExitWriteLock();
  621. }
  622. }
  623. }