From bc0a19e8ab55911f38442ac49407cd1707ec0f69 Mon Sep 17 00:00:00 2001 From: Jordan Brown Date: Sun, 29 Jul 2018 13:27:57 -0400 Subject: [PATCH] Fix chat for IRC at least --- .../Rights/ChatSettingsRights.cs | 2 +- .../Components/Chat/Chat.cs | 124 ++++++++++++++---- .../Components/Chat/Providers/IrcProvider.cs | 57 +++++--- .../Controllers/ChatController.cs | 10 +- .../Tgstation.Server.Host.csproj | 2 +- 5 files changed, 147 insertions(+), 48 deletions(-) diff --git a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs index c4336d76dd..a071e2bf38 100644 --- a/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs +++ b/src/Tgstation.Server.Api/Rights/ChatSettingsRights.cs @@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights /// WriteConnectionString = 8, /// - /// User can read + /// User can read requires /// ReadConnectionString = 16, /// diff --git a/src/Tgstation.Server.Host/Components/Chat/Chat.cs b/src/Tgstation.Server.Host/Components/Chat/Chat.cs index 4f3d2ba469..8748e414a8 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Chat.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Chat.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -132,8 +133,8 @@ namespace Tgstation.Server.Host.Components.Chat Task task; lock (mappedChannels) { - foreach (var kvp in mappedChannels.Where(x => x.Value.ProviderId == connectionId)) - mappedChannels.Remove(kvp.Key); + foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == connectionId).Select(x => x.Key).ToList()) + mappedChannels.Remove(I); if (updateTrackings) lock (trackingContexts) @@ -156,6 +157,32 @@ namespace Tgstation.Server.Host.Components.Chat { logger.LogTrace("Chat message: {0}. User (Note unconverted provider Id): {1}", message.Content, JsonConvert.SerializeObject(message.User)); + //map the channel if it's private and we haven't seen it + if (message.User.Channel.IsPrivate) + lock (providers) + lock (mappedChannels) + { + if (!provider.Connected) + return; + var enumerable = mappedChannels.Where(x => x.Value.ProviderChannelId == message.User.Channel.RealId); + if (!enumerable.Any()) + { + ulong newId; + lock (this) + newId = channelIdCounter++; + mappedChannels.Add(newId, new ChannelMapping + { + IsWatchdogChannel = false, + ProviderChannelId = message.User.Channel.RealId, + ProviderId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(), + Channel = message.User.Channel + }); + message.User.Channel.RealId = newId; + } + else + message.User.Channel.RealId = enumerable.First().Key; + } + var splits = new List(message.Content.TrimEnd().Split(' ')); var address = splits[0]; if (address.Length > 1 && (address[address.Length - 1] == ':' || address[address.Length - 1] == ',')) @@ -163,18 +190,21 @@ namespace Tgstation.Server.Host.Components.Chat address = address.ToUpperInvariant(); - if (address != CommonMention.ToUpperInvariant() && address != provider.BotMention.ToUpperInvariant()) + var addressed = address == CommonMention.ToUpperInvariant() || address == provider.BotMention.ToUpperInvariant(); + + if (!addressed && !message.User.Channel.IsPrivate) //no mention return; - if (splits.Count == 1) + if ((splits.Count == 1 && !message.User.Channel.IsPrivate) || splits.Count == 0) { //just a mention await SendMessage("Hi!", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } - splits.RemoveAt(0); + if (addressed) + splits.RemoveAt(0); var command = splits[0].ToUpperInvariant(); splits.RemoveAt(0); @@ -182,16 +212,46 @@ namespace Tgstation.Server.Host.Components.Chat try { - if (!builtinCommands.TryGetValue(command, out ICommand commandHandler)) + async Task GetCommand(string commandName) { - var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); - await Task.WhenAll(tasks).ConfigureAwait(false); - commandHandler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == command).FirstOrDefault(); + if (!builtinCommands.TryGetValue(commandName, out var handler)) + { + var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + handler = tasks.SelectMany(x => x.Result).Where(x => x.Name.ToUpperInvariant() == commandName).FirstOrDefault(); + } + return handler; + }; + + const string UnknownCommandMessage = "Unknown command! Type '?' or 'help' for available commands."; + + if (command == "HELP" || command == "?") + { + string helpText; + if (splits.Count == 0) + { + var allCommands = builtinCommands.Select(x => x.Value).ToList(); + var tasks = trackingContexts.Select(x => x.GetCustomCommands(cancellationToken)); + await Task.WhenAll(tasks).ConfigureAwait(false); + allCommands.AddRange(tasks.SelectMany(x => x.Result)); + helpText = String.Format(CultureInfo.InvariantCulture, "Available commands: Type 'help' and then a command name for more details: {0}", String.Join(", ", allCommands.Select(x => x.Name))); + } + else + { + var helpHandler = await GetCommand(splits[0].ToUpperInvariant()).ConfigureAwait(false); + if (helpHandler != default) + helpText = String.Format(CultureInfo.InvariantCulture, "{0}: {1}", helpHandler.Name, helpHandler.HelpText); + else + helpText = UnknownCommandMessage; + } + await SendMessage(helpText, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + return; } - if (command == default) + var commandHandler = await GetCommand(command).ConfigureAwait(false); + if (commandHandler == default) { - await SendMessage("Invalid command! Type '?' or 'help' for available commands.", new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); + await SendMessage(UnknownCommandMessage, new List { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false); return; } @@ -220,9 +280,8 @@ namespace Tgstation.Server.Host.Components.Chat while (!cancellationToken.IsCancellationRequested) { //prune disconnected providers - foreach (var I in messageTasks) - if (!I.Key.Connected) - messageTasks.Remove(I.Key); + foreach (var I in messageTasks.Where(x => !x.Key.Connected).ToList()) + messageTasks.Remove(I.Key); //add new ones foreach (var I in providers) @@ -237,19 +296,16 @@ namespace Tgstation.Server.Host.Components.Chat //wait for a message await Task.WhenAny(messageTasks.Select(x => x.Value)).ConfigureAwait(false); - - var toRemove = new List(); - //process completed ones - foreach (var I in messageTasks.Where(x => x.Value.IsCompleted)) + + //process completed ones + foreach (var I in messageTasks.Where(x => x.Value.IsCompleted).ToList()) { var message = await I.Value.ConfigureAwait(false); await ProcessMessage(I.Key, message, cancellationToken).ConfigureAwait(false); - toRemove.Add(I.Key); + messageTasks.Remove(I.Key); } - foreach (var I in toRemove) - messageTasks.Remove(I); } } catch (OperationCanceledException) { } @@ -310,23 +366,43 @@ namespace Tgstation.Server.Host.Components.Chat if (newSettings == null) throw new ArgumentNullException(nameof(newSettings)); IProvider provider; + + async Task DisconnectProvider(IProvider p) + { + try + { + await p.Disconnect(cancellationToken).ConfigureAwait(false); + } + finally + { + p.Dispose(); + } + } + + Task disconnectTask; lock (providers) { //raw settings changes forces a rebuild of the provider if (providers.TryGetValue(newSettings.Id, out provider)) { providers.Remove(newSettings.Id); - provider.Dispose(); + disconnectTask = DisconnectProvider(provider); } + else + disconnectTask = Task.CompletedTask; if (newSettings.Enabled.Value) { provider = providerFactory.CreateProvider(newSettings); providers.Add(newSettings.Id, provider); } } + lock (mappedChannels) - foreach (var channelId in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key)) - mappedChannels.Remove(channelId); + foreach (var I in mappedChannels.Where(x => x.Value.ProviderId == newSettings.Id).Select(x => x.Key).ToList()) + mappedChannels.Remove(I); + + await disconnectTask.ConfigureAwait(false); + if (newSettings.Enabled.Value && started) await provider.Connect(cancellationToken).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 557b980fd8..adb436d08a 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -133,7 +133,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - public override void Dispose() => client.Disconnect(); //just closes the socket + public override void Dispose() + { + if(Connected) + client.Disconnect(); //just closes the socket + } /// /// Handle an IRC message @@ -142,10 +146,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// If this is a query message void HandleMessage(IrcEventArgs e, bool isPrivate) { - if (e.Data.From.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) + if (e.Data.Nick.ToUpperInvariant() == client.Nickname.ToUpperInvariant()) return; - var username = e.Data.From; + var username = e.Data.Nick; var channelName = isPrivate ? username : e.Data.Channel; ulong channelId = 0; lock (this) @@ -279,19 +283,26 @@ namespace Tgstation.Server.Host.Components.Chat.Providers { if (!Connected) return; - await Task.Factory.StartNew(() => + try { - try + await Task.Factory.StartNew(() => { - client.RfcQuit(); - } - catch (Exception e) - { - logger.LogWarning("Error quitting IRC: {0}", e); - } - }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); - Dispose(); - await listenTask.ConfigureAwait(false); + try + { + client.RfcQuit("Mr. Stark, I don't feel so good...", Priority.Critical); //priocritical otherwise Disconnect will hard block + } + catch (Exception e) + { + logger.LogWarning("Error quitting IRC: {0}", e); + } + }, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false); + Dispose(); + await listenTask.ConfigureAwait(false); + } + catch (Exception e) + { + logger.LogWarning("Error disconnecting from IRC! Exception: {0}", e); + } } /// @@ -315,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers client.RfcJoin(I); return (IReadOnlyList)channels.Select(x => { - ulong id = channelIdCounter; + var id = channelIdCounter; if (!channelIdMap.Any(y => { if (y.Value != x.IrcChannel) @@ -323,9 +334,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers id = y.Key; return true; })) + { channelIdMap.Add(id, x.IrcChannel); - else ++channelIdCounter; + } return new Channel { RealId = id, @@ -341,11 +353,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() => { - var channelName = channelIdMap[channelId] ?? queryChannelIdMap[channelId]; + var channelName = channelIdMap[channelId]; + SendType sendType; + if (channelName == null) + { + channelName = queryChannelIdMap[channelId]; + sendType = SendType.Notice; + } + else + sendType = SendType.Message; try { - if (client.JoinedChannels.Contains(channelName)) - client.SendMessage(SendType.Message, channelName, message); + client.SendMessage(sendType, channelName, message); } catch(Exception e) { diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index a06c115667..ebbdd55548 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -219,11 +219,15 @@ namespace Tgstation.Server.Host.Controllers //have to rebuild the thing first await chat.ChangeSettings(current, cancellationToken).ConfigureAwait(false); - if (model.Channels != null) + if (model.Channels != null || anySettingsModified) await chat.ChangeChannels(current.Id, current.Channels, cancellationToken).ConfigureAwait(false); - if(userRights.HasFlag(ChatSettingsRights.Read)) - return Json(current); + if (userRights.HasFlag(ChatSettingsRights.Read)) + { + if (!userRights.HasFlag(ChatSettingsRights.ReadConnectionString)) + current.ConnectionString = null; + return Json(current.ToApi()); + } return Ok(); } } diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index f7ba838484..3643b12614 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -36,7 +36,7 @@ - +