-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathIRCBot.cs
320 lines (291 loc) · 12.4 KB
/
IRCBot.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
using Microsoft.Data.Sqlite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Sockets;
using System.IO;
using Microsoft.Win32;
using System.Net;
using System.Xml;
using System.Configuration;
using System.Collections.Specialized;
using Discord;
using System.Threading;
using System.Threading.Tasks;
using Discord.WebSocket;
namespace LunaIntegration
{
//IRC structure
//Basic idea: 1 main IRC instance for the central relay bot (the one that mirrors channel messages)
//1 IRC instance per user who asks to participate in the mirror
class IRCBot : IDisposable
{
private TcpClient IRCConnection = null;
private IRCConfig config;
private NetworkStream ns = null;
private StreamReader sr = null;
private StreamWriter sw = null;
private long lastInput;
public List<QueueObject> queue;
public UInt64 discordId;
private List<IrcCommand> LaunchCommands;
public IRCBot(IRCConfig config, UInt64 associatedId)
{
this.config = config;
this.discordId = associatedId;
LaunchCommands = new List<IrcCommand>();
this.queue = new List<QueueObject>(); //Queue is where all incoming IRC messages as seen by this instance are stored
}
public IRCBot(IRCConfig config, UInt64 associatedId, List<IrcCommand> commands)
{
this.config = config;
this.discordId = associatedId;
LaunchCommands = commands;
this.queue = new List<QueueObject>(); //Queue is where all incoming IRC messages as seen by this instance are stored
}
public List<QueueObject> getDiscordQueue()
{
return queue;
}
public String getNick()
{
return config.nick;
}
public void setNick(string nick)
{
config.nick = nick;
}
public void setConfig(IRCConfig conf)
{
config = conf;
}
public void setCommands(List<IrcCommand> cmds)
{
LaunchCommands = null;
LaunchCommands = cmds;
}
public void clearQueue()
{
queue.Clear();
}
public Boolean isConnected()
{
return config.joined;
}
public Boolean Connected()
{
return IRCConnection.Connected;
}
public void Connect()
{
config.joined = false;
try
{
IRCConnection = new TcpClient(config.server, config.port);
}
catch
{
Console.WriteLine("Connection Error");
throw;
}
try
{
ns = IRCConnection.GetStream();
sr = new StreamReader(ns);
sw = new StreamWriter(ns);
sendData("PASS", config.password);
sendData("NICK", config.nick);
sendData("USER", "discordusr localhost " + ConfigurationManager.AppSettings.Get("IRCServer") + " :Discord User");
}
catch
{
Console.WriteLine("Communication error");
throw;
}
}
public void sendData(string cmd, string param)
{
if (param == null)
{
sw.WriteLine(cmd);
sw.Flush();
Console.WriteLine(cmd);
}
else
{
sw.WriteLine(cmd + " " + param);
sw.Flush();
Console.WriteLine(cmd + " " + param);
}
}
private String getUser(String input)
{
string pattern = @"(?<=:).*?(?=!)"; //make sure coming from an actual user
Regex rgx = new Regex(pattern, RegexOptions.IgnoreCase);
MatchCollection matches = rgx.Matches(input);
if (matches.Count > 0)
return matches[0].Value;
else
return "";
}
private string StripFormat(string input) //remove IRC format codes
{
return Regex.Replace(input, @"(?:|||||(?:(?:\d+)(?:,\d+)?)?)", "");
}
public void IRCWork() //Main area for handling raw IRC input
{
string[] ex;
string data;
bool shouldRun = true;
while (shouldRun)
{
if (Connected())
{
data = sr.ReadLine();
Console.WriteLine(data);
char[] charSeparator = new char[] { ' ' };
ex = data.Split(charSeparator, 4); //Split the data into 5 parts to get metadata
if (!config.joined)
{
if (ex[1] == "001")
{
if (discordId == 0)
{
sendData("PRIVMSG NICKSERV RECOVER", config.nick + " " + ConfigurationManager.AppSettings.Get("NickservPass"));
sendData("PRIVMSG NICKSERV identify", ConfigurationManager.AppSettings.Get("NickservPass"));
}
else
{
for (var i = 0; i < LaunchCommands.Count; i++)
{
sendData(LaunchCommands[i].command, LaunchCommands[i].param);
}
}
sendData("JOIN", config.channel);
config.joined = true;
}
}
if (ex[0] == "PING")
{
sendData("PONG", ex[1]);
}
Int32 timestamp = ((Int32)(DateTime.UtcNow.Subtract(new DateTime(1970, 1, 1))).TotalSeconds);
if (ex[1] == "NOTICE" && getUser(ex[0]) == "NickServ" && discordId == 0) //logic for responding to nickserv password attempts
{
if (ex[3].Contains("isn't registered")) //requested nick not regged
{
string[] ex2;
ex2 = StripFormat(ex[3]).Split(charSeparator);
this.queue.Add(new QueueObject("NICKSERV", timestamp, null, "false", ex2[1], discordId));
}
else //see if requested nick is regged
{
string[] ex2;
ex2 = StripFormat(ex[3]).Split(charSeparator);
if (ex2[1] == "is")
{
this.queue.Add(new QueueObject("NICKSERV", timestamp, null, "true", ex2[0].Remove(0, 1), discordId));
}
}
}
if (ex[1] == "NOTICE" && getUser(ex[0]) == "NickServ") //logic for responding to nickserv registration investigation
{
if (ex[3].Contains("Password incorrect")) //requested nick not regged
{
this.queue.Add(new QueueObject("REJECT", timestamp, null, "true", config.nick, discordId));
shouldRun = false;
config.joined = false;
break;
}
else if (ex[3].Contains("Password accepted")) //requested nick not regged
{
this.queue.Add(new QueueObject("ACCEPT", timestamp, null, "true", config.nick, discordId));
}
}
if (ex[1] == "NOTICE" && getUser(ex[0]) == "discord") //this tells the IRC thread to exit when the user leaves the server
{
if (ex[3].Contains("TERMINATE")) //requested nick not regged
{
shouldRun = false;
config.joined = false;
this.queue.Add(new QueueObject("TERMINATE", timestamp, null, "true", config.nick, discordId));
break;
}
}
//handle luna !id response
else if (ex.Length == 4 && (ex[1] == "PRIVMSG" || (ex[1] == "NOTICE" && ex[2] != "*"))) //handler for all channel and private messages
{
string thisUser = getUser(ex[0]);
string channel = ex[2];
if (thisUser != "OperServ") //do not send session limit msgs
this.queue.Add(new QueueObject("MSG", timestamp, channel, StripFormat(ex[3]), thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
if (ex[1] == "INVITE")
{
string thisUser = getUser(ex[0]);
string channel = ex[2];
if (thisUser == ConfigurationManager.AppSettings.Get("AuthorizedInviter")) //Only process the invite if it comes from a user authorized to do it
{
sendData("JOIN", ex[3]);
this.queue.Add(new QueueObject(ex[1], timestamp, channel, ex[3], thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
}
if (ex[1] == "JOIN")
{
string thisUser = getUser(ex[0]);
string channel = ex[2];
this.queue.Add(new QueueObject("JOIN", timestamp, channel, "", thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
if (ex[1] == "PART")
{
string thisUser = getUser(ex[0]);
string channel = ex[2].Remove(0, 1);
this.queue.Add(new QueueObject("PART", timestamp, channel, "", thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
if (ex[1] == "QUIT")
{
string thisUser = getUser(ex[0]);
this.queue.Add(new QueueObject("QUIT", timestamp, null, String.Join(" ", ex, 2, ex.Length - 2).Remove(0, 1), thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
if (ex[1] == "KICK")
{
string thisUser = getUser(ex[0]);
string channel = ex[2].Remove(0, 1);
this.queue.Add(new QueueObject("KICK", timestamp, channel, "", thisUser, discordId)); //Queue this message to be sent to the appropriate Discord destination on next polling thread loop
}
//whois response
if (ex[1] == "401")
{
string[] ex2;
ex2 = ex[3].Split(charSeparator);
this.queue.Add(new QueueObject("WHOIS", timestamp, null, "false", ex2[0], discordId));
}
if (ex[1] == "311")
{
string[] ex2;
ex2 = ex[3].Split(charSeparator);
this.queue.Add(new QueueObject("WHOIS", timestamp, null, "true", ex2[0], discordId));
}
}
}
return;
}
public void disassociate()
{
discordId = 1;
}
public void Dispose()
{
if (IRCConnection != null)
IRCConnection.Close();
if (sr != null)
sr.Close();
if (sw != null)
sw.Close();
if (ns != null)
ns.Close();
}
}
}