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 27KB

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