diff --git a/commands/no-group/commands.cs b/commands/no-group/commands.cs index 46d71ec..6824b1f 100644 --- a/commands/no-group/commands.cs +++ b/commands/no-group/commands.cs @@ -17,6 +17,7 @@ using Bob.ColorMethods; using Bob.Moderation; using Bob.Time.Timestamps; +using System.Net; namespace Bob.Commands; @@ -63,7 +64,7 @@ public async Task Tag([Summary("tag", "The tag you want to use.")][Autocomplete( [CommandContextType(InteractionContextType.Guild, InteractionContextType.BotDm, InteractionContextType.PrivateChannel)] [IntegrationType(ApplicationIntegrationType.UserInstall, ApplicationIntegrationType.GuildInstall)] - [SlashCommand("analyze-link", "Bob checks a link, IP, or Port and sees where it takes you.")] + [SlashCommand("analyze-link", "Trace a link's route: check latency, server headers, status codes, cookies, and for Rick-Rolls.")] public async Task AnalyzeLink([Summary("link", "The link, IP, or Host to analyze.")] string link) { string originalInput = link.Trim(); @@ -82,6 +83,23 @@ public async Task AnalyzeLink([Summary("link", "The link, IP, or Host to analyze return; } + bool isIP = IPAddress.TryParse(uriResult.Host, out _); + bool hasValidTld = uriResult.Host.Contains('.') && !uriResult.Host.EndsWith("."); + + if (!isIP && !hasValidTld) + { + StringBuilder errorMsg = new(); + errorMsg.AppendLine("❌ **That doesn't look like a valid target.**"); + errorMsg.AppendLine("\nBob can analyze domains, IP addresses, and specific ports. For example:"); + errorMsg.AppendLine("- **Domains:** `bobthebot.net` or `https://bobthebot.net` pieces"); + errorMsg.AppendLine("- **IPv4:** `1.1.1.1` or `8.8.8.8:53` pieces"); + errorMsg.AppendLine("- **IPv6:** `[2606:4700:4700::1111]`"); + errorMsg.AppendLine("\n*Note: Local addresses like `localhost` or `127.0.0.1` are blocked.*"); + + await RespondAsync(text: errorMsg.ToString(), ephemeral: true); + return; + } + // SSRF SECURITY CHECK: Prevent Bob from attacking its own server. if (Analyze.IsPrivateIP(uriResult.Host)) { @@ -126,6 +144,23 @@ public async Task AnalyzeMessageLink(IMessage message) return; } + bool isIP = IPAddress.TryParse(uriResult.Host, out _); + bool hasValidTld = uriResult.Host.Contains('.') && !uriResult.Host.EndsWith("."); + + if (!isIP && !hasValidTld) + { + StringBuilder errorMsg = new(); + errorMsg.AppendLine("❌ **That doesn't look like a valid target.**"); + errorMsg.AppendLine("\nBob can analyze domains, IP addresses, and specific ports. For example:"); + errorMsg.AppendLine("- **Domains:** `bobthebot.net` or `https://bobthebot.net` pieces"); + errorMsg.AppendLine("- **IPv4:** `1.1.1.1` or `8.8.8.8:53` pieces"); + errorMsg.AppendLine("- **IPv6:** `[2606:4700:4700::1111]`"); + errorMsg.AppendLine("\n*Note: Local addresses like `localhost` or `127.0.0.1` are blocked.*"); + + await RespondAsync(text: errorMsg.ToString(), ephemeral: true); + return; + } + // SSRF SECURITY CHECK: Use the same logic as the Slash Command if (Analyze.IsPrivateIP(uriResult.Host)) { diff --git a/commands/no-group/helpers/analyzeLink.cs b/commands/no-group/helpers/analyzeLink.cs index 76cb190..91f612c 100644 --- a/commands/no-group/helpers/analyzeLink.cs +++ b/commands/no-group/helpers/analyzeLink.cs @@ -85,13 +85,41 @@ public static async Task AnalyzeLink(string link) } else { - string errorMessage = l.SpecialCase != null ? $"**Failed:** {l.SpecialCase}" : "**Failed to visit link.**"; - description.AppendLine($"❌ {errorMessage}"); - description.AppendLine($"> <{l.Link}>\n"); - description.AppendLine($"> ⏱️ `{l.LatencyMs}ms` | 🗄️ `{l.Server ?? "Unknown"}`\n"); + string error = l.SpecialCase ?? "Unknown Error"; + + if (error.Contains("SSL connection could not be established")) + { + error = "SSL/TLS Handshake Failed (Site may not support HTTPS)"; + } + else if (error.Contains("Name or service not known")) + { + error = "DNS Lookup Failed (Domain does not exist)"; + } + else if (error.Contains("No such host is known")) + { + error = "Host Unknown (Check the spelling of the domain)"; + } + else if (error.Contains("Connection refused") || error.Contains("An error occurred while sending the request")) + { + error = "Unreachable (No response from web server)"; + } + else if (error.Contains("timed out") || error.Contains("Request Timeout")) + { + error = "Request Timed Out (Server is too slow or down)"; + } + + // Display the cleaned error + description.AppendLine($"❌ **{error}**"); + description.AppendLine($"> <{l.Link}>"); + + if (l.LatencyMs > 0 && !string.IsNullOrEmpty(l.Server) && l.Server != "Unknown") + { + description.AppendLine($"> ⏱️ `{l.LatencyMs}ms` | 🗄️ `{l.Server}`"); + } + if (!failed) { - warnings.AppendLine($"- {l.SpecialCase ?? "For an unknown reason, Bob could not open this page (it might not exist)"}. "); + warnings.AppendLine($"- {error}. "); failed = true; } } @@ -308,7 +336,7 @@ private static async Task> GetUrlTrail(string link) // If it's a relative root "/" or contains login keywords if (urlRedirect == "/" || loginTriggers.Any(t => urlRedirect.Contains(t, StringComparison.OrdinalIgnoreCase))) { - specialCase = "Login/Return Path"; + specialCase = "URL Redirect - Login/Return Path"; } trail.Add(new LinkInfo diff --git a/commands/no-group/helpers/help.cs b/commands/no-group/helpers/help.cs index e4e0032..ec03084 100644 --- a/commands/no-group/helpers/help.cs +++ b/commands/no-group/helpers/help.cs @@ -2068,7 +2068,7 @@ public static Embed GetCommandEmbed(CommandInfo command) { Name = "analyze-link", InheritGroupName = false, - Description = "See where a link will take you, and check for rick rolls. Valid formats include: `bobthebot.net` and `https://bobthebot.net`", + Description = "See the route a link or IP will take you through, see latency, servers, response codes, and check for rick rolls. Valid formats include: `bobthebot.net` and `https://bobthebot.net`, or an IP address such as `1.1.1.1`. Private and localhost IPs are blocked.", Url = "https://docs.bobthebot.net#analyze-link" }, new CommandInfo