diff --git a/FormExample/DebugForm.Designer.cs b/FormExample/DebugForm.Designer.cs index b746fbd..26be90f 100644 --- a/FormExample/DebugForm.Designer.cs +++ b/FormExample/DebugForm.Designer.cs @@ -28,18 +28,20 @@ protected override void Dispose(bool disposing) /// private void InitializeComponent() { - this.DebugOutput = new System.Windows.Forms.RichTextBox(); this.ClearButton = new System.Windows.Forms.Button(); + this.debugTabs = new System.Windows.Forms.TabControl(); + this.ircDebug = new System.Windows.Forms.TabPage(); + this.dccDebug = new System.Windows.Forms.TabPage(); + this.ircDebugRichTextbox = new System.Windows.Forms.RichTextBox(); + this.dccDebugRichTextBox = new System.Windows.Forms.RichTextBox(); + this.ircRawOutput = new System.Windows.Forms.TabPage(); + this.rawIrcOutput = new System.Windows.Forms.RichTextBox(); + this.debugTabs.SuspendLayout(); + this.ircDebug.SuspendLayout(); + this.dccDebug.SuspendLayout(); + this.ircRawOutput.SuspendLayout(); this.SuspendLayout(); // - // DebugOutput - // - this.DebugOutput.Location = new System.Drawing.Point(12, 12); - this.DebugOutput.Name = "DebugOutput"; - this.DebugOutput.Size = new System.Drawing.Size(449, 376); - this.DebugOutput.TabIndex = 0; - this.DebugOutput.Text = ""; - // // ClearButton // this.ClearButton.Location = new System.Drawing.Point(12, 394); @@ -50,23 +52,100 @@ private void InitializeComponent() this.ClearButton.UseVisualStyleBackColor = true; this.ClearButton.Click += new System.EventHandler(this.ClearButton_Click); // + // debugTabs + // + this.debugTabs.Controls.Add(this.ircDebug); + this.debugTabs.Controls.Add(this.dccDebug); + this.debugTabs.Controls.Add(this.ircRawOutput); + this.debugTabs.Location = new System.Drawing.Point(12, 0); + this.debugTabs.Name = "debugTabs"; + this.debugTabs.SelectedIndex = 0; + this.debugTabs.Size = new System.Drawing.Size(460, 388); + this.debugTabs.TabIndex = 2; + // + // ircDebug + // + this.ircDebug.Controls.Add(this.ircDebugRichTextbox); + this.ircDebug.Location = new System.Drawing.Point(4, 22); + this.ircDebug.Name = "ircDebug"; + this.ircDebug.Padding = new System.Windows.Forms.Padding(3); + this.ircDebug.Size = new System.Drawing.Size(452, 362); + this.ircDebug.TabIndex = 0; + this.ircDebug.Text = "IRC Debug"; + this.ircDebug.UseVisualStyleBackColor = true; + // + // dccDebug + // + this.dccDebug.Controls.Add(this.dccDebugRichTextBox); + this.dccDebug.Location = new System.Drawing.Point(4, 22); + this.dccDebug.Name = "dccDebug"; + this.dccDebug.Padding = new System.Windows.Forms.Padding(3); + this.dccDebug.Size = new System.Drawing.Size(452, 362); + this.dccDebug.TabIndex = 1; + this.dccDebug.Text = "DCC Debug"; + this.dccDebug.UseVisualStyleBackColor = true; + // + // ircDebugRichTextbox + // + this.ircDebugRichTextbox.Location = new System.Drawing.Point(6, 6); + this.ircDebugRichTextbox.Name = "ircDebugRichTextbox"; + this.ircDebugRichTextbox.Size = new System.Drawing.Size(439, 350); + this.ircDebugRichTextbox.TabIndex = 0; + this.ircDebugRichTextbox.Text = ""; + // + // dccDebugRichTextBox + // + this.dccDebugRichTextBox.Location = new System.Drawing.Point(6, 6); + this.dccDebugRichTextBox.Name = "dccDebugRichTextBox"; + this.dccDebugRichTextBox.Size = new System.Drawing.Size(440, 353); + this.dccDebugRichTextBox.TabIndex = 0; + this.dccDebugRichTextBox.Text = ""; + // + // ircRawOutput + // + this.ircRawOutput.Controls.Add(this.rawIrcOutput); + this.ircRawOutput.Location = new System.Drawing.Point(4, 22); + this.ircRawOutput.Name = "ircRawOutput"; + this.ircRawOutput.Size = new System.Drawing.Size(452, 362); + this.ircRawOutput.TabIndex = 2; + this.ircRawOutput.Text = "Irc Raw Output"; + this.ircRawOutput.UseVisualStyleBackColor = true; + // + // rawIrcOutput + // + this.rawIrcOutput.Location = new System.Drawing.Point(4, 4); + this.rawIrcOutput.Name = "rawIrcOutput"; + this.rawIrcOutput.Size = new System.Drawing.Size(441, 355); + this.rawIrcOutput.TabIndex = 0; + this.rawIrcOutput.Text = ""; + // // DebugForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; this.ClientSize = new System.Drawing.Size(473, 427); + this.Controls.Add(this.debugTabs); this.Controls.Add(this.ClearButton); - this.Controls.Add(this.DebugOutput); this.Name = "DebugForm"; this.Text = "DebugForm"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.DebugForm_FormClosing); + this.Load += new System.EventHandler(this.DebugForm_Load); + this.debugTabs.ResumeLayout(false); + this.ircDebug.ResumeLayout(false); + this.dccDebug.ResumeLayout(false); + this.ircRawOutput.ResumeLayout(false); this.ResumeLayout(false); } #endregion - - private System.Windows.Forms.RichTextBox DebugOutput; private System.Windows.Forms.Button ClearButton; + private System.Windows.Forms.TabControl debugTabs; + private System.Windows.Forms.TabPage ircDebug; + private System.Windows.Forms.RichTextBox ircDebugRichTextbox; + private System.Windows.Forms.TabPage dccDebug; + private System.Windows.Forms.RichTextBox dccDebugRichTextBox; + private System.Windows.Forms.TabPage ircRawOutput; + private System.Windows.Forms.RichTextBox rawIrcOutput; } } \ No newline at end of file diff --git a/FormExample/DebugForm.cs b/FormExample/DebugForm.cs index 09cd896..5263a86 100644 --- a/FormExample/DebugForm.cs +++ b/FormExample/DebugForm.cs @@ -7,50 +7,151 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms; +using SimpleIRCLib; namespace FormExample { public partial class DebugForm : Form { - public DebugForm() + private readonly SimpleIRC _simpleIrc; + + /// + /// Constructor for the debug form. + /// + /// SimpleIRC instance + public DebugForm(SimpleIRC irc) { + _simpleIrc = irc; InitializeComponent(); } - private void ClearButton_Click(object sender, EventArgs e) + /// + /// Clears a specific rich textbox + /// + /// + /// + public void ClearButton_Click(object sender, EventArgs e) { - DebugOutput.Clear(); + RichTextBox selectedRtb = (RichTextBox)debugTabs.SelectedTab.Controls[0]; + selectedRtb.Clear(); } /// - /// Appends debug messages from irc client to debug richtextbox output box. - /// Invoke is needed because this method executes on a different thread! + /// Makes sure that the form doesn't actually close, but hides instead, so debug messages can still be appended! /// - /// defines the message to be appended to the debug richtextbox output box - public void AppendToDebugOutput(string debugMessage) + /// + /// + public void DebugForm_FormClosing(object sender, FormClosingEventArgs e) { - if (this.DebugOutput.InvokeRequired) + if (e.CloseReason == CloseReason.UserClosing) { - this.DebugOutput.Invoke(new MethodInvoker(() => AppendToDebugOutput(debugMessage))); + e.Cancel = true; + Hide(); + } + } + + /// + /// Register event handlers for the IrcClient and DCCClient when the form loads. + /// + /// + /// + public void DebugForm_Load(object sender, EventArgs e) + { + _simpleIrc.IrcClient.OnDebugMessage += OnIrcDebugMessage; + _simpleIrc.IrcClient.OnRawMessageReceived += OnRawMessageReceived; + _simpleIrc.DccClient.OnDccDebugMessage += OnDccDebugMessage; + } + + /// + /// Event for receiving debug messages from the IrcClient + /// + /// source class + /// IrcDebugMessageEventArgs contains debug message and type + public void OnIrcDebugMessage(object source, IrcDebugMessageEventArgs args) + { + OnIrcDebugMessageLocal(args.Type, args.Message); + } + + /// + /// For appending the debug message on the main thread using invoke required. + /// + /// Debug message type, handy for figuring out where the debug message came from + /// message to append to the rich textbox + public void OnIrcDebugMessageLocal(string type, string message) + { + if (this.InvokeRequired) + { + this.Invoke(new MethodInvoker(() => OnIrcDebugMessageLocal(type, message))); } else { - this.DebugOutput.AppendText(debugMessage + "\n"); + if (debugTabs != null) + { + RichTextBox selectedRtb = (RichTextBox)debugTabs.TabPages[0].Controls[0]; + selectedRtb.AppendText(type + " | " + message + Environment.NewLine); + } } } /// - /// Makes sure that the form doesn't actually close, but hides instead, so debug messages can still be appended! + /// Event for receiving raw messages from the irc server. /// - /// - /// - private void DebugForm_FormClosing(object sender, FormClosingEventArgs e) + /// source class + /// IrcRawReceivedEventArgs contains the message received + public void OnRawMessageReceived(object source, IrcRawReceivedEventArgs args) { - if (e.CloseReason == CloseReason.UserClosing) + OnRawMessageReceivedLocal(args.Message); + } + + /// + /// For appending the rawmessage on the main thread using invoke required. + /// + /// message to append to the rich textbox + public void OnRawMessageReceivedLocal(string message) + { + if (this.InvokeRequired) { - e.Cancel = true; - Hide(); + this.Invoke(new MethodInvoker(() => OnRawMessageReceivedLocal(message))); + } + else + { + if (debugTabs != null) + { + RichTextBox selectedRtb = (RichTextBox) debugTabs.TabPages[2].Controls[0]; + selectedRtb.AppendText(message + Environment.NewLine); + } + } + } + + /// + /// Event for receiving debug messages from the DccClient + /// + /// source class + /// DCCDebugMessageArgs contains the debug message and type + public void OnDccDebugMessage(object source, DCCDebugMessageArgs args) + { + OnDccDebugMessageLocal(args.Type, args.Message); + } + + /// + /// For appending the debug message on the main thread using invoke required. + /// + /// Debug message type, handy for figuring out where the debug message came from + /// message to append to the rich textbox + public void OnDccDebugMessageLocal(string type, string message) + { + if (this.InvokeRequired) + { + this.Invoke(new MethodInvoker(() => OnDccDebugMessageLocal(type, message))); + } + else + { + if (debugTabs != null) + { + RichTextBox selectedRtb = (RichTextBox) debugTabs.TabPages[1].Controls[0]; + selectedRtb.AppendText(type + " | " + message + Environment.NewLine); + } } } } diff --git a/FormExample/Form1.cs b/FormExample/Form1.cs deleted file mode 100644 index a9e7ed2..0000000 --- a/FormExample/Form1.cs +++ /dev/null @@ -1,305 +0,0 @@ -using System; -using System.Windows.Forms; -using System.IO; -using System.Diagnostics; -//the library -using SimpleIRCLib; -using System.Collections.Generic; - -namespace FormExample -{ - public partial class Form1 : Form - { - //initiate irc client - public SimpleIRC irc = new SimpleIRC(); - - //initiate debugform - public DebugForm debugForm = new DebugForm(); - - - - public string defaultDownloadDirectory = ""; - - public Form1() - { - InitializeComponent(); - defaultDownloadDirectory = Directory.GetCurrentDirectory(); - } - - /// - /// Gets the values from the input fields and starts the client - /// - /// - /// - private void ConnectButton_Click(object sender, EventArgs e) - { - if (ServerInput.Text != "" && PortInput.Text != "" && UsernameInput.Text != "" && ChannelsInput.Text != "" && irc.isClientRunning() == false) - { - int port = -1; - if ((port = int.Parse(PortInput.Text)) != -1) - { - //parameters as follows: ip or address to irc server, username, password(not functional), channels, and method to execute when message is received (see line 103) - irc.setupIrc(ServerInput.Text, port, UsernameInput.Text, "", ChannelsInput.Text, AppendChatMessageToChatOutput); - - //sets the method for appending text to a debug form, see Class "DebugForm.cs" line: 27 - irc.setDebugCallback(debugForm.AppendToDebugOutput); - - //sets method for updating download information while downloading, see line: 119 - irc.setDownloadStatusChangeCallback(OnDownloadEvent); - - //sets the download dir to where the application runs - irc.setCustomDownloadDir(defaultDownloadDirectory); - - //set callback when the list with users arrives - irc.setUserListReceivedCallback(UserListReceived); - - //Start client - irc.startClient(); - } - } else - { - MessageBox.Show("You need to fill in all the information fields!"); - } - } - - /// - /// Disconnects the irc client, if connected - /// - /// - /// - private void DisconnectButton_Click(object sender, EventArgs e) - { - if (irc.isClientRunning()) - { - irc.stopClient(); - } - } - - /// - /// Sends a message to the irc server on button click, if connected - /// - /// - /// - private void SendMessageButton_Click(object sender, EventArgs e) - { - if (MessageInput.Text != "" && irc.isClientRunning()) - { - irc.sendMessage(MessageInput.Text); - } - } - - /// - /// Sends a message to the irc server on enter press, if connected - /// - /// - /// - private void MessageInput_KeyDown(object sender, KeyEventArgs e) - { - if(e.KeyCode == Keys.Enter) - { - if (MessageInput.Text != "" && irc.isClientRunning()) - { - irc.sendMessage(MessageInput.Text); - } - } - - } - - /// - /// Appends chat message received from irc server to richtextbox output box. - /// Invoke is needed because this method executes on a different thread! - /// - /// The user where the messsage came from - /// The actual message - private void AppendChatMessageToChatOutput(string user, string message) - { - if (this.ChatOutput.InvokeRequired) - { - this.ChatOutput.Invoke(new MethodInvoker(() => AppendChatMessageToChatOutput(user, message))); - } else - { - this.ChatOutput.AppendText(user + " : " + message + "\n"); - } - } - - /// - /// Adds all users from channel to the list - /// - /// - private void UserListReceived(string[] list) - { - if (this.ChatOutput.InvokeRequired) - { - this.ChatOutput.Invoke(new MethodInvoker(() => UserListReceived(list))); - } - else - { - this.UserList.Items.Clear(); - foreach (string user in list) - { - this.UserList.Items.Add(user); - } - } - } - - /// - /// Gets the information about the download - /// - private void OnDownloadEvent() - { - string fileName = irc.getDownloadProgress("filename").ToString(); - int downloadProgress = (int)irc.getDownloadProgress("progress"); - string downloadSpeed = irc.getDownloadProgress("kbps").ToString(); - string status = irc.getDownloadProgress("status").ToString(); - - string fullDownloadInformation = fileName + " | " + status + " | " + downloadSpeed + " kb/s | " + defaultDownloadDirectory; - - //see line 144 - updateDownloadList(fullDownloadInformation, fileName); - //see line 183 - updateProgressBar(downloadProgress); - - if (status.Contains("COMPLETED")) - { - updateProgressBar(100); - } - - } - - /// - /// Updates the DownloadList object on the main form while the download is going, invoke is necesary because - /// method is being called from a different Thread! - /// - /// string that needs to be added/updated - /// the filename which is used for searching the item in the download list for updating - private void updateDownloadList(string toUpdate, string fileName) - { - if (this.DownloadsList.InvokeRequired) - { - this.DownloadsList.Invoke(new MethodInvoker(() => updateDownloadList(toUpdate, fileName))); - } - else - { - int indexOfDownloadItem = 0; - for (int i = indexOfDownloadItem; i < DownloadsList.Items.Count; ++i) - { - string lbString = DownloadsList.Items[i].ToString(); - if (lbString.Contains(fileName)) - { - indexOfDownloadItem = i; - break; - } - } - - try - { - DownloadsList.Items.RemoveAt(indexOfDownloadItem); - DownloadsList.Items.Insert(indexOfDownloadItem, toUpdate); - DownloadsList.SelectedIndex = indexOfDownloadItem; - } - catch - { - DownloadsList.Items.Add(toUpdate); - } - } - } - - /// - /// Updates the progress bar - /// - /// defines the current progress - private void updateProgressBar(int progress) - { - if (this.DownloadProgressBar.InvokeRequired) - { - this.DownloadProgressBar.Invoke(new MethodInvoker(() => updateProgressBar(progress))); - } else - { - this.DownloadProgressBar.Value = progress; - } - } - - /// - /// Opens the debug form - /// - /// - /// - private void ShowDebugButton_Click(object sender, EventArgs e) - { - try - { - debugForm.Show(); - } catch - { - - } - } - - /// - /// Opens the folder where the selected file is being downloaded to - /// - /// - /// - private void DownloadsList_MouseDoubleClick(object sender, MouseEventArgs e) - { - int currentlySelected = DownloadsList.SelectedIndex; - try - { - string currentItem = DownloadsList.Items[currentlySelected].ToString(); - - if (currentItem.Contains("|")) - { - string fileLocation = currentItem.Split('|')[currentItem.Split('|').Length - 1].Trim(); - Process.Start(fileLocation); - } - } - catch - { - - } - } - - /// - /// Set download directory to a custom directory - /// - /// - /// - private void SetDownloadFolderButton_Click(object sender, EventArgs e) - { - DialogResult result = OpenFolderDialog.ShowDialog(); - if (result == DialogResult.OK) - { - string newDownloadDirectory = OpenFolderDialog.SelectedPath; - defaultDownloadDirectory = newDownloadDirectory; - irc.setCustomDownloadDir(newDownloadDirectory); - } - } - - /// - /// Stops the irc client on form close, otherwise it would keep running in the background!!! - /// - /// - /// - private void Form1_FormClosing(object sender, FormClosingEventArgs e) - { - if (irc.isClientRunning()) - { - irc.stopClient(); - } - } - - /// - /// Gets the users in the current channel. - /// - /// - /// - private void UpdateUserList_Click(object sender, EventArgs e) - { - this.UserList.Items.Clear(); - if (irc.isClientRunning()) - { - irc.getUsersInCurrentChannel(); - } - } - } -} diff --git a/FormExample/FormExample.csproj b/FormExample/FormExample.csproj index 86511f0..c347403 100644 --- a/FormExample/FormExample.csproj +++ b/FormExample/FormExample.csproj @@ -22,6 +22,7 @@ DEBUG;TRACE prompt 4 + bin\Debug\FormExample.xml AnyCPU @@ -31,9 +32,11 @@ TRACE prompt 4 + bin\Release\FormExample.xml - + + False ..\SimpleIRCLib\bin\Debug\SimpleIRCLib.dll @@ -55,19 +58,19 @@ DebugForm.cs - + Form - - Form1.cs + + IrcClientForm.cs DebugForm.cs - - Form1.cs + + IrcClientForm.cs ResXFileCodeGenerator diff --git a/FormExample/FormExample.md b/FormExample/FormExample.md new file mode 100644 index 0000000..d609978 --- /dev/null +++ b/FormExample/FormExample.md @@ -0,0 +1,57 @@ +## `DebugForm` + +```csharp +public class FormExample.DebugForm + : Form, IComponent, IDisposable, IOleControl, IOleObject, IOleInPlaceObject, IOleInPlaceActiveObject, IOleWindow, IViewObject, IViewObject2, IPersist, IPersistStreamInit, IPersistPropertyBag, IPersistStorage, IQuickActivate, ISupportOleDropSource, IDropTarget, ISynchronizeInvoke, IWin32Window, IArrangedElement, IBindableComponent, IContainerControl + +``` + +Methods + +| Type | Name | Summary | +| --- | --- | --- | +| `void` | ClearButton_Click(`Object` sender, `EventArgs` e) | Clears a specific rich textbox | +| `void` | DebugForm_FormClosing(`Object` sender, `FormClosingEventArgs` e) | Makes sure that the form doesn't actually close, but hides instead, so debug messages can still be appended! | +| `void` | DebugForm_Load(`Object` sender, `EventArgs` e) | Register event handlers for the IrcClient and DCCClient when the form loads. | +| `void` | Dispose(`Boolean` disposing) | Clean up any resources being used. | +| `void` | OnDccDebugMessage(`Object` source, `DCCDebugMessageArgs` args) | Event for receiving debug messages from the DccClient | +| `void` | OnDccDebugMessageLocal(`String` type, `String` message) | For appending the debug message on the main thread using invoke required. | +| `void` | OnIrcDebugMessage(`Object` source, `IrcDebugMessageEventArgs` args) | Event for receiving debug messages from the IrcClient | +| `void` | OnIrcDebugMessageLocal(`String` type, `String` message) | For appending the debug message on the main thread using invoke required. | +| `void` | OnRawMessageReceived(`Object` source, `IrcRawReceivedEventArgs` args) | Event for receiving raw messages from the irc server. | +| `void` | OnRawMessageReceivedLocal(`String` message) | For appending the rawmessage on the main thread using invoke required. | + + +## `IrcClientForm` + +This class is meant as example on how to use SimpleIRCLib, this does not mean that this is the correct way to program! It's meant to showcase a few of the available methods within SimpleIRCLib, you should figure out on your own how to implement it to suit your needs! It lacks a few options, such as leaving a specific channel, which will be implemented in the future. If your knowledged, you could send a raw message to the server containing commands to PART from a channel and use the OnRawMessageReceived event to check the server response. +```csharp +public class FormExample.IrcClientForm + : Form, IComponent, IDisposable, IOleControl, IOleObject, IOleInPlaceObject, IOleInPlaceActiveObject, IOleWindow, IViewObject, IViewObject2, IPersist, IPersistStreamInit, IPersistPropertyBag, IPersistStorage, IQuickActivate, ISupportOleDropSource, IDropTarget, ISynchronizeInvoke, IWin32Window, IArrangedElement, IBindableComponent, IContainerControl + +``` + +Methods + +| Type | Name | Summary | +| --- | --- | --- | +| `void` | ConnectButton_Click(`Object` sender, `EventArgs` e) | Gets the values from the input fields and starts the client | +| `void` | DisconnectButton_Click(`Object` sender, `EventArgs` e) | Disconnects the irc client, closes all open tabs. | +| `void` | Dispose(`Boolean` disposing) | Clean up any resources being used. | +| `void` | DownloadsList_MouseDoubleClick(`Object` sender, `MouseEventArgs` e) | Opens the folder where the selected file is being downloaded to | +| `void` | Form1_FormClosing(`Object` sender, `FormClosingEventArgs` e) | Stops the irc client on form close, otherwise it would keep running in the background!!! | +| `void` | MessageInput_KeyDown(`Object` sender, `KeyEventArgs` e) | Sends if enter is pressed. | +| `void` | OnDccEvent(`Object` sender, `DCCEventArgs` args) | Event that fires when DCCClient starts downloading. | +| `void` | OnMessagesReceived(`Object` sender, `IrcReceivedEventArgs` args) | Event handler for receiving messages from the Irc Client. | +| `void` | OnMessagesReceivedLocal(`String` channel, `String` user, `String` message) | Method that gets invoked on the main thread, adds a message to the richtextbox within a the correct channel tab. | +| `void` | OnUserListReceived(`Object` sender, `IrcUserListReceivedEventArgs` args) | Event that gets fired when a user list has been received. | +| `void` | OnUserListReceivedLocal(`Dictionary>` userList) | Method to invoke on the main thread, checks if a tab for the chat exists with name of the channel, if not, it creates it, same goes for the tab with the user name list. | +| `void` | SendToAll_Click(`Object` sender, `EventArgs` e) | Sends a message to all channels. | +| `void` | SendToChannel_Click(`Object` sender, `EventArgs` e) | Sends a message to a specific channel. | +| `void` | SetDownloadFolderButton_Click(`Object` sender, `EventArgs` e) | Set download directory to a custom directory | +| `void` | ShowDebugButton_Click(`Object` sender, `EventArgs` e) | Opens the debug form | +| `void` | UpdateDownloadList(`String` toUpdate, `String` fileName) | Updates the DownloadList object on the main form while the download is going, invoke is necesary because method is being called from a different Thread! | +| `void` | UpdateProgressBar(`Int32` progress) | Updates the progress bar | +| `void` | UpdateUserList_Click(`Object` sender, `EventArgs` e) | Gets the users in the current channel. | + + diff --git a/FormExample/Home.md b/FormExample/Home.md new file mode 100644 index 0000000..4116942 --- /dev/null +++ b/FormExample/Home.md @@ -0,0 +1,7 @@ +# References + +## [FormExample](FormExample) + +- [`DebugForm`](FormExample#debugform) +- [`IrcClientForm`](FormExample#ircclientform) + diff --git a/FormExample/Form1.Designer.cs b/FormExample/IrcClientForm.Designer.cs similarity index 82% rename from FormExample/Form1.Designer.cs rename to FormExample/IrcClientForm.Designer.cs index 055f9be..96dd252 100644 --- a/FormExample/Form1.Designer.cs +++ b/FormExample/IrcClientForm.Designer.cs @@ -1,6 +1,6 @@ namespace FormExample { - partial class Form1 + partial class IrcClientForm { /// /// Required designer variable. @@ -39,9 +39,8 @@ private void InitializeComponent() this.ConnectButton = new System.Windows.Forms.Button(); this.backgroundWorker1 = new System.ComponentModel.BackgroundWorker(); this.DisconnectButton = new System.Windows.Forms.Button(); - this.ChatOutput = new System.Windows.Forms.RichTextBox(); this.MessageInput = new System.Windows.Forms.TextBox(); - this.SendMessageButton = new System.Windows.Forms.Button(); + this.SendToAll = new System.Windows.Forms.Button(); this.DownloadsList = new System.Windows.Forms.ListBox(); this.ShowDebugButton = new System.Windows.Forms.Button(); this.SetDownloadFolderButton = new System.Windows.Forms.Button(); @@ -50,8 +49,10 @@ private void InitializeComponent() this.label5 = new System.Windows.Forms.Label(); this.DownloadProgressBar = new System.Windows.Forms.ProgressBar(); this.OpenFolderDialog = new System.Windows.Forms.FolderBrowserDialog(); - this.UserList = new System.Windows.Forms.ListBox(); this.UpdateUserList = new System.Windows.Forms.Button(); + this.ircChatTabs = new System.Windows.Forms.TabControl(); + this.SendToChannel = new System.Windows.Forms.Button(); + this.userListTabs = new System.Windows.Forms.TabControl(); this.SuspendLayout(); // // ServerInput @@ -138,40 +139,32 @@ private void InitializeComponent() this.DisconnectButton.UseVisualStyleBackColor = true; this.DisconnectButton.Click += new System.EventHandler(this.DisconnectButton_Click); // - // ChatOutput - // - this.ChatOutput.Location = new System.Drawing.Point(244, 37); - this.ChatOutput.Name = "ChatOutput"; - this.ChatOutput.Size = new System.Drawing.Size(307, 252); - this.ChatOutput.TabIndex = 10; - this.ChatOutput.Text = ""; - // // MessageInput // - this.MessageInput.Location = new System.Drawing.Point(49, 314); + this.MessageInput.Location = new System.Drawing.Point(233, 311); this.MessageInput.Name = "MessageInput"; - this.MessageInput.Size = new System.Drawing.Size(432, 20); + this.MessageInput.Size = new System.Drawing.Size(500, 20); this.MessageInput.TabIndex = 11; this.MessageInput.KeyDown += new System.Windows.Forms.KeyEventHandler(this.MessageInput_KeyDown); // - // SendMessageButton + // SendToAll // - this.SendMessageButton.Location = new System.Drawing.Point(487, 312); - this.SendMessageButton.Name = "SendMessageButton"; - this.SendMessageButton.Size = new System.Drawing.Size(64, 23); - this.SendMessageButton.TabIndex = 12; - this.SendMessageButton.Text = "Send"; - this.SendMessageButton.UseVisualStyleBackColor = true; - this.SendMessageButton.Click += new System.EventHandler(this.SendMessageButton_Click); + this.SendToAll.Location = new System.Drawing.Point(752, 309); + this.SendToAll.Name = "SendToAll"; + this.SendToAll.Size = new System.Drawing.Size(83, 23); + this.SendToAll.TabIndex = 12; + this.SendToAll.Text = "Send To All"; + this.SendToAll.UseVisualStyleBackColor = true; + this.SendToAll.Click += new System.EventHandler(this.SendToAll_Click); // // DownloadsList // this.DownloadsList.FormattingEnabled = true; this.DownloadsList.HorizontalScrollbar = true; - this.DownloadsList.Location = new System.Drawing.Point(49, 362); + this.DownloadsList.Location = new System.Drawing.Point(106, 359); this.DownloadsList.Name = "DownloadsList"; this.DownloadsList.ScrollAlwaysVisible = true; - this.DownloadsList.Size = new System.Drawing.Size(502, 121); + this.DownloadsList.Size = new System.Drawing.Size(854, 121); this.DownloadsList.TabIndex = 13; this.DownloadsList.MouseDoubleClick += new System.Windows.Forms.MouseEventHandler(this.DownloadsList_MouseDoubleClick); // @@ -224,23 +217,14 @@ private void InitializeComponent() // // DownloadProgressBar // - this.DownloadProgressBar.Location = new System.Drawing.Point(49, 503); + this.DownloadProgressBar.Location = new System.Drawing.Point(106, 495); this.DownloadProgressBar.Name = "DownloadProgressBar"; - this.DownloadProgressBar.Size = new System.Drawing.Size(502, 23); + this.DownloadProgressBar.Size = new System.Drawing.Size(854, 23); this.DownloadProgressBar.TabIndex = 19; // - // UserList - // - this.UserList.FormattingEnabled = true; - this.UserList.Location = new System.Drawing.Point(581, 37); - this.UserList.Name = "UserList"; - this.UserList.ScrollAlwaysVisible = true; - this.UserList.Size = new System.Drawing.Size(229, 446); - this.UserList.TabIndex = 20; - // // UpdateUserList // - this.UpdateUserList.Location = new System.Drawing.Point(581, 503); + this.UpdateUserList.Location = new System.Drawing.Point(966, 495); this.UpdateUserList.Name = "UpdateUserList"; this.UpdateUserList.Size = new System.Drawing.Size(229, 23); this.UpdateUserList.TabIndex = 21; @@ -248,13 +232,41 @@ private void InitializeComponent() this.UpdateUserList.UseVisualStyleBackColor = true; this.UpdateUserList.Click += new System.EventHandler(this.UpdateUserList_Click); // - // Form1 + // ircChatTabs + // + this.ircChatTabs.Location = new System.Drawing.Point(233, 37); + this.ircChatTabs.Name = "ircChatTabs"; + this.ircChatTabs.SelectedIndex = 0; + this.ircChatTabs.Size = new System.Drawing.Size(727, 252); + this.ircChatTabs.TabIndex = 22; + // + // SendToChannel + // + this.SendToChannel.Location = new System.Drawing.Point(841, 309); + this.SendToChannel.Name = "SendToChannel"; + this.SendToChannel.Size = new System.Drawing.Size(112, 23); + this.SendToChannel.TabIndex = 23; + this.SendToChannel.Text = "Send To Channel"; + this.SendToChannel.UseVisualStyleBackColor = true; + this.SendToChannel.Click += new System.EventHandler(this.SendToChannel_Click); + // + // userListTabs + // + this.userListTabs.Location = new System.Drawing.Point(966, 37); + this.userListTabs.Name = "userListTabs"; + this.userListTabs.SelectedIndex = 0; + this.userListTabs.Size = new System.Drawing.Size(229, 452); + this.userListTabs.TabIndex = 24; + // + // IrcClientForm // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(822, 530); + this.ClientSize = new System.Drawing.Size(1207, 530); + this.Controls.Add(this.userListTabs); + this.Controls.Add(this.SendToChannel); + this.Controls.Add(this.ircChatTabs); this.Controls.Add(this.UpdateUserList); - this.Controls.Add(this.UserList); this.Controls.Add(this.DownloadProgressBar); this.Controls.Add(this.label5); this.Controls.Add(this.label4); @@ -262,9 +274,8 @@ private void InitializeComponent() this.Controls.Add(this.SetDownloadFolderButton); this.Controls.Add(this.ShowDebugButton); this.Controls.Add(this.DownloadsList); - this.Controls.Add(this.SendMessageButton); + this.Controls.Add(this.SendToAll); this.Controls.Add(this.MessageInput); - this.Controls.Add(this.ChatOutput); this.Controls.Add(this.DisconnectButton); this.Controls.Add(this.ConnectButton); this.Controls.Add(this.labelsomething); @@ -275,8 +286,8 @@ private void InitializeComponent() this.Controls.Add(this.UsernameInput); this.Controls.Add(this.PortInput); this.Controls.Add(this.ServerInput); - this.Name = "Form1"; - this.Text = "Form1"; + this.Name = "IrcClientForm"; + this.Text = "SimpleIRCLib Irc Client"; this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.Form1_FormClosing); this.ResumeLayout(false); this.PerformLayout(); @@ -296,9 +307,8 @@ private void InitializeComponent() private System.Windows.Forms.Button ConnectButton; private System.ComponentModel.BackgroundWorker backgroundWorker1; private System.Windows.Forms.Button DisconnectButton; - private System.Windows.Forms.RichTextBox ChatOutput; private System.Windows.Forms.TextBox MessageInput; - private System.Windows.Forms.Button SendMessageButton; + private System.Windows.Forms.Button SendToAll; private System.Windows.Forms.ListBox DownloadsList; private System.Windows.Forms.Button ShowDebugButton; private System.Windows.Forms.Button SetDownloadFolderButton; @@ -307,8 +317,10 @@ private void InitializeComponent() private System.Windows.Forms.Label label5; private System.Windows.Forms.ProgressBar DownloadProgressBar; private System.Windows.Forms.FolderBrowserDialog OpenFolderDialog; - private System.Windows.Forms.ListBox UserList; private System.Windows.Forms.Button UpdateUserList; + private System.Windows.Forms.TabControl ircChatTabs; + private System.Windows.Forms.Button SendToChannel; + private System.Windows.Forms.TabControl userListTabs; } } diff --git a/FormExample/IrcClientForm.cs b/FormExample/IrcClientForm.cs new file mode 100644 index 0000000..fb93799 --- /dev/null +++ b/FormExample/IrcClientForm.cs @@ -0,0 +1,434 @@ +using System; +using System.Windows.Forms; +using System.IO; +using System.Diagnostics; +//the library +using SimpleIRCLib; +using System.Collections.Generic; + +namespace FormExample +{ + /// + /// This class is meant as example on how to use SimpleIRCLib, this does not mean that this is the correct way to program! + /// It's meant to showcase a few of the available methods within SimpleIRCLib, you should figure out on your own how to implement it to suit your needs! + /// It lacks a few options, such as leaving a specific channel, which will be implemented in the future. If your knowledged, you could send a raw message + /// to the server containing commands to PART from a channel and use the OnRawMessageReceived event to check the server response. + /// + public partial class IrcClientForm : Form + { + //initiate irc client + private readonly SimpleIRC _irc; + + //initiate debugform + private readonly DebugForm _debugForm; + + private string _defaultDownloadDirectory; + + /// + /// Form Contstructor, initializes SimpleIRC Library and registers event handlers. + /// + public IrcClientForm() + { + _irc = new SimpleIRC(); + _irc.IrcClient.OnMessageReceived += OnMessagesReceived; + _irc.IrcClient.OnUserListReceived += OnUserListReceived; + _irc.DccClient.OnDccEvent += OnDccEvent; + _debugForm = new DebugForm(_irc); + _defaultDownloadDirectory = Directory.GetCurrentDirectory(); + + InitializeComponent(); + } + + /// + /// Gets the values from the input fields and starts the client + /// + /// + /// + public void ConnectButton_Click(object sender, EventArgs e) + { + if (ServerInput.Text != "" && PortInput.Text != "" && UsernameInput.Text != "" && ChannelsInput.Text != "" && _irc.IsClientRunning() == false) + { + int port = -1; + if ((port = int.Parse(PortInput.Text)) != -1) + { + //parameters as follows: ip or address to irc server, username, password(not functional), channels, and method to execute when message is received (see line 103) + _irc.SetupIrc(ServerInput.Text, UsernameInput.Text, ChannelsInput.Text, int.Parse(PortInput.Text)); + + //Sets event handlers for all the possible events (!IMPORTANT: do this after intializing irc.SetupIRC !!!) + + + _irc.StartClient(); + } + } else + { + MessageBox.Show("You need to fill in all the information fields!"); + } + } + + /// + /// Disconnects the irc client, closes all open tabs. + /// + /// + /// + public void DisconnectButton_Click(object sender, EventArgs e) + { + if (_irc.StopClient()) + { + ircChatTabs.TabPages.Clear(); + userListTabs.TabPages.Clear(); + } + } + + /// + /// Sends if enter is pressed. + /// + /// + /// + public void MessageInput_KeyDown(object sender, KeyEventArgs e) + { + if(e.KeyCode == Keys.Enter) + { + if (MessageInput.Text != "" && _irc.IsClientRunning()) + { + _irc.SendMessageToAll(MessageInput.Text); + } + } + + } + + /// + /// Event handler for receiving messages from the Irc Client. + /// + /// Values of the class that fired the event + /// IrcReceivedEventArgs containing the message information + public void OnMessagesReceived(object sender, IrcReceivedEventArgs args) + { + OnMessagesReceivedLocal(args.Channel, args.User, args.Message); + } + + /// + /// Method that gets invoked on the main thread, adds a message to the richtextbox within a the correct channel tab. + /// + /// channel messaged was received on + /// user that send the message to the channel + /// message that the user had send on the channel + public void OnMessagesReceivedLocal(string channel, string user, string message) + { + if (this.ircChatTabs.InvokeRequired) + { + this.ircChatTabs.Invoke(new MethodInvoker(() => OnMessagesReceivedLocal(channel, user, message))); + } + else + { + //searches for the tab for the correct channel + bool found = false; + int index = 0; + foreach (TabPage tab in ircChatTabs.TabPages) + { + if (channel.Contains(tab.Name)) + { + found = true; + break; + } + + index++; + } + + //if the tab has been found, add a message to the richtextbox within that tab. + if (found) + { + if (ircChatTabs.TabPages[index].Controls.ContainsKey(channel)) + { + RichTextBox selectedRtb = (RichTextBox)ircChatTabs.TabPages[index].Controls[channel]; + selectedRtb.AppendText(user + " : " + message + Environment.NewLine); + selectedRtb.ScrollToCaret(); + } + } + + } + } + + /// + /// Event that gets fired when a user list has been received. + /// + /// Values of the class that fired the event + /// IrcUserListReceivedEventArgs contains the Dictionary where the key is the channel and the list contains the usernames + public void OnUserListReceived(object sender, IrcUserListReceivedEventArgs args ) + { + OnUserListReceivedLocal(args.UsersPerChannel); + } + + /// + /// Method to invoke on the main thread, checks if a tab for the chat exists with name of the channel, if not, it creates it, same goes for the tab with the user name list. + /// + /// Dictionary where the key is the channel and the list contains the usernames + public void OnUserListReceivedLocal(Dictionary> userList) + { + if (this.InvokeRequired) + { + this.Invoke(new MethodInvoker(() => OnUserListReceivedLocal(userList))); + } + else + { + //iterate through each channel + foreach (KeyValuePair> channelsAndUsers in userList) + { + //search for a tab with the same channel name within the chatTabs + bool foundChatTab = false; + int indexChatTab = 0; + foreach (TabPage tab in ircChatTabs.TabPages) + { + if (channelsAndUsers.Key.Equals(tab.Name)) + { + Debug.WriteLine("FOUND TAB: " + tab.Name); + foundChatTab = true; + break; + } + + indexChatTab++; + } + + if (!foundChatTab) + { + TabPage newTab = new TabPage(channelsAndUsers.Key); + newTab.Name = channelsAndUsers.Key; + RichTextBox rtb = new RichTextBox(); + rtb.Dock = DockStyle.Fill; + rtb.BorderStyle = BorderStyle.None; + rtb.Name = channelsAndUsers.Key; + newTab.Controls.Add(rtb); + ircChatTabs.TabPages.Add(newTab); + } + + //search for a tab with the same channel name within the userListTabs + bool foundUserListTab = false; + int userListTabIndex = 0; + foreach (TabPage tab in userListTabs.TabPages) + { + if (channelsAndUsers.Key.Equals(tab.Name)) + { + foundUserListTab = true; + break; + } + + userListTabIndex++; + } + + if (!foundUserListTab) + { + TabPage newTab = new TabPage(channelsAndUsers.Key); + newTab.Name = channelsAndUsers.Key; + RichTextBox rtb = new RichTextBox(); + rtb.Dock = DockStyle.Fill; + rtb.BorderStyle = BorderStyle.None; + rtb.Name = channelsAndUsers.Key; + foreach (string user in channelsAndUsers.Value) + { + rtb.AppendText(user + Environment.NewLine); + } + rtb.ScrollToCaret(); + newTab.Controls.Add(rtb); + userListTabs.TabPages.Add(newTab); + } + else + { + + if (userListTabs.TabPages[userListTabIndex].Controls.ContainsKey(channelsAndUsers.Key)) + { + RichTextBox selectedRtb = (RichTextBox)userListTabs.TabPages[userListTabIndex].Controls[channelsAndUsers.Key]; + foreach (string user in channelsAndUsers.Value) + { + selectedRtb.AppendText(user + Environment.NewLine); + } + selectedRtb.ScrollToCaret(); + } + } + } + } + } + + /// + /// Event that fires when DCCClient starts downloading. + /// + /// Values of the class that fired the event + /// DCCEventArgs contains all the information about the download update + public void OnDccEvent(object sender, DCCEventArgs args) + { + string fileName = args.FileName; + int downloadProgress = args.Progress; + string downloadSpeed = args.KBytesPerSecond.ToString(); + string status = args.Status; + + string fullDownloadInformation = fileName + " | " + status + " | " + downloadSpeed + " kb/s | " + _defaultDownloadDirectory; + + UpdateDownloadList(fullDownloadInformation, fileName); + + UpdateProgressBar(downloadProgress); + + if (status.Contains("COMPLETED")) + { + UpdateProgressBar(100); + } + + } + + /// + /// Updates the DownloadList object on the main form while the download is going, invoke is necesary because + /// method is being called from a different Thread! + /// + /// string that needs to be added/updated + /// the filename which is used for searching the item in the download list for updating + public void UpdateDownloadList(string toUpdate, string fileName) + { + if (this.DownloadsList.InvokeRequired) + { + this.DownloadsList.Invoke(new MethodInvoker(() => UpdateDownloadList(toUpdate, fileName))); + } + else + { + int indexOfDownloadItem = 0; + for (int i = indexOfDownloadItem; i < DownloadsList.Items.Count; ++i) + { + string lbString = DownloadsList.Items[i].ToString(); + if (lbString.Contains(fileName)) + { + indexOfDownloadItem = i; + break; + } + } + + try + { + DownloadsList.Items.RemoveAt(indexOfDownloadItem); + DownloadsList.Items.Insert(indexOfDownloadItem, toUpdate); + DownloadsList.SelectedIndex = indexOfDownloadItem; + } + catch + { + DownloadsList.Items.Add(toUpdate); + } + } + } + + /// + /// Updates the progress bar + /// + /// defines the current progress + public void UpdateProgressBar(int progress) + { + if (this.DownloadProgressBar.InvokeRequired) + { + this.DownloadProgressBar.Invoke(new MethodInvoker(() => UpdateProgressBar(progress))); + } else + { + this.DownloadProgressBar.Value = progress; + } + } + + /// + /// Opens the debug form + /// + /// + /// + public void ShowDebugButton_Click(object sender, EventArgs e) + { + try + { + _debugForm.Show(); + } catch + { + + } + } + + /// + /// Opens the folder where the selected file is being downloaded to + /// + /// + /// + public void DownloadsList_MouseDoubleClick(object sender, MouseEventArgs e) + { + int currentlySelected = DownloadsList.SelectedIndex; + try + { + string currentItem = DownloadsList.Items[currentlySelected].ToString(); + + if (currentItem.Contains("|")) + { + string fileLocation = currentItem.Split('|')[currentItem.Split('|').Length - 1].Trim(); + Process.Start(fileLocation); + } + } + catch + { + + } + } + + /// + /// Set download directory to a custom directory + /// + /// + /// + public void SetDownloadFolderButton_Click(object sender, EventArgs e) + { + DialogResult result = OpenFolderDialog.ShowDialog(); + if (result == DialogResult.OK) + { + string newDownloadDirectory = OpenFolderDialog.SelectedPath; + _defaultDownloadDirectory = newDownloadDirectory; + _irc.SetCustomDownloadDir(newDownloadDirectory); + } + } + + /// + /// Stops the irc client on form close, otherwise it would keep running in the background!!! + /// + /// + /// + public void Form1_FormClosing(object sender, FormClosingEventArgs e) + { + if (_irc.IsClientRunning()) + { + _irc.StopClient(); + } + } + + /// + /// Gets the users in the current channel. + /// + /// + /// + public void UpdateUserList_Click(object sender, EventArgs e) + { + RichTextBox selectedRtb = (RichTextBox)userListTabs.SelectedTab.Controls[0]; + selectedRtb.Clear(); + if (_irc.IsClientRunning()) + { + _irc.GetUsersInDifferentChannel(userListTabs.SelectedTab.Name); + } + } + + /// + /// Sends a message to a specific channel. + /// + /// + /// + public void SendToChannel_Click(object sender, EventArgs e) + { + string channel = ircChatTabs.SelectedTab.Name; + _irc.SendMessageToChannel(MessageInput.Text, channel); + } + + /// + /// Sends a message to all channels. + /// + /// + /// + public void SendToAll_Click(object sender, EventArgs e) + { + _irc.SendMessageToAll(MessageInput.Text); + } + + } +} diff --git a/FormExample/Form1.resx b/FormExample/IrcClientForm.resx similarity index 100% rename from FormExample/Form1.resx rename to FormExample/IrcClientForm.resx diff --git a/FormExample/Program.cs b/FormExample/Program.cs index 18e2d42..9ad3fde 100644 --- a/FormExample/Program.cs +++ b/FormExample/Program.cs @@ -16,7 +16,7 @@ static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new Form1()); + Application.Run(new IrcClientForm()); } } } diff --git a/IrcLibTest/IrcLibTest.csproj b/IrcLibTest/IrcLibTest.csproj index 6f6fac4..56597cd 100644 --- a/IrcLibTest/IrcLibTest.csproj +++ b/IrcLibTest/IrcLibTest.csproj @@ -33,7 +33,7 @@ 4 - + False ..\SimpleIRCLib\bin\Debug\SimpleIRCLib.dll diff --git a/IrcLibTest/Program.cs b/IrcLibTest/Program.cs index 2e268fb..0cb1e40 100644 --- a/IrcLibTest/Program.cs +++ b/IrcLibTest/Program.cs @@ -21,27 +21,26 @@ static void Main(string[] args) string password; string channel; - //setup screen: - Console.WriteLine("Server IP(default is : 54.229.0.87(irc.rizon.net)) = "); + Console.WriteLine("Server IP(default is : irc.rizon.net) = "); if ((ip = Console.ReadLine()) == "") { - ip = "54.229.0.87"; + ip = "irc.rizon.net"; } - Console.WriteLine("Server Port(default is : 6667) = "); + Console.WriteLine("Server Port(default is : 6697 with ssl enabled) = "); if (Console.ReadLine() != "") { port = Convert.ToInt32(Console.ReadLine()); } else { - port = 6667; + port = 6697; } Console.WriteLine("Username(default is : RareIRC_Client) = "); if ((username = Console.ReadLine()) == "") { - username = "RoflHerp"; + username = "RareIRC_ConsoleTestClient"; } Console.WriteLine("Password(not working yet, default is : ) = "); @@ -57,45 +56,82 @@ static void Main(string[] args) } irc = new SimpleIRC(); - irc.setupIrc(ip, port, username, password, channel, chatOutputCallback); - irc.setRawOutput(rawOutputCallback); - irc.setDebugCallback(debugOutputCallback); - irc.startClient(); - irc.setDownloadStatusChangeCallback(downloadStatusChanged); + + irc.SetupIrc(ip, username, channel, port); + + irc.IrcClient.OnDebugMessage += debugOutputCallback; + irc.IrcClient.OnMessageReceived += chatOutputCallback; + irc.IrcClient.OnRawMessageReceived += rawOutputCallback; + irc.IrcClient.OnUserListReceived += userListCallback; + + irc.DccClient.OnDccDebugMessage += dccDebugCallback; + irc.DccClient.OnDccEvent += downloadStatusChanged; + + irc.StartClient(); while (true) { string Input = Console.ReadLine(); - if (Input != null || Input != "" || Input != String.Empty && irc.isClientRunning()) + if (Input != null || Input != "" || Input != String.Empty && irc.IsClientRunning()) { - irc.sendMessage(Input); + irc.SendMessageToAll(Input); } } } - public static void downloadStatusChanged() + public static void downloadStatusChanged(object source, DCCEventArgs args) { - Console.WriteLine("DOWNLOAD STATUS: " + irc.getDownloadProgress("status") + "%"); - Console.WriteLine("DOWNLOAD PROGRESS: " + irc.getDownloadProgress("progress") + "%"); + Console.WriteLine("===============DCC EVENT==============="); + Console.WriteLine("DOWNLOAD STATUS: " + args.Status); + Console.WriteLine("DOWNLOAD FILENAME: " + args.FileName); + Console.WriteLine("DOWNLOAD PROGRESS: " + args.Progress + "%"); + Console.WriteLine("===============END DCC EVENT==============="); + Console.WriteLine(""); } - public static void chatOutputCallback(string user, string message) + public static void chatOutputCallback(object source, IrcReceivedEventArgs args) { - Console.WriteLine(user + ": " + message); + Console.WriteLine("===============IRC MESSAGE==============="); + Console.WriteLine(args.Channel + " | " + args.User + ": " + args.Message); + Console.WriteLine("===============END IRC MESSAGE==============="); + Console.WriteLine(""); } - public static void rawOutputCallback(string rawData) + public static void rawOutputCallback(object source, IrcRawReceivedEventArgs args) { - //Console.WriteLine("RAW: " + rawData); + Console.WriteLine("===============RAW MESSAGE==============="); + Console.WriteLine("RAW: " + args.Message); + Console.WriteLine("===============END RAW MESSAGE==============="); } - public static void debugOutputCallback(string debug) + public static void debugOutputCallback(object source, IrcDebugMessageEventArgs args) { - Console.WriteLine("===============DEBUG MESSAGE==============="); - Console.WriteLine(debug); - Console.WriteLine("===============END DEBUG MESSAGE==============="); + Console.WriteLine("===============IRC DEBUG MESSAGE==============="); + Console.WriteLine(args.Type + "|" + args.Message); + Console.WriteLine("===============END IRC DEBUG MESSAGE==============="); } + + public static void userListCallback(object source, IrcUserListReceivedEventArgs args) + { + foreach(KeyValuePair> usersPerChannel in args.UsersPerChannel) + { + Console.WriteLine("===============USERS ON CHANNEL " + usersPerChannel.Key + " ==============="); + foreach (string user in usersPerChannel.Value) + { + Console.WriteLine(user); + } + Console.WriteLine("===============END USERS ON CHANNEL " + usersPerChannel.Key + " ==============="); + } + } + + public static void dccDebugCallback(object source, DCCDebugMessageArgs args) + { + Console.WriteLine("===============IRC DEBUG MESSAGE==============="); + Console.WriteLine(args.Type + "|" + args.Message); + Console.WriteLine("===============END IRC DEBUG MESSAGE==============="); + } + } } diff --git a/README.md b/README.md index 6110bf6..2861ea5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # SimpleIRCLib for Csharp **THIS LIBRARY IS STILL IN DEVELOPMENT** +**STARTING AT V2.0.0 THIS LIBRARY IS NOT BACK-WARDS COMPATIBLE WITH PREVIOUS VERSIONS!** This library is designed to make communication through IRC easier to implement in your application. In comparison to other C# IRC libraries, this library also enables you to download using the DCC (XDCC) protocol, used by IRC. @@ -74,136 +75,32 @@ It's main features are: - Default download directory is now set to the same directory where the libary resides. - Added flag to check if an error occured of any kind within the library (doesn't tell what kind of error yet). +2.0.0 +- Rewritten IrcConnect class (now called IrcClient) to prevent Race conditions! +- Added timeout warnings. +- Added support for TLS/SSL +- Added better error handeling using the error codes from RFC 1459 IRC Protocol +- Added full support for receiving and sending to seperate channels +- Added comments and changed names to fit C# code convention +- Under the hood DCC fixes for more stability and error handeling when downloads go wrong +- **Changed Action based callback methods to Event handlers** +### Wiki +To get a better picture of the available methods and properties, go to this wiki: +[SimpleIRCLib Wiki](https://github.com/EldinZenderink/SimpleIRCLib/wiki/SimpleIRCLib-Methods-Wiki#simpleirc) -### Usage - Console Application +For a full WinForms example, go to: +[WinForm Example](https://github.com/EldinZenderink/SimpleIRCLib/tree/master/FormExample) -*TIP: If you do not want a seperate DLL file with your program you can either copy the .cs files to your solution/project and manually change the Namespace, or you can use a program called [ILMerge](https://www.microsoft.com/en-us/download/details.aspx?id=17630) to combine a exe and dll together(not tested)!* +For a simplified console example: +[Console Example](https://github.com/EldinZenderink/SimpleIRCLib/tree/master/IrcLibTest) -This is a list with the most important methods available to you: - - (void) setupIrc(ip, port, username, password, channel, chatOutputCallback); - (void) setDebugCallback(debugOutputCallback); - (void) setRawOutput(setRawOutputCallback); - (string) downloadDir; //is now string, can be changed while running instance - (void) setDownloadStatus(downloadStatusCallback); - (void) startClient(); - (void) stopClient(); - (bool) isClientRunning(); - (bool) stopXDCCDownload(); - (object) getDownloadProgress(string whichdownloaddetail) //see below - (void) getUsersInCurrentChannel(); - (void) getUsersInDifferentChannel(string channel) - (void) sendMessage(message); - (void) sendRawMessage(message); - (string) newUsername; //is now string field instead of method - (string) newChannel; //is now string field instead of method - - -Before you start programming, you need to get the package on the NuGet page for this library, or you need to download the dll file manually and reference it in your c# solution/project. Afterwards, you need to do the following: - -`using SimpleIrcLib;` - -After doing that, add the following code to start your irc client: - - SimpleIRC irc = new SimpleIRC(); - irc.setupIrc(ip, port, username, password, channel, chatOutputCallback); - irc.setDebugCallback(debugOutputCallback); - irc.setRawOutput(rawOutputCallback); - irc.setDownloadStatusChangeCallback(downloadStatusCallback); - irc.setUserListReceivedCallback(userListReceivedCallback); - irc.startClient(); - -Your callbacks should/could look like this: - -**chatOutputCallback:** - - void chatOutputCallback(string user, string message) - { - Console.WriteLine(user + ":" + message); - } - - -**debugOutputCallback:** - - void debugOutputCallback(string debug) - { - Console.WriteLine("===============DEBUG MESSAGE==============="); - Console.WriteLine(debug); - Console.WriteLine("===============END DEBUG MESSAGE==============="); - } - -**rawOutputCallback:** - - void rawOutputCallback(string output) - { - Console.WriteLine(output); - } - -**downloadStatusCallback:** - - void downloadStatusCallback() //see below for definition of each index in this array - { - Object information = irc.getDownloadProgress("progress"); - Object speedkbps = irc.getDownloadProgress("kbps"); - Object status = irc.getDownloadProgress("status"); - Object filename = irc.getDownloadProgress("filename"); - } - -**userListReceivedCallback** - - void userListReceivedCallback(string[] users) //see below for definition of each index in this array - { - foreach(string user in users){ - Console.WriteLine(user); - } - } - - -And here is a bit of gibrish code for sending messages to the irc server: - - while (true) //irc output and such are handled in different threads - { - string Input = Console.ReadLine(); - if (Input != null || Input != "" || Input != String.Empty) - { - irc.sendMessage(Input); - } - } - - -For getting information about the download in progress (in `downloadStatusChangeCallback()`), you can use this function: - -`getDownloadProgress(string whichdownloaddetail)` - -This will return an array of strings when a download is running, but if there is no download while you are requesting information, it will return a empty array, except for the first index which will contain the string "NULL". - -Array that will be returned when downloading: - -| Whichdownloaddetail | What it is | Explanation | -| ------------- |:-------------:| ----- | -| dccstring | DCC receive string | The bot sends you a string with connection details.| -| filename | Filename | Filename of the file that you currently are downloading. | -| size | Filesize | Size of the file that you currently are downloading. (In bytes)| -| ip | Server IP | IP from the file server to which you have connected. | -| port | Server Port | Port from the file server to which you have connected. | -| pack | Pack Number | Packnumer corresponding to the file you have requested through XDCC.| -| bot | Bot Name | Bot from which you requested the file. | -| progress | Progress | Progress of completion is %. | -| status | status of download/connection| Gives status about download and connection, such as, waiting, downloading, failed | etc -| bps | Bytes Per Second | | -| kbps | KBytes Per Second | | -| mbps | MBytes Per Second | | - - - -### Full Example -An (quick and dirty) example can be found here: -[Example](https://github.com/EldinZenderink/SimpleIRCLib/blob/master/IrcLibTest/Program.cs) - -A winform example including video tutorial (v1.1.2): +### Tutorial +A winform example including video tutorial (v1.1.2) **OLD - NOT SUPPORTED FOR v2.0.0**: [YouTube](https://www.youtube.com/watch?v=Y5JPdwFwoSI) +-New v2.0.0 and up tutorial comming soon! + ### Development I will try to fix (significant) bugs as quick as possible, but due to my study taking a rollercoaster dive in a few days it might take a while before an actual update will appear. It is very barebone and does need some refinement. So, progress in development will come down to how much free time I have and how much of it I want to spend working on this library. diff --git a/SimpleIRCLib.sln b/SimpleIRCLib.sln index 312ddc3..fde0a79 100644 --- a/SimpleIRCLib.sln +++ b/SimpleIRCLib.sln @@ -1,11 +1,14 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 14 -VisualStudioVersion = 14.0.23107.0 +# Visual Studio 15 +VisualStudioVersion = 15.0.27428.2005 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleIRCLib", "SimpleIRCLib\SimpleIRCLib.csproj", "{3D881A50-DB1A-464E-A51E-4CE5EB8BD5F1}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IrcLibTest", "IrcLibTest\IrcLibTest.csproj", "{27C58F1A-4851-4A5B-85F7-C387971543DA}" + ProjectSection(ProjectDependencies) = postProject + {3D881A50-DB1A-464E-A51E-4CE5EB8BD5F1} = {3D881A50-DB1A-464E-A51E-4CE5EB8BD5F1} + EndProjectSection EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FormExample", "FormExample\FormExample.csproj", "{A46528E0-6BB9-4200-8276-3F840E0A7C72}" EndProject @@ -31,4 +34,7 @@ Global GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {584E8A95-B483-45A0-A38B-985C12EF6371} + EndGlobalSection EndGlobal diff --git a/SimpleIRCLib/DCCClient.cs b/SimpleIRCLib/DCCClient.cs index 769f945..8e52af9 100644 --- a/SimpleIRCLib/DCCClient.cs +++ b/SimpleIRCLib/DCCClient.cs @@ -8,240 +8,322 @@ namespace SimpleIRCLib { - class DCCClient + /// + /// Class for downloading files using the DCC protocol on a sperarate thread from the main IRC Client thread. + /// + public class DCCClient { - //public available information for checking download Status and information - public string newDccString { get; set; } - public string newFileName { get; set; } - public int newPortNum { get; set; } - public Int64 newFileSize { get; set; } - public string newIp { get; set; } - public string newIp2 { get; set; } + + /// + /// For firing update event using DCCEventArgs from DCCEventArgs.cs + /// + public event EventHandler OnDccEvent; + /// + /// For firing debug event using DCCDebugMessageArgs from DCCEventArgs.cs + /// + public event EventHandler OnDccDebugMessage; + + /// + /// Raw DCC String used for getting the file location (server) and some basic file information + /// + public string NewDccString { get; set; } + /// + /// File name of the file being downloaded + /// + public string NewFileName { get; set; } + /// + /// Pack ID of file on bot where file resides + /// + public int NewPortNum { get; set; } + /// + /// FileSize of the file being downloaded + /// + public Int64 NewFileSize { get; set; } + /// + /// Port of server of file location + /// + public string NewIp { get; set; } + /// + /// Progress from 0-100 (%) + /// public int Progress { get; set; } + /// + /// Download status, such as: WAITING,DOWNLOADING,FAILED:[ERROR],ABORTED + /// public string Status { get; set; } - public Int64 Bytes_Seconds { get; set; } - public int KBytes_Seconds { get; set; } - public int MBytes_Seconds { get; set; } - public string botName { get; set; } - public string packNum { get; set; } - public bool isDownloading { get; set; } - public string currentFilePath { get; set; } - - - //class linking of some sort - private SimpleIRC simpleirc; - private IrcConnect ircConnect; - - //contains the downloaddir - private string curDownloadDir; - //async thread for downloading - private Thread downloader; - - //overload constructor - public DCCClient(SimpleIRC sirc, IrcConnect ircCon) + /// + /// Download speed in: KB/s + /// + public Int64 BytesPerSecond { get; set; } + /// + /// Download speed in: MB/s + /// + public int KBytesPerSecond { get; set; } + /// + /// Download status, such as: WAITING,DOWNLOADING,FAILED:[ERROR],ABORTED + /// + public int MBytesPerSecond { get; set; } + /// + /// Bot name where file resides + /// + public string BotName { get; set; } + /// + /// Pack ID of file on bot where file resides + /// + public string PackNum { get; set; } + /// + /// Check for status of DCCClient + /// + public bool IsDownloading { get; set; } + /// + /// Path to the file that is being downloaded, or has been downloaded + /// + public string CurrentFilePath { get; set; } + + /// + /// Local bool to tell the while loop within the download thread to stop. + /// + private bool _shouldAbort = false; + + /// + /// Client that is currently running, used for sending abort messages when a download fails, or a dcc string fails to parse. + /// + private IrcClient _ircClient; + + /// + /// Current download directory that will be used when starting a download + /// + private string _curDownloadDir; + + /// + /// Thread where download logic is running + /// + private Thread _downloader; + + /// + /// Initial constructor. + /// + public DCCClient() { - simpleirc = sirc; - ircConnect = ircCon; + IsDownloading = false; } - //parses data from dcc string and start the downloader thread - public void startDownloader(string dccString, string downloaddir, string bot, string pack) + /// + /// Starts a downloader by parsing the received message from the irc server on information + /// + /// message from irc server + /// download directory + /// bot where the file came from + /// pack on bot where the file came from + /// irc client used the moment it received the dcc message, used for sending abort messages when download fails unexpectedly + public void StartDownloader(string dccString, string downloaddir, string bot, string pack, IrcClient client) { - if ((dccString ?? downloaddir ?? bot ?? pack) != null && dccString.Contains("SEND") && !isDownloading) + if ((dccString ?? downloaddir ?? bot ?? pack) != null && dccString.Contains("SEND") && !IsDownloading) { - newDccString = dccString; - curDownloadDir = downloaddir; - botName = bot; - packNum = pack; + NewDccString = dccString; + _curDownloadDir = downloaddir; + BotName = bot; + PackNum = pack; + _ircClient = client; //parsing the data for downloader thread - updateStatus("PARSING"); - bool isParsed = parseData(dccString); + UpdateStatus("PARSING"); + bool isParsed = ParseData(dccString); //try to set the necesary information for the downloader if (isParsed) { - //start the downloader thread - downloader = new Thread(new ThreadStart(this.Downloader)); - downloader.IsBackground = true; - downloader.Start(); + _shouldAbort = false; + //start the downloader thread + _downloader = new Thread(new ThreadStart(this.Downloader)); + _downloader.IsBackground = true; + _downloader.Start(); } else { - simpleirc.DebugCallBack("Can't parse dcc string and start downloader, failed to parse data, removing from que\n"); - ircConnect.sendMsg("/msg " + botName + " xdcc remove " + packNum); - ircConnect.sendMsg("/msg " + botName + " xdcc cancel"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs( + "Can't parse dcc string and start downloader, failed to parse data, removing from que\n", "DCC STARTER")); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc remove " + PackNum); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc cancel"); } } else { - if (isDownloading) + if (IsDownloading) { - simpleirc.DebugCallBack("You are already downloading! Ignore SEND request\n"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("You are already downloading! Ignore SEND request\n", "DCC STARTER")); } else { - simpleirc.DebugCallBack("DCC String does not contain SEND and/or invalid values for parsing! Ignore SEND request\n"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCC String does not contain SEND and/or invalid values for parsing! Ignore SEND request\n", "DCC STARTER")); } } } - //parses data received by the dcc strings, necesary for details to connect to the right tcp server where file is located - private bool parseData(string dccString) + /// + /// Parses the received DCC string + /// + /// dcc string + /// returns true if parsing was succesfull, false if failed + private bool ParseData(string dccString) { /* - * :bot PRIVMSG nickname :DCC SEND \"filename\" ip_networkbyteorder port filesize + * :_bot PRIVMSG nickname :DCC SEND \"filename\" ip_networkbyteorder port filesize *AnimeDispenser!~desktop@Rizon-6AA4F43F.ip-37-187-118.eu PRIVMSG WeebIRCDev :DCC SEND "[LNS] Death Parade - 02 [BD 720p] [7287AE5C].mkv" 633042523 59538 258271780 *HelloKitty!~nyaa@ny.aa.ny.aa PRIVMSG WeebIRCDev :DCC SEND [Coalgirls]_Spirited_Away_(1280x692_Blu-ray_FLAC)_[5805EE6B].mkv 3281692293 35567 10393049211 :[EWG]-bOnez!EWG@CRiTEN-BB8A59E9.ip-158-69-126.net PRIVMSG LittleWeeb_jtokck :DCC SEND The.Good.Doctor.S01E13.Seven.Reasons.1080p.AMZN.WEB-DL.DD+5.1.H.264-QOQ.mkv 2655354388 55000 1821620363 *Ginpa2:DCC SEND "[HorribleSubs] Dies Irae - 05 [480p].mkv" 84036312 35016 153772128 */ - //bot - //file - //size - //ip - //port dccString = RemoveSpecialCharacters(dccString).Substring(1); - simpleirc.DebugCallBack("DCCClient: DCC STRING: " + dccString); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient: DCC STRING: " + dccString, "DCC PARSER")); if (!dccString.Contains(" :DCC")) { - botName = dccString.Split(':')[0]; + BotName = dccString.Split(':')[0]; if (dccString.Contains("\"")) { - newFileName = dccString.Split('"')[1]; + NewFileName = dccString.Split('"')[1]; - simpleirc.DebugCallBack("DCCClient1: FILENAME PARSED: " + newFileName); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient1: FILENAME PARSED: " + NewFileName, "DCC PARSER")); string[] thaimportantnumbers = dccString.Split('"')[2].Trim().Split(' '); if (thaimportantnumbers[0].Contains(":")) { - newIp = thaimportantnumbers[0]; + NewIp = thaimportantnumbers[0]; } else { try { - simpleirc.DebugCallBack("DCCClient1: PARSING FOLLOWING IPBYTES USING NTOH: " + thaimportantnumbers[0]); - Int64 hostmode = (Int64)IPAddress.NetworkToHostOrder(Int64.Parse(thaimportantnumbers[0])); - string ipAddress = UInt64ToIPAddress(hostmode); - newIp = ipAddress; + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient1: PARSING FOLLOWING IPBYTES: " + thaimportantnumbers[0], "DCC PARSER")); + string ipAddress = UInt64ToIPAddress(Int64.Parse(thaimportantnumbers[0])); + NewIp = ipAddress; } catch { - simpleirc.DebugCallBack("DCCClient1: PARSING FOLLOWING IPBYTES: " + thaimportantnumbers[0]); - string ipAddress = UInt64ToIPAddress(Int64.Parse(thaimportantnumbers[0])); - newIp = ipAddress; + return false; } } - simpleirc.DebugCallBack("DCCClient1: IP PARSED: " + newIp); - newPortNum = int.Parse(thaimportantnumbers[1]); - newFileSize = Int64.Parse(thaimportantnumbers[2]); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient1: IP PARSED: " + NewIp, "DCC PARSER")); + NewPortNum = int.Parse(thaimportantnumbers[1]); + NewFileSize = Int64.Parse(thaimportantnumbers[2]); return true; } else { - newFileName = dccString.Split(' ')[2]; + NewFileName = dccString.Split(' ')[2]; - simpleirc.DebugCallBack("DCCClient2: FILENAME PARSED: " + newFileName); + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient2: FILENAME PARSED: " + NewFileName, "DCC PARSER")); if (dccString.Split(' ')[3].Contains(":")) { - newIp = dccString.Split(' ')[3]; + NewIp = dccString.Split(' ')[3]; } else { try { - simpleirc.DebugCallBack("DCCClient2: PARSING FOLLOWING IPBYTES USING NTOH: " + dccString.Split(' ')[3]); - Int64 hostmode = (Int64)IPAddress.NetworkToHostOrder(Int64.Parse(dccString.Split(' ')[3])); - string ipAddress = UInt64ToIPAddress(hostmode); - newIp = ipAddress; + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient2: PARSING FOLLOWING IPBYTES DIRECTLY: " + dccString.Split(' ')[3], "DCC PARSER")); + string ipAddress = UInt64ToIPAddress(Int64.Parse(dccString.Split(' ')[3])); + NewIp = ipAddress; } catch { - simpleirc.DebugCallBack("DCCClient2: PARSING FOLLOWING IPBYTES DIRECTLY: " + dccString.Split(' ')[3]); - string ipAddress = UInt64ToIPAddress(Int64.Parse(dccString.Split(' ')[3])); - newIp = ipAddress; + return false; } } - simpleirc.DebugCallBack("DCCClient2: IP PARSED: " + newIp); - newPortNum = int.Parse(dccString.Split(' ')[4]); - newFileSize = Int64.Parse(dccString.Split(' ')[5]); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient2: IP PARSED: " + NewIp, "DCC PARSER")); + NewPortNum = int.Parse(dccString.Split(' ')[4]); + NewFileSize = Int64.Parse(dccString.Split(' ')[5]); return true; } } else { - botName = dccString.Split('!')[0]; + BotName = dccString.Split('!')[0]; if (dccString.Contains("\"")) { - newFileName = dccString.Split('"')[1]; + NewFileName = dccString.Split('"')[1]; - simpleirc.DebugCallBack("DCCClient3: FILENAME PARSED: " + newFileName); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient3: FILENAME PARSED: " + NewFileName, "DCC PARSER")); string[] thaimportantnumbers = dccString.Split('"')[2].Trim().Split(' '); if (thaimportantnumbers[0].Contains(":")) { - newIp = thaimportantnumbers[0]; + NewIp = thaimportantnumbers[0]; } else { try { - simpleirc.DebugCallBack("DCCClient3: PARSING FOLLOWING IPBYTES USING NTOH: " + thaimportantnumbers[0]); - Int64 hostmode = (Int64)IPAddress.NetworkToHostOrder(Int64.Parse(thaimportantnumbers[0])); - string ipAddress = UInt64ToIPAddress(hostmode); - newIp = ipAddress; + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient3: PARSING FOLLOWING IPBYTES DIRECTLY: " + thaimportantnumbers[0], "DCC PARSER")); + string ipAddress = UInt64ToIPAddress(Int64.Parse(thaimportantnumbers[0])); + NewIp = ipAddress; } catch { - simpleirc.DebugCallBack("DCCClient3: PARSING FOLLOWING IPBYTES DIRECTLY: " + thaimportantnumbers[0]); - string ipAddress = UInt64ToIPAddress(Int64.Parse(thaimportantnumbers[0])); - newIp = ipAddress; + return false; } } - - simpleirc.DebugCallBack("DCCClient3: IP PARSED: " + newIp); - newPortNum = int.Parse(thaimportantnumbers[1]); - newFileSize = Int64.Parse(thaimportantnumbers[2]); + + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient3: IP PARSED: " + NewIp, "DCC PARSER")); + NewPortNum = int.Parse(thaimportantnumbers[1]); + NewFileSize = Int64.Parse(thaimportantnumbers[2]); return true; } else { - newFileName = dccString.Split(' ')[5]; + NewFileName = dccString.Split(' ')[5]; + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient4: FILENAME PARSED: " + NewFileName, "DCC PARSER")); - simpleirc.DebugCallBack("DCCClient4: FILENAME PARSED: " + newFileName); - - if(dccString.Split(' ')[6].Contains(":")) + if (dccString.Split(' ')[6].Contains(":")) { - newIp = dccString.Split(' ')[6]; + NewIp = dccString.Split(' ')[6]; } else { try { - simpleirc.DebugCallBack("DCCClient4: PARSING FOLLOWING IPBYTES USING NTOH: " + dccString.Split(' ')[6]); - Int64 hostmode = Int64.Parse(dccString.Split(' ')[6]); - string ipAddress = UInt64ToIPAddress(hostmode).ToString(); - newIp = ipAddress; + + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient4: PARSING FOLLOWING IPBYTES DIRECTLY: " + dccString.Split(' ')[6], "DCC PARSER")); + string ipAddress = UInt64ToIPAddress(Int64.Parse(dccString.Split(' ')[6])); + NewIp = ipAddress; } catch { - simpleirc.DebugCallBack("DCCClient4: PARSING FOLLOWING IPBYTES DIRECTLY: " + dccString.Split(' ')[6]); - string ipAddress = UInt64ToIPAddress(Int64.Parse(dccString.Split(' ')[6])); - newIp = ipAddress; + return false; } } - simpleirc.DebugCallBack("DCCClient4: IP PARSED: " + newIp); - newPortNum = int.Parse(dccString.Split(' ')[7]); - newFileSize = Int64.Parse(dccString.Split(' ')[8]); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient4: IP PARSED: " + NewIp, "DCC PARSER")); + NewPortNum = int.Parse(dccString.Split(' ')[7]); + NewFileSize = Int64.Parse(dccString.Split(' ')[8]); return true; } @@ -249,67 +331,104 @@ private bool parseData(string dccString) } - return false; } - //creates a tcp socket connection for the retrieved ip/port from the dcc tcp by the dcc bot/server - //and creates a file, to where it writes the incomming data from the tcp connection. + /// + /// Method ran within downloader thread, starts a connection to the file server, and receives the file accordingly, sends updates using event handler during the download. + /// public void Downloader() { - updateStatus("WAITING"); + UpdateStatus("WAITING"); //combining download directory path with filename - if (!Directory.Exists(curDownloadDir)) + if (_curDownloadDir != null) + { + if (_curDownloadDir != string.Empty) + { + if (_curDownloadDir.Length > 0) + { + if (!Directory.Exists(_curDownloadDir)) + { + Directory.CreateDirectory(_curDownloadDir); + } + } + else + { + _curDownloadDir = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Downloads"); + if (!Directory.Exists(_curDownloadDir)) + { + Directory.CreateDirectory(_curDownloadDir); + } + } + } + else + { + _curDownloadDir = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Downloads"); + if (!Directory.Exists(_curDownloadDir)) + { + Directory.CreateDirectory(_curDownloadDir); + } + } + } + else { - Directory.CreateDirectory(curDownloadDir); - } - string[] pathToCombine = new string[] { curDownloadDir, newFileName }; - string dlDirAndFileName = Path.Combine(pathToCombine); - currentFilePath = dlDirAndFileName; + _curDownloadDir = Path.Combine(Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location), "Downloads"); + if (!Directory.Exists(_curDownloadDir)) + { + Directory.CreateDirectory(_curDownloadDir); + } + } + + string dlDirAndFileName = Path.Combine(_curDownloadDir, NewFileName); + CurrentFilePath = dlDirAndFileName; try { if (!File.Exists(dlDirAndFileName)) { - simpleirc.DebugCallBack("File does not exist yet, start connection \n "); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File does not exist yet, start connection \n ", "DCC DOWNLOADER")); //start connection with tcp server - using (TcpClient dltcp = new TcpClient(newIp, newPortNum)) + using (TcpClient dltcp = new TcpClient(NewIp, NewPortNum)) { using (NetworkStream dlstream = dltcp.GetStream()) { //succesfully connected to tcp server, status is downloading - updateStatus("DOWNLOADING"); + UpdateStatus("DOWNLOADING"); //values to keep track of progress Int64 bytesReceived = 0; Int64 oldBytesReceived = 0; - Int64 oneprocent = newFileSize / 100; + Int64 oneprocent = NewFileSize / 100; DateTime start = DateTime.Now; bool timedOut = false; - simpleirc.downloadStatusChange(); //values to keep track of download position int count; //to me this buffer size seemed to be the most efficient. byte[] buffer; - if (newFileSize > 1048576) + if (NewFileSize > 1048576) { - simpleirc.DebugCallBack("DCC Downloader: Big file, big buffer (1 mb) \n "); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Big file, big buffer (1 mb) \n ", "DCC DOWNLOADER")); buffer = new byte[16384]; - } else if(newFileSize < 1048576 && newFileSize > 2048) + } else if(NewFileSize < 1048576 && NewFileSize > 2048) { - simpleirc.DebugCallBack("DCC Downloader: Smaller file (< 1 mb), smaller buffer (2 kb) \n "); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Smaller file (< 1 mb), smaller buffer (2 kb) \n ", "DCC DOWNLOADER")); buffer = new byte[2048]; - } else if (newFileSize < 2048 && newFileSize > 128) + } else if (NewFileSize < 2048 && NewFileSize > 128) { - simpleirc.DebugCallBack("DCC Downloader: Small file (< 2kb mb), small buffer (128 b) \n "); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Small file (< 2kb mb), small buffer (128 b) \n ", "DCC DOWNLOADER")); buffer = new byte[128]; } else { - simpleirc.DebugCallBack("DCC Downloader: Tiny file (< 128 b), tiny buffer (2 b) \n "); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Tiny file (< 128 b), tiny buffer (2 b) \n ", "DCC DOWNLOADER")); buffer = new byte[2]; } @@ -317,22 +436,28 @@ public void Downloader() //create file to write to using (FileStream writeStream = new FileStream(dlDirAndFileName, FileMode.Append, FileAccess.Write, FileShare.Read)) { - writeStream.SetLength(newFileSize); - isDownloading = true; + writeStream.SetLength(NewFileSize); + IsDownloading = true; //download while connected and filesize is not reached - while (dltcp.Connected && bytesReceived < newFileSize && !simpleirc.shouldClientStop) + while (dltcp.Connected && bytesReceived < NewFileSize && !_shouldAbort) { + if (_shouldAbort) + { + dltcp.Close(); + dlstream.Dispose(); + writeStream.Close(); + } //keep track of progress DateTime end = DateTime.Now; if (end.Second != start.Second) { - Bytes_Seconds = bytesReceived - oldBytesReceived; - KBytes_Seconds = (int)(Bytes_Seconds / 1024); - MBytes_Seconds = (KBytes_Seconds / 1024); + BytesPerSecond = bytesReceived - oldBytesReceived; + KBytesPerSecond = (int)(BytesPerSecond / 1024); + MBytesPerSecond = (KBytesPerSecond / 1024); oldBytesReceived = bytesReceived; start = DateTime.Now; - simpleirc.downloadStatusChange(); + UpdateStatus("DOWNLOADING"); } //count bytes received @@ -345,28 +470,6 @@ public void Downloader() bytesReceived += count; Progress = (int)(bytesReceived / oneprocent); - //check if data is still available, to avoid stalling of download thread - /* int timeOut = 0; - while (!dlstream.DataAvailable) - { - if (timeOut == 1000) - { - break; - } - else if (!dltcp.Connected) - { - break; - } - timeOut++; - Thread.Sleep(4); - } - - //stop download thread if timeout is reached - if (timeOut == 1000) - { - timedOut = true; - break; - } */ } //close all connections and streams (just to be save) @@ -374,123 +477,155 @@ public void Downloader() dlstream.Dispose(); writeStream.Close(); - //consider 95% downloaded as done, files are sequentually downloaded, sometimes download stops early, but the file still is usable - if (Progress < 95 && !simpleirc.shouldClientStop) + IsDownloading = false; + + if (_shouldAbort) { - updateStatus("FAILED"); - simpleirc.DebugCallBack("Download stopped at < 95 % finished, deleting file: " + newFileName + " \n"); - simpleirc.DebugCallBack("Download stopped at : " + bytesReceived + " bytes, a total of " + Progress + "%"); - File.Delete(dlDirAndFileName); - timedOut = false; + try + { + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Downloader Stopped", "DCC DOWNLOADER")); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File " + CurrentFilePath + " will be deleted due to aborting", "DCC DOWNLOADER")); + File.Delete(CurrentFilePath); + } + catch (Exception e) + { + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File " + CurrentFilePath + " probably doesn't exist :X", "DCC DOWNLOADER")); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs(e.ToString(), "DCC DOWNLOADER")); + } - } - else if (timedOut && Progress < 95 && !simpleirc.shouldClientStop) - { - updateStatus("FAILED: TIMED OUT"); - simpleirc.DebugCallBack("Download timed out at < 95 % finished, deleting file: " + newFileName + " \n"); - simpleirc.DebugCallBack("Download stopped at : " + bytesReceived + " bytes, a total of " + Progress + "%"); - File.Delete(dlDirAndFileName); - timedOut = false; - } else if(!simpleirc.shouldClientStop) + UpdateStatus("ABORTED"); + } else { - //make sure that in the event something happens and the downloader calls delete after finishing, the file will remain where it is. - dlDirAndFileName = ""; - updateStatus("COMPLETED"); + + //consider 95% downloaded as done, files are sequentually downloaded, sometimes download stops early, but the file still is usable + if (Progress < 95 && !_shouldAbort) + { + UpdateStatus("FAILED"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Download stopped at < 95 % finished, deleting file: " + NewFileName + " \n", "DCC DOWNLOADER")); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Download stopped at : " + bytesReceived + " bytes, a total of " + Progress + "%", "DCC DOWNLOADER")); + File.Delete(dlDirAndFileName); + timedOut = false; + + + } + else if (timedOut && Progress < 95 && !_shouldAbort) + { + UpdateStatus("FAILED: TIMED OUT"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Download timed out at < 95 % finished, deleting file: " + NewFileName + " \n", "DCC DOWNLOADER")); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Download stopped at : " + bytesReceived + " bytes, a total of " + Progress + "%", "DCC DOWNLOADER")); + File.Delete(dlDirAndFileName); + timedOut = false; + } + else if (!_shouldAbort) + { + //make sure that in the event something happens and the downloader calls delete after finishing, the file will remain where it is. + dlDirAndFileName = ""; + UpdateStatus("COMPLETED"); + } + } + _shouldAbort = false; + } } } } else { - simpleirc.DebugCallBack("File already exists, removing from xdcc que!\n"); - ircConnect.sendMsg("/msg " + botName + " xdcc remove " + packNum); - ircConnect.sendMsg("/msg " + botName + " xdcc cancel"); - updateStatus("FAILED: ALREADY EXISTS"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File already exists, removing from xdcc que!\n", "DCC DOWNLOADER")); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc remove " + PackNum); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc cancel"); + UpdateStatus("FAILED: ALREADY EXISTS"); } - simpleirc.DebugCallBack("File has been downloaded! \n File Location:" + dlDirAndFileName); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File has been downloaded! \n File Location:" + CurrentFilePath , "DCC DOWNLOADER")); } catch (SocketException e) { - simpleirc.DebugCallBack("Something went wrong while downloading the file! Removing from xdcc que to be sure! Error:\n" + e.ToString()); - ircConnect.sendMsg("/msg " + botName + " xdcc remove " + packNum); - ircConnect.sendMsg("/msg " + botName + " xdcc cancel"); - updateStatus("FAILED: CONNECTING"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Something went wrong while downloading the file! Removing from xdcc que to be sure! Error:\n" + e.ToString(), "DCC DOWNLOADER")); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc remove " + PackNum); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc cancel"); + UpdateStatus("FAILED: CONNECTING"); } catch (Exception ex) { - simpleirc.DebugCallBack("Something went wrong while downloading the file! Removing from xdcc que to be sure! Error:\n" + ex.ToString()); - ircConnect.sendMsg("/msg " + botName + " xdcc remove " + packNum); - ircConnect.sendMsg("/msg " + botName + " xdcc cancel"); - updateStatus("FAILED: UNKNOWN"); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("Something went wrong while downloading the file! Removing from xdcc que to be sure! Error:\n" + ex.ToString(), "DCC DOWNLOADER")); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc remove " + PackNum); + _ircClient.SendMessageToAll("/msg " + BotName + " xdcc cancel"); + UpdateStatus("FAILED: UNKNOWN"); } - simpleirc.downloadStatusChange(); - isDownloading = false; + IsDownloading = false; } - //updates status about the download besides the progress, such as if it failed etc... - private void updateStatus(string statusin) + /// + /// Fires the event with the update about the download currently running + /// + /// the current status of the download + private void UpdateStatus(string statusin) { Status = statusin; - simpleirc.downloadStatusChange(); + OnDccEvent?.Invoke(this, new DCCEventArgs(this)); } - //stops the downloader - public void abortDownloader() + /// + /// Stops a download if one is running, checks if the donwnloader thread actually stops. + /// + /// true if stopped succesfully + public bool AbortDownloader(int timeOut) { - simpleirc.DebugCallBack("Downloader Stopped"); - if (isDownloading) + if (CheckIfDownloading()) { + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("File " + CurrentFilePath + " will be deleted after aborting.", "DCC DOWNLOADER")); - isDownloading = false; - downloader.Abort(); - - try - { - simpleirc.DebugCallBack("File " + currentFilePath + " will be deleted due to aborting"); - File.Delete(currentFilePath); - } - catch (Exception e) - { - simpleirc.DebugCallBack("File " + currentFilePath + " probably doesn't exist :X"); - simpleirc.DebugCallBack(e.ToString()); - } - } - - updateStatus("ABORTED"); - - simpleirc.downloadStatusChange(); - } - - public bool checkIfDownloading() - { - return isDownloading; - } + _shouldAbort = true; - private int lastIndexOfFileName(string str) - { - int strlen = str.Length; - int i; - for (i = strlen - 1; i > 0; i--) - { - if(str[i] != ' ') + int timeout = 0; + while (CheckIfDownloading()) { - - int value = 0; - int.TryParse(str[i].ToString(), out value); - if(value < 0) + if (timeout > timeOut) { - return i; + return false; } - + timeout++; + Thread.Sleep(1); } + return true; + } + else + { + return false; } - return -1; } + /// + /// Checks if download is still running. + /// + /// true if still downloading + public bool CheckIfDownloading() + { + return IsDownloading; + } + + /// + /// Removes special characters from a string (used for filenames) + /// + /// string to parse + /// string wihtout special chars private string RemoveSpecialCharacters(string str) { StringBuilder sb = new StringBuilder(); @@ -504,24 +639,38 @@ private string RemoveSpecialCharacters(string str) return sb.ToString(); } - private string reverseIp(string ip) + /// + /// Reverses IP from little endian to big endian or vice versa depending on what succeeds. + /// + /// ip string + /// reversed ip string + private string ReverseIp(string ip) { string[] parts = ip.Trim().Split('.'); if(parts.Length >= 3) { - simpleirc.DebugCallBack("DCCClient: converting ip: " + ip); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient: converting ip: " + ip, "DCC IP PARSER")); string newip = parts[3] + "." + parts[2] + "." + parts[1] + "." + parts[0]; - simpleirc.DebugCallBack("DCCClient: to: " + newip); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient: to: " + newip, "DCC IP PARSER")); return newip; } else { - simpleirc.DebugCallBack("DCCClient: converting ip: " + ip); - simpleirc.DebugCallBack("DCCClient: amount of parts: " + parts.Length); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient: converting ip: " + ip, "DCC IP PARSER")); + OnDccDebugMessage?.Invoke(this, + new DCCDebugMessageArgs("DCCClient: amount of parts: " + parts.Length, "DCC IP PARSER")); return "0.0.0.0"; } } + /// + /// Converts a long/int64 to a ip string. + /// + /// int64 numbers representing IP address + /// string with ip private string UInt64ToIPAddress(Int64 address) { string ip = string.Empty; diff --git a/SimpleIRCLib/DCCEventArgs.cs b/SimpleIRCLib/DCCEventArgs.cs new file mode 100644 index 0000000..e1fd54a --- /dev/null +++ b/SimpleIRCLib/DCCEventArgs.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleIRCLib +{ + /// + /// Event Class for containing eventhandler for Download Updates from DCCClient.cs + /// + public class DCCEventArgs + { + /// + /// Event for download updates, containging information about the following + /// DccString : unparsed dccstring received from server, handy for debugging purposes + /// FileName : file that is currently being downloaded + /// FileSize : size of file that is currently being downloaded + /// Ip : server address where file originates from + /// Port : port of server where file originates from + /// Pack : original pack that the user requested + /// Bot : original bot where the user requested a pack (file) from + /// BytesPerSecond : current download speed in bytes p/s + /// KBytesPerSecond : current download speed in kbytes p/s + /// MBytesPerSecond : current download speed in mbytes p/s + /// Status : current download status, for example: (WAITING, DOWNLOADING, FAILED, ABORTED, etc) + /// Progress : percentage downloaded (0-100%) (is int!) + /// + /// + public DCCEventArgs(DCCClient currentClient) + { + DccString = currentClient.NewDccString; + FileName = currentClient.NewFileName; + FileSize = currentClient.NewFileSize; + Ip = currentClient.NewIp; + Port = currentClient.NewPortNum; + Pack = currentClient.PackNum; + Bot = currentClient.BotName; + BytesPerSecond = currentClient.BytesPerSecond; + KBytesPerSecond = currentClient.KBytesPerSecond; + MBytesPerSecond = currentClient.MBytesPerSecond; + Status = currentClient.Status; + Progress = currentClient.Progress; + FilePath = currentClient.CurrentFilePath; + } + + /// + /// Raw DCC String used for getting the file location (server) and some basic file information + /// + public string DccString { get; } + /// + /// File name of the file being downloaded + /// + public string FileName { get; } + /// + /// FileSize of the file being downloaded + /// + public Int64 FileSize { get; } + /// + /// Server address of file location + /// + public string Ip { get; } + /// + /// Port of server of file location + /// + public int Port { get; } + /// + /// Pack ID of file on bot where file resides + /// + public string Pack { get; } + /// + /// Bot name where file resides + /// + public string Bot { get; } + /// + /// Download speed in: B/s + /// + public long BytesPerSecond { get; } + /// + /// Download speed in: KB/s + /// + public int KBytesPerSecond { get; } + /// + /// Download speed in: MB/s + /// + public int MBytesPerSecond { get; } + /// + /// Download status, such as: WAITING,DOWNLOADING,FAILED:[ERROR],ABORTED + /// + public string Status { get; } + /// + /// Progress from 0-100 (%) + /// + public int Progress { get; } + /// + /// Path to file that is being downloaded + /// + public string FilePath { get; } + } + + /// + /// Event Class for handeling debug events fired within DCCClient.cs + /// + public class DCCDebugMessageArgs + { + /// + /// Event for debug messages specific to the DCC Client + /// + /// debug message + /// type of debug message, handy for determing where message occured + public DCCDebugMessageArgs(string message, string type) + { + Message = message; + Type = type; + } + + /// + /// Containing debug message + /// + public string Message { get; } + /// + /// Containing debug type + /// + public string Type { get; } + } +} diff --git a/SimpleIRCLib/Home.md b/SimpleIRCLib/Home.md new file mode 100644 index 0000000..bdb595f --- /dev/null +++ b/SimpleIRCLib/Home.md @@ -0,0 +1,14 @@ +# References + +## [SimpleIRCLib](SimpleIRCLib) + +- [`DCCClient`](SimpleIRCLib#dccclient) +- [`DCCDebugMessageArgs`](SimpleIRCLib#dccdebugmessageargs) +- [`DCCEventArgs`](SimpleIRCLib#dcceventargs) +- [`IrcClient`](SimpleIRCLib#ircclient) +- [`IrcDebugMessageEventArgs`](SimpleIRCLib#ircdebugmessageeventargs) +- [`IrcRawReceivedEventArgs`](SimpleIRCLib#ircrawreceivedeventargs) +- [`IrcReceivedEventArgs`](SimpleIRCLib#ircreceivedeventargs) +- [`IrcUserListReceivedEventArgs`](SimpleIRCLib#ircuserlistreceivedeventargs) +- [`SimpleIRC`](SimpleIRCLib#simpleirc) + diff --git a/SimpleIRCLib/IrcClient.cs b/SimpleIRCLib/IrcClient.cs new file mode 100644 index 0000000..3fd8312 --- /dev/null +++ b/SimpleIRCLib/IrcClient.cs @@ -0,0 +1,788 @@ +using System; +using System.Threading.Tasks; +using System.Net.Sockets; +using System.IO; +using System.Threading; +using System.Text.RegularExpressions; +using System.Collections.Generic; +using System.Diagnostics; +using System.Net.Security; + +namespace SimpleIRCLib +{ + /// + /// For running IRC Client logic, here is where all the magic happens + /// + public class IrcClient + { + + /// + /// Event Handler for firing when a (parsed) message is received on a channel from other users, event uses IrcReceivedEventArgs from IrcEventArgs.cs + /// + public event EventHandler OnMessageReceived; + + /// + /// Event Handler for firing when a raw message is received from the irc server, event uses IrcRawReceivedEventArgs from IrcEventArgs.cs + /// + public event EventHandler OnRawMessageReceived; + + /// + /// Event Handler for firing when a list with users per channel is received, event uses IrcUserListReceivedEventArgs from IrcEventArgs.cs + /// + public event EventHandler OnUserListReceived; + + /// + /// Event Handler for firing when this client wants to send a debug message, event uses IrcDebugMessageEventArgs from IrcEventArgs.cs + /// + public event EventHandler OnDebugMessage; + + /// + /// Port of irc server to connect to + /// + private int _newPort; + /// + /// Username to register on irc server + /// + private string _newUsername; + /// + /// Password to connect to a secured irc server + /// + private string _newPassword; + /// + /// Ip address of irc server + /// + private string _newIp; + /// + /// List with channels seperated with ',' to join when connecting to IRC server + /// + private string _newChannels; + /// + /// timeout before throwing timeout errors (using OnDebugMessage) + /// + private int _timeOut; + /// + /// download directory used for DCCClient.cs + /// + private string _downloadDirectory; + /// + /// for enabling tls/ssl secured connection to the irc server + /// + private bool _enableSSL; + /// + /// for checking if a connection is succesfully established + /// + private bool _isConnectionEstablised; + /// + /// for checking if client has started listening to the server + /// + private bool _isClientRunning; + /// + /// packnumber used to initialize dccclient downloader + /// + private string _packNumber; + /// + /// bot used to initialzie dccclient downloader + /// + private string _bot; + /// + /// DCCClient to be used for downloading + /// + private DCCClient _dccClient; + /// + /// TcpClient used for connecting to the irc server + /// + private TcpClient _tcpClient; + /// + /// IRC commands + /// + private IrcCommands _ircCommands; + /// + /// unsecure network stream for listening and reading to the irc server + /// + private NetworkStream _networkStream; + /// + /// secure network stream for listening and reading to the irc server + /// + private SslStream _networkSStream; + /// + /// used for writing to the irc server + /// + private StreamWriter _streamWriter; + /// + /// used for reading from the irc server + /// + private StreamReader _streamReader; + /// + /// receiver task + /// + private Task _receiverTask = null; + /// + /// used for stopping the receiver task + /// + private bool _stopTask = false; + + /// + /// Default constructor, needed so that the client can register events before starting the connection. + /// + public IrcClient() + { + _isConnectionEstablised = false; + _isClientRunning = false; + } + + /// + /// Sets up the information needed for the client to start a connection to the irc server. Sends a warning to the debug message event if ports are out of the standard specified ports for IRC. + /// + /// Server address, possibly works with dns addresses (irc.xxx.x), but ip addresses works just fine (of type string) + /// Username the client wants to use, of type string + /// Channel(s) the client wants to connect to, possible to connect to multiple channels at once by seperating each channel with a ',' (Example: #chan1,#chan2), of type string + /// DCC Client, for downloading using the DCC protocol + /// Download Directory, used by the DCC CLient to store files in the specified directory, if left empty, it will create a "Download" directory within the same folder where this library resides + /// Port, optional parameter, where default = 0 (Automatic port selection), is port of the server you want to connect to, of type int + /// Password, optional parameter, where default value is "", can be used to connect to a password protected server. + /// Timeout, optional parameter, where default value is 3000 milliseconds, the maximum time before a server needs to answer, otherwise errors are thrown. + /// Timeout, optional parameter, where default value is 3000 milliseconds, the maximum time before a server needs to answer, otherwise errors are thrown. + public void SetConnectionInformation(string ip, string username, string channels, + DCCClient dccClient, string downloadDirectory, int port = 0, string password = "", int timeout = 3000, bool enableSSL = true) + { + _newIp = ip; + _newPort = port; + _newUsername = username; + _newPassword = password; + _newChannels = channels; + _isConnectionEstablised = false; + _isClientRunning = false; + _timeOut = timeout; + _downloadDirectory = downloadDirectory; + _dccClient = dccClient; + _enableSSL = enableSSL; + + if (_enableSSL) + { + if (port == 0) + { + port = 6697; + } else if (port != 6697) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("PORT: " + port.ToString() + " IS NOT COMMONLY USED FOR TLS/SSL CONNECTIONS, PREFER TO USE 6697 FOR SSL!", "SETUP WARNING")); + } + } + else + { + if (port == 0) + { + port = 6667; + } + else if (port < 6665 && port > 6669) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("PORT: " + port.ToString() + " IS NOT COMMONLY USED FOR NON TLS/SSL CONNECTIONS, PREFER TO USE PORTS BETWEEN 6665 & 6669!", "SETUP WARNING")); + } + } + } + + /// + /// Changes the download directory, will apply to the next instantiated download. + /// + /// Path to directory (creates it if it does not exist) + public void SetDownloadDirectory(string downloadDirectory) + { + _downloadDirectory = downloadDirectory; + } + + /// + /// Starts the connection to the irc server, sends the register user command and register nick name command. + /// + /// true/false depending if error coccured + public bool Connect() + { + try + { + _isConnectionEstablised = false; + _receiverTask = new Task(StartReceivingChat); + _receiverTask.Start(); + + int timeout = 0; + while (!_isConnectionEstablised) + { + Thread.Sleep(1); + if (timeout > _timeOut) + { + return false; + } + timeout++; + } + + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("IRC CLIENT SUCCESFULLY RUNNING", "IRC SETUP")); + return true; + } + catch(Exception e) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(e.ToString(), "SETUP ERROR")); + return false; + } + } + + /// + /// Starts quiting procedure, sends QUIT message to server and waits until server closes connection with the client, after that, it shuts down every reader and stream, and stops the receiver task. + /// + /// + public bool QuitConnect() + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("STARTING IRC CLIENT SHUTDOWN PROCEDURE", "QUIT")); + //send quit to server + if (WriteIrc("QUIT")) + { + int timeout = 0; + while (_tcpClient.Connected) + { + Thread.Sleep(1); + if (timeout >= _timeOut) + { + return false; + } + timeout++; + } + + _stopTask = true; + Thread.Sleep(200); + //stop everything in right order + _receiverTask.Dispose(); + _streamReader.Dispose(); + _networkStream.Close(); + _streamWriter.Close(); + _tcpClient.Close(); + + _isConnectionEstablised = false; + + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("FINISHED SHUTDOWN PROCEDURE", "QUIT")); + return true; + + } + else + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("COULD NOT WRITE QUIT COMMAND TO SERVER", "QUIT")); + return true; + } + } + + /// + /// Starts the receiver task. + /// + public void StartReceivingChat() + { + + _tcpClient = new TcpClient(_newIp, _newPort); + + int timeout = 0; + while (!_tcpClient.Connected) + { + Thread.Sleep(1); + + if (timeout >= _timeOut) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("TIMEOUT, COULD NOT CONNECT TO TCP SOCKET", "IRC SETUP")); + } + timeout++; + } + + + if (!_enableSSL) + { + _networkStream = _tcpClient.GetStream(); Thread.Sleep(500); + _streamReader = new StreamReader(_networkStream); + _streamWriter = new StreamWriter(_networkStream); + _ircCommands = new IrcCommands(_networkStream); + } + else + { + _networkSStream = new SslStream(_tcpClient.GetStream()); + _networkSStream.AuthenticateAsClient(_newIp); + _streamReader = new StreamReader(_networkSStream); + _streamWriter = new StreamWriter(_networkSStream); + _ircCommands = new IrcCommands(_networkSStream); + + } + + + _isConnectionEstablised = true; + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("CONNECTED TO TCP SOCKET", "IRC SETUP")); + + + + if (_newPassword.Length > 0) + { + if (!_ircCommands.SetPassWord(_newPassword)) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(_ircCommands.GetErrorMessage(), "IRC SETUP ERROR")); + _isConnectionEstablised = false; + } + } + Debug.WriteLine("Joining channels: " + _newChannels); + if (!_ircCommands.JoinNetwork(_newUsername, _newChannels)) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(_ircCommands.GetErrorMessage(), "IRC SETUP ERROR")); + _isConnectionEstablised = false; + } + + if (_isConnectionEstablised) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("CONNECTED TO IRC SERVER", "IRC SETUP")); + + Task.Run(() => ReceiveChat()); + } + } + + /// + /// Receiver task, receives messages from server, handles joining intial join to channels, if the server responses with a 004 (which is a welcome message, meaning it has succesfully connected) + /// Handles PRIVMSG messages + /// Handles PING messages + /// Handles JOIN messages + /// Handles QUIT messages (though it's not yet possible to determine which channels these users have left + /// Handles 353 messages (Users list per channel) + /// Handles 366 messages (Finished user list per channel) + /// Handles DCC SEND messages + /// + private void ReceiveChat() + { + try + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("STARTING LISTENER!", "IRC RECEIVER")); + + + Dictionary> usersPerChannelDictionary = new Dictionary>(); + + _isClientRunning = true; + _isConnectionEstablised = true; + while (!_stopTask) + { + + string ircData = _streamReader.ReadLine(); + + OnRawMessageReceived?.Invoke(this, new IrcRawReceivedEventArgs(ircData)); + + + + if (ircData.Contains("PING")) + { + string pingID = ircData.Split(':')[1]; + WriteIrc("PONG :" + pingID); + } + if ( ircData.Contains("PRIVMSG")) + { + + + //:MrRareie!~MrRareie_@Rizon-AC4B78B2.cm-3-2a.dynamic.ziggo.nl PRIVMSG #RareIRC :wassup + + + + try + { + string messageAndChannel = ircData.Split(new string[] { "PRIVMSG" }, StringSplitOptions.None)[1]; + string message = messageAndChannel.Split(':')[1].Trim(); + string channel = messageAndChannel.Split(':')[0].Trim(); + string user = ircData.Split(new string[] { "PRIVMSG" }, StringSplitOptions.None)[0].Split('!')[0].Substring(1); + + channel = channel.Replace("=", string.Empty); + + OnMessageReceived?.Invoke(this, new IrcReceivedEventArgs(message, user, channel)); + } + catch(Exception ex) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(ex.ToString(), "MESSAGE RECEIVED ERROR (PRIVMSG)")); + } + + + + } + else if (ircData.Contains("JOIN")) + { + //RAW: :napiz!~napiz@Rizon-BF96A69D.dyn.optonline.net JOIN :#NIBL + + + try + { + string channel = ircData.Split(new string[] { "JOIN" }, StringSplitOptions.None)[1].Split(':')[1]; + string userThatJoined = ircData.Split(new string[] { "JOIN" }, StringSplitOptions.None)[0].Split(':')[1].Split('!')[0]; + + OnMessageReceived?.Invoke(this, new IrcReceivedEventArgs("User Joined", userThatJoined, channel)); + } + catch (Exception ex) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(ex.ToString(), "MESSAGE RECEIVED ERROR (JOIN)")); + } + + } + else if (ircData.Contains("QUIT")) + { + + //RAW: :MrRareie!~MrRareie_@Rizon-AC4B78B2.cm-3-2a.dynamic.ziggo.nl QUIT + try + { + string user = ircData.Split(new string[] { "QUIT" }, StringSplitOptions.None)[0].Split('!')[0].Substring(1); + + OnMessageReceived?.Invoke(this, new IrcReceivedEventArgs("User Left", user, "unknown")); + } + catch (Exception ex) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(ex.ToString(), "MESSAGE RECEIVED ERROR (JOIN)")); + }; + } + + if (ircData.Contains("DCC SEND") && ircData.Contains(_newUsername)) + { + _dccClient.StartDownloader(ircData, _downloadDirectory, _bot, _packNumber, this); + } + + //RareIRC_Client = #weebirc :RareIRC_Client + if (ircData.Contains(" 353 ")) + { + //:irc.x2x.cc 353 RoflHerp = #RareIRC :RoflHerp @MrRareie + try + { + string channel = ircData.Split(new[] { " " + _newUsername + " ="}, StringSplitOptions.None)[1].Split(':')[0].Replace(" ", string.Empty); + string userListFullString = ircData.Split(new[] { " " + _newUsername + " =" }, StringSplitOptions.None)[1].Split(':')[1]; + + + if (!channel.Contains(_newUsername) && !channel.Contains("=")) + { + string[] users = userListFullString.Split(' '); + if (usersPerChannelDictionary.ContainsKey(channel)) + { + usersPerChannelDictionary.TryGetValue(channel, out var currentUsers); + + + foreach (string name in users) + { + if (!name.Contains(_newUsername)) + { + currentUsers.Add(name); + } + } + usersPerChannelDictionary[channel.Trim()] = currentUsers; + } + else + { + List currentUsers = new List(); + foreach (string name in users) + { + currentUsers.Add(name); + } + usersPerChannelDictionary.Add(channel.Trim(), currentUsers); + } + } + + + } catch (Exception ex) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs(ex.ToString(), "MESSAGE RECEIVED ERROR (USERLIST)")); + } + + + } + + if(ircData.ToLower().Contains(" 366 ")) + { + OnUserListReceived?.Invoke(this, new IrcUserListReceivedEventArgs(usersPerChannelDictionary)); + usersPerChannelDictionary.Clear(); + } + Thread.Sleep(1); + } + + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("RECEIVER HAS STOPPED RUNNING", "MESSAGE RECEIVER")); + + QuitConnect(); + _stopTask = false; + } catch (Exception ioex) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("LOST CONNECTION: " + ioex.ToString(), "MESSAGE RECEIVER")); + if (_isConnectionEstablised) + { + _stopTask = false; + QuitConnect(); + } + } + _isClientRunning = false; + } + + + /// + /// Sends message to all channels, if message is not one of the following: + /// /msg [bot] xdcc send [pack] + /// /msg [bot] xdcc cancel + /// /msg [bot] xdcc remove [pack] + /// /names [channels] (can be empty) + /// /join [channels] + /// /quit + /// /msg [channel/user] [message] + /// + /// String to send + /// true / false depending if it could send the message to the server + public bool SendMessageToAll(string input) + { + + input = input.Trim(); + Regex regex1 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(send)+(\s))(.*)))"); + Match matches1 = regex1.Match(input.ToLower()); + Regex regex2 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(cancel))(.*)))"); + Match matches2 = regex2.Match(input.ToLower()); + Regex regex3 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(remove)+(\s))(.*)))"); + Match matches3 = regex3.Match(input.ToLower()); + + if (matches1.Success) + { + _bot = matches1.Groups["botname"].Value.Trim(); + _packNumber = matches1.Groups["packnum"].Value.Trim(); + string xdccdl = "PRIVMSG " + _bot + " :XDCC SEND " + _packNumber; + return WriteIrc(xdccdl); + } + else if (matches2.Success) + { + _bot = matches2.Groups["botname"].Value; + string xdcccl = "PRIVMSG " + _bot + " :XDCC CANCEL"; + return WriteIrc(xdcccl); + } + else if (matches3.Success) + { + _bot = matches3.Groups["botname"].Value; + _packNumber = matches3.Groups["packnum"].Value; + string xdccdl = "PRIVMSG " + _bot + " :XDCC REMOVE " + _packNumber; + return WriteIrc(xdccdl); + } else if (input.ToLower().Contains("/names")) + { + if (input.Contains("#")) + { + string channelList = input.Split(' ')[1]; + return GetUsersInChannel(channelList); + } else + { + return GetUsersInChannel(""); + } + } + else if (input.ToLower().Contains("/join")) + { + + if (input.Split(' ').Length > 0) + { + string channels = input.Split(' ')[1]; + _newChannels += channels; + return WriteIrc("JOIN " + channels); + } + else + { + return false; + } + } + else if(input.ToLower().Contains("/quit")) + { + return QuitConnect(); + } + else if (input.ToLower().Contains("/msg")) + { + return WriteIrc(input.Replace("/msg", "PRIVMSG")); + } + else + { + return WriteIrc("PRIVMSG " + _newChannels + " :" + input); + } + + + } + + + /// + /// Sends message to specific channels, if message is not one of the following: + /// /msg [bot] xdcc send [pack] + /// /msg [bot] xdcc cancel + /// /msg [bot] xdcc remove [pack] + /// /names [channels] (can be empty) + /// /join [channels] + /// /quit + /// /msg [channel/user] [message] + /// + /// String to send + /// Channel(s) to send to + /// true / false depending if it could send the message to the server + public bool SendMessageToChannel(string input, string channel) + { + + input = input.Trim(); + Regex regex1 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(send)+(\s))(.*)))"); + Match matches1 = regex1.Match(input.ToLower()); + Regex regex2 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(cancel))(.*)))"); + Match matches2 = regex2.Match(input.ToLower()); + Regex regex3 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(remove)+(\s))(.*)))"); + Match matches3 = regex3.Match(input.ToLower()); + + if (matches1.Success) + { + _bot = matches1.Groups["botname"].Value.Trim(); + _packNumber = matches1.Groups["packnum"].Value.Trim(); + string xdccdl = "PRIVMSG " + _bot + " :XDCC SEND " + _packNumber; + return WriteIrc(xdccdl); + } + else if (matches2.Success) + { + _bot = matches2.Groups["botname"].Value; + string xdcccl = "PRIVMSG " + _bot + " :XDCC CANCEL"; + return WriteIrc(xdcccl); + } + else if (matches3.Success) + { + _bot = matches3.Groups["botname"].Value; + _packNumber = matches3.Groups["packnum"].Value; + string xdccdl = "PRIVMSG " + _bot + " :XDCC REMOVE " + _packNumber; + return WriteIrc(xdccdl); + } + else if (input.ToLower().Contains("/names")) + { + if (input.Contains("#")) + { + string channelList = input.Split(' ')[1]; + return GetUsersInChannel(channelList); + } + else + { + return GetUsersInChannel(channel); + } + } + else if (input.ToLower().Contains("/join")) + { + + if (input.Split(' ').Length > 0) + { + string channels = input.Split(' ')[1]; + _newChannels += channels; + return WriteIrc("JOIN " + channels); + } + else + { + return false; + } + } + else if (input.ToLower().Contains("/quit")) + { + return QuitConnect(); + } + else if (input.ToLower().Contains("/msg")) + { + return WriteIrc(input.Replace("/msg", "PRIVMSG")); + } + else + { + return WriteIrc("PRIVMSG " + channel + " :" + input); + } + + + } + + /// + /// Sends a raw message to the irc server, without any parsing applied + /// + /// message to send + /// true/false depending if it could write to the irc server + public bool SendRawMsg(string msg) + { + return WriteIrc(msg); + } + + /// + /// Sends the get names for specific channels or for all channels. + /// + /// channel, optional, default = "" = all channels, + /// true/false depending if it could write to the server + public bool GetUsersInChannel(string channel = "") + { + + if (channel != "") + { + return WriteIrc("NAMES " + channel); + } else + { + return WriteIrc("NAMES " + _newChannels); + } + } + + /// + /// Writes a message to the irc server. + /// + /// Message to send + /// true/false depending if it could write to the irc server + public bool WriteIrc(string input) + { + try + { + _streamWriter.Write(input + Environment.NewLine); + _streamWriter.Flush(); + return true; + } catch(Exception e) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("Could not send message" + input + ", _tcpClient client is not running :X, error : " + e.ToString(), "MESSAGE SENDER")); ; + return false; + } + + } + + /// + /// Stops a download if a download is running. + /// + /// true/false depending if an error occured or not + public bool StopXDCCDownload() + { + try + { + return !_dccClient.AbortDownloader(_timeOut); + } catch (Exception e) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("Could not stop XDCC Download, error: " + e.ToString(), "IRC CLIENT XDCC STOP")); + return true; + } + } + + /// + /// Checks if a download is running or not. + /// + /// true/false depending if a download is running, or if an error occured + public bool CheckIfDownloading() + { + try + { + return _dccClient.CheckIfDownloading(); + } + catch (Exception e) + { + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("Could not check if download has started, error: " + e.ToString(), "IRC CLIENT XDCC CHECK")); + return false; + } + } + + /// + /// Stops the client. + /// + public bool StopClient() + { + _stopTask = true; + OnDebugMessage?.Invoke(this, new IrcDebugMessageEventArgs("CLOSING CLIENT", "CLOSE")); + return QuitConnect(); + } + + /// + /// Gets the connection status + /// + /// true/false depending on the connection status + public bool IsConnectionEstablished() + { + return _isConnectionEstablised; + } + + /// + /// Gets the client status + /// + /// true or false depending if the client is running or not + public bool IsClientRunning() + { + return _isClientRunning; + } + } + + +} diff --git a/SimpleIRCLib/IrcCommands.cs b/SimpleIRCLib/IrcCommands.cs new file mode 100644 index 0000000..9aa6bd6 --- /dev/null +++ b/SimpleIRCLib/IrcCommands.cs @@ -0,0 +1,250 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Net.Security; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace SimpleIRCLib +{ + /// + /// Class used for sending specific commands to the IRC server, and waiting for responses from the irc server before continueing + /// + class IrcCommands + { + /// + /// reader to read from the irc server stream + /// + private readonly StreamReader _reader; + + /// + /// reader to write to the irc server stream + /// + private readonly StreamWriter _writer; + + /// + /// global error message string, used for defining the error that might occur with a readable string + /// + private string _errorMessage; + + /// + /// global error integer, used for getting the specific error id specified within the IRC specs RFC 2812 + /// + private int _responseNumber; + + /// + /// user name that has been registered + /// + private string _username; + + /// + /// channel that hs been joined + /// + private string _channels; + + /// + /// Constructor, that requires the stream reader and writer set before initializing + /// + /// Stream to read/write from/to + public IrcCommands(NetworkStream stream) + { + _reader = new StreamReader(stream); + _writer = new StreamWriter(stream); + } + + /// + /// Constructor, that requires the stream reader and writer set before initializing + /// + /// Stream to read/write from/to + public IrcCommands(SslStream stream) + { + _reader = new StreamReader(stream); + _writer = new StreamWriter(stream); + } + + /// + /// Get the error message that probably has occured when calling this method + /// + /// returns error message + public string GetErrorMessage() + { + return _errorMessage; + } + + /// + /// Get the error number that probably had occured when calling this method (RFC 2812) + /// + /// + public int GetErrorNumber() + { + return _responseNumber; + } + + /// + /// Sets the password for a connection and waits if there is any response, if there is, it may continue, unless the reponse contains an error message + /// + /// password to set + /// true/false depending if error occured + public bool SetPassWord(string password) + { + _writer.WriteLine("PASS " + password + Environment.NewLine); + _writer.Flush(); + + while (true) + { + string ircData = _reader.ReadLine(); + if (ircData.Contains("462")) + { + _responseNumber = 462; + _errorMessage = "PASSWORD ALREADY REGISTERED"; + return false; + } + else if (ircData.Contains("461")) + { + _responseNumber = 461; + _errorMessage = "PASSWORD COMMAND NEEDS MORE PARAMETERS"; + return false; + }else if (ircData.Contains("004")) + { + return true; + } + else + { + return true; + } + } + } + + /// + /// Sends the user and nick command to the irc server, checks for error messages (it waits for a reply to come through first, before deciding what to do). + /// + /// Username + /// + /// True/False, depending if error occured or not + public bool JoinNetwork(string user, string channels) + { + _username = user; + _channels = channels; + _writer.WriteLine("NICK " + user + Environment.NewLine); + _writer.Flush(); + _writer.WriteLine("USER " + user + " 8 * :" + user + "_SimpleIRCLib" + Environment.NewLine); + _writer.Flush(); + + while (true) + { + try + { + string ircData = _reader.ReadLine(); + Debug.WriteLine("Got response: " + ircData); + + if (ircData.Contains("PING")) + { + string pingID = ircData.Split(':')[1]; + _writer.WriteLine("PONG :" + pingID); + _writer.Flush(); + } + + if (CheckMessageForError(ircData)) + { + Debug.WriteLine("Checking 266 = " + _responseNumber.ToString()); + if (_responseNumber == 266) + { + return JoinChannel(channels, user); + } + } + } + catch (Exception e) + { + + } + } + } + + /// + /// Sends a join request to the irc server, then waits for a response before continueing + /// + /// Channels to join + /// Username that joins + /// True on sucess, false on error + public bool JoinChannel(string channels, string username) + { + _channels = channels; + + _writer.WriteLine("JOIN " + channels + Environment.NewLine); + _writer.Flush(); + Debug.WriteLine("Joining channel: " + _channels) ; + while (true) + { + + string ircData = _reader.ReadLine(); + Debug.WriteLine("Response: " + ircData); + if (ircData.Contains("PING")) + { + string pingID = ircData.Split(':')[1]; + _writer.WriteLine("PONG :" + pingID); + _writer.Flush(); + } + + if (ircData.Contains(username) && ircData.Contains("JOIN")) + { + return true; + } + + } + } + + /// + /// Sends the set nickname request to the server, waits until a response is given from the server before deciding to continue + /// + /// Nickname + /// True on success, false on error. + public bool SetNickName(string nickname) + { + _writer.WriteLine("NICK " + nickname + Environment.NewLine); + _writer.Flush(); + + while (true) + { + + string ircData = _reader.ReadLine(); + + return CheckMessageForError(ircData); + } + } + + + + public bool CheckMessageForError(string message) + { + string codeString = message.Split(' ')[1].Trim(); + Debug.WriteLine("CODE: " + codeString); + if (int.TryParse(codeString, out _responseNumber)) + { + Debug.WriteLine("parsed number: " + _responseNumber.ToString()); + foreach (string errorMessage in RFC1459Codes.ListWithErrors) + { + if (errorMessage.Contains(codeString)) + { + _errorMessage = errorMessage; + return false; + } + } + + _errorMessage = "Message does not contain Error Code!"; + return true; + } + else + { + Debug.WriteLine("Could not parse number"); + _responseNumber = 0; + _errorMessage = "Message does not contain Error Code, could not parse number!"; + return true; + } + + + } + } +} diff --git a/SimpleIRCLib/IrcConnect.cs b/SimpleIRCLib/IrcConnect.cs deleted file mode 100644 index de8d2f8..0000000 --- a/SimpleIRCLib/IrcConnect.cs +++ /dev/null @@ -1,438 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Net.Sockets; -using System.IO; -using System.Threading; -using System.Text.RegularExpressions; -using System.Collections.Generic; -using System.Linq; - -namespace SimpleIRCLib -{ - public class IrcConnect - { - //main connecting details - public int newPort; - public string newUsername; - public string newPassword; - public string newIP; - public string newChannel; - - //global var to check if connection is establised - public bool isConnectionEstablised { get; set; } - - //for dcc downloader, information about dcc - public string packNumber { get; set; } - public string bot { get; set; } - - //all seperate classes initialized - private DCCClient dcc { get; set; } - private SimpleIRC simpleirc { get; set; } - - //connection - private StreamReader reader; - private NetworkStream stream; - private TcpClient irc; - private static StreamWriter writer; - - //async task for receiving messages from the server - private Task receiverTask = null; - - private bool stopTask = false; - - //for userlist - public List Users = new List(); - - //Overload Constructor - safe way to get variables - public IrcConnect(string IP, int Port, string Username, string Password, string Channel, SimpleIRC sirc) - { - newIP = IP; - newPort = Port; - newUsername = Username; - newPassword = Password; - newChannel = Channel; - simpleirc = sirc; - isConnectionEstablised = false; - dcc = new DCCClient(simpleirc, this); - Users = new List(); - } - - //connects to irc server, gives a boolean back on succesfull connect etc - public bool Connect() - { - try - { - irc = new TcpClient(newIP, newPort); - stream = irc.GetStream(); - - reader = new StreamReader(stream); - writer = new StreamWriter(stream); - - receiverTask = new Task(StartReceivingChat); - receiverTask.Start(); - receiverTask.Wait(); - if(newPassword.Length > 0) - { - writeIrc("PASS " + newPassword); - Thread.Sleep(100); - } - - writeIrc("USER " + newUsername + " 8 * :" + newUsername + "_SimpleIRCLib"); - Thread.Sleep(500); - writeIrc("NICK " + newUsername); - Thread.Sleep(500); - writeIrc("JOIN 0"); - Thread.Sleep(500); - writeIrc("JOIN " + newChannel); - Thread.Sleep(500); - simpleirc.DebugCallBack("succesful connected to the irc server!"); - return true; - } - catch(Exception e) - { - simpleirc.DebugCallBack("Error Connecting to IRC Server: \n " + e.ToString()); - return false; - } - } - - - public void quitConnect() - { - simpleirc.DebugCallBack("\n STARTING QUITING PROCEDURE "); - - //send quit to server - writeIrc("QUIT"); - - simpleirc.shouldClientStop = true; - stopTask = true; - simpleirc.DebugCallBack("\n WRITTEN QUIT "); - Thread.Sleep(200); - //stop everything in right order - receiverTask.Dispose(); - reader.Dispose(); - stream.Close(); - writer.Close(); - irc.Close(); - - simpleirc.DebugCallBack("\n CLOSED EVERY THING "); - isConnectionEstablised = false; - - } - - public void StartReceivingChat() - { - Task.Run(() => ReceiveChat()); - } - - private void ReceiveChat() - { - string ircData; - try { - while ((ircData = reader.ReadLine()) != null && !simpleirc.shouldClientStop && !stopTask) - { - string userName; - isConnectionEstablised = true; - try - { - simpleirc.rawOutput(ircData); - } - catch - { - - } - - if (ircData.Contains("PING")) - { - string pingID = ircData.Split(':')[1]; - simpleirc.DebugCallBack("RECEIVED PING WITH ID: " + pingID + "\n"); - writeIrc("PONG :" + pingID); - - } - if (!ircData.ToLower().Contains("callerid") && !ircData.ToLower().Contains("chantypes") && !ircData.ToLower().Contains("version")) - { - - - - string[] messageSplitFromData = ircData.Split(new string[] { newChannel }, StringSplitOptions.None); - string message = messageSplitFromData[messageSplitFromData.Length - 1]; - try - { - message = message.Split(new string[] { newUsername + " :" }, StringSplitOptions.None)[1]; - } - catch - { - - } - - string user = messageSplitFromData[0].Split('!')[0].Substring(1); - - simpleirc.chatOutput(user, message); - - } - else if (ircData.Contains("JOIN")) - { - - Regex regex1 = new Regex(@"(?(?<=:)(.*\n?)(?=!~))"); - Match matches1 = regex1.Match(ircData); - - if (matches1.Success) - { - userName = matches1.Value; - simpleirc.chatOutput(userName, "JOINED"); - } - - } - else if (ircData.Contains("QUIT")) - { - Regex regex1 = new Regex(@"(?(?<=:)(.*\n?)(?=!~))"); - Match matches1 = regex1.Match(ircData); - - if (matches1.Success) - { - userName = matches1.Value; - simpleirc.chatOutput(userName, "QUITED"); - } - } - - if (ircData.Contains("DCC SEND") && ircData.Contains(newUsername)) - { - dcc.startDownloader(ircData, simpleirc.downloadDir, bot, packNumber); - - - simpleirc.DebugCallBack("\n DCC SERVER REPLY: " + dcc.newDccString); - simpleirc.DebugCallBack("\n FILENAME: " + dcc.newFileName); - simpleirc.DebugCallBack("\n FILESIZE: " + dcc.newFileSize); - simpleirc.DebugCallBack("\n IP: " + dcc.newIp); - simpleirc.DebugCallBack("\n PORT: " + dcc.newPortNum); - simpleirc.DebugCallBack("\n PACK: " + dcc.packNum); - simpleirc.DebugCallBack("\n BOT: " + dcc.botName); - } - - //RareIRC_Client = #weebirc :RareIRC_Client - if (ircData.Contains(newUsername + " = #")) - { - try - { - string userListFullString = ircData.Split(new[] { " = " }, StringSplitOptions.None)[1].Substring(newChannel.Length + 2); - string[] users = userListFullString.Split(' '); - foreach (string user in users) - { - Users.Add(user); - } - } catch (Exception e) - { - simpleirc.DebugCallBack("SOMETHING HAPPEND WITH USERNAME PARSING: " + e.ToString()); - } - - - } - - if(ircData.ToLower().Contains("end of /names list")) - { - try - { - - string[] userarray = Users.ToArray(); - simpleirc.UsersListReceived(userarray); - } catch - { - - } - } - Thread.Sleep(1); - } - simpleirc.DebugCallBack("\n STOPPED RECEIVER: "); - - quitConnect(); - stopTask = false; - } catch (Exception ioex) - { - simpleirc.DebugCallBack("ERROR: LOST CONNECTION TO SERVER PROBABLY. \n" + ioex.ToString() + "\n"); - if (isConnectionEstablised) - { - stopTask = false; - quitConnect(); - } - } - } - - public object[] passDownloadDetails() - { - if (dcc.isDownloading) - { - object[] downloadDetails = new object[12]; - - downloadDetails[0] = dcc.newDccString; - downloadDetails[1] = dcc.newFileName; - downloadDetails[2] = dcc.newFileSize; - downloadDetails[3] = dcc.newIp; - downloadDetails[4] = dcc.newPortNum; - downloadDetails[5] = dcc.packNum; - downloadDetails[6] = dcc.botName; - downloadDetails[7] = dcc.Bytes_Seconds; - downloadDetails[8] = dcc.KBytes_Seconds; - downloadDetails[9] = dcc.MBytes_Seconds; - downloadDetails[10] = dcc.Progress; - downloadDetails[11] = dcc.Status; - return downloadDetails; - } else - { - object[] downloadDetails = new object[12]; - - downloadDetails[0] = "NO DOWNLOAD"; - downloadDetails[1] = "NO DOWNLOAD"; - downloadDetails[2] = 0; - downloadDetails[3] = "0.0.0.0"; - downloadDetails[4] = 0; - downloadDetails[5] = "#0"; - downloadDetails[6] = "NO DOWNLOAD"; - downloadDetails[7] = 0; - downloadDetails[8] = 0; - downloadDetails[9] = 0; - downloadDetails[10] = 0; - downloadDetails[11] = dcc.Status; - return downloadDetails; - } - - } - - // parse message to send - public void sendMsg(string Input) - { - - Input = Input.Trim(); - Regex regex1 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(send)+(\s))(.*)))"); - Match matches1 = regex1.Match(Input.ToLower()); - Regex regex2 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(cancel))(.*)))"); - Match matches2 = regex2.Match(Input.ToLower()); - Regex regex3 = new Regex(@"^(?=.*(?(?<=/msg)(.*)(?=xdcc)))(?=.*(?(?<=(remove)+(\s))(.*)))"); - Match matches3 = regex3.Match(Input.ToLower()); - - - - if (matches1.Success) - { - bot = matches1.Groups["botname"].Value.Trim(); - packNumber = matches1.Groups["packnum"].Value.Trim(); - string xdccdl = "PRIVMSG " + bot + " :XDCC SEND " + packNumber; - simpleirc.DebugCallBack("XDCC FOUND: " + xdccdl + "\n"); - writeIrc(xdccdl); - } - else if (matches2.Success) - { - bot = matches2.Groups["botname"].Value; - string xdcccl = "PRIVMSG " + bot + " :XDCC CANCEL"; - simpleirc.DebugCallBack("XDCC CANCELED"); - writeIrc(xdcccl); - } - else if (matches3.Success) - { - bot = matches3.Groups["botname"].Value; - packNumber = matches3.Groups["packnum"].Value; - string xdccdl = "PRIVMSG " + bot + " :XDCC REMOVE " + packNumber; - simpleirc.DebugCallBack("XDCC REMOVED"); - writeIrc(xdccdl); - } else if (Input.ToLower().Contains("/names")) - { - simpleirc.DebugCallBack("Requested nicknames on channel(s): " + Input); - if (Input.Contains("#")) - { - string channelList = Input.Split(' ')[1]; - getUsersInChannel(channelList); - } else - { - getUsersInChannel(""); - } - } - else if(Input.ToLower().Contains("/quit")) - { - simpleirc.DebugCallBack("STARTED QUITTING"); - quitConnect(); - } - else if (Input.ToLower().Contains("/msg")) - { - writeIrc(Input.Replace("/msg", "PRIVMSG")); - } - else - { - writeIrc("PRIVMSG " + newChannel + " :" + Input); - } - - simpleirc.chatOutput(newUsername, Input); - } - - public void sendRawMsg(string msg) - { - writeIrc(msg); - } - - //asks the server for all (visible) users in channel x (send empty string if you want over all channels on server) - public void getUsersInChannel(string channel) - { - - Users = new List(); - if (channel != "") - { - writeIrc("NAMES " + channel); - simpleirc.DebugCallBack("Asking for nicknames in channel: " + channel); - } else - { - writeIrc("NAMES " + newChannel); - simpleirc.DebugCallBack("Asking for nicknames in channel: " + channel); - } - } - - //function to write to the irc server, bit easier to use and better looking - public void writeIrc(string input) - { - try - { - if (writer.BaseStream != null) - { - writer.Write(input + Environment.NewLine); - writer.Flush(); - } - else - { - simpleirc.DebugCallBack("Could not send message" + input + ", irc client is not running :X, error: \n"); - } - } catch(Exception e) - { - simpleirc.DebugCallBack("Could not send message" + input + ", irc client is not running :X, error: \n"); - simpleirc.DebugCallBack(e.ToString()); - } - - } - - //function for stopping the dcc downloader - public bool stopXDCCDownload() - { - try - { - dcc.abortDownloader(); - return true; - } catch (Exception e) - { - simpleirc.DebugCallBack("You probably are not downloading..."); - simpleirc.DebugCallBack(e.ToString()); - return false; - } - } - //function for stopping the dcc downloader - public bool checkIfDownloading() - { - try - { - return dcc.checkIfDownloading(); - } - catch (Exception e) - { - simpleirc.DebugCallBack("You probably are not downloading..."); - simpleirc.DebugCallBack(e.ToString()); - return false; - } - } - } - - -} diff --git a/SimpleIRCLib/IrcEventArgs.cs b/SimpleIRCLib/IrcEventArgs.cs new file mode 100644 index 0000000..0038fd3 --- /dev/null +++ b/SimpleIRCLib/IrcEventArgs.cs @@ -0,0 +1,105 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleIRCLib +{ + /// + /// Event class for receiving messages from specific channels and users, fired within IrcClient.cs + /// + public class IrcReceivedEventArgs + { + /// + /// Event for receiving messages from server, contains information about who send a message, from where, with the message send. + /// + /// message the user send + /// name of the user + /// channel where the user send it to + public IrcReceivedEventArgs(string message, string user, string channel) + { + Message = message; + User = user; + Channel = channel; + } + + /// + /// Containing message from user on a specific channel + /// + public string Message { get; } + /// + /// Containing user name whom send the message on a specific channel + /// + public string User { get; } + /// + /// Containing channel name where user send his/hers/its message + /// + public string Channel { get; } + } + + /// + /// Event class for receiving raw messages from the irc server, fired within IrcClient.cs + /// + public class IrcRawReceivedEventArgs + { + /// + /// Event for handeling raw messages from the server without any parsing applied. + /// + /// message from server + public IrcRawReceivedEventArgs(string message) + { + Message = message; + } + + /// + /// Containing raw message from the irc server + /// + public string Message { get; } + } + + /// + /// Event class for receiving a list with users per channel, fired within IrcClient.cs + /// + public class IrcUserListReceivedEventArgs + { + /// + /// Event for getting the usersperchannel list from the server. + /// + /// Dictionary containg a list with names per key (channel) + public IrcUserListReceivedEventArgs(Dictionary> usersPerChannel) + { + UsersPerChannel = usersPerChannel; + } + + /// + /// Dicitonary containing a list with user names per channel + /// + public Dictionary> UsersPerChannel { get; } + } + + /// + /// Event class for receiving debug messages from the IrcClient, fired within IrcClient.cs + /// + public class IrcDebugMessageEventArgs + { + /// + /// Event for receiving debug messages from the client + /// + /// debug message itself + /// type of message, handy for identifying where the message occured + public IrcDebugMessageEventArgs(string message, string type) + { + Message = message; + Type = type; + } + + /// + /// Containing debug message. + /// + public string Message { get; } + /// + /// Containing type of message, handy for determining where the message originates from + /// + public string Type { get; } + } +} diff --git a/SimpleIRCLib/Properties/AssemblyInfo.cs b/SimpleIRCLib/Properties/AssemblyInfo.cs index b79553e..56142d8 100644 --- a/SimpleIRCLib/Properties/AssemblyInfo.cs +++ b/SimpleIRCLib/Properties/AssemblyInfo.cs @@ -33,7 +33,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0.0")] +[assembly: AssemblyVersion("1.2.*")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: NeutralResourcesLanguage("")] diff --git a/SimpleIRCLib/RFC1459Codes.cs b/SimpleIRCLib/RFC1459Codes.cs new file mode 100644 index 0000000..97f0cdf --- /dev/null +++ b/SimpleIRCLib/RFC1459Codes.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace SimpleIRCLib +{ + /// + /// Contians all the error codes specified by the RFC1459 protocol + /// + class RFC1459Codes + { + /// + /// List with error messages. + /// + public static string[] ListWithErrors = new string[] { @" + 401 ERR_NOSUCHNICK + "" :No such nick/channel"" + + - Used to indicate the nickname parameter supplied to a + command is currently unused. + ", @" + + 402 ERR_NOSUCHSERVER + "" :No such server"" + + - Used to indicate the server name given currently + doesn't exist. + ", @" + 403 ERR_NOSUCHCHANNEL + "" :No such channel"" + + - Used to indicate the given channel name is invalid. + ", @" + 404 ERR_CANNOTSENDTOCHAN + "" :Cannot send to channel"" + + - Sent to a user who is either (a) not on a channel + which is mode +n or (b) not a chanop (or mode +v) on + a channel which has mode +m set and is trying to send + a PRIVMSG message to that channel. + ", @" + 405 ERR_TOOMANYCHANNELS + "" :You have joined too many \ + channels"" + - Sent to a user when they have joined the maximum + number of allowed channels and they try to join + another channel. + ", @" + 406 ERR_WASNOSUCHNICK + "" :There was no such nickname"" + + - Returned by WHOWAS to indicate there is no history + information for that nickname. + ", @" + 407 ERR_TOOMANYTARGETS + "" :Duplicate recipients. No message delivered"" + + - Returned to a client which is attempting to send a + PRIVMSG/NOTICE using the user@host destination format + and for a user@host which has several occurrences. + ", @" + 409 ERR_NOORIGIN + "":No origin specified"" + + - PING or PONG message missing the originator parameter + which is required since these commands must work + without valid prefixes. + ", @" + 411 ERR_NORECIPIENT + "":No recipient given ()"" + ", @" + 412 ERR_NOTEXTTOSEND + "":No text to send"" + ", @" + 413 ERR_NOTOPLEVEL + "" :No toplevel domain specified"" + ", @" + 414 ERR_WILDTOPLEVEL + "" :Wildcard in toplevel domain"" + + - 412 - 414 are returned by PRIVMSG to indicate that + the message wasn't delivered for some reason. + ERR_NOTOPLEVEL and ERR_WILDTOPLEVEL are errors that + are returned when an invalid use of + ""PRIVMSG $"" or ""PRIVMSG #"" is attempted. + ", @" + 421 ERR_UNKNOWNCOMMAND + "" :Unknown command"" + + - Returned to a registered client to indicate that the + command sent is unknown by the server. + ", @" + 422 ERR_NOMOTD + "":MOTD File is missing"" + + - Server's MOTD file could not be opened by the server. + ", @" + 423 ERR_NOADMININFO + "" :No administrative info available"" + + - Returned by a server in response to an ADMIN message + when there is an error in finding the appropriate + information. + ", @" + 424 ERR_FILEERROR + "":File error doing on "" + + - Generic error message used to report a failed file + operation during the processing of a message. + ", @" + 431 ERR_NONICKNAMEGIVEN + "":No nickname given"" + + - Returned when a nickname parameter expected for a + command and isn't found. + ", @" + 432 ERR_ERRONEUSNICKNAME + "" :Erroneus nickname"" + + - Returned after receiving a NICK message which contains + characters which do not fall in the defined set. See + section x.x.x for details on valid nicknames. + ", @" + 433 ERR_NICKNAMEINUSE + "" :Nickname is already in use"" + + - Returned when a NICK message is processed that results + in an attempt to change to a currently existing + nickname. + ", @" + 436 ERR_NICKCOLLISION + "" :Nickname collision KILL"" + + - Returned by a server to a client when it detects a + nickname collision (registered of a NICK that + already exists by another server). + ", @" + 441 ERR_USERNOTINCHANNEL + "" :They aren't on that channel"" + + - Returned by the server to indicate that the target + user of the command is not on the given channel. + ", @" + 442 ERR_NOTONCHANNEL + "" :You're not on that channel"" + + - Returned by the server whenever a client tries to + perform a channel effecting command for which the + client isn't a member. + ", @" + 443 ERR_USERONCHANNEL + "" :is already on channel"" + + - Returned when a client tries to invite a user to a + channel they are already on. + ", @" + 444 ERR_NOLOGIN + "" :User not logged in"" + + - Returned by the summon after a SUMMON command for a + user was unable to be performed since they were not + logged in. + ", @" + 445 ERR_SUMMONDISABLED + "":SUMMON has been disabled"" + + - Returned as a response to the SUMMON command. Must be + returned by any server which does not implement it. + ", @" + 446 ERR_USERSDISABLED + "":USERS has been disabled"" + + - Returned as a response to the USERS command. Must be + returned by any server which does not implement it. + ", @" + 451 ERR_NOTREGISTERED + "":You have not registered"" + + - Returned by the server to indicate that the client + must be registered before the server will allow it + to be parsed in detail. + ", @" + 461 ERR_NEEDMOREPARAMS + "" :Not enough parameters"" + + - Returned by the server by numerous commands to + indicate to the client that it didn't supply enough + parameters. + ", @" + 462 ERR_ALREADYREGISTRED + "":You may not reregister"" + + - Returned by the server to any link which tries to + change part of the registered details (such as + password or user details from second USER message). + ", @" + 463 ERR_NOPERMFORHOST + "":Your host isn't among the privileged"" + + - Returned to a client which attempts to register with + a server which does not been setup to allow + connections from the host the attempted connection + is tried. + ", @" + 464 ERR_PASSWDMISMATCH + "":Password incorrect"" + + - Returned to indicate a failed attempt at registering + a connection for which a password was required and + was either not given or incorrect. + ", @" + 465 ERR_YOUREBANNEDCREEP + "":You are banned from this server"" + + - Returned after an attempt to connect and register + yourself with a server which has been setup to + explicitly deny connections to you. + ", @" + 467 ERR_KEYSET + "" :Channel key already set"" + ", @" + 471 ERR_CHANNELISFULL + "" :Cannot join channel (+l)"" + ", @" + 472 ERR_UNKNOWNMODE + "" :is unknown mode char to me"" + ", @" + 473 ERR_INVITEONLYCHAN + "" :Cannot join channel (+i)"" + ", @" + 474 ERR_BANNEDFROMCHAN + "" :Cannot join channel (+b)"" + ", @" + 475 ERR_BADCHANNELKEY + "" :Cannot join channel (+k)"" + ", @" + 481 ERR_NOPRIVILEGES + "":Permission Denied- You're not an IRC operator"" + + - Any command requiring operator privileges to operate + must return this error to indicate the attempt was + unsuccessful. + ", @" + 482 ERR_CHANOPRIVSNEEDED + "" :You're not channel operator"" + + - Any command requiring 'chanop' privileges (such as + MODE messages) must return this error if the client + making the attempt is not a chanop on the specified + channel. + ", @" + 483 ERR_CANTKILLSERVER + "":You cant kill a server!"" + + - Any attempts to use the KILL command on a server + are to be refused and this error returned directly + to the client. + ", @" + 491 ERR_NOOPERHOST + "":No O-lines for your host"" + + - If a client sends an OPER message and the server has + not been configured to allow connections from the + client's host as an operator, this error must be + returned. + ", @" + 501 ERR_UMODEUNKNOWNFLAG + "":Unknown MODE flag"" + + - Returned by the server to indicate that a MODE + message was sent with a nickname parameter and that + the a mode flag sent was not recognized. + ", @" + 502 ERR_USERSDONTMATCH + "":Cant change mode for other users"" + + - Error sent to any user trying to view or change the + user mode for a user other than themselves. + " }; + + } +} diff --git a/SimpleIRCLib/SimpleIRC.cs b/SimpleIRCLib/SimpleIRC.cs index 900d54a..ae97f1e 100644 --- a/SimpleIRCLib/SimpleIRC.cs +++ b/SimpleIRCLib/SimpleIRC.cs @@ -1,408 +1,216 @@ using System; using System.IO; +using System.Reflection; +using System.Threading; namespace SimpleIRCLib { - // This project can output the Class library as a NuGet Package. - // To enable this option, right-click on the project and select the Properties menu item. In the Build tab select "Produce outputs on build". + /// + /// A combiner class that combines all the logic from both the IrcClient & DCCClient with simple methods to control these clients. + /// public class SimpleIRC { - //sets the method to be called when there is a debug message - public Action DebugCallBack; - //sets the method to be called when there is a change in the status of the current download - public Action downloadStatusChange; - - //sets the method to be called when there is a chat message to be shown - public Action UsersListReceived; - - //sets the method to be called when there is a chat message to be shown - public Action chatOutput; - - //sets the method to be called when there is a message from the irc server, completely raw, so you can do your own stuff. - public Action rawOutput; - - //sets the download folder for the current download and following downloads (can be changed when instance is running) - public string downloadDir { get; set; } - - //public bool which should stop every running tasks and s - public bool shouldClientStop { get; set; } - - //public bool which tells the library user if there is an error, this could mean that a function could return a false postive false (XD) - public bool didErrorHappen { get; set; } - - - //public available information - - public string newIP { get; set; } - public int newPort { get; set; } - public string newUsername { get; set; } - public string newChannel { get; set; } - public string newPassword { get; set; } - - //private variables - private IrcConnect con; /// - /// Constructor. + /// Ip address of irc server /// - public SimpleIRC() - { - newIP = ""; - newPort = 0; - newUsername = ""; - newPassword = ""; - newChannel = ""; - downloadDir = ""; - shouldClientStop = false; - didErrorHappen = false; - } - - + private string _newIP { get; set; } /// - /// Setup the client to be able to connect to the server + /// Port of irc server to connect to /// - /// Server address, possibly works with dns addresses (irc.xxx.x), but ip addresses works just fine (of type string) - /// Port of the server you want to connect to, of type int - /// Username the client wants to use, of type string - /// Password, currently not used!, of type string - /// Channel(s) the client wants to connect to, possible to connect to multiple channels at once by seperating each channel with a ',' (Example: #chan1,#chan2), of type string - /// callback method when messages are received from the server, method to be used as callback needs two string parameters, one for username, second for the actual message. - public void setupIrc(string IP, int Port, string Username, string Password, string Channel, Action chatoutput) - { - newIP = IP; - newPort = Port; - newUsername = Username; - newPassword = Password; - newChannel = Channel; - chatOutput = chatoutput; - DebugCallBack = null; - downloadStatusChange = null; - shouldClientStop = false; - downloadDir = ""; - } - + private int _newPort { get; set; } /// - /// Sets the download directory for dcc downloads. + /// Username to register on irc server /// - /// Requires a path to a directory of type string as parameter. - public void setCustomDownloadDir(string downloaddir) - { - downloadDir = downloaddir; - } + private string _newUsername { get; set; } + /// + /// List with channels seperated with ',' to join when connecting to IRC server + /// + /// Password to connect to a secured irc server + /// + private string _newPassword { get; set; } + /// + /// download directory used for DCCClient.cs + /// + private string _downloadDir { get; set; } /// - ///Set debug callback method. Will be executed when there is a new debug message available + /// Irc Client for sending and receiving messages to a irc server /// - /// A method which should have a string parameter of its own. - public void setDebugCallback(Action callback) - { - DebugCallBack = callback; - } + public IrcClient IrcClient { get; set; } + /// + /// DCCClient used by the IRCClient for starting a download on a separate thread using the DCC Protocol + /// + public DCCClient DccClient { get; set; } + /// - /// Executes a method when there is new information about the current download + /// Constructor, sets up bot ircclient and dccclient, so that users can register event handlers. /// - /// takes a method as parameter, which will be executed - public void setDownloadStatusChangeCallback(Action callback) + public SimpleIRC() { - downloadStatusChange = callback; + _newIP = ""; + _newPort = 0; + _newUsername = ""; + _newPassword = ""; + _newChannels = ""; + _downloadDir = ""; + IrcClient = new IrcClient(); + DccClient = new DCCClient(); } + /// - ///set user list received callback, - /// - /// you should provide a method where the method you pass through needs to have a string[] as parameter - public void setUserListReceivedCallback(Action callback) - { - UsersListReceived = callback; + /// Setup the client to be able to connect to the server + /// + /// Server address, possibly works with dns addresses (irc.xxx.x), but ip addresses works just fine (of type string) + /// Username the client wants to use, of type string + /// Channel(s) the client wants to connect to, possible to connect to multiple channels at once by seperating each channel with a ',' (Example: #chan1,#chan2), of type string + /// Port, optional parameter, where default = 0 (Automatic port selection), is port of the server you want to connect to, of type int + /// Password, optional parameter, where default value is "", can be used to connect to a password protected server. + /// Timeout, optional parameter, where default value is 3000 milliseconds, the maximum time before a server needs to answer, otherwise errors are thrown. + /// Timeout, optional parameter, where default value is 3000 milliseconds, the maximum time before a server needs to answer, otherwise errors are thrown. + public void SetupIrc(string ip, string username, string channels, int port = 0, string password = "", int timeout = 3000, bool enableSSL = true) + { + _newIP = ip; + _newPort = port; + _newUsername = username; + _newPassword = password; + _newChannels = channels; + _downloadDir = ""; + + IrcClient.SetConnectionInformation(ip, username, channels, DccClient, _downloadDir, port, password, timeout, enableSSL); + } /// /// Sets the download directory for dcc downloads. /// /// Requires a path to a directory of type string as parameter. - public void setRawOutput(Action rawoutput) + public void SetCustomDownloadDir(string downloaddir) { - rawOutput = rawoutput; + _downloadDir = downloaddir; } /// /// Starts the irc client with the given parameters in the constructor /// - public void startClient() + /// true or false depending if it starts succesfully + public bool StartClient() { - - con = new IrcConnect(newIP, newPort, newUsername, newPassword, newChannel, this); - if (con != null) + if (IrcClient != null) { - if (!con.isConnectionEstablised) + if (!IrcClient.IsConnectionEstablished()) { - con.Connect(); - } - else - { - DebugCallBack("You are already connected to a server, disconnect first! \n"); - } - } else - { - if (DebugCallBack != null) - { - DebugCallBack("You are already connected to a server, disconnect first! \n"); + IrcClient.Connect(); + + int timeout = 0; + while (!IrcClient.IsClientRunning()) + { + Thread.Sleep(1); + if (timeout >= 3000) + { + return false; + } + timeout++; + } + return true; } } + + return false; } /// /// Checks if the client is running. /// /// true or false - public bool isClientRunning() + public bool IsClientRunning() { - try - { - didErrorHappen = false; - return con.isConnectionEstablised; - } catch(Exception e) - { - if (DebugCallBack != null) - { - DebugCallBack("Could not check if connection is established: " + e.ToString()); - } - didErrorHappen = true; - return false; - } + return IrcClient.IsClientRunning(); } - /// /// Stops the client /// /// true or false depending on succes - public bool stopClient() + public bool StopClient() { //execute quit stuff - try - { + bool check = false; - didErrorHappen = false; - if (con.isConnectionEstablised) - { - shouldClientStop = true; - con.quitConnect(); - return true; - } else - { - return false; - } - } catch(Exception e) - { - if (DebugCallBack != null) - { - DebugCallBack("Could not quit irc due to: " + e.ToString()); - } - didErrorHappen = true; - return false; - } - + check = IrcClient.StopClient(); + check = IrcClient.StopXDCCDownload(); + return check; } - /// ///returns true or false upon calling this method, for telling you if the downlaod has been stopped or not /// /// - public bool stopXDCCDownload() + public bool StopXDCCDownload() { - try - { - didErrorHappen = false; - return con.stopXDCCDownload(); - } catch(Exception e) - { - if (DebugCallBack != null) - { - DebugCallBack("Could not stop xdcc download due to: " + e.ToString()); - } - didErrorHappen = true; - return false; - } + return IrcClient.StopXDCCDownload(); } - - // - /// - /// gets the download details by defining which detail you want - /// available (use this as the string for the parameter): - /// - /// Possible inputs, in order: mbps,kbps,bps,filename,bot,pack,dccstring,ip,port,progress,status,size - /// Object of current download, in order: Megabytes Per Second, Kilobytes Per Second, Bytes Per Second, filename, bot (source), pack (unique id for bot), dccstring (raw server return when asked for download), ip (of server where file is located), port (of server where file is located),progress, status of the current download(failed, running etc), size of file - public object getDownloadProgress(string whichdownloaddetail) - { - object[] dlDetails = con.passDownloadDetails(); - if(whichdownloaddetail == "mbps") - { - return dlDetails[9]; - } - else if (whichdownloaddetail == "kbps") - { - return dlDetails[8]; - } - else if(whichdownloaddetail == "bps") - { - return dlDetails[7]; - } - else if (whichdownloaddetail == "filename") - { - return dlDetails[1]; - } - else if (whichdownloaddetail == "bot") - { - return dlDetails[6]; - } - else if (whichdownloaddetail == "pack") - { - return dlDetails[5]; - } - else if (whichdownloaddetail == "dccstring") - { - return dlDetails[0]; - } - else if (whichdownloaddetail == "ip") - { - return dlDetails[3]; - } - else if (whichdownloaddetail == "port") - { - return dlDetails[4]; - } - else if (whichdownloaddetail == "progress") - { - return dlDetails[10]; - } - else if (whichdownloaddetail == "status") - { - return dlDetails[11]; - } - else if (whichdownloaddetail == "size") - { - return dlDetails[2]; - } - else - { - return null; - } - - } - + /// ///returns true or false upon calling this method, for telling you if the downlaod has been stopped or not /// /// - public bool checkIfDownload() + public bool CheckIfDownload() { - try - { - didErrorHappen = false; - return con.checkIfDownloading(); - } catch(Exception e) - { - if (DebugCallBack != null) - { - DebugCallBack("Could not check if downloading: " + e.ToString()); - } - didErrorHappen = true; - return false; - } + return IrcClient.CheckIfDownloading(); } - /// ///get users in current channel /// - public void getUsersInCurrentChannel() + public void GetUsersInCurrentChannel() { - try - { - - con.getUsersInChannel(""); - } catch(Exception e) - { - DebugCallBack("Could not request users from channel: " + e.ToString()); - } + IrcClient.GetUsersInChannel(); } - /// ///get users in different channel, parameter is the channel name of type string (example: "#yourchannel") /// /// - public void getUsersInDifferentChannel(string channel) + public void GetUsersInDifferentChannel(string channel) { - try - { - - con.getUsersInChannel(channel); - } catch(Exception e) - { - DebugCallBack("Could not request users from channel: " + e.ToString()); - } + IrcClient.GetUsersInChannel(channel); } - /// - ///send message + ///send message to all channels /// - /// - /// - public bool sendMessage(string message) + /// message to send + /// true if succesfully send + public bool SendMessageToAll(string message) { - try - { - didErrorHappen = false; - if (con.isConnectionEstablised) - { - con.sendMsg(message); - return true; - } else - { - return false; - } - } catch(Exception e) { - if (DebugCallBack != null) - { - DebugCallBack("Could not send message: " + e.ToString()); - } - didErrorHappen = true; - return false; - } - + return IrcClient.SendMessageToAll(message); } - public bool sendRawMessage(string message) + /// + /// Sends a message to a specific channel. + /// + /// message to send + /// channel for destination + /// true/false depending if sending was succesfull + public bool SendMessageToChannel(string message, string channel) { - try - { - didErrorHappen = false; - if (con.isConnectionEstablised) - { - con.sendRawMsg(message); - return true; - } - else - { - return false; - } - } - catch(Exception e) - { - if (DebugCallBack != null) - { - DebugCallBack("Could not send raw message: " + e.ToString()); - } - didErrorHappen = true; - return false; - } + return IrcClient.SendMessageToChannel(message, channel); + } + /// + /// Sends a raw message to irc server + /// + /// message to send + /// true/false depending if sending was succesfull + public bool SendRawMessage(string message) + { + return IrcClient.SendRawMsg(message); } } diff --git a/SimpleIRCLib/SimpleIRCLib.csproj b/SimpleIRCLib/SimpleIRCLib.csproj index afe8781..3de0918 100644 --- a/SimpleIRCLib/SimpleIRCLib.csproj +++ b/SimpleIRCLib/SimpleIRCLib.csproj @@ -28,6 +28,7 @@ TRACE prompt 4 + bin\Release\SimpleIRCLib.xml false @@ -35,16 +36,14 @@ - - - - - - - + + + + + diff --git a/SimpleIRCLib/SimpleIRCLib.md b/SimpleIRCLib/SimpleIRCLib.md new file mode 100644 index 0000000..36598fb --- /dev/null +++ b/SimpleIRCLib/SimpleIRCLib.md @@ -0,0 +1,216 @@ +## `DCCClient` + +```csharp +public class SimpleIRCLib.DCCClient + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | BotName | | +| `Int64` | BytesPerSecond | | +| `String` | CurrentFilePath | | +| `Boolean` | IsDownloading | | +| `Int32` | KBytesPerSecond | | +| `Int32` | MBytesPerSecond | | +| `String` | NewDccString | | +| `String` | NewFileName | | +| `Int64` | NewFileSize | | +| `String` | NewIp | | +| `String` | NewIp2 | | +| `Int32` | NewPortNum | | +| `String` | PackNum | | +| `Int32` | Progress | | +| `String` | Status | | + + +Events + +| Type | Name | Summary | +| --- | --- | --- | +| `EventHandler` | OnDccDebugMessage | | +| `EventHandler` | OnDccEvent | | + + +Methods + +| Type | Name | Summary | +| --- | --- | --- | +| `Boolean` | AbortDownloader(`Int32` timeOut) | | +| `Boolean` | CheckIfDownloading() | | +| `void` | Downloader() | | +| `void` | StartDownloader(`String` dccString, `String` downloaddir, `String` bot, `String` pack, `IrcClient` client) | | + + +## `DCCDebugMessageArgs` + +```csharp +public class SimpleIRCLib.DCCDebugMessageArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | Message | | +| `String` | Type | | + + +## `DCCEventArgs` + +```csharp +public class SimpleIRCLib.DCCEventArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | Bot | | +| `Int64` | BytesPerSecond | | +| `String` | DccString | | +| `String` | FileName | | +| `Int64` | FileSize | | +| `String` | Ip | | +| `Int32` | KBytesPerSecond | | +| `Int32` | MBytesPerSecond | | +| `String` | Pack | | +| `Int32` | Port | | +| `Int32` | Progress | | +| `String` | Status | | + + +## `IrcClient` + +```csharp +public class SimpleIRCLib.IrcClient + +``` + +Events + +| Type | Name | Summary | +| --- | --- | --- | +| `EventHandler` | OnDebugMessageReceived | | +| `EventHandler` | OnMessageReceived | | +| `EventHandler` | OnRawMessageReceived | | +| `EventHandler` | OnUserListReceived | | + + +Methods + +| Type | Name | Summary | +| --- | --- | --- | +| `Boolean` | CheckIfDownloading() | | +| `Boolean` | Connect() | | +| `Boolean` | GetUsersInChannel(`String` channel = ) | | +| `Boolean` | IsClientRunning() | | +| `Boolean` | IsConnectionEstablished() | | +| `Boolean` | QuitConnect() | | +| `Boolean` | SendMessageToAll(`String` input) | | +| `Boolean` | SendMessageToChannel(`String` input, `String` channel) | | +| `Boolean` | SendRawMsg(`String` msg) | | +| `void` | SetConnectionInformation(`String` ip, `String` username, `String` channel, `DCCClient` dccClient, `String` downloadDirectory, `Int32` port = 0, `String` password = , `Int32` timeout = 3000, `Boolean` enableSSL = True) | | +| `void` | SetDownloadDirectory(`String` downloadDirectory) | | +| `void` | StartReceivingChat() | | +| `void` | StopClient() | | +| `Boolean` | StopXDCCDownload() | | +| `Boolean` | WriteIrc(`String` input) | | + + +## `IrcDebugMessageEventArgs` + +```csharp +public class SimpleIRCLib.IrcDebugMessageEventArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | Message | | +| `String` | Type | | + + +## `IrcRawReceivedEventArgs` + +```csharp +public class SimpleIRCLib.IrcRawReceivedEventArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | Message | | + + +## `IrcReceivedEventArgs` + +```csharp +public class SimpleIRCLib.IrcReceivedEventArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `String` | Channel | | +| `String` | Message | | +| `String` | User | | + + +## `IrcUserListReceivedEventArgs` + +```csharp +public class SimpleIRCLib.IrcUserListReceivedEventArgs + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `Dictionary>` | UsersPerChannel | | + + +## `SimpleIRC` + +```csharp +public class SimpleIRCLib.SimpleIRC + +``` + +Properties + +| Type | Name | Summary | +| --- | --- | --- | +| `DCCClient` | DccClient | | +| `IrcClient` | IrcClient | | + + +Methods + +| Type | Name | Summary | +| --- | --- | --- | +| `Boolean` | CheckIfDownload() | | +| `void` | GetUsersInCurrentChannel() | | +| `void` | GetUsersInDifferentChannel(`String` channel) | | +| `Boolean` | IsClientRunning() | | +| `Boolean` | SendMessageToAll(`String` message) | | +| `Boolean` | SendMessageToChannel(`String` message, `String` channel) | | +| `Boolean` | SendRawMessage(`String` message) | | +| `void` | SetCustomDownloadDir(`String` downloaddir) | | +| `void` | SetupIrc(`String` ip, `String` username, `String` channel, `Int32` port = 0, `String` password = , `Int32` timeout = 3000, `Boolean` enableSSL = True) | | +| `Boolean` | StartClient() | | +| `Boolean` | StopClient() | | +| `Boolean` | StopXDCCDownload() | | + +