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 @@
-
+