diff --git a/SkyBot/Commands/CommandTemplate.cs b/SkyBot/Commands/CommandTemplate.cs index d725003..9758d30 100644 --- a/SkyBot/Commands/CommandTemplate.cs +++ b/SkyBot/Commands/CommandTemplate.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -16,14 +17,9 @@ namespace SkyBot.Commands { ConcurrentDictionary channelCache = ctx.ChannelCache; long channelId = ctx.ChannelId; - PlanetMember member = ctx.Member; - - string message = $""; - if (channelCache.TryGetValue(channelId, out var channel)) - { - await MessageHelper.ReplyAsync(ctx, channel, message); - } + if (!channelCache.TryGetValue(channelId, out var channel)) return; + } } } \ No newline at end of file diff --git a/SkyBot/Commands/Dev/Test.cs b/SkyBot/Commands/Dev/Test.cs index abf2287..85a9239 100644 --- a/SkyBot/Commands/Dev/Test.cs +++ b/SkyBot/Commands/Dev/Test.cs @@ -3,29 +3,45 @@ using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Client; using Valour.Sdk.Models; +using Valour.Shared; namespace SkyBot.Commands { public class Test : ICommand { - public string Name => "test"; + public string Name => "edit"; public string[] Aliases => []; - public string Description => "Just a test command"; + public string Description => "Edit the bots message"; public string Section => "Dev"; - public string Usage => "test"; + public string Usage => "reply -> edit "; public async Task Execute(CommandContext ctx) { ConcurrentDictionary channelCache = ctx.ChannelCache; - long channelId = ctx.ChannelId; ValourClient client = ctx.Client; + long channelId = ctx.ChannelId; PlanetMember member = ctx.Member; Message message = ctx.Message; - Planet planet = ctx.Planet; + string[] args = ctx.Args; if (channelCache.TryGetValue(channelId, out var channel)) { - await MessageHelper.ReplyAsync(ctx, channel, "This is a test message"); + if(!PermissionHelper.IsOwner(member)) + { + await MessageHelper.ReplyAsync(ctx, channel, "This is a Dev only command."); + } + + if (message.ReplyToId == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please reply to a message."); + return; + } + + if (client.Cache.Messages.TryGet(message.ReplyToId.Value, out var msg)) + { + await MessageHelper.EditAsync(channel, msg, string.Join(" ", args)); + return; + } } } } diff --git a/SkyBot/Commands/Fun/8ball.cs b/SkyBot/Commands/Fun/8ball.cs new file mode 100644 index 0000000..992bf70 --- /dev/null +++ b/SkyBot/Commands/Fun/8ball.cs @@ -0,0 +1,64 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class EightBall : ICommand + { + public string Name => "8ball"; + public string[] Aliases => []; + public string Description => "Ask the magic 8ball a question."; + public string Section => "Fun"; + public string Usage => "8ball "; + + private static readonly string[] Responses = + [ + "It is certain.", + "It is decidedly so.", + "Without a doubt.", + "Yes, definitely.", + "You may rely on it.", + "As I see it, yes.", + "Most likely.", + "Outlook good.", + "Yes.", + "Signs point to yes.", + "Reply hazy, try again.", + "Ask again later.", + "Better not tell you now.", + "Cannot predict now.", + "Concentrate and ask again.", + "Don't count on it.", + "My reply is no.", + "My sources say no.", + "Outlook not so good.", + "Very doubtful." + ]; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + + if (channelCache.TryGetValue(channelId, out var channel)) + { + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please ask a question."); + return; + } + + + TaskResult result = await MessageHelper.ReplyAsync(ctx, channel, $"🎱 Thinking..."); + await channel.SendIsTyping(); + await Task.Delay(2000); + string response = Responses[Random.Shared.Next(Responses.Length)]; + await MessageHelper.EditAsync(channel, result.Data, $"🎱 {response}"); + } + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/Choose.cs b/SkyBot/Commands/Fun/Choose.cs new file mode 100644 index 0000000..0716a02 --- /dev/null +++ b/SkyBot/Commands/Fun/Choose.cs @@ -0,0 +1,38 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class Choose : ICommand + { + public string Name => "choose"; + public string[] Aliases => ["pick"]; + public string Description => "Picks one of the given options."; + public string Section => "Fun"; + public string Usage => "choose ..."; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + if (ctx.Args.Length < 2) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide at least two options."); + return; + } + + TaskResult result = await MessageHelper.ReplyAsync(ctx, channel, "🤔 Choosing..."); + await Task.Delay(1000); + + string choice = args[Random.Shared.Next(args.Length)]; + await MessageHelper.EditAsync(channel, result.Data, $"I choose **{choice}**!"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/CoinFlip.cs b/SkyBot/Commands/Fun/CoinFlip.cs new file mode 100644 index 0000000..5b25b70 --- /dev/null +++ b/SkyBot/Commands/Fun/CoinFlip.cs @@ -0,0 +1,32 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class CoinFlip : ICommand + { + public string Name => "coinflip"; + public string[] Aliases => ["cf"]; + public string Description => "Flips a coin."; + public string Section => "Fun"; + public string Usage => "coinflip"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + TaskResult result = await MessageHelper.ReplyAsync(ctx, channel, "🪙 Flipping..."); + await channel.SendIsTyping(); + await Task.Delay(3000); + + string outcome = Random.Shared.Next(2) == 0 ? "Heads" : "Tails"; + await MessageHelper.EditAsync(channel, result.Data, $"🪙 {outcome}"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/Dice.cs b/SkyBot/Commands/Fun/Dice.cs new file mode 100644 index 0000000..6a820e3 --- /dev/null +++ b/SkyBot/Commands/Fun/Dice.cs @@ -0,0 +1,60 @@ +using System.Collections.Concurrent; +using System.Runtime.InteropServices.Marshalling; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class Dice : ICommand + { + public string Name => "dice"; + public string[] Aliases => ["roll"]; + public string Description => "Rolls dice."; + public string Section => "Fun"; + public string Usage => "roll (e.g. 2d6, d20, 3d8)"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + PlanetMember member = ctx.Member; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string input = args.Length > 0 ? args[0].ToLower() : "1d6"; + + string[] parts = input.Split('d'); + if (parts.Length != 2 || !int.TryParse(parts[1], out int sides) || sides < 2) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid dice format. Use something like `2d6` or `d20`."); + return; + } + + int count = 1; + if (!string.IsNullOrWhiteSpace(parts[0]) && !int.TryParse(parts[0], out count)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid dice format. Use something like `2d6` or `d20`."); + return; + } + + if (count < 1 || count > 100) + { + await MessageHelper.ReplyAsync(ctx, channel, "You can only roll between 1 and 100 dice at a time."); + return; + } + + IEnumerable rolls = Enumerable.Range(0, count).Select(_ => Random.Shared.Next(1, sides+1)).ToList(); + int total = rolls.Sum(); + + TaskResult rolling = await MessageHelper.ReplyAsync(ctx, channel, "🎲 Rolling..."); + await channel.SendIsTyping(); + await Task.Delay(2000); + + string rollDisplay = count > 1 ? $"({string.Join(" + ", rolls)}) = **{total}**" : $"**{total}**"; + await MessageHelper.EditAsync(channel, rolling.Data, $"🎲 Rolled {input}: {rollDisplay}"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/Echo.cs b/SkyBot/Commands/Fun/Echo.cs index c680f76..42f6e64 100644 --- a/SkyBot/Commands/Fun/Echo.cs +++ b/SkyBot/Commands/Fun/Echo.cs @@ -1,5 +1,4 @@ using System.Collections.Concurrent; -using System.Net.NetworkInformation; using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -19,24 +18,21 @@ namespace SkyBot.Commands ConcurrentDictionary channelCache = ctx.ChannelCache; long channelId = ctx.ChannelId; PlanetMember member = ctx.Member; - String[] args = ctx.Args; + string[] args = ctx.Args; Message message = ctx.Message; string reply = string.Join(" ", args); - if (channelCache.TryGetValue(channelId, out var channel)) + if (!channelCache.TryGetValue(channelId, out var channel)) return; + if (string.IsNullOrWhiteSpace(reply)) await MessageHelper.ReplyAsync(ctx, channel, $"Enter a message to echo."); + + reply = $"{member.Name} » {reply}"; + if (reply.Length > 2048) { - if (string.IsNullOrWhiteSpace(reply)) await channel.SendMessageAsync($"{MentionHelper.Mention(member)} Enter a message to echo."); - - reply = $"{member.Name} » {reply}"; - - if (reply.Length > 2048) - { - reply = reply.Substring(0, 2048); - } - - await MessageHelper.ReplyAsync(ctx, channel, reply); + reply = reply.Substring(0, 2048); } + + await MessageHelper.ReplyAsync(ctx, channel, reply); } } } \ No newline at end of file diff --git a/SkyBot/Commands/Fun/Mock.cs b/SkyBot/Commands/Fun/Mock.cs new file mode 100644 index 0000000..2befd95 --- /dev/null +++ b/SkyBot/Commands/Fun/Mock.cs @@ -0,0 +1,51 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class Mock : ICommand + { + public string Name => "mock"; + public string[] Aliases => []; + public string Description => "Mock text"; + public string Section => "Fun"; + public string Usage => "mock [text] (Or reply to a message)"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + Message message = ctx.Message; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string text; + + if (message.ReplyToId.HasValue) + { + var replyMessage = await message.FetchReplyMessageAsync(); + text = replyMessage?.Content ?? ""; + } else + { + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide some text to mock or reply to a message."); + return; + } + text = string.Join(" ", args); + } + + if (string.IsNullOrWhiteSpace(text)) + { + await MessageHelper.ReplyAsync(ctx, channel, "No text to mock."); + return; + } + + string mocked = new string(text.Select((c, i) => i % 2 == 0 ? char.ToLower(c) : char.ToUpper(c)).ToArray()); + await MessageHelper.ReplyAsync(ctx, channel, mocked); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/Reverse.cs b/SkyBot/Commands/Fun/Reverse.cs new file mode 100644 index 0000000..5c9393f --- /dev/null +++ b/SkyBot/Commands/Fun/Reverse.cs @@ -0,0 +1,51 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class Reverse : ICommand + { + public string Name => "reverse"; + public string[] Aliases => []; + public string Description => "Reverses yours or a replied text."; + public string Section => "Fun"; + public string Usage => "reverse [text] (Or reply to a message)"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + Message message = ctx.Message; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + string text; + + if (message.ReplyToId.HasValue) + { + var replyMessage = await message.FetchReplyMessageAsync(); + text = replyMessage?.Content ?? ""; + } else + { + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide some text to reverse or reply to a message."); + return; + } + text = string.Join(" ", args); + } + + if (string.IsNullOrWhiteSpace(text)) + { + await MessageHelper.ReplyAsync(ctx, channel, "No text to reverse."); + return; + } + + string reversed = new string(text.Reverse().ToArray()); + await MessageHelper.ReplyAsync(ctx, channel, $"Reversed: {reversed}"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Fun/RockPaperScissors.cs b/SkyBot/Commands/Fun/RockPaperScissors.cs new file mode 100644 index 0000000..cf4738f --- /dev/null +++ b/SkyBot/Commands/Fun/RockPaperScissors.cs @@ -0,0 +1,61 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class RockPaperScissors : ICommand + { + public string Name => "rps"; + public string[] Aliases => []; + public string Description => "Play Rock Paper Scissors against the bot."; + public string Section => "Fun"; + public string Usage => "rps "; + + private static readonly string[] Choices = ["rock", "paper", "scissors"]; + private static readonly string[] Emojis = ["🪨", "📄", "✂️"]; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please choose `Rock`, `Paper`, or `Scissors`."); + return; + } + + string input = args[0].ToLower(); + int playerIndex = Array.IndexOf(Choices, input); + + if (playerIndex == -1) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid choice. Please choose `Rock`, `Paper`, or `Scissors`."); + return; + } + + TaskResult result = await MessageHelper.ReplyAsync(ctx, channel, "🤔 Thinking..."); + await Task.Delay(1000); + + int botIndex = Random.Shared.Next(3); + + string playerChoice = $"{Emojis[playerIndex]} {Choices[playerIndex].ToTitleCase()}"; + string botChoice = $"{Emojis[botIndex]} {Choices[botIndex].ToTitleCase()}"; + + string outcome; + if (playerIndex == botIndex) outcome = "It's a tie!"; + else if ((playerIndex == 0 && botIndex == 2) || + (playerIndex == 1 && botIndex == 0) || + (playerIndex == 2 && botIndex == 1)) outcome = "You win! 🎉"; + else outcome = "You Lose! 🥲"; + + await MessageHelper.EditAsync(channel, result.Data, $"**You**: {playerChoice}\nvs\n**Bot**: {botChoice}\n[]()\n{outcome}"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Info/Devcentral.cs b/SkyBot/Commands/Info/Devcentral.cs index d0c5556..7d30469 100644 --- a/SkyBot/Commands/Info/Devcentral.cs +++ b/SkyBot/Commands/Info/Devcentral.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -10,7 +11,7 @@ namespace SkyBot.Commands public string[] Aliases => ["dev"]; public string Description => "Sends an invite link to the Dev Central Planet."; public string Section => "Info"; - public string Usage => "devcentral|dev"; + public string Usage => "devcentral"; public async Task Execute(CommandContext ctx) { diff --git a/SkyBot/Commands/Info/Help.cs b/SkyBot/Commands/Info/Help.cs index 340ac63..2a3fba9 100644 --- a/SkyBot/Commands/Info/Help.cs +++ b/SkyBot/Commands/Info/Help.cs @@ -13,7 +13,7 @@ namespace SkyBot.Commands public string[] Aliases => ["h"]; public string Description => "Shows all the commands and their descriptions."; public string Section => "Info"; - public string Usage => "help|h [section] [page]"; + public string Usage => "help [section] [page]"; private const int PageSize = 5; public async Task Execute(CommandContext ctx) @@ -23,8 +23,6 @@ namespace SkyBot.Commands string[] args = ctx.Args; PlanetMember member = ctx.Member; - bool isOwner = await PermissionHelper.IsOwner(member); - if (!channelCache.TryGetValue(channelId, out var channel)) return; // Show all sections. @@ -35,8 +33,8 @@ namespace SkyBot.Commands foreach (var section in CommandRegistry.Sections.Keys) { if (section == "template") continue; - if (section == "dev" && !isOwner) continue; - if (section == "mod" && !PermissionHelper.HasPermAsync(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles]).Result) continue; + if (section == "dev" && !PermissionHelper.IsOwner(member)) continue; + if (section == "mod" && !PermissionHelper.HasPerm(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles])) continue; sb.AppendLine($"- `{section.ToTitleCase()}` ({CommandRegistry.Sections[section].Count})"); } sb.AppendLine($"\nUse `{Config.Prefix}help ` to see commands in a category."); @@ -52,13 +50,13 @@ namespace SkyBot.Commands return; } - if (sectionName == "dev" && !isOwner) + if (sectionName == "dev" && !PermissionHelper.IsOwner(member)) { await MessageHelper.ReplyAsync(ctx, channel, $"Unknown category `{sectionName}`."); return; } - if (sectionName == "mod" && !PermissionHelper.HasPermAsync(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles]).Result) + if (sectionName == "mod" && !PermissionHelper.HasPerm(member, [PlanetPermissions.Kick, PlanetPermissions.Ban, PlanetPermissions.ManageRoles])) { await MessageHelper.ReplyAsync(ctx, channel, $"Unknown category `{sectionName}`."); return; diff --git a/SkyBot/Commands/Info/Info.cs b/SkyBot/Commands/Info/Info.cs new file mode 100644 index 0000000..dcd0dd6 --- /dev/null +++ b/SkyBot/Commands/Info/Info.cs @@ -0,0 +1,109 @@ +using System.Collections.Concurrent; +using System.Text; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; + +namespace SkyBot.Commands +{ + public class Info : ICommand + { + public string Name => "info"; + public string[] Aliases => []; + public string Description => "Shows the info about a User or the Planet."; + public string Section => "Info"; + public string Usage => "info [mention|memberid]"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + string[] args = ctx.Args; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + if (args.Length == 0) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please specify `user` or `planet`."); + return; + } + + switch (args[0].ToLower()) + { + case "user": + case "u": + await HandleUserInfo(ctx, channel); + break; + case "planet": + case "p": + await HandlePlanetInfo(ctx, channel); + break; + default: + await MessageHelper.ReplyAsync(ctx, channel, "invalid option. Use `user` or `planet`."); + break; + } + } + + private async Task HandleUserInfo(CommandContext ctx, Channel channel) + { + Message message = ctx.Message; + Planet planet = ctx.Planet; + string[] args = ctx.Args; + PlanetMember? target; + + try + { + if (message.Mentions != null && message.Mentions.Any()) + { + target = await planet.FetchMemberAsync(message.Mentions.First().TargetId); + } + else if (args.Length > 1 && long.TryParse(args[1], out long memberid)) + { + target = await planet.FetchMemberAsync(memberid); + } + else + { + target = ctx.Member; + } + } + catch (Exception ex) + { + Console.WriteLine($"Exception: {ex.Message}"); + target = ctx.Member; + } + + if (target == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member."); + return; + } + + var sb = new StringBuilder(); + sb.AppendLine($"**{target.Name}**"); + sb.AppendLine($"User ID: `{target.UserId}`"); + sb.AppendLine($"Member ID: `{target.Id}`"); + sb.AppendLine($"Nickname: `{(string.IsNullOrWhiteSpace(target.Nickname) ? "None" : target.Nickname)}`"); + sb.AppendLine($"Status: `{(string.IsNullOrWhiteSpace(target.Status) ? "None" : target.Status)}`"); + sb.AppendLine($"Primary Role: `{target.PrimaryRole?.Name ?? "None"}`"); + sb.AppendLine($"Roles: `{string.Join(", ", target.Roles.Select(r => r.Name))}`"); + + await MessageHelper.ReplyAsync(ctx, channel, sb.ToString()); + } + + private async Task HandlePlanetInfo(CommandContext ctx, Channel channel) + { + var planet = ctx.Planet; + + var sb = new StringBuilder(); + sb.AppendLine($"**{planet.Name}**"); + sb.AppendLine($"Planet ID: `{planet.Id}`"); + sb.AppendLine($"Owner ID: `{planet.OwnerId}`"); + sb.AppendLine($"Member Count: `{planet.Members?.Count ?? 0}`"); + sb.AppendLine($"Channel Count: `{planet.Channels?.Count ?? 0}`"); + sb.AppendLine($"Role Count: `{planet.Roles?.Count ?? 0}`"); + sb.AppendLine($"Description: `{(string.IsNullOrWhiteSpace(planet.Description) ? "None" : planet.Description)}`"); + + await MessageHelper.ReplyAsync(ctx, channel, sb.ToString()); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Info/JoinSite.cs b/SkyBot/Commands/Info/JoinSite.cs index 70bc667..65b10aa 100644 --- a/SkyBot/Commands/Info/JoinSite.cs +++ b/SkyBot/Commands/Info/JoinSite.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; diff --git a/SkyBot/Commands/Info/Minecraft.cs b/SkyBot/Commands/Info/Minecraft.cs index af1b987..a046dee 100644 --- a/SkyBot/Commands/Info/Minecraft.cs +++ b/SkyBot/Commands/Info/Minecraft.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -10,7 +11,7 @@ namespace SkyBot.Commands public string[] Aliases => ["mc"]; public string Description => "Sends the Unofficial ValourSMP IPs"; public string Section => "Info"; - public string Usage => "minecraft|mc"; + public string Usage => "minecraft"; public async Task Execute(CommandContext ctx) { diff --git a/SkyBot/Commands/Info/Ping.cs b/SkyBot/Commands/Info/Ping.cs new file mode 100644 index 0000000..5ddc23d --- /dev/null +++ b/SkyBot/Commands/Info/Ping.cs @@ -0,0 +1,31 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class Ping : ICommand + { + public string Name => "ping"; + public string[] Aliases => []; + public string Description => "Shows the bot's response time."; + public string Section => "Info"; + public string Usage => "ping"; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + DateTime start = DateTime.UtcNow; + TaskResult message = await MessageHelper.ReplyAsync(ctx, channel, "🏓 Pinging..."); + double elapsed = (DateTime.UtcNow - start).TotalMilliseconds; + + await MessageHelper.EditAsync(channel, message.Data, $"🏓 Pong! `{elapsed:F0}ms`"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Info/Source.cs b/SkyBot/Commands/Info/Source.cs index ca77c87..5e87c1f 100644 --- a/SkyBot/Commands/Info/Source.cs +++ b/SkyBot/Commands/Info/Source.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; diff --git a/SkyBot/Commands/Info/Suggest.cs b/SkyBot/Commands/Info/Suggest.cs index e152666..6a85cb6 100644 --- a/SkyBot/Commands/Info/Suggest.cs +++ b/SkyBot/Commands/Info/Suggest.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; diff --git a/SkyBot/Commands/Info/SwaggerAPI.cs b/SkyBot/Commands/Info/SwaggerAPI.cs index 34ffa92..9522432 100644 --- a/SkyBot/Commands/Info/SwaggerAPI.cs +++ b/SkyBot/Commands/Info/SwaggerAPI.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -10,7 +11,7 @@ namespace SkyBot.Commands public string[] Aliases => ["api"]; public string Description => "Sends a link to the Valour.gg Swagger API."; public string Section => "Info"; - public string Usage => "swagger|api"; + public string Usage => "swagger"; public async Task Execute(CommandContext ctx) { diff --git a/SkyBot/Commands/Info/Uptime.cs b/SkyBot/Commands/Info/Uptime.cs new file mode 100644 index 0000000..fa03450 --- /dev/null +++ b/SkyBot/Commands/Info/Uptime.cs @@ -0,0 +1,30 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; + +namespace SkyBot.Commands +{ + public class Uptime : ICommand + { + public string Name => "uptime"; + public string[] Aliases => ["up"]; + public string Description => "Shows how long the bot has been running."; + public string Section => "Info"; + public string Usage => "uptime"; + + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + + if (!channelCache.TryGetValue(channelId, out var channel)) return; + + TimeSpan uptime = DateTime.UtcNow - SkyBot.StartTime; + string formatted = $"{(int)uptime.TotalDays}d {uptime.Hours}h {uptime.Minutes}m {uptime.Seconds}s"; + await MessageHelper.ReplyAsync(ctx, channel, $"⏱️ Uptime: `{formatted}`"); + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Info/UserCount.cs b/SkyBot/Commands/Info/UserCount.cs index 92e0421..2303173 100644 --- a/SkyBot/Commands/Info/UserCount.cs +++ b/SkyBot/Commands/Info/UserCount.cs @@ -11,13 +11,12 @@ namespace SkyBot.Commands public string[] Aliases => ["users"]; public string Description => "Shows the user count of Valour."; public string Section => "Info"; - public string Usage => "usercount|users"; + public string Usage => "usercount"; public async Task Execute(CommandContext ctx) { ConcurrentDictionary channelCache = ctx.ChannelCache; long channelId = ctx.ChannelId; - PlanetMember member = ctx.Member; string message = @$"Current Valour user count is: {ValourUsercountHelper.ValourUsercount:N0} You can see a graph of the user count here: /meow"; diff --git a/SkyBot/Commands/Info/Version.cs b/SkyBot/Commands/Info/Version.cs index f097293..f0c3836 100644 --- a/SkyBot/Commands/Info/Version.cs +++ b/SkyBot/Commands/Info/Version.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; @@ -10,7 +11,7 @@ namespace SkyBot.Commands public string[] Aliases => []; public string Description => "Shows the current version of the Bot and Valour."; public string Section => "Info"; - public string Usage => ""; + public string Usage => "version"; public async Task Execute(CommandContext ctx) { diff --git a/SkyBot/Commands/Mod/Ban.cs b/SkyBot/Commands/Mod/Ban.cs index a776b6c..6f93f8c 100644 --- a/SkyBot/Commands/Mod/Ban.cs +++ b/SkyBot/Commands/Mod/Ban.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using SkyBot.Helpers; using SkyBot.Models; using Valour.Sdk.Models; +using Valour.Shared; using Valour.Shared.Authorization; namespace SkyBot.Commands @@ -11,26 +12,106 @@ namespace SkyBot.Commands public string Name => "ban"; public string[] Aliases => []; public string Description => "Bans a user from the planet."; - public string Section => "mod"; - public string Usage => "ban [reason]"; + public string Section => "Mod"; + public string Usage => "ban [reason] [length (y=year, M=month, w=week, d=day, h=hour, m=minute)]"; public async Task Execute(CommandContext ctx) { ConcurrentDictionary channelCache = ctx.ChannelCache; long channelId = ctx.ChannelId; + Planet planet = ctx.Planet; + Message message = ctx.Message; + string[] args = ctx.Args; PlanetMember member = ctx.Member; + PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id); + if (channelCache.TryGetValue(channelId, out var channel)) { - if (!PermissionHelper.HasPermAsync(member, [PlanetPermissions.Ban]).Result) + if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban])) { await MessageHelper.ReplyAsync(ctx, channel, $"You don't have permission to use this command."); return; } - string message = $"Work in progress..."; - await MessageHelper.ReplyAsync(ctx, channel, message); + if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, channel, $"I don't have permission to ban members."); + return; + } + + if (!message.Mentions.Any() && args.Length < 1) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please mention someone or user their id."); + return; + } + + + long targetId; + if (message.Mentions.Any()) + { + targetId = message.Mentions.First().TargetId; + } + else if (!long.TryParse(args[0], out targetId)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid member ID."); + return; + } + PlanetMember victim = await planet.FetchMemberAsync(targetId); + + if (victim == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member."); + return; + } + + DateTime? expires = null; + List remainingArgs = args[1..].ToList(); + + for (int i = 0; i < remainingArgs.Count; i++) + { + var parsed = MessageHelper.ParseDuration(remainingArgs[i]); + if (parsed != null) + { + expires = parsed; + remainingArgs.RemoveAt(i); + break; + } + } + + string reason = remainingArgs.Count > 0 ? string.Join(" ", remainingArgs) : "No reason provided."; + + PlanetBan ban = new PlanetBan(ctx.Client) + { + PlanetId = planet.Id, + TargetId = victim.UserId, + IssuerId = ctx.Client.Me.Id, + Reason = reason, + TimeCreated = DateTime.UtcNow, + TimeExpires = expires + }; + + + + TaskResult result = await ban.CreateAsync(); + if (!result.Success) + { + await MessageHelper.ReplyAsync(ctx, channel, $"Failed to ban {victim.Name}: {result.Message}"); + return; + } + + if (expires == null) + { + await MessageHelper.ReplyAsync(ctx, channel, $"Permanently Banned `{victim.Name}`. Reason: `{reason}`"); + } else + { + await MessageHelper.ReplyAsync(ctx, channel, $"Banned `{victim.Name}` until `{expires}`. Reason: `{reason}`"); + } + + } } + + } } \ No newline at end of file diff --git a/SkyBot/Commands/Mod/Bans.cs b/SkyBot/Commands/Mod/Bans.cs new file mode 100644 index 0000000..17a4a73 --- /dev/null +++ b/SkyBot/Commands/Mod/Bans.cs @@ -0,0 +1,88 @@ +using System.Collections.Concurrent; +using System.Text; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared.Authorization; +using Valour.Shared.Models; + +namespace SkyBot.Commands +{ + public class GetBans : ICommand + { + public string Name => "bans"; + public string[] Aliases => [""]; + public string Description => "Lists all bans in the planet."; + public string Section => "Mod"; + public string Usage => "bans [page]"; + + private const int PageSize = 10; + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + Planet planet = ctx.Planet; + string[] args = ctx.Args; + PlanetMember member = ctx.Member; + + if (channelCache.TryGetValue(channelId, out var channel)) + { + if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command."); + return; + } + + int page = 1; + if (args.Length > 0 && int.TryParse(args[0], out int parsedPage)) + page = parsedPage; + + int skip = (page - 1) * PageSize; + + var queryResult = await planet.Node.PostAsyncWithResponse>( + $"api/planets/{planet.Id}/bans/query", + new { skip, take = PageSize, options = new { } } + ); + + if (!queryResult.Success || queryResult.Data?.Items == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Failed to fetch bans."); + return; + } + + if (!queryResult.Data.Items.Any()) + { + await MessageHelper.ReplyAsync(ctx, channel, "No bans found."); + return; + } + + int totalPages = (int)Math.Ceiling(queryResult.Data.TotalCount / (double)PageSize); + + var sb = new StringBuilder(); + sb.AppendLine($"**Bans** (Page {page}/{totalPages}):"); + + IEnumerable activeBans = queryResult.Data.Items.Where(b => b.Permanent || b.TimeExpires > DateTime.UtcNow); + + if (!activeBans.Any()) + { + await MessageHelper.ReplyAsync(ctx, channel, "No active bans found."); + return; + } + + foreach (var ban in activeBans) + { + var user = await ctx.Client.UserService.FetchUserAsync(ban.TargetId); + string username = user?.NameAndTag ?? "Unknown"; + string expires = ban.TimeExpires.HasValue + ? $"{ban.TimeExpires}" + : "Never"; + sb.AppendLine($"**{username}** `{user?.Id}` - {ban.Reason} (Expires: `{expires}`)"); + } + + sb.AppendLine($"\nUse `{Config.Prefix}bans ` to see more."); + await MessageHelper.ReplyAsync(ctx, channel, sb.ToString()); + } + } + } +} \ No newline at end of file diff --git a/SkyBot/Commands/Mod/Kick.cs b/SkyBot/Commands/Mod/Kick.cs index 67becc3..176988c 100644 --- a/SkyBot/Commands/Mod/Kick.cs +++ b/SkyBot/Commands/Mod/Kick.cs @@ -11,26 +11,66 @@ namespace SkyBot.Commands public string Name => "kick"; public string[] Aliases => []; public string Description => "Kicks a user from the planet."; - public string Section => "mod"; + public string Section => "Mod"; public string Usage => "kick [reason]"; public async Task Execute(CommandContext ctx) { ConcurrentDictionary channelCache = ctx.ChannelCache; long channelId = ctx.ChannelId; + Planet planet = ctx.Planet; + Message message = ctx.Message; + string[] args = ctx.Args; PlanetMember member = ctx.Member; + PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id); if (channelCache.TryGetValue(channelId, out var channel)) { - if (!PermissionHelper.HasPermAsync(member, [PlanetPermissions.Kick]).Result) + if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Kick])) { await MessageHelper.ReplyAsync(ctx, channel, $"You don't have permission to use this command."); return; } - string message = $"Work in progress..."; + if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Kick])) + { + await MessageHelper.ReplyAsync(ctx, channel, $"I don't have permission to kick members."); + return; + } - await MessageHelper.ReplyAsync(ctx, channel, message); + if (!message.Mentions.Any() && args.Length < 2) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please mention someone or user their id."); + return; + } + + + long targetId; + if (message.Mentions.Any()) + { + targetId = message.Mentions.First().TargetId; + } + else if (!long.TryParse(args[1], out targetId)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid member ID."); + return; + } + PlanetMember victim = await planet.FetchMemberAsync(targetId); + + if (victim == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Could not find that member."); + return; + } + + string reason = args.Length > 1 && !message.Mentions.Any() + ? string.Join(" ", args[2..]) + : args.Length > 1 + ? string.Join(" ", args[1..]) + : "No reason provided."; + + await victim.DeleteAsync(); + await MessageHelper.ReplyAsync(ctx, channel, $"Kicked {victim.Name}. Reason: {reason}"); } } } diff --git a/SkyBot/Commands/Mod/Unban.cs b/SkyBot/Commands/Mod/Unban.cs new file mode 100644 index 0000000..579e016 --- /dev/null +++ b/SkyBot/Commands/Mod/Unban.cs @@ -0,0 +1,107 @@ +using System.Collections.Concurrent; +using SkyBot.Helpers; +using SkyBot.Models; +using Valour.Sdk.Models; +using Valour.Shared; +using Valour.Shared.Authorization; +using Valour.Shared.Models; + +namespace SkyBot.Commands +{ + public class Unban : ICommand + { + public string Name => "unban"; + public string[] Aliases => []; + public string Description => "Unbans a user from the planet."; + public string Section => "Mod"; + public string Usage => "unban "; + + // public async Task Execute(CommandContext ctx) + // { + // ConcurrentDictionary channelCache = ctx.ChannelCache; + // long channelId = ctx.ChannelId; + + // if (channelCache.TryGetValue(channelId, out var channel)) + // { + // await MessageHelper.ReplyAsync(ctx, channel, "Unbanning is currently unavailable due to a Valour server bug."); + // } + // } + + public async Task Execute(CommandContext ctx) + { + ConcurrentDictionary channelCache = ctx.ChannelCache; + long channelId = ctx.ChannelId; + Planet planet = ctx.Planet; + Message message = ctx.Message; + string[] args = ctx.Args; + PlanetMember member = ctx.Member; + PlanetMember bot = await planet.FetchMemberByUserAsync(ctx.Client.Me.Id); + + if (channelCache.TryGetValue(channelId, out var channel)) + { + if (!PermissionHelper.HasPerm(member, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, channel, "You don't have permission to use this command."); + return; + } + + if (!PermissionHelper.HasPerm(bot, [PlanetPermissions.Ban])) + { + await MessageHelper.ReplyAsync(ctx, channel, "I don't have permission to unban members."); + return; + } + + if (args.Length < 1) + { + await MessageHelper.ReplyAsync(ctx, channel, "Please provide a user ID."); + return; + } + + if (!long.TryParse(args[0], out long targetUserId)) + { + await MessageHelper.ReplyAsync(ctx, channel, "Invalid user ID."); + return; + } + + PlanetBan? ban = null; + int skip = 0; + int take = 50; + + while (ban == null) + { + var queryResult = await planet.Node.PostAsyncWithResponse>( + $"api/planets/{planet.Id}/bans/query", + new {skip, take, options = new { }} + ); + + if (!queryResult.Success || queryResult.Data?.Items == null || !queryResult.Data.Items.Any()) + break; + + ban = queryResult.Data.Items.FirstOrDefault(b => b.TargetId == targetUserId && (b.Permanent || b.TimeExpires > DateTime.UtcNow)); + + if (queryResult.Data.Items.Count < take) + break; + + skip += take; + } + + if (ban == null) + { + await MessageHelper.ReplyAsync(ctx, channel, "Could not find a ban for that user."); + return; + } + + ban.TimeExpires = DateTime.UtcNow.AddSeconds(-1); + TaskResult result = await planet.Node.PutAsyncWithResponse($"api/bans/{ban.Id}", ban); + if (!result.Success) + { + await MessageHelper.ReplyAsync(ctx, channel, "Failed to unban."); + return; + } + + User user = await ctx.Client.UserService.FetchUserAsync(targetUserId); + await MessageHelper.ReplyAsync(ctx, channel, $"Unbanned `{user?.NameAndTag ?? targetUserId.ToString()}`."); + } + } + } +} \ No newline at end of file diff --git a/SkyBot/Helpers/MentionHelper.cs b/SkyBot/Helpers/MentionHelper.cs deleted file mode 100644 index 2c6544b..0000000 --- a/SkyBot/Helpers/MentionHelper.cs +++ /dev/null @@ -1,10 +0,0 @@ -using Valour.Sdk.Models; - -namespace SkyBot.Helpers -{ - public static class MentionHelper - { - public static string Mention(this PlanetMember member) => $"«@m-{member.Id}»"; - public static string Mention(this User user) => $"«@u-{user.Id}»"; - } -} \ No newline at end of file diff --git a/SkyBot/Helpers/MessageHelper.cs b/SkyBot/Helpers/MessageHelper.cs index 2d4de9c..bdb543a 100644 --- a/SkyBot/Helpers/MessageHelper.cs +++ b/SkyBot/Helpers/MessageHelper.cs @@ -1,24 +1,53 @@ using SkyBot.Models; using Valour.Sdk.Models; +using Valour.Shared; -public static class MessageHelper +namespace SkyBot.Helpers { - public static async Task ReplyAsync(CommandContext ctx, Channel channel, string content) + public static class MessageHelper { - long? replyToId = ctx.Message.ReplyToId.HasValue ? ctx.Message.ReplyToId : ctx.Message.Id; - var msg = new Message(ctx.Client) + public static string Mention(this PlanetMember member) => $"«@m-{member.Id}»"; + public static string Mention(this User user) => $"«@u-{user.Id}»"; + public static string ToTitleCase(this string str) => System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str); + public static async Task> ReplyAsync(CommandContext ctx, Channel channel, string content) { - Content = content, - ChannelId = channel.Id, - PlanetId = ctx.Planet.Id, - AuthorUserId = ctx.Client.Me.Id, - AuthorMemberId = channel.Planet?.MyMember.Id, - ReplyToId = replyToId, - Fingerprint = Guid.NewGuid().ToString() - }; - await ctx.Client.MessageService.SendMessage(msg); - } + long? replyToId = ctx.Message.ReplyToId.HasValue ? ctx.Message.ReplyToId : ctx.Message.Id; - public static string ToTitleCase(this string str) => System.Globalization.CultureInfo.CurrentCulture.TextInfo.ToTitleCase(str); + var msg = new Message(ctx.Client) + { + Content = content, + ChannelId = channel.Id, + PlanetId = ctx.Planet.Id, + AuthorUserId = ctx.Client.Me.Id, + AuthorMemberId = channel.Planet?.MyMember.Id, + ReplyToId = replyToId, + Fingerprint = Guid.NewGuid().ToString() + }; + return await ctx.Client.MessageService.SendMessage(msg); + } + public static async Task> EditAsync(Channel channel, Message message, string content) + { + message.Content = content; + return await channel.Planet.Node.PutAsyncWithResponse($"api/messages/{message.Id}", message); + } + public static DateTime? ParseDuration(string input) + { + if (string.IsNullOrWhiteSpace(input)) return null; + + var unit = input[^1]; + if (!int.TryParse(input[..^1], out int value)) return null; + + return unit switch + { + 'm' => DateTime.UtcNow.AddMinutes(value), + 'h' => DateTime.UtcNow.AddHours(value), + 'd' => DateTime.UtcNow.AddDays(value), + 'w' => DateTime.UtcNow.AddDays(value * 7), + 'M' => DateTime.UtcNow.AddMonths(value), + 'y' => DateTime.UtcNow.AddYears(value), + _ => null + }; + } + } } \ No newline at end of file diff --git a/SkyBot/Helpers/PermissionHelper.cs b/SkyBot/Helpers/PermissionHelper.cs index a567b74..91289ed 100644 --- a/SkyBot/Helpers/PermissionHelper.cs +++ b/SkyBot/Helpers/PermissionHelper.cs @@ -5,7 +5,7 @@ namespace SkyBot.Helpers { public static class PermissionHelper { - public static async Task HasPermAsync(PlanetMember member, PlanetPermission[] permissions, bool requireAll = false) + public static bool HasPerm(PlanetMember member, PlanetPermission[] permissions, bool requireAll = false) { if (member == null) return false; if (member.HasPermission(PlanetPermissions.FullControl)) return true; @@ -16,7 +16,7 @@ namespace SkyBot.Helpers : permissions.Any(permission => member.HasPermission(permission)); } - public static async Task IsOwner(PlanetMember member) + public static bool IsOwner(PlanetMember member) { if (member == null) return false; diff --git a/SkyBot/Services/Messages/Create.cs b/SkyBot/Services/Messages/Create.cs index e038458..fe549cc 100644 --- a/SkyBot/Services/Messages/Create.cs +++ b/SkyBot/Services/Messages/Create.cs @@ -29,23 +29,25 @@ namespace SkyBot.Services.Messages string command = parts[0].ToLower(); string[] args = parts[1..]; + CommandContext ctx = new CommandContext + { + ChannelCache = channelCache, + ChannelId = channelId, + Member = member, + Planet = message.Planet, + Args = args, + Message = message, + Client = client + }; + if (CommandRegistry.Commands.TryGetValue(command, out var handler)) { - await handler.Execute(new CommandContext - { - ChannelCache = channelCache, - ChannelId = channelId, - Member = member, - Planet = message.Planet, - Args = args, - Message = message, - Client = client - }); + await handler.Execute(ctx); } else { if (channelCache.TryGetValue(channelId, out var channel)) { - await channel.SendMessageAsync($"{MentionHelper.Mention(member)} Unknown command."); + await MessageHelper.ReplyAsync(ctx, channel, $"Unknown command `{command}`."); } } } diff --git a/SkyBot/SkyBot.cs b/SkyBot/SkyBot.cs index 890299e..0d9138e 100644 --- a/SkyBot/SkyBot.cs +++ b/SkyBot/SkyBot.cs @@ -10,6 +10,7 @@ namespace SkyBot private readonly ValourClient _client; private readonly ConcurrentDictionary _channelCache = new(); private readonly ConcurrentDictionary _initializedPlanets = new(); + public static DateTime StartTime; public SkyBot() { @@ -19,6 +20,7 @@ namespace SkyBot public async Task StartAsync() { + StartTime = DateTime.UtcNow; await BotService.InitializeBotAsync(_client, _channelCache, _initializedPlanets); } } @@ -32,6 +34,7 @@ namespace SkyBot try { await new SkyBot().StartAsync(); + Console.WriteLine("Ready and listening..."); await Task.Delay(Timeout.Infinite);