diff --git a/build/Version.props b/build/Version.props index ba7d174e52..a5f8a360ad 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,11 +3,11 @@ - 5.2.4 + 5.3.0 4.4.0 - 9.7.0 - 10.1.0 - 11.1.0 + 9.8.0 + 10.2.0 + 11.2.0 6.0.6 5.3.0 1.2.0 diff --git a/src/Tgstation.Server.Api/Models/ChatChannel.cs b/src/Tgstation.Server.Api/Models/ChatChannel.cs index 49dc7c0898..07b643fcab 100644 --- a/src/Tgstation.Server.Api/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Api/Models/ChatChannel.cs @@ -1,49 +1,38 @@ -using System.ComponentModel.DataAnnotations; +using System; +using System.ComponentModel.DataAnnotations; + +using Tgstation.Server.Api.Models.Internal; namespace Tgstation.Server.Api.Models { /// /// Indicates a chat channel. /// - public class ChatChannel + public class ChatChannel : ChatChannelBase { + /// + /// The channel identifier. Supercedes and . + /// For , it's the IRC channel name and optional password colon separated. + /// For , it's the stringified Discord channel snowflake. + /// + [Required] + [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] + public string? ChannelData { get; set; } + /// /// 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. /// [ResponseOptions] [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] + [Obsolete($"Use {nameof(ChannelData)}")] public string? IrcChannel { get; set; } /// /// The Discord channel ID. /// + [Obsolete($"Use {nameof(ChannelData)}")] [ResponseOptions] public ulong? DiscordChannelId { get; set; } - - /// - /// If the is an admin channel. - /// - [Required] - public bool? IsAdminChannel { get; set; } - - /// - /// If the is a watchdog channel. - /// - [Required] - public bool? IsWatchdogChannel { get; set; } - - /// - /// If the is an updates channel. - /// - [Required] - public bool? IsUpdatesChannel { get; set; } - - /// - /// A custom tag users can define to group channels together. - /// - [ResponseOptions] - [StringLength(Limits.MaximumStringLength)] - public string? Tag { get; set; } } } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs b/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs index ef82e1e5cb..2c4dcd8875 100644 --- a/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs +++ b/src/Tgstation.Server.Api/Models/Internal/ChatBotApiBase.cs @@ -22,8 +22,8 @@ namespace Tgstation.Server.Api.Models.Internal return true; return Provider.Value switch { - ChatProvider.Discord => Channels?.Select(x => x.DiscordChannelId.HasValue && x.IrcChannel == null).All(x => x) ?? true, - ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && x.IrcChannel != null).All(x => x) ?? true, + ChatProvider.Discord => Channels?.Select(x => (x.DiscordChannelId.HasValue || ulong.TryParse(x.ChannelData, out _)) && x.IrcChannel == null).All(x => x) ?? true, + ChatProvider.Irc => Channels?.Select(x => !x.DiscordChannelId.HasValue && (x.IrcChannel != null || x.ChannelData != null)).All(x => x) ?? true, _ => throw new InvalidOperationException("Invalid provider type!"), }; } diff --git a/src/Tgstation.Server.Api/Models/Internal/ChatChannelBase.cs b/src/Tgstation.Server.Api/Models/Internal/ChatChannelBase.cs new file mode 100644 index 0000000000..87da5746ae --- /dev/null +++ b/src/Tgstation.Server.Api/Models/Internal/ChatChannelBase.cs @@ -0,0 +1,35 @@ +using System.ComponentModel.DataAnnotations; + +namespace Tgstation.Server.Api.Models.Internal +{ + /// + /// Base chat channel class. + /// + public abstract class ChatChannelBase + { + /// + /// If the is an admin channel. + /// + [Required] + public bool? IsAdminChannel { get; set; } + + /// + /// If the is a watchdog channel. + /// + [Required] + public bool? IsWatchdogChannel { get; set; } + + /// + /// If the is an updates channel. + /// + [Required] + public bool? IsUpdatesChannel { get; set; } + + /// + /// A custom tag users can define to group channels together. + /// + [ResponseOptions] + [StringLength(Limits.MaximumStringLength)] + public string? Tag { get; set; } + } +} diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 0840359482..d6179cd730 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -16,7 +16,7 @@ https://github.com/tgstation/tgstation-server 2018-2022 json web api tgstation-server tgstation ss13 byond - Retargeted to netstandard2.0 to support migrator. + Added ChannelData field to ChatChannels model. true snupkg ../../build/analyzers.ruleset diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 65f7edce9f..5f9daa6809 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -16,7 +16,7 @@ https://github.com/tgstation/tgstation-server 2018-2022 json web api tgstation-server tgstation ss13 byond client - Retargeted to netstandard2.0 to support migrator. Fixed nullablity of IByondClient.SetActiveVersion's Stream parameter. + Updated to API library 10.2.0. true snupkg ../../build/analyzers.ruleset diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs index 0d2cf0fd64..97460ba2fd 100644 --- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs @@ -88,7 +88,7 @@ namespace Tgstation.Server.Host.Components.Chat readonly object synchronizationLock; /// - /// The for the . + /// The for the . /// ICustomCommandHandler customCommandHandler; @@ -175,7 +175,7 @@ namespace Tgstation.Server.Host.Components.Chat } /// - public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) + public async Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken) { if (newChannels == null) throw new ArgumentNullException(nameof(newChannels)); @@ -554,7 +554,7 @@ namespace Tgstation.Server.Host.Components.Chat async Task RemapProvider(IProvider provider, CancellationToken cancellationToken) { logger.LogTrace("Remapping channels for provider reconnection..."); - IEnumerable channelsToMap; + IEnumerable channelsToMap; long providerId; lock (providers) providerId = providers.Where(x => x.Value == provider).Select(x => x.Key).First(); diff --git a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs index 2cad279068..41e9acfa40 100644 --- a/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs +++ b/src/Tgstation.Server.Host/Components/Chat/IChatManager.cs @@ -40,10 +40,10 @@ namespace Tgstation.Server.Host.Components.Chat /// Change chat channels. /// /// The of the connection. - /// An of the new list of s. + /// An of the new list of s. /// The for the operation. /// A representing the running operation. - Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken); + Task ChangeChannels(long connectionId, IEnumerable newChannels, CancellationToken cancellationToken); /// /// Queue a chat to a given set of . diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs index dbb4d6e4c5..aba57ae759 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs @@ -608,14 +608,14 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override async Task>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) + protected override async Task>> MapChannelsImpl(IEnumerable channels, CancellationToken cancellationToken) { if (channels == null) throw new ArgumentNullException(nameof(channels)); var remapRequired = false; - async Task> GetModelChannelFromDBChannel(Api.Models.ChatChannel channelFromDB) + async Task> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB) { if (!channelFromDB.DiscordChannelId.HasValue) throw new InvalidOperationException("ChatChannel missing DiscordChannelId!"); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs index 5ec71c169e..702a51e984 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers Task InitialConnectionJob { get; } /// - /// Indicate to the provider that at least one call has successfully completed. + /// Indicate to the provider that at least one call has successfully completed. /// void InitialMappingComplete(); @@ -42,7 +42,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// The for the operation. /// A resulting in the next available or if the needed to reconnect. - /// Note that private messages will come in the form of s not returned in . Do not the on continuations run from the returned . + /// Note that private messages will come in the form of s not returned in . Do not the on continuations run from the returned . Task NextMessage(CancellationToken cancellationToken); /// @@ -57,8 +57,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers /// /// The s to map. /// The for the operation. - /// A resulting in a of the 's s representing . - Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken); + /// A resulting in a of the 's s representing . + Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken); /// /// Send a message to the . diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs index 1a3d22f682..42c26c813f 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs @@ -252,8 +252,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } /// - protected override Task>> MapChannelsImpl( - IEnumerable channels, + protected override Task>> MapChannelsImpl( + IEnumerable channels, CancellationToken cancellationToken) => Task.Factory.StartNew( () => @@ -285,10 +285,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers else client.RfcJoin(channelToJoin); - return (IReadOnlyCollection>)channels - .Select(apiChannel => + return (IReadOnlyCollection>)channels + .Select(dbChannel => { - var channelName = apiChannel.GetIrcChannelName(); + var channelName = dbChannel.GetIrcChannelName(); ulong? id = null; if (!channelIdMap.Any(y => { @@ -303,15 +303,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers } return Tuple.Create( - apiChannel, + dbChannel, new ChannelRepresentation { RealId = id.Value, - IsAdminChannel = apiChannel.IsAdminChannel == true, + IsAdminChannel = dbChannel.IsAdminChannel == true, ConnectionName = address, FriendlyName = channelIdMap[id.Value], IsPrivateChannel = false, - Tag = apiChannel.Tag, + Tag = dbChannel.Tag, }); }) .ToList(); diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs index 17f2b35fb2..3c339a6666 100644 --- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs +++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs @@ -117,7 +117,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers public void InitialMappingComplete() => initialConnectionTcs.TrySetResult(null); /// - public async Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken) + public async Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken) { try { @@ -193,13 +193,13 @@ namespace Tgstation.Server.Host.Components.Chat.Providers protected abstract Task DisconnectImpl(CancellationToken cancellationToken); /// - /// Implementation of . + /// Implementation of . /// - /// The s to map. + /// The s to map. /// The for the operation. - /// A resulting in a of the 's s representing . - protected abstract Task>> MapChannelsImpl( - IEnumerable channels, + /// A resulting in a of the 's s representing . + protected abstract Task>> MapChannelsImpl( + IEnumerable channels, CancellationToken cancellationToken); /// diff --git a/src/Tgstation.Server.Host/Controllers/ChatController.cs b/src/Tgstation.Server.Host/Controllers/ChatController.cs index 0ce4d56bf3..27227b1f81 100644 --- a/src/Tgstation.Server.Host/Controllers/ChatController.cs +++ b/src/Tgstation.Server.Host/Controllers/ChatController.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Linq.Expressions; using System.Net; @@ -57,16 +58,39 @@ namespace Tgstation.Server.Host.Controllers /// Converts to a . /// /// The . + /// The channel's . /// A based on . - static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api) => new () + static Models.ChatChannel ConvertApiChatChannel(Api.Models.ChatChannel api, ChatProvider chatProvider) { - DiscordChannelId = api.DiscordChannelId, - IrcChannel = api.IrcChannel, - IsAdminChannel = api.IsAdminChannel ?? false, - IsWatchdogChannel = api.IsWatchdogChannel ?? false, - IsUpdatesChannel = api.IsUpdatesChannel ?? false, - Tag = api.Tag, - }; + var result = new Models.ChatChannel + { +#pragma warning disable CS0618 + DiscordChannelId = api.DiscordChannelId, + IrcChannel = api.IrcChannel, +#pragma warning restore CS0618 + IsAdminChannel = api.IsAdminChannel ?? false, + IsWatchdogChannel = api.IsWatchdogChannel ?? false, + IsUpdatesChannel = api.IsUpdatesChannel ?? false, + Tag = api.Tag, + }; + + if (api.ChannelData != null) + { + switch (chatProvider) + { + case ChatProvider.Discord: + result.DiscordChannelId = ulong.Parse(api.ChannelData, CultureInfo.InvariantCulture); + break; + case ChatProvider.Irc: + result.IrcChannel = api.ChannelData; + break; + default: + throw new InvalidOperationException($"Invalid chat provider: {chatProvider}"); + } + } + + return result; + } /// /// Create a new chat bot . @@ -106,7 +130,7 @@ namespace Tgstation.Server.Host.Controllers Name = model.Name, ConnectionString = model.ConnectionString, Enabled = model.Enabled, - Channels = model.Channels?.Select(x => ConvertApiChatChannel(x)).ToList() ?? new List(), // important that this isn't null + Channels = model.Channels?.Select(x => ConvertApiChatChannel(x, model.Provider.Value)).ToList() ?? new List(), // important that this isn't null InstanceId = Instance.Id.Value, Provider = model.Provider, ReconnectionInterval = model.ReconnectionInterval, @@ -319,7 +343,7 @@ namespace Tgstation.Server.Host.Controllers DatabaseContext.ChatChannels.RemoveRange(current.Channels); if (hasChannels) { - var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x)).ToList(); + var dbChannels = model.Channels.Select(x => ConvertApiChatChannel(x, model.Provider ?? current.Provider.Value)).ToList(); DatabaseContext.ChatChannels.AddRange(dbChannels); current.Channels = dbChannels; } diff --git a/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs index e2ef1bd72b..5a3fd8ac26 100644 --- a/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs +++ b/src/Tgstation.Server.Host/Extensions/ChatChannelExtensions.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; -using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Models; namespace Tgstation.Server.Host.Extensions { diff --git a/src/Tgstation.Server.Host/Models/ChatBot.cs b/src/Tgstation.Server.Host/Models/ChatBot.cs index f19b724cd0..8bef6104a8 100644 --- a/src/Tgstation.Server.Host/Models/ChatBot.cs +++ b/src/Tgstation.Server.Host/Models/ChatBot.cs @@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Models /// public ChatBotResponse ToApi() => new ChatBotResponse { - Channels = Channels.Select(x => x.ToApi()).ToList(), + Channels = Channels.Select(x => x.ToApi(Provider.Value)).ToList(), ConnectionString = ConnectionString, Enabled = Enabled, Provider = Provider, diff --git a/src/Tgstation.Server.Host/Models/ChatChannel.cs b/src/Tgstation.Server.Host/Models/ChatChannel.cs index 59a903f5a4..7e88f80306 100644 --- a/src/Tgstation.Server.Host/Models/ChatChannel.cs +++ b/src/Tgstation.Server.Host/Models/ChatChannel.cs @@ -1,7 +1,12 @@ -namespace Tgstation.Server.Host.Models +using System.ComponentModel.DataAnnotations; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Api.Models.Internal; + +namespace Tgstation.Server.Host.Models { /// - public sealed class ChatChannel : Api.Models.ChatChannel, IApiTransformable + public sealed class ChatChannel : ChatChannelBase { /// /// The row Id. @@ -9,23 +14,41 @@ public long Id { get; set; } /// - /// The . + /// The . /// public long ChatSettingsId { get; set; } + /// + /// See . + /// + [StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)] + public string IrcChannel { get; set; } + + /// + /// See . + /// + public ulong? DiscordChannelId { get; set; } + /// /// The . /// public ChatBot ChatSettings { get; set; } - /// - public Api.Models.ChatChannel ToApi() => new Api.Models.ChatChannel + /// + /// Convert to a . + /// + /// The channel's . + /// The converted . + public Api.Models.ChatChannel ToApi(ChatProvider chatProvider) => new Api.Models.ChatChannel { + ChannelData = chatProvider == ChatProvider.Discord ? DiscordChannelId.ToString() : IrcChannel, +#pragma warning disable CS0618 + IrcChannel = IrcChannel, DiscordChannelId = DiscordChannelId, +#pragma warning restore CS0618 IsAdminChannel = IsAdminChannel, IsWatchdogChannel = IsWatchdogChannel, IsUpdatesChannel = IsUpdatesChannel, - IrcChannel = IrcChannel, Tag = Tag, }; } diff --git a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs index 71a0f2ff03..f354110e89 100644 --- a/tests/Tgstation.Server.Tests/Instance/ChatTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/ChatTest.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Threading; @@ -81,7 +81,10 @@ namespace Tgstation.Server.Tests.Instance IsUpdatesChannel = true, IsWatchdogChannel = true, Tag = "butt2", - IrcChannel = channelId + ChannelData = channelId, +#pragma warning disable CS0618 + IrcChannel = "should_not_be_this!!!JHF*WW(#*(*$&(#*@))(" +#pragma warning restore CS0618 } } }, cancellationToken); @@ -93,8 +96,11 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(true, updatedBot.Channels.First().IsUpdatesChannel); Assert.AreEqual(true, updatedBot.Channels.First().IsWatchdogChannel); Assert.AreEqual("butt2", updatedBot.Channels.First().Tag); +#pragma warning disable CS0618 Assert.AreEqual(channelId, updatedBot.Channels.First().IrcChannel); Assert.IsNull(updatedBot.Channels.First().DiscordChannelId); +#pragma warning restore CS0618 + Assert.AreEqual(channelId, updatedBot.Channels.First().ChannelData); } async Task RunDiscord(CancellationToken cancellationToken) @@ -138,17 +144,6 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(true, updatedBot.Enabled); var channelId = UInt64.Parse(Environment.GetEnvironmentVariable("TGS_TEST_DISCORD_CHANNEL")); - firstBot.Channels = new List - { - new ChatChannel - { - IsAdminChannel = true, - IsUpdatesChannel = true, - IsWatchdogChannel = true, - Tag = "butt", - DiscordChannelId = channelId - } - }; updatedBot = await chatClient.Update(new ChatBotUpdateRequest { @@ -161,7 +156,10 @@ namespace Tgstation.Server.Tests.Instance IsUpdatesChannel = true, IsWatchdogChannel = true, Tag = "butt", - DiscordChannelId = channelId + ChannelData = channelId.ToString(), +#pragma warning disable CS0618 + DiscordChannelId = 1234, +#pragma warning restore CS0618 } } }, cancellationToken); @@ -173,8 +171,11 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(true, updatedBot.Channels.First().IsUpdatesChannel); Assert.AreEqual(true, updatedBot.Channels.First().IsWatchdogChannel); Assert.AreEqual("butt", updatedBot.Channels.First().Tag); +#pragma warning disable CS0618 Assert.AreEqual(channelId, updatedBot.Channels.First().DiscordChannelId); Assert.IsNull(updatedBot.Channels.First().IrcChannel); +#pragma warning restore CS0618 + Assert.AreEqual(channelId.ToString(), updatedBot.Channels.First().ChannelData); } public async Task RunPostTest(CancellationToken cancellationToken) @@ -215,7 +216,7 @@ namespace Tgstation.Server.Tests.Instance IsUpdatesChannel = false, IsWatchdogChannel = true, Tag = "butt", - DiscordChannelId = discordBotReq.Channels.First().DiscordChannelId + ChannelData = discordBotReq.Channels.First().ChannelData }); await ApiAssert.ThrowsException(() => chatClient.Update(discordBotReq, cancellationToken), ErrorCode.ChatBotMaxChannels); diff --git a/tools/Tgstation.Server.Migrator.Comms/Program.cs b/tools/Tgstation.Server.Migrator.Comms/Program.cs index 826e07a80c..2aee2a3001 100644 --- a/tools/Tgstation.Server.Migrator.Comms/Program.cs +++ b/tools/Tgstation.Server.Migrator.Comms/Program.cs @@ -204,13 +204,9 @@ static class Program IsWatchdogChannel = providerInfo.WatchdogChannels.Any(x => NormalizeChannelId(x) == channelIdentifier), IsAdminChannel = providerInfo.AdminChannels.Any(x => NormalizeChannelId(x) == channelIdentifier), IsUpdatesChannel = providerInfo.DevChannels.Any(x => NormalizeChannelId(x) == channelIdentifier), + ChannelData = channelIdentifier, }; - if (isDiscordProvider) - newChatChannel.DiscordChannelId = ulong.Parse(channelIdentifier); - else - newChatChannel.IrcChannel = channelIdentifier; - createRequest.Channels.Add(newChatChannel); }