Merge pull request #950 from tgstation/865-IRCKeys

Allows joining IRC channels with keys
This commit is contained in:
Jordan Brown
2020-04-27 22:46:06 -04:00
committed by GitHub
5 changed files with 110 additions and 56 deletions
+1 -1
View File
@@ -3,7 +3,7 @@
<!-- This is the authorative version list -->
<!-- Integration tests will ensure they match across the board -->
<TgsCoreVersion>4.1.0</TgsCoreVersion>
<TgsApiVersion>6.0.0</TgsApiVersion>
<TgsApiVersion>6.1.0</TgsApiVersion>
<TgsClientVersion>6.0.0</TgsClientVersion>
<TgsDmapiVersion>5.0.0</TgsDmapiVersion>
<TgsControlPanelVersion>0.4.0</TgsControlPanelVersion>
@@ -8,7 +8,8 @@ namespace Tgstation.Server.Api.Models
public class ChatChannel
{
/// <summary>
/// The IRC channel name
/// The IRC channel name. Also potentially contains the channel passsword (if separated by a colon).
/// If multiple copies of the same channel with different keys are added to the server, the one that will be used is undefined.
/// </summary>
[StringLength(Limits.MaximumIndexableStringLength)]
public string IrcChannel { get; set; }
@@ -9,6 +9,7 @@ using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Core;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.System;
namespace Tgstation.Server.Host.Components.Chat.Providers
@@ -376,52 +377,68 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
/// <inheritdoc />
public override Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(IEnumerable<Api.Models.ChatChannel> channels, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
{
if (channels.Any(x => x.IrcChannel == null))
throw new InvalidOperationException("ChatChannel missing IrcChannel!");
lock (this)
public override Task<IReadOnlyCollection<ChannelRepresentation>> MapChannels(
IEnumerable<ChatChannel> channels,
CancellationToken cancellationToken)
=> Task.Factory.StartNew(() =>
{
var hs = new HashSet<string>(); // for unique inserts
foreach (var I in channels)
hs.Add(I.IrcChannel);
var toPart = new List<string>();
foreach (var I in client.JoinedChannels)
if (!hs.Remove(I))
toPart.Add(I);
foreach (var I in toPart)
client.RfcPart(I, "Pretty nice abscond!");
foreach (var I in hs)
client.RfcJoin(I);
return (IReadOnlyCollection<ChannelRepresentation>)channels.Select(x =>
if (channels.Any(x => x.IrcChannel == null))
throw new InvalidOperationException("ChatChannel missing IrcChannel!");
lock (this)
{
ulong? id = null;
if (!channelIdMap.Any(y =>
var channelsWithKeys = new Dictionary<string, string>();
var hs = new HashSet<string>(); // for unique inserts
foreach (var channel in channels)
{
if (y.Value != x.IrcChannel)
return false;
id = y.Key;
return true;
}))
{
id = channelIdCounter++;
channelIdMap.Add(id.Value, x.IrcChannel);
var name = channel.GetIrcChannelName();
var key = channel.GetIrcChannelKey();
if (hs.Add(name) && key != null)
channelsWithKeys.Add(name, key);
}
return new ChannelRepresentation
{
RealId = id.Value,
IsAdminChannel = x.IsAdminChannel == true,
ConnectionName = address,
FriendlyName = channelIdMap[id.Value],
IsPrivateChannel = false,
Tag = x.Tag
};
}).ToList();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
var toPart = new List<string>();
foreach (var activeChannel in client.JoinedChannels)
if (!hs.Remove(activeChannel))
toPart.Add(activeChannel);
foreach (var channelToLeave in toPart)
client.RfcPart(channelToLeave, "Pretty nice abscond!");
foreach (var channelToJoin in hs)
if (channelsWithKeys.TryGetValue(channelToJoin, out var key))
client.RfcJoin(channelToJoin, key);
else
client.RfcJoin(channelToJoin);
return (IReadOnlyCollection<ChannelRepresentation>)channels
.Select(x =>
{
var channelName = x.GetIrcChannelName();
ulong? id = null;
if (!channelIdMap.Any(y =>
{
if (y.Value != channelName)
return false;
id = y.Key;
return true;
}))
{
id = channelIdCounter++;
channelIdMap.Add(id.Value, channelName);
}
return new ChannelRepresentation
{
RealId = id.Value,
IsAdminChannel = x.IsAdminChannel == true,
ConnectionName = address,
FriendlyName = channelIdMap[id.Value],
IsPrivateChannel = false,
Tag = x.Tag
};
})
.ToList();
}
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
/// <inheritdoc />
public override Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken) => Task.Factory.StartNew(() =>
@@ -0,0 +1,49 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Extensions
{
/// <summary>
/// Extensions for the <see cref="ChatChannel"/> <see langword="class"/>.
/// </summary>
static class ChatChannelExtensions
{
/// <summary>
/// Gets the IRC channel name from a given <paramref name="chatChannel"/>.
/// </summary>
/// <param name="chatChannel">The <see cref="ChatChannel"/> to retrieve information from.</param>
/// <returns>The IRC channel name stored in the <paramref name="chatChannel"/>.</returns>
public static string GetIrcChannelName(this ChatChannel chatChannel) => GetIrcChannelSplits(chatChannel).First();
/// <summary>
/// Gets the IRC channel key from a given <paramref name="chatChannel"/>.
/// </summary>
/// <param name="chatChannel">The <see cref="ChatChannel"/> to retrieve information from.</param>
/// <returns>The IRC channel key stored in the <paramref name="chatChannel"/> if it exists, <see langword="null"/> otherwise.</returns>
public static string GetIrcChannelKey(this ChatChannel chatChannel)
{
var splits = GetIrcChannelSplits(chatChannel);
if (splits.Count < 2)
return null;
return splits.Last();
}
/// <summary>
/// Split a given <paramref name="chatChannel"/>'s <see cref="ChatChannel.IrcChannel"/>.
/// </summary>
/// <param name="chatChannel">The <see cref="ChatChannel"/> to work with.</param>
/// <returns>A <see cref="IReadOnlyCollection{T}"/> of the <paramref name="chatChannel"/>'s <see cref="ChatChannel.IrcChannel"/> <see cref="string"/> separated by the ':' <see cref="char"/>.</returns>
static IReadOnlyCollection<string> GetIrcChannelSplits(ChatChannel chatChannel)
{
if (chatChannel == null)
throw new ArgumentNullException(nameof(chatChannel));
if (chatChannel.IrcChannel == null)
throw new ArgumentException("IrcChannel must be set!", nameof(chatChannel));
return chatChannel.IrcChannel.Split(new[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
}
}
}
@@ -60,19 +60,6 @@ namespace Tgstation.Server.Tests
Assert.AreEqual(expected, actual);
}
[TestMethod]
public void TestClientVersionFollowingApiVersion()
{
var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value + ".0";
Assert.IsNotNull(versionString);
Assert.IsTrue(Version.TryParse(versionString, out var apiVersion));
versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsClientVersion").Value + ".0";
Assert.IsNotNull(versionString);
Assert.IsTrue(Version.TryParse(versionString, out var clientVersion));
Assert.IsTrue(clientVersion >= apiVersion);
}
[TestMethod]
public void TestWatchdogVersion()
{