Fix chat for IRC at least

This commit is contained in:
Jordan Brown
2018-07-29 13:27:57 -04:00
parent 21d9a7b871
commit bc0a19e8ab
5 changed files with 147 additions and 48 deletions
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights
/// </summary>
WriteConnectionString = 8,
/// <summary>
/// User can read <see cref="Models.Internal.ChatSettings.ConnectionString"/>
/// User can read <see cref="Models.Internal.ChatSettings.ConnectionString"/> requires <see cref="Read"/>
/// </summary>
ReadConnectionString = 16,
/// <summary>
+100 -24
View File
@@ -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<string>(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<ulong> { 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<ICommand> 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<ulong> { 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<ulong> { message.User.Channel.RealId }, cancellationToken).ConfigureAwait(false);
await SendMessage(UnknownCommandMessage, new List<ulong> { 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<IProvider>();
//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);
}
@@ -133,7 +133,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
/// <inheritdoc />
public override void Dispose() => client.Disconnect(); //just closes the socket
public override void Dispose()
{
if(Connected)
client.Disconnect(); //just closes the socket
}
/// <summary>
/// Handle an IRC message
@@ -142,10 +146,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// <param name="isPrivate">If this is a query message</param>
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);
}
}
/// <inheritdoc />
@@ -315,7 +326,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
client.RfcJoin(I);
return (IReadOnlyList<Channel>)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
/// <inheritdoc />
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)
{
@@ -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();
}
}
@@ -36,7 +36,7 @@
<ItemGroup>
<PackageReference Include="Byond.TopicSender" Version="1.1.1" />
<PackageReference Include="Cyberboss.AspNetCore.AsyncInitializer" Version="1.1.0" />
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.5" />
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.6" />
<PackageReference Include="Discord.Net.WebSocket" Version="1.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.26.0-preview-0027" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />