diff --git a/build/Version.props b/build/Version.props
index 9ef2360ec9..26f8009ffe 100644
--- a/build/Version.props
+++ b/build/Version.props
@@ -3,7 +3,7 @@
- 5.9.0
+ 5.10.0
4.5.0
9.9.0
10.3.0
diff --git a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
index 7b204b0b05..97f966d5c2 100644
--- a/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/ChatManager.cs
@@ -212,15 +212,17 @@ namespace Tgstation.Server.Host.Components.Chat
.ToList();
}
- var newMappings = results.Select(tuple => new ChannelMapping
- {
- IsWatchdogChannel = tuple.Item1.IsWatchdogChannel == true,
- IsUpdatesChannel = tuple.Item1.IsUpdatesChannel == true,
- IsAdminChannel = tuple.Item1.IsAdminChannel == true,
- ProviderChannelId = tuple.Item2.RealId,
- ProviderId = connectionId,
- Channel = tuple.Item2,
- });
+ var newMappings = results.SelectMany(
+ kvp => kvp.Value.Select(
+ channelRepresentation => new ChannelMapping
+ {
+ IsWatchdogChannel = kvp.Key.IsWatchdogChannel == true,
+ IsUpdatesChannel = kvp.Key.IsUpdatesChannel == true,
+ IsAdminChannel = kvp.Key.IsAdminChannel == true,
+ ProviderChannelId = channelRepresentation.RealId,
+ ProviderId = connectionId,
+ Channel = channelRepresentation,
+ }));
ulong baseId;
lock (synchronizationLock)
@@ -594,10 +596,11 @@ namespace Tgstation.Server.Host.Components.Chat
///
/// The who recevied .
/// The to process. If , this indicates the provider reconnected.
+ /// If we are called recursively after remapping the provider.
/// The for the operation.
/// A representing the running operation.
#pragma warning disable CA1502
- async Task ProcessMessage(IProvider provider, Message message, CancellationToken cancellationToken)
+ async Task ProcessMessage(IProvider provider, Message message, bool recursed, CancellationToken cancellationToken)
#pragma warning restore CA1502
{
if (!provider.Connected)
@@ -617,6 +620,7 @@ namespace Tgstation.Server.Host.Components.Chat
var providerChannelId = message.User.Channel.RealId;
KeyValuePair? mappedChannel;
long providerId;
+ bool hasChannelZero;
lock (providers)
{
// important, otherwise we could end up processing during shutdown
@@ -630,6 +634,18 @@ namespace Tgstation.Server.Host.Components.Chat
.Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == providerChannelId)
.Select(x => (KeyValuePair?)x)
.FirstOrDefault();
+ hasChannelZero = mappedChannels
+ .Where(x => x.Value.ProviderId == providerId && x.Value.ProviderChannelId == 0)
+ .Any();
+ }
+
+ if (!recursed && !mappedChannel.HasValue && hasChannelZero)
+ {
+ logger.LogInformation("Receieved message from unmapped channel whose provider contains ID 0. Remapping...");
+ await RemapProvider(provider, cancellationToken);
+ logger.LogTrace("Resume processing original message...");
+ await ProcessMessage(provider, message, true, cancellationToken);
+ return;
}
if (message.User.Channel.IsPrivateChannel)
@@ -646,11 +662,17 @@ namespace Tgstation.Server.Host.Components.Chat
newId);
mappedChannels.Add(newId, new ChannelMapping
{
- IsWatchdogChannel = false,
ProviderChannelId = message.User.Channel.RealId,
ProviderId = providerId,
Channel = message.User.Channel,
});
+
+ logger.LogTrace(
+ "Mapping DM {connectionName}:{userId} ({userFriendlyName}) as {newId}",
+ message.User.Channel.ConnectionName,
+ message.User.RealId,
+ message.User.FriendlyName,
+ newId);
message.User.Channel.RealId = newId;
}
else
@@ -668,7 +690,7 @@ namespace Tgstation.Server.Host.Components.Chat
{
message.User.Channel.RealId,
},
- null,
+ message,
new MessageContent
{
Text = "TGS: Processing error, check logs!",
@@ -892,7 +914,7 @@ namespace Tgstation.Server.Host.Components.Chat
using (LogContext.PushProperty("ChatMessage", messageNumber))
try
{
- await ProcessMessage(completedMessageTaskKvp.Key, message, cancellationToken);
+ await ProcessMessage(completedMessageTaskKvp.Key, message, false, cancellationToken);
}
catch (Exception ex)
{
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
index bd29609528..787b0a8e9f 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/DiscordProvider.cs
@@ -13,14 +13,17 @@ using Remora.Discord.API.Abstractions.Gateway.Commands;
using Remora.Discord.API.Abstractions.Gateway.Events;
using Remora.Discord.API.Abstractions.Objects;
using Remora.Discord.API.Abstractions.Rest;
+using Remora.Discord.API.Abstractions.Results;
using Remora.Discord.API.Objects;
using Remora.Discord.Gateway;
using Remora.Discord.Gateway.Extensions;
using Remora.Rest.Core;
+using Remora.Rest.Results;
using Remora.Results;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Components.Interop;
+using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.System;
@@ -47,6 +50,17 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
}
+ ///
+ /// The s supported by the for mapping.
+ ///
+ static readonly ChannelType[] SupportedGuildChannelTypes = new[]
+ {
+ ChannelType.GuildText,
+ ChannelType.GuildAnnouncement,
+ ChannelType.PrivateThread,
+ ChannelType.PublicThread,
+ };
+
///
/// The for the .
///
@@ -102,11 +116,6 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
Snowflake currentUserId;
- ///
- /// The bot's username at the time of connection.
- ///
- string initialUserName;
-
///
/// If is being disposed.
///
@@ -253,46 +262,36 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!result.IsSuccess)
Logger.LogWarning(
- "Failed to send to channel {channelId}: {error}",
+ "Failed to send to channel {channelId}: {result}",
channelId,
- result.Error);
+ result.LogFormat());
}
try
{
if (channelId == 0)
{
- var usersClient = serviceProvider.GetRequiredService();
- var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
- if (!currentGuildsResponse.IsSuccess)
- {
- Logger.LogWarning(
- "Error retrieving current discord guilds: {error}",
- currentGuildsResponse.Error.Message);
- return;
- }
-
- var guildsClient = serviceProvider.GetRequiredService();
-
- var guildsChannelsTasks = currentGuildsResponse.Entity.Select(
- guild => guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken));
-
- await Task.WhenAll(guildsChannelsTasks);
-
- var unmappedTextChannels = guildsChannelsTasks
- .Select(task => task.Result)
- .SelectMany(guildChannels => guildChannels.Entity)
- .Where(guildChannel => guildChannel.Type == ChannelType.GuildText);
-
+ IEnumerable unmappedTextChannels;
+ var allAccessibleTextChannels = await GetAllAccessibleTextChannels(cancellationToken);
lock (mappedChannels)
- unmappedTextChannels = unmappedTextChannels
+ {
+ unmappedTextChannels = allAccessibleTextChannels
.Where(x => !mappedChannels.Contains(x.ID.Value))
.ToList();
+ var remapRequired = unmappedTextChannels.Any()
+ || mappedChannels.Any(
+ mappedChannel => !allAccessibleTextChannels.Any(
+ accessibleTextChannel => accessibleTextChannel.ID == new Snowflake(mappedChannel)));
+
+ if (remapRequired)
+ EnqueueMessage(null);
+ }
+
// discord API confirmed weak boned: https://stackoverflow.com/a/52462336
if (unmappedTextChannels.Any())
{
- Logger.LogTrace("Dispatching to {0} unmapped channels...", unmappedTextChannels.Count());
+ Logger.LogDebug("Dispatching to {count} unmapped channels...", unmappedTextChannels.Count());
await Task.WhenAll(
unmappedTextChannels.Select(
x => SendToChannel(x.ID)));
@@ -342,7 +341,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
Timestamp = estimatedCompletionTime ?? default,
};
- Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
+ Logger.LogTrace("Attempting to post deploy embed to channel {channelId}...", channelId);
var channelsClient = serviceProvider.GetRequiredService();
var messageResponse = await channelsClient.CreateMessageAsync(
@@ -353,7 +352,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
;
if (!messageResponse.IsSuccess)
- Logger.LogWarning("Failed to post deploy embed to channel {0}: {1}", channelId, messageResponse.Error.Message);
+ Logger.LogWarning("Failed to post deploy embed to channel {channelId}: {result}", channelId, messageResponse.LogFormat());
return async (errorMessage, dreamMakerOutput) =>
{
@@ -412,8 +411,8 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!createUpdatedMessageResponse.IsSuccess)
Logger.LogWarning(
- "Creating updated deploy embed failed! Error: {0}",
- createUpdatedMessageResponse.Error.Message);
+ "Creating updated deploy embed failed: {result}",
+ createUpdatedMessageResponse.LogFormat());
}
if (!messageResponse.IsSuccess)
@@ -431,9 +430,9 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!editResponse.IsSuccess)
{
Logger.LogWarning(
- "Updating deploy embed {0} failed, attempting new post! Error: {1}",
+ "Updating deploy embed {messageId} failed, attempting new post: {result}",
messageResponse.Entity.ID,
- editResponse.Error.Message);
+ editResponse.LogFormat());
await CreateUpdatedMessage();
}
}
@@ -481,7 +480,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
if (!channelResponse.IsSuccess)
{
Logger.LogWarning(
- "Failed to get channel {0} in response to message {1}!",
+ "Failed to get channel {channelId} in response to message {messageId}!",
messageCreateEvent.ChannelID,
messageCreateEvent.ID);
@@ -503,7 +502,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
{
if (mentionedUs)
Logger.LogTrace(
- "Ignoring mention from {0} ({1}) by {2} ({3}). Channel not mapped!",
+ "Ignoring mention from {channelId} ({channelName}) by {authorId} ({authorName}). Channel not mapped!",
messageCreateEvent.ChannelID,
channelResponse.Entity.Name,
messageCreateEvent.Author.ID,
@@ -521,9 +520,10 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
guildName = messageGuildResponse.Entity.Name;
else
Logger.LogWarning(
- "Failed to get channel {0} in response to message {1}!",
+ "Failed to get channel {channelID} in response to message {messageID}: {result}",
messageCreateEvent.ChannelID,
- messageCreateEvent.ID);
+ messageCreateEvent.ID,
+ messageGuildResponse.LogFormat());
}
var result = new DiscordMessage
@@ -600,12 +600,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
var currentUserResult = await userClient.GetCurrentUserAsync(localCombinedCts.Token);
if (!currentUserResult.IsSuccess)
{
- Logger.LogWarning("Unable to retrieve current user: {0}", currentUserResult.Error.Message);
+ Logger.LogWarning("Unable to retrieve current user: {result}", currentUserResult.LogFormat());
throw new JobException(ErrorCode.ChatCannotConnectProvider);
}
currentUserId = currentUserResult.Entity.ID;
- initialUserName = currentUserResult.Entity.Username;
}
finally
{
@@ -639,97 +638,139 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
localGatewayCts.Cancel();
var gatewayResult = await localGatewayTask;
if (!gatewayResult.IsSuccess)
- Logger.LogWarning("Gateway issue: {0}", gatewayResult.Error.Message);
+ Logger.LogWarning("Gateway issue: {result}", gatewayResult.LogFormat());
localGatewayCts.Dispose();
}
///
- 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;
+ var guildsClient = serviceProvider.GetRequiredService();
- async Task> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
+ async Task>> GetModelChannelFromDBChannel(Models.ChatChannel channelFromDB)
{
if (!channelFromDB.DiscordChannelId.HasValue)
throw new InvalidOperationException("ChatChannel missing DiscordChannelId!");
var channelId = channelFromDB.DiscordChannelId.Value;
- string connectionName;
- string friendlyName;
- if (channelId == 0)
+ var channelsClient = serviceProvider.GetRequiredService();
+ var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
+ if (!discordChannelResponse.IsSuccess)
{
- connectionName = initialUserName;
- friendlyName = "(Unmapped accessible channels)";
+ Logger.LogWarning(
+ "Error retrieving discord channel {channelId}: {result}",
+ channelId,
+ discordChannelResponse.LogFormat());
+
+ remapRequired |= !(discordChannelResponse.Error is RestResultError restResultError
+ && (restResultError.Error?.Code == DiscordError.MissingAccess
+ || restResultError.Error?.Code == DiscordError.UnknownChannel));
+ return null;
}
- else
+
+ var channelType = discordChannelResponse.Entity.Type;
+ if (!SupportedGuildChannelTypes.Contains(channelType))
{
- var channelsClient = serviceProvider.GetRequiredService();
- var discordChannelResponse = await channelsClient.GetChannelAsync(new Snowflake(channelId), cancellationToken);
- if (!discordChannelResponse.IsSuccess)
- {
- Logger.LogWarning(
- "Error retrieving discord channel {channelId}: {error} Inner: {innerError}",
- channelId,
- discordChannelResponse.Error.Message,
- discordChannelResponse.Inner?.Error?.Message);
- remapRequired = true;
- return null;
- }
+ Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
+ return null;
+ }
- var channelType = discordChannelResponse.Entity.Type;
- if (channelType != ChannelType.GuildText && channelType != ChannelType.GuildAnnouncement)
- {
- Logger.LogWarning("Cound not map channel {channelId}! Incorrect type: {channelType}", channelId, discordChannelResponse.Entity.Type);
- return null;
- }
+ var guildId = discordChannelResponse.Entity.GuildID.Value;
- friendlyName = discordChannelResponse.Entity.Name.Value;
- var guildId = discordChannelResponse.Entity.GuildID.Value;
-
- var guildsClient = serviceProvider.GetRequiredService();
- var guildsResponse = await guildsClient.GetGuildAsync(
+ var guildsResponse = await guildsClient.GetGuildAsync(
+ guildId,
+ false,
+ cancellationToken);
+ if (!guildsResponse.IsSuccess)
+ {
+ Logger.LogWarning(
+ "Error retrieving discord guild {guildID}: {result}",
guildId,
- false,
- cancellationToken);
- if (!guildsResponse.IsSuccess)
- {
- Logger.LogWarning(
- "Error retrieving discord guild {guildID}: {error} Inner: {innerError}",
- guildId,
- guildsResponse.Error.Message,
- guildsResponse.Inner?.Error?.Message);
- remapRequired = true;
- return null;
- }
-
- connectionName = guildsResponse.Entity.Name;
+ guildsResponse.LogFormat());
+ remapRequired |= true;
+ return null;
}
+ var connectionName = guildsResponse.Entity.Name;
+
var channelModel = new ChannelRepresentation
{
RealId = channelId,
IsAdminChannel = channelFromDB.IsAdminChannel == true,
- ConnectionName = connectionName,
- FriendlyName = friendlyName,
+ ConnectionName = guildsResponse.Entity.Name,
+ FriendlyName = discordChannelResponse.Entity.Name.Value,
IsPrivateChannel = false,
Tag = channelFromDB.Tag,
EmbedsSupported = true,
};
- Logger.LogTrace("Mapped channel {0}: {1}", channelModel.RealId, channelModel.FriendlyName);
- return Tuple.Create(channelFromDB, channelModel);
+ Logger.LogTrace("Mapped channel {realId}: {friendlyName}", channelModel.RealId, channelModel.FriendlyName);
+ return Tuple.Create>(
+ channelFromDB,
+ new List { channelModel });
}
var tasks = channels
- .Select(x => GetModelChannelFromDBChannel(x))
+ .Where(x => x.DiscordChannelId != 0)
+ .Select(GetModelChannelFromDBChannel)
.ToList();
await Task.WhenAll(tasks);
+ var channelIdZeroModel = channels.FirstOrDefault(x => x.DiscordChannelId == 0);
+ if (channelIdZeroModel != null)
+ {
+ Logger.LogInformation("Mapping ALL additional accessible text channels");
+ var allAccessibleChannels = await GetAllAccessibleTextChannels(cancellationToken);
+ var unmappedTextChannels = allAccessibleChannels
+ .Where(x => !tasks.Any(task => task.Result != null && new Snowflake(task.Result.Item1.DiscordChannelId.Value) == x.ID));
+
+ async Task>> CreateMappingsForUnmappedChannels()
+ {
+ var unmappedTasks =
+ unmappedTextChannels.Select(
+ async unmappedTextChannel =>
+ {
+ var fakeChannelModel = new Models.ChatChannel
+ {
+ DiscordChannelId = unmappedTextChannel.ID.Value,
+ IsAdminChannel = channelIdZeroModel.IsAdminChannel,
+ Tag = channelIdZeroModel.Tag,
+ };
+
+ var tuple = await GetModelChannelFromDBChannel(fakeChannelModel);
+ return tuple?.Item2.First();
+ })
+ .ToList();
+
+ // Add catch-all channel
+ unmappedTasks.Add(Task.FromResult(
+ new ChannelRepresentation
+ {
+ IsAdminChannel = channelIdZeroModel.IsAdminChannel.Value,
+ ConnectionName = "(Unknown Discord Guilds)",
+ EmbedsSupported = true,
+ FriendlyName = "(Unknown Discord Channels)",
+ RealId = 0,
+ Tag = channelIdZeroModel.Tag,
+ }));
+
+ await Task.WhenAll(unmappedTasks);
+ return Tuple.Create>(
+ channelIdZeroModel,
+ unmappedTasks.Select(x => x.Result).Where(x => x != null).ToList());
+ }
+
+ var task = CreateMappingsForUnmappedChannels();
+ await task;
+ tasks.Add(task);
+ }
+
var enumerator = tasks
.Select(x => x.Result)
.Where(x => x != null)
@@ -738,13 +779,76 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
lock (mappedChannels)
{
mappedChannels.Clear();
- mappedChannels.AddRange(enumerator.Select(x => x.Item2.RealId));
+ mappedChannels.AddRange(enumerator.SelectMany(x => x.Item2).Select(x => x.RealId));
}
if (remapRequired)
+ {
+ Logger.LogWarning("Some channels failed to load with unknown errors. We will request that these be remapped, but it may result in communication spam. Please check prior logs and report an issue if this occurs.");
EnqueueMessage(null);
+ }
- return enumerator;
+ return new Dictionary>(enumerator.Select(x => new KeyValuePair>(x.Item1, x.Item2)));
+ }
+
+ ///
+ /// Get all text s accessible to and supported by the bot.
+ ///
+ /// The for the operation.
+ /// A resulting in an of accessible and compatible s.
+ async Task> GetAllAccessibleTextChannels(CancellationToken cancellationToken)
+ {
+ var usersClient = serviceProvider.GetRequiredService();
+ var currentGuildsResponse = await usersClient.GetCurrentUserGuildsAsync(ct: cancellationToken);
+ if (!currentGuildsResponse.IsSuccess)
+ {
+ Logger.LogWarning(
+ "Error retrieving current discord guilds: {result}",
+ currentGuildsResponse.LogFormat());
+ return Enumerable.Empty();
+ }
+
+ var guildsClient = serviceProvider.GetRequiredService();
+
+ async Task> GetGuildChannels(IPartialGuild guild)
+ {
+ var channelsTask = guildsClient.GetGuildChannelsAsync(guild.ID.Value, cancellationToken);
+ var threads = await guildsClient.ListActiveGuildThreadsAsync(guild.ID.Value, cancellationToken);
+ if (!threads.IsSuccess)
+ Logger.LogWarning(
+ "Error retrieving discord guild threads {guildId} ({guildName}): {result}",
+ guild.ID,
+ guild.Name,
+ threads.LogFormat());
+
+ var channels = await channelsTask;
+ if (!channels.IsSuccess)
+ Logger.LogWarning(
+ "Error retrieving discord guild channels {guildId} ({guildName}): {result}",
+ guild.ID,
+ guild.Name,
+ channels.LogFormat());
+
+ if (!channels.IsSuccess && !threads.IsSuccess)
+ return Enumerable.Empty();
+
+ if (channels.IsSuccess && threads.IsSuccess)
+ return channels.Entity.Concat(threads.Entity.Threads ?? Enumerable.Empty());
+
+ return channels.Entity ?? threads.Entity?.Threads ?? Enumerable.Empty();
+ }
+
+ var guildsChannelsTasks = currentGuildsResponse.Entity
+ .Select(GetGuildChannels)
+ .ToList();
+
+ await Task.WhenAll(guildsChannelsTasks);
+
+ var allAccessibleChannels = guildsChannelsTasks
+ .SelectMany(task => task.Result)
+ .Where(guildChannel => SupportedGuildChannelTypes.Contains(guildChannel.Type));
+
+ return allAccessibleChannels;
}
///
@@ -753,7 +857,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
/// The to convert.
/// The parameter for sending a single .
#pragma warning disable CA1502
- private Optional> ConvertEmbed(ChatEmbed embed)
+ Optional> ConvertEmbed(ChatEmbed embed)
{
if (embed == null)
return default;
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
index ab2aaeb742..abf1eefe20 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IProvider.cs
@@ -58,8 +58,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 5d08910ed9..0fcb845771 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/IrcProvider.cs
@@ -267,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
}
///
- protected override Task>> MapChannelsImpl(
+ protected override Task>> MapChannelsImpl(
IEnumerable channels,
CancellationToken cancellationToken)
=> Task.Factory.StartNew(
@@ -300,37 +300,40 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
else
client.RfcJoin(channelToJoin);
- return (IReadOnlyCollection>)channels
- .Select(dbChannel =>
- {
- var channelName = dbChannel.GetIrcChannelName();
- ulong? id = null;
- if (!channelIdMap.Any(y =>
+ return new Dictionary>(
+ channels
+ .Select(dbChannel =>
{
- if (y.Value != channelName)
- return false;
- id = y.Key;
- return true;
- }))
- {
- id = channelIdCounter++;
- channelIdMap.Add(id.Value, channelName);
- }
-
- return Tuple.Create(
- dbChannel,
- new ChannelRepresentation
+ var channelName = dbChannel.GetIrcChannelName();
+ ulong? id = null;
+ if (!channelIdMap.Any(y =>
{
- RealId = id.Value,
- IsAdminChannel = dbChannel.IsAdminChannel == true,
- ConnectionName = address,
- FriendlyName = channelIdMap[id.Value],
- IsPrivateChannel = false,
- Tag = dbChannel.Tag,
- EmbedsSupported = false,
- });
- })
- .ToList();
+ if (y.Value != channelName)
+ return false;
+ id = y.Key;
+ return true;
+ }))
+ {
+ id = channelIdCounter++;
+ channelIdMap.Add(id.Value, channelName);
+ }
+
+ return new KeyValuePair>(
+ dbChannel,
+ new List
+ {
+ new ChannelRepresentation
+ {
+ RealId = id.Value,
+ IsAdminChannel = dbChannel.IsAdminChannel == true,
+ ConnectionName = address,
+ FriendlyName = channelIdMap[id.Value],
+ IsPrivateChannel = false,
+ Tag = dbChannel.Tag,
+ EmbedsSupported = false,
+ },
+ });
+ }));
}
},
cancellationToken,
diff --git a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
index 0422d24e51..1e2bf22ff0 100644
--- a/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
+++ b/src/Tgstation.Server.Host/Components/Chat/Providers/Provider.cs
@@ -118,7 +118,7 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
public void InitialMappingComplete() => initialConnectionTcs.TrySetResult();
///
- public async Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken)
+ public async Task>> MapChannels(IEnumerable channels, CancellationToken cancellationToken)
{
try
{
@@ -198,15 +198,15 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
///
/// The s to map.
/// The for the operation.
- /// A resulting in a of the 's s representing .
- protected abstract Task>> MapChannelsImpl(
+ /// A resulting in a of the 's s representing .
+ protected abstract Task>> MapChannelsImpl(
IEnumerable channels,
CancellationToken cancellationToken);
///
/// Queues a for .
///
- /// The to queue.
+ /// The to queue. A value of indicates the channel mappings a out of date.
protected void EnqueueMessage(Message message)
{
if (message == null)
diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
index 359ed590fe..55862af80f 100644
--- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs
+++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs
@@ -686,7 +686,7 @@ namespace Tgstation.Server.Host.Controllers
/// A resulting in the new or if ports could not be allocated.
async Task CreateDefaultInstance(InstanceCreateRequest initialSettings, CancellationToken cancellationToken)
{
- var ddPort = await portAllocator.GetAvailablePort(1, false, cancellationToken);
+ var ddPort = await portAllocator.GetAvailablePort(1024, false, cancellationToken);
if (!ddPort.HasValue)
return null;
diff --git a/src/Tgstation.Server.Host/Extensions/ResultExtensions.cs b/src/Tgstation.Server.Host/Extensions/ResultExtensions.cs
new file mode 100644
index 0000000000..e91128f515
--- /dev/null
+++ b/src/Tgstation.Server.Host/Extensions/ResultExtensions.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+using Remora.Discord.API.Abstractions.Objects;
+using Remora.Discord.API.Objects;
+using Remora.Rest.Results;
+using Remora.Results;
+
+namespace Tgstation.Server.Host.Extensions
+{
+ ///
+ /// Extensions for .
+ ///
+ static class ResultExtensions
+ {
+ ///
+ /// Converts a given into a log entry .
+ ///
+ /// The to convert.
+ /// Used internally for nesting.
+ /// The formatted .
+ public static string LogFormat(this IResult result, uint level = 0)
+ {
+ if (result == null)
+ throw new ArgumentNullException(nameof(result));
+
+ if (result.IsSuccess)
+ return "SUCCESS?";
+
+ var stringBuilder = new StringBuilder();
+ if (result.Error != null)
+ {
+ stringBuilder.Append(result.Error.Message);
+ if (result.Error is RestResultError restError)
+ {
+ stringBuilder.Append(" (");
+ if (restError.Error != null)
+ {
+ stringBuilder.Append(restError.Error.Code);
+ stringBuilder.Append(": ");
+ stringBuilder.Append(restError.Error.Message);
+ stringBuilder.Append('|');
+ }
+
+ stringBuilder.Append(restError.Message);
+ if ((restError.Error?.Errors.HasValue ?? false) && restError.Error.Errors.Value.Count > 0)
+ {
+ stringBuilder.Append(" (");
+ foreach (var error in restError.Error.Errors.Value)
+ {
+ stringBuilder.Append(error.Key);
+ stringBuilder.Append(':');
+ if (error.Value.IsT0)
+ {
+ FormatErrorDetails(error.Value.AsT0, stringBuilder);
+ }
+ else
+ FormatErrorDetails(error.Value.AsT1, stringBuilder);
+ stringBuilder.Append(',');
+ }
+
+ stringBuilder.Remove(stringBuilder.Length - 1, 1);
+ }
+
+ stringBuilder.Append(')');
+ }
+ }
+
+ if (result.Inner != null)
+ {
+ stringBuilder.Append(Environment.NewLine);
+ ++level;
+ for (var i = 0; i < level; ++i)
+ stringBuilder.Append('\t');
+ stringBuilder.Append(result.Inner.LogFormat(level));
+ }
+
+ return stringBuilder.ToString();
+ }
+
+ ///
+ /// Formats given into a given .
+ ///
+ /// The .
+ /// The to mutate.
+ static void FormatErrorDetails(IPropertyErrorDetails propertyErrorDetails, StringBuilder stringBuilder)
+ {
+ if (propertyErrorDetails == null)
+ return;
+
+ FormatErrorDetails(propertyErrorDetails.Errors, stringBuilder);
+
+ if (propertyErrorDetails.Errors != null && propertyErrorDetails.MemberErrors != null)
+ {
+ stringBuilder.Append(',');
+ }
+
+ if (propertyErrorDetails.MemberErrors != null)
+ {
+ stringBuilder.Append('{');
+ foreach (var error in propertyErrorDetails.MemberErrors)
+ {
+ stringBuilder.Append(error.Key);
+ stringBuilder.Append(':');
+ FormatErrorDetails(error.Value, stringBuilder);
+ stringBuilder.Append(',');
+ }
+
+ stringBuilder.Remove(stringBuilder.Length - 1, 1);
+ stringBuilder.Append('}');
+ }
+ }
+
+ ///
+ /// Formats given into a given .
+ ///
+ /// The of .
+ /// The to mutate.
+ static void FormatErrorDetails(IEnumerable errorDetails, StringBuilder stringBuilder)
+ {
+ if (errorDetails == null)
+ return;
+
+ stringBuilder.Append('[');
+ foreach (var error in errorDetails)
+ {
+ stringBuilder.Append(error.Code);
+ stringBuilder.Append(':');
+ stringBuilder.Append(error.Message);
+ stringBuilder.Append(',');
+ }
+
+ stringBuilder.Remove(stringBuilder.Length - 1, 1);
+ stringBuilder.Append(']');
+ }
+ }
+}