1 using Meebey.SmartIrc4net;
2 using Microsoft.Extensions.Logging;
4 using System.Collections.Generic;
9 using System.Threading.Tasks;
20 const int TimeoutSeconds = 5;
23 public override bool Connected => client.IsConnected;
26 public override string BotMention => client.Nickname;
31 readonly ILogger<IrcProvider>
logger;
97 this.logger = logger ??
throw new ArgumentNullException(nameof(logger));
98 if (application == null)
99 throw new ArgumentNullException(nameof(application));
101 this.address = address ??
throw new ArgumentNullException(nameof(address));
103 this.nickname = nickname ??
throw new ArgumentNullException(nameof(nickname));
105 if (passwordType.HasValue && password == null)
106 throw new ArgumentNullException(nameof(password));
108 if (password != null && !passwordType.HasValue)
109 throw new ArgumentNullException(nameof(passwordType));
111 this.password = password;
112 this.passwordType = passwordType;
114 client =
new IrcFeatures
116 SupportNonRfc =
true,
117 CtcpUserInfo =
"You are going to play. And I am going to watch. And everything will be just fine...",
119 AutoRejoinOnKick =
true,
122 AutoRetryLimit = TimeoutSeconds,
123 AutoRetryDelay = TimeoutSeconds,
124 ActiveChannelSyncing =
true,
125 AutoNickHandling =
true,
130 client.ValidateServerCertificate =
true;
132 client.OnChannelMessage += Client_OnChannelMessage;
133 client.OnQueryMessage += Client_OnQueryMessage;
135 channelIdMap =
new Dictionary<ulong, string>();
136 queryChannelIdMap =
new Dictionary<ulong, string>();
137 channelIdCounter = 1;
138 disconnecting =
false;
146 disconnecting =
true;
158 if (e.Data.Nick.ToUpperInvariant() == client.Nickname.ToUpperInvariant())
161 var username = e.Data.Nick;
162 var channelName = isPrivate ? username : e.Data.Channel;
164 ulong MapAndGetChannelId(Dictionary<ulong, string> dicToCheck)
166 ulong? resultId = null;
167 if (!dicToCheck.Any(x =>
169 if (x.Value != channelName)
175 resultId = channelIdCounter++;
176 dicToCheck.Add(resultId.Value, channelName);
177 if (dicToCheck == queryChannelIdMap)
178 channelIdMap.Add(resultId.Value, null);
180 return resultId.Value;
183 ulong userId, channelId;
186 userId = MapAndGetChannelId(queryChannelIdMap);
187 channelId = isPrivate ? userId : MapAndGetChannelId(channelIdMap);
192 Content = e.Data.Message,
197 ConnectionName = address,
198 FriendlyName = isPrivate ? String.Format(CultureInfo.InvariantCulture,
"PM: {0}", channelName) : channelName,
200 IsPrivate = isPrivate
203 FriendlyName = username,
209 EnqueueMessage(message);
217 void Client_OnQueryMessage(
object sender, IrcEventArgs e) => HandleMessage(e,
true);
224 void Client_OnChannelMessage(
object sender, IrcEventArgs e) => HandleMessage(e,
false);
227 public override Task<bool> Connect(CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
229 disconnecting =
false;
233 client.Connect(address, port);
235 cancellationToken.ThrowIfCancellationRequested();
238 client.Login(nickname, nickname, 0, nickname, password);
243 client.WriteLine(
"CAP REQ :sasl", Priority.Critical);
244 cancellationToken.ThrowIfCancellationRequested();
246 client.Login(nickname, nickname, 0, nickname);
251 cancellationToken.ThrowIfCancellationRequested();
252 client.SendMessage(SendType.Message,
"NickServ", String.Format(CultureInfo.InvariantCulture,
"IDENTIFY {0}", password));
257 var recievedAck =
false;
258 var recievedPlus =
false;
259 client.OnReadLine += (sender, e) =>
261 if (e.Line.Contains(
"ACK :sasl", StringComparison.Ordinal))
263 else if (e.Line.Contains(
"AUTHENTICATE +", StringComparison.Ordinal))
267 var startTime = DateTimeOffset.Now;
268 var endTime = DateTimeOffset.Now.AddSeconds(TimeoutSeconds);
269 cancellationToken.ThrowIfCancellationRequested();
270 for (; !recievedAck && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult())
271 client.Listen(
false);
273 client.WriteLine(
"AUTHENTICATE PLAIN", Priority.Critical);
274 cancellationToken.ThrowIfCancellationRequested();
276 for (; !recievedPlus && DateTimeOffset.Now <= endTime; Task.Delay(10, cancellationToken).GetAwaiter().GetResult())
277 client.Listen(
false);
280 var authString = String.Format(CultureInfo.InvariantCulture,
"{0}{1}{0}{1}{2}", nickname,
'\0', password);
281 var b64 = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString));
282 var authLine = String.Format(CultureInfo.InvariantCulture,
"AUTHENTICATE {0}", b64);
283 var chars = authLine.ToCharArray();
284 client.WriteLine(authLine, Priority.Critical);
286 cancellationToken.ThrowIfCancellationRequested();
287 client.WriteLine(
"CAP END", Priority.Critical);
290 client.Listen(
false);
292 listenTask = Task.Factory.StartNew(() =>
294 while (!disconnecting && client.IsConnected)
296 client.ListenOnce(true);
297 if (disconnecting || !client.IsConnected)
299 client.Listen(false);
301 if (client.Nickname != nickname && client.GetIrcUser(nickname) == null)
302 client.RfcNick(nickname);
304 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
308 logger.LogWarning(
"Unable to connect to IRC: {0}", e);
311 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
314 public override async Task
Disconnect(CancellationToken cancellationToken)
320 await Task.Factory.StartNew(() =>
324 client.RfcQuit(
"Mr. Stark, I don't feel so good...", Priority.Critical);
328 logger.LogWarning(
"Error quitting IRC: {0}", e);
330 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(
false);
332 await listenTask.ConfigureAwait(
false);
336 logger.LogWarning(
"Error disconnecting from IRC! Exception: {0}", e);
341 public override Task<IReadOnlyList<Channel>> MapChannels(IEnumerable<ChatChannel> channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
343 if (channels.Any(x => x.IrcChannel == null))
344 throw new InvalidOperationException(
"ChatChannel missing IrcChannel!");
347 var hs =
new HashSet<string>();
348 foreach (var I
in channels)
349 hs.Add(I.IrcChannel);
350 var toPart =
new List<string>();
351 foreach (var I
in client.JoinedChannels)
355 foreach (var I
in toPart)
356 client.RfcPart(I,
"Pretty nice abscond!");
357 foreach (var I
in hs)
360 return (IReadOnlyList<Channel>)channels.Select(x =>
363 if (!channelIdMap.Any(y =>
365 if (y.Value != x.IrcChannel)
371 id = channelIdCounter++;
372 channelIdMap.Add(
id.Value, x.IrcChannel);
377 IsAdmin = x.IsAdminChannel ==
true,
378 ConnectionName = address,
379 FriendlyName = channelIdMap[
id.Value],
385 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
388 public override Task SendMessage(ulong channelId,
string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
390 var channelName = channelIdMap[channelId];
392 if (channelName == null)
394 channelName = queryChannelIdMap[channelId];
395 sendType = SendType.Notice;
398 sendType = SendType.Message;
401 client.SendMessage(sendType, channelName, message);
405 logger.LogWarning(
"Unable to send to channel: {0}", e);
407 }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
ulong channelIdCounter
Id counter for channelIdMap
readonly Dictionary< ulong, string > queryChannelIdMap
Map of Channel.RealIds to query users
readonly IrcFeatures client
The IrcFeatures client
diff --git a/_tgstation_8_server_8_api_8_assembly_info_8cs.html b/_tgstation_8_server_8_api_8_assembly_info_8cs.html
index 88fc90fce7..823d805b2e 100644
--- a/_tgstation_8_server_8_api_8_assembly_info_8cs.html
+++ b/_tgstation_8_server_8_api_8_assembly_info_8cs.html
@@ -4,7 +4,7 @@
-
tgstation-server: src/Tgstation.Server.Api/obj/Release/netstandard2.0/Tgstation.Server.Api.AssemblyInfo.cs File Reference
+
tgstation-server: src/Tgstation.Server.Api/obj/Debug/netstandard2.0/Tgstation.Server.Api.AssemblyInfo.cs File Reference
@@ -84,7 +84,7 @@ var searchBox = new SearchBox("searchBox", "search",false,'Search');