Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 36 additions & 1 deletion commands/no-group/commands.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
using Bob.ColorMethods;
using Bob.Moderation;
using Bob.Time.Timestamps;
using System.Net;

namespace Bob.Commands;

Expand Down Expand Up @@ -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();
Expand All @@ -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))
{
Expand Down Expand Up @@ -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))
{
Expand Down
40 changes: 34 additions & 6 deletions commands/no-group/helpers/analyzeLink.cs
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,41 @@ public static async Task<Embed> 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;
}
}
Expand Down Expand Up @@ -308,7 +336,7 @@ private static async Task<List<LinkInfo>> 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
Expand Down
2 changes: 1 addition & 1 deletion commands/no-group/helpers/help.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading