diff --git a/.env-sample b/.env-sample
index 25230f3..55760c1 100644
--- a/.env-sample
+++ b/.env-sample
@@ -13,3 +13,8 @@ AF_PublicContemptChannel=
AF_HallOfShameChannel=
AF_RecruitInfoChannel=
AF_RecruitAskChannel=
+AF_ServerManagerUrl=
+AF_ServerManagerApiKey=
+AF_LogWebhookId=
+AF_LogWebhookToken=
+AF_LogWebhookLevel=
diff --git a/ArmaforcesMissionBot/ArmaforcesMissionBot.csproj b/ArmaforcesMissionBot/ArmaforcesMissionBot.csproj
index 798dd30..db5347e 100644
--- a/ArmaforcesMissionBot/ArmaforcesMissionBot.csproj
+++ b/ArmaforcesMissionBot/ArmaforcesMissionBot.csproj
@@ -23,6 +23,10 @@
+
+
+
+
diff --git a/ArmaforcesMissionBot/Controllers/ApiController.cs b/ArmaforcesMissionBot/Controllers/ApiController.cs
index 7aff02e..8db5560 100644
--- a/ArmaforcesMissionBot/Controllers/ApiController.cs
+++ b/ArmaforcesMissionBot/Controllers/ApiController.cs
@@ -8,6 +8,7 @@
using Discord.WebSocket;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
@@ -24,6 +25,7 @@ public class ApiController : ControllerBase
private readonly BanHelper _banHelper;
private readonly SignupHelper _signupHelper;
private readonly MiscHelper _miscHelper;
+ private readonly ILogger _logger;
public ApiController(
MissionsArchiveData missionsArchiveData,
@@ -31,7 +33,8 @@ public ApiController(
DiscordSocketClient client,
BanHelper banHelper,
SignupHelper signupHelper,
- MiscHelper miscHelper)
+ MiscHelper miscHelper,
+ ILogger logger)
{
_missionsArchiveData = missionsArchiveData;
_signupsData = signupsData;
@@ -39,6 +42,7 @@ public ApiController(
_banHelper = banHelper;
_signupHelper = signupHelper;
_miscHelper = miscHelper;
+ _logger = logger;
}
[HttpGet("currentMission")]
@@ -345,7 +349,7 @@ public void Users()
[HttpPost("createMission")]
public async Task CreateMissionAsync(Mission mission)
{
- Console.WriteLine(JsonConvert.SerializeObject(mission));
+ _logger.LogTrace($"Create mission request: {JsonConvert.SerializeObject(mission)}");
mission.Editing = ArmaforcesMissionBotSharedClasses.Mission.EditEnum.New;
_signupsData.Missions.Add(mission);
diff --git a/ArmaforcesMissionBot/DataClasses/Config.cs b/ArmaforcesMissionBot/DataClasses/Config.cs
index 1f0ac7a..c31275b 100644
--- a/ArmaforcesMissionBot/DataClasses/Config.cs
+++ b/ArmaforcesMissionBot/DataClasses/Config.cs
@@ -1,10 +1,9 @@
-using dotenv.net;
-using Newtonsoft.Json;
+#nullable enable
+using dotenv.net;
using System;
-using System.Collections.Generic;
-using System.IO;
using System.Reflection;
-using System.Text;
+using Serilog;
+using Serilog.Events;
namespace ArmaforcesMissionBot.DataClasses
{
@@ -19,6 +18,22 @@ public class Config
public ulong BotRole { get; set; }
public ulong RecruiterRole { get; set; }
public ulong RecruitRole { get; set; }
+
+ ///
+ /// Discord webhook ID for log messages.
+ ///
+ public ulong? LogWebhookId { get; set; }
+
+ ///
+ /// Discord webhook token for log messages.
+ ///
+ public string? LogWebhookToken { get; set; }
+
+ ///
+ /// Minimum level that will be logged to Discord webhook.
+ ///
+ public LogEventLevel LogWebhookLevel { get; set; } = LogEventLevel.Error;
+
public string KickImageUrl { get; set; }
public string BanImageUrl { get; set; }
public string ServerManagerUrl { get; set; }
@@ -28,19 +43,61 @@ public class Config
public ulong PublicContemptChannel { get; set; }
public ulong HallOfShameChannel { get; set; }
public ulong RecruitInfoChannel { get; set; }
- public ulong RecruitAskChannel { get; set; }
+ public ulong RecruitAskChannel { get; set; }
public void Load()
{
DotEnv.Config(false);
PropertyInfo[] properties = typeof(Config).GetProperties(BindingFlags.Public | BindingFlags.Instance);
- foreach (var prop in properties)
+ foreach (var propertyInfo in properties)
+ {
+ if(propertyInfo.PropertyType == typeof(string))
+ TrySetStringValue(propertyInfo);
+ else if (propertyInfo.PropertyType == typeof(ulong) || propertyInfo.PropertyType == typeof(ulong?))
+ TrySetUlongValue(propertyInfo);
+ else if (propertyInfo.PropertyType == typeof(LogEventLevel))
+ TrySetEnumValue(propertyInfo);
+ }
+ }
+
+ private void TrySetEnumValue(PropertyInfo propertyInfo) where T : struct
+ {
+ var stringValue = GetVariable(propertyInfo.Name);
+ if (stringValue != null && Enum.TryParse(stringValue, out T valueToSet))
+ {
+ propertyInfo.SetValue(this, valueToSet);
+ }
+ }
+
+ private void TrySetStringValue(PropertyInfo propertyInfo)
+ {
+ var stringValue = GetVariable(propertyInfo.Name);
+ if (stringValue != null)
+ {
+ propertyInfo.SetValue(this, stringValue);
+ }
+ }
+
+ private void TrySetUlongValue(PropertyInfo propertyInfo)
+ {
+ var stringValue = GetVariable(propertyInfo.Name);
+ if (stringValue != null && ulong.TryParse(stringValue, out var valueToSet))
+ {
+ propertyInfo.SetValue(this, valueToSet);
+ }
+ }
+
+ private static string? GetVariable(string variableName)
+ {
+ try
+ {
+ return Environment.GetEnvironmentVariable("AF_" + variableName);
+ }
+ catch (Exception exception)
{
- if(prop.PropertyType == typeof(string))
- prop.SetValue(this, Environment.GetEnvironmentVariable("AF_" + prop.Name));
- if (prop.PropertyType == typeof(ulong))
- prop.SetValue(this, ulong.Parse(Environment.GetEnvironmentVariable("AF_" + prop.Name)));
+ Log.Warning(exception, "Failed to read environment variable {Name}", variableName);
+ return null;
}
}
}
diff --git a/ArmaforcesMissionBot/Handlers/CommandHandler.cs b/ArmaforcesMissionBot/Handlers/CommandHandler.cs
index 6750158..30bdcda 100644
--- a/ArmaforcesMissionBot/Handlers/CommandHandler.cs
+++ b/ArmaforcesMissionBot/Handlers/CommandHandler.cs
@@ -6,6 +6,7 @@
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
+using Serilog;
namespace ArmaforcesMissionBot.Handlers
{
@@ -50,17 +51,24 @@ private async Task HandleCommandAsync(SocketMessage messageParam)
// Keep in mind that result does not indicate a return value
// rather an object stating if the command executed successfully.
- var result = await _commands.ExecuteAsync(
- context: context,
- argPos: argPos,
- services: _services);
+ try
+ {
+ var result = await _commands.ExecuteAsync(
+ context: context,
+ argPos: argPos,
+ services: _services);
- // Optionally, we may inform the user if the command fails
- // to be executed; however, this may not always be desired,
- // as it may clog up the request queue should a user spam a
- // command.
- if (!result.IsSuccess)
- await context.Channel.SendMessageAsync(result.ErrorReason);
+ // Optionally, we may inform the user if the command fails
+ // to be executed; however, this may not always be desired,
+ // as it may clog up the request queue should a user spam a
+ // command.
+ if (!result.IsSuccess)
+ await context.Channel.SendMessageAsync(result.ErrorReason);
+ }
+ catch (Exception exception)
+ {
+ Log.Error(exception, "An error occured while handling a command {@Message}", message);
+ }
}
}
}
diff --git a/ArmaforcesMissionBot/Handlers/LoadupHandler.cs b/ArmaforcesMissionBot/Handlers/LoadupHandler.cs
index 7bacfc0..4505d36 100644
--- a/ArmaforcesMissionBot/Handlers/LoadupHandler.cs
+++ b/ArmaforcesMissionBot/Handlers/LoadupHandler.cs
@@ -6,11 +6,11 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ArmaforcesMissionBot.Features.Modsets;
using ArmaforcesMissionBot.Features.Modsets.Legacy;
+using Microsoft.Extensions.Logging;
using static ArmaforcesMissionBot.DataClasses.SignupsData;
namespace ArmaforcesMissionBot.Handlers
@@ -22,9 +22,12 @@ public class LoadupHandler : IInstallable
private Config _config;
private ModsetProvider _newModsetProvider;
private LegacyModsetProvider _legacyModsetProvider;
-
+ private ILogger _logger;
+
public async Task Install(IServiceProvider map)
{
+ _logger = map.GetRequiredService>();
+
_client = map.GetService();
_config = map.GetService();
_newModsetProvider = new ModsetProvider(map.GetService());
@@ -36,7 +39,7 @@ public async Task Install(IServiceProvider map)
private async Task Load(SocketGuild guild)
{
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loading up from: {guild.Name}");
+ _logger.LogInformation("Loading up from server: {ServerName}", guild.Name);
await LoadMissions(guild);
await LoadBans(guild);
@@ -50,7 +53,7 @@ private async Task LoadMissions(SocketGuild guild)
var channels = guild.CategoryChannels.Single(x => x.Id == _config.SignupsCategory);
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loading missions");
+ _logger.LogInformation("Loading missions");
foreach (var channel in channels.Channels.Where(x => x.Id != _config.SignupsArchive && x.Id != _config.CreateMissionChannel && x.Id != _config.HallOfShameChannel).Reverse())
{
@@ -118,7 +121,7 @@ await messages.ForEachAsync(async x =>
pattern += $"{match.Groups[0]} ";
team.Slots.Add(slot);
- Console.WriteLine($"New slot {slot.Emoji} [{slot.Count}] {slot.Name}");
+ _logger.LogDebug("New slot {Emoji} [{Count}] {Name}", slot.Emoji, slot.Count, slot.Name);
}
team.Name = team.Name.Replace("|", "");
@@ -133,13 +136,13 @@ await messages.ForEachAsync(async x =>
{
var signedID = ulong.Parse(match.Groups[2].Value);
mission.SignedUsers.Add(signedID);
- Console.WriteLine($"{match.Groups[1].Value} : {match.Groups[2].Value} ({signedID})");
+ _logger.LogTrace("{Match1} : {Match2} ({UserId})", match.Groups[1].Value, match.Groups[2].Value, signedID);
team.Slots.Single(x => x.Emoji == match.Groups[1].Value).Signed.Add(signedID);
}
}
- catch(Exception e)
+ catch (Exception exception)
{
- Console.WriteLine($"Failed loading team {team.Name} : {e.Message}");
+ _logger.LogWarning(exception, "Failed loading team {Name}", team.Name);
}
}
@@ -197,7 +200,7 @@ private async Task LoadBans(SocketGuild guild)
{
var signups = _services.GetService();
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loading bans");
+ _logger.LogInformation("Loading bans");
var banChannel = guild.Channels.Single(x => x.Id == _config.HallOfShameChannel) as SocketTextChannel;
var messages = banChannel.GetMessagesAsync();
@@ -269,7 +272,7 @@ private async Task LoadBanHistory(SocketGuild guild)
var channels = guild.CategoryChannels.Single(x => x.Id == _config.SignupsCategory);
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loading ban history");
+ _logger.LogInformation("Loading ban history");
// History of bans
var shameChannel = guild.Channels.Single(x => x.Id == _config.HallOfShameChannel) as SocketTextChannel;
var messages = shameChannel.GetMessagesAsync();
@@ -305,7 +308,7 @@ await messages.ForEachAsync(async x =>
uint.Parse(match.Groups[2].Value),
uint.Parse(match.Groups[3].Value)));
}
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loaded signup ban history");
+ _logger.LogInformation("Loaded signup ban history");
}
}
finally
@@ -335,7 +338,7 @@ await messages.ForEachAsync(async x =>
DateTime.Parse(match.Groups[3].Value),
(BanType)Enum.Parse(typeof(BanType), match.Groups[4].Value)));
}
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loaded reaction spam ban history");
+ _logger.LogInformation("Loaded reaction spam ban history");
}
}
finally
@@ -352,7 +355,7 @@ private async Task LoadMissionsArchive(SocketGuild guild)
var channels = guild.CategoryChannels.Single(x => x.Id == _config.SignupsCategory);
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loading mission history");
+ _logger.LogInformation("Loading mission history");
archive.ArchiveMissions.Clear();
// History of missions
@@ -389,7 +392,7 @@ await messages.ForEachAsync(async x =>
DateTimeStyles.RoundtripKind,
out date))
{
- Console.WriteLine($"Loading failed on mission date: {embed.Footer.Value.Text}");
+ _logger.LogWarning("Failed to parse archive mission date {Date}", embed.Footer.Value.Text);
continue;
}
@@ -436,7 +439,7 @@ await messages.ForEachAsync(async x =>
return x.Date.CompareTo(y.Date);
});
- Console.WriteLine($"[{DateTime.Now.ToString()}] Loaded {archive.ArchiveMissions.Count} archive missions");
+ _logger.LogInformation("Loaded {Count} archive missions", archive.ArchiveMissions.Count);
}
private string GetModsetNameFromUnknownUrl(string unknownUrl)
diff --git a/ArmaforcesMissionBot/Handlers/SignupHandler.cs b/ArmaforcesMissionBot/Handlers/SignupHandler.cs
index e5d7859..d355059 100644
--- a/ArmaforcesMissionBot/Handlers/SignupHandler.cs
+++ b/ArmaforcesMissionBot/Handlers/SignupHandler.cs
@@ -5,11 +5,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
-using System.Text;
-using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Timers;
using ArmaforcesMissionBot.Helpers;
+using Microsoft.Extensions.Logging;
namespace ArmaforcesMissionBot.Handlers
{
@@ -32,9 +31,11 @@ public class SignupHandler : IInstallable
private IServiceProvider _services;
private Config _config;
private Timer _timer;
+ private ILogger _logger;
public async Task Install(IServiceProvider map)
{
+ _logger = map.GetRequiredService>();
_client = map.GetService();
_config = map.GetService();
_miscHelper = map.GetService();
@@ -63,7 +64,7 @@ private async Task HandleReactionAdded(Cacheable message, I
var mission = signups.Missions.Single(x => x.SignupChannel == channel.Id);
await HandleReactionChange(message, channel, reaction, signups);
- Console.WriteLine($"[{DateTime.Now.ToString()}] {reaction.User.Value.Username} added reaction {reaction.Emote.Name}");
+ _logger.LogInformation("{User} added reaction {Emoji}", reaction.User.Value.Username, reaction.Emote.Name);
if (signups.SignupBans.ContainsKey(reaction.User.Value.Id) && signups.SignupBans[reaction.User.Value.Id] > mission.Date)
{
@@ -133,7 +134,7 @@ private async Task HandleReactionAdded(Cacheable message, I
else if(signups.Missions.Any(x => x.SignupChannel == channel.Id) && reaction.UserId != _client.CurrentUser.Id)
{
var user = _client.GetUser(reaction.UserId);
- Console.WriteLine($"Naprawiam reakcje po spamie {user.Username}");
+ _logger.LogInformation("Fixing reactions after {User} spam", user.Username);
var teamMsg = await channel.GetMessageAsync(message.Id) as IUserMessage;
await teamMsg.RemoveReactionAsync(reaction.Emote, user);
}
@@ -150,7 +151,7 @@ private async Task HandleReactionRemoved(Cacheable message,
var mission = signups.Missions.Single(x => x.SignupChannel == channel.Id);
var user = await (channel as IGuildChannel).Guild.GetUserAsync(reaction.UserId);
- Console.WriteLine($"[{DateTime.Now.ToString()}] {user.Username} removed reaction {reaction.Emote.Name}");
+ _logger.LogInformation("{User} removed reaction {Emoji}", user.Username, reaction.Emote.Name);
await mission.Access.WaitAsync(-1);
try
@@ -218,7 +219,7 @@ private async Task HandleReactionChange(Cacheable message,
signups.ReactionTimes[reaction.User.Value.Id].Enqueue(DateTime.Now);
- Console.WriteLine($"[{ DateTime.Now.ToString()}] { reaction.User.Value.Username} spam counter: { signups.ReactionTimes[reaction.User.Value.Id].Count}");
+ _logger.LogDebug("{User} spam counter increased: {Count}", reaction.User.Value.Username, signups.ReactionTimes[reaction.User.Value.Id].Count);
if (signups.ReactionTimes[reaction.User.Value.Id].Count >= 10 && !signups.SpamBans.ContainsKey(reaction.User.Value.Id))
{
diff --git a/ArmaforcesMissionBot/Helpers/BanHelper.cs b/ArmaforcesMissionBot/Helpers/BanHelper.cs
index 3152c44..84ddcd5 100644
--- a/ArmaforcesMissionBot/Helpers/BanHelper.cs
+++ b/ArmaforcesMissionBot/Helpers/BanHelper.cs
@@ -6,6 +6,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
+using Serilog;
namespace ArmaforcesMissionBot.Helpers
{
@@ -54,9 +55,9 @@ public static async Task MakeBanMessage(IServiceProvider map, SocketGuild
return sentMessage.Id;
}
}
- catch(Exception e)
+ catch (Exception e)
{
- Console.WriteLine($"[{DateTime.Now.ToString()}] MakeBanMessageFailed: {e.Message}");
+ Log.Error(e, "Ban message creation failed");
}
return banMessageId;
@@ -97,9 +98,9 @@ public static async Task MakeBanHistoryMessage(IServiceProvider map, SocketGuild
signups.SignupBansHistoryMessage = sentMessage.Id;
}
}
- catch(Exception e)
+ catch (Exception e)
{
- Console.WriteLine($"[{DateTime.Now.ToString()}] MakeBanHistoryMessageFailed: {e.Message}");
+ Log.Error(e, "Creating ban history message failed");
}
}
@@ -278,9 +279,9 @@ public static async Task BanUserSpam(IServiceProvider map, IUser user)
PermValue.Deny,
PermValue.Deny));
}
- catch(Exception e)
+ catch (Exception e)
{
- Console.WriteLine($"Woops, banning user from channel failed : {e.Message}");
+ Log.Error(e, "Banning {User} from {Channel} failed", user.Username, missionChannel.Name);
}
}
}
diff --git a/ArmaforcesMissionBot/Helpers/LoggingHelper.cs b/ArmaforcesMissionBot/Helpers/LoggingHelper.cs
new file mode 100644
index 0000000..c52ec14
--- /dev/null
+++ b/ArmaforcesMissionBot/Helpers/LoggingHelper.cs
@@ -0,0 +1,40 @@
+using ArmaforcesMissionBot.DataClasses;
+using Serilog;
+using Serilog.Core;
+using Serilog.Sinks.Discord;
+
+namespace ArmaforcesMissionBot.Helpers
+{
+ internal class LoggingHelper
+ {
+ private const string OutputTemplate = "{Timestamp:yyyy-MM-ddTHH:mm:ss.fffzzz} [{Level:u3}] {SourceContext}: {Message:lj}{NewLine}{Exception}";
+
+ public static readonly LoggingLevelSwitch LoggingLevelSwitch = new LoggingLevelSwitch();
+
+ public static ILogger CreateSerilogLogger()
+ {
+ return CreateLoggerConfiguration()
+ .CreateLogger();
+ }
+
+ private static LoggerConfiguration CreateLoggerConfiguration()
+ {
+ var config = new Config();
+ config.Load();
+
+ var loggerConfiguration = new LoggerConfiguration()
+ .Enrich.FromLogContext()
+ .WriteTo.Console(outputTemplate: OutputTemplate);
+
+ if (config.LogWebhookId.HasValue && config.LogWebhookToken != null)
+ {
+ loggerConfiguration.WriteTo.Discord(
+ webhookId: config.LogWebhookId.Value,
+ webhookToken: config.LogWebhookToken,
+ restrictedToMinimumLevel: config.LogWebhookLevel);
+ }
+
+ return loggerConfiguration;
+ }
+ }
+}
\ No newline at end of file
diff --git a/ArmaforcesMissionBot/Helpers/MiscHelper.cs b/ArmaforcesMissionBot/Helpers/MiscHelper.cs
index bbb2dfd..df7435f 100644
--- a/ArmaforcesMissionBot/Helpers/MiscHelper.cs
+++ b/ArmaforcesMissionBot/Helpers/MiscHelper.cs
@@ -1,166 +1,167 @@
-using ArmaforcesMissionBot.DataClasses;
-using Discord;
-using Discord.Commands;
-using Discord.WebSocket;
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text.RegularExpressions;
-using System.Web;
-using static ArmaforcesMissionBot.DataClasses.OpenedDialogs;
-
-namespace ArmaforcesMissionBot.Helpers
-{
- public class MiscHelper
- {
- private readonly DiscordSocketClient _client;
- private readonly Config _config;
-
- public MiscHelper(DiscordSocketClient client, Config config)
- {
- _client = client;
- _config = config;
- }
-
- public List BuildTeamSlots(ArmaforcesMissionBotSharedClasses.Mission.Team team)
- {
- List results = new List();
- results.Add("");
- foreach (var slot in team.Slots)
- {
- for (var i = 0; i < slot.Count; i++)
- {
- string description = $"{HttpUtility.HtmlDecode(slot.Emoji)}";
- if (slot.Name != "" && i == 0)
- description += $"({slot.Name})";
- description += "-";
- if (i < slot.Signed.Count)
- {
- var user = _client.GetGuild(_config.AFGuild).GetUser(slot.Signed.ElementAt(i));
- if(user != null)
- description += user.Mention;
- }
- description += "\n";
-
- if(results.Last().Length + description.Length > 1024)
- results.Add("");
-
- results[results.Count-1] += description;
- }
- }
-
- /*foreach (var prebeton in team.Signed)
- {
- Console.WriteLine(prebeton.Value + " " + prebeton.Key);
- Console.WriteLine(HttpUtility.HtmlDecode(prebeton.Value) + " " + HttpUtility.HtmlDecode(prebeton.Key));
- var regex = new Regex(Regex.Escape(HttpUtility.HtmlDecode(prebeton.Value)) + @"-(?:$|\n)");
- description = regex.Replace(description, HttpUtility.HtmlDecode(prebeton.Value) + "-" + HttpUtility.HtmlDecode(prebeton.Key) + "\n", 1);
- }*/
-
- return results;
- }
-
- public void BuildTeamsEmbed(List teams, EmbedBuilder builder, bool removeSlotNamesFromName = false)
- {
- foreach (var team in teams)
- {
- var slots = BuildTeamSlots(team);
-
- var teamName = team.Name;
- if (removeSlotNamesFromName)
- {
- foreach (var slot in team.Slots)
- {
- if (teamName.Contains(slot.Emoji))
- teamName = teamName.Remove(teamName.IndexOf(slot.Emoji));
- }
- }
-
- if(slots.Count == 1)
- builder.AddField(teamName, slots[0], true);
- else if(slots.Count > 1)
- {
- foreach(var part in slots)
- {
- builder.AddField(teamName, part, true);
- }
- }
- }
- }
-
- public static string BuildEditTeamsPanel(List teams, int highlightIndex)
- {
- string result = "";
-
- int index = 0;
- foreach (var team in teams)
- {
- if (highlightIndex == index)
- result += "**";
- result += $"{team.Name}";
- if (highlightIndex == index)
- result += "**";
- result += "\n";
- index++;
- }
-
- return result;
- }
-
- public static int CountFreeSlots(ArmaforcesMissionBotSharedClasses.Mission mission)
- {
- return CountAllSlots(mission) - mission.SignedUsers.Count;
- }
-
- public static int CountAllSlots(ArmaforcesMissionBotSharedClasses.Mission mission)
- {
- int slots = 0;
- foreach (var team in mission.Teams)
- {
- foreach (var slot in team.Slots)
- {
- slots += slot.Count;
- }
- }
-
- return slots;
- }
-
- public async void CreateConfirmationDialog(
- OpenedDialogs openedDialogs,
- SocketCommandContext context,
- Embed description,
- Action