refactor: Initial release

This commit is contained in:
Mira 2025-01-27 17:17:53 +01:00
commit 9505750e29
Signed by untrusted user who does not match committer: Xorog
GPG key ID: 983798ED9C3E7C36
447 changed files with 41522 additions and 0 deletions

View file

@ -0,0 +1,262 @@
// Project Makoto
// Copyright (C) 2024 Fortunevale
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY
namespace ProjectMakoto.Events;
internal sealed class PhishingProtectionEvents(Bot bot) : RequiresTranslation(bot)
{
Translations.events.phishing tKey
=> this.t.Events.Phishing;
internal async Task MessageCreated(DiscordClient sender, MessageCreateEventArgs e)
{
_ = this.CheckMessage(sender, e.Guild, e.Message).Add(this.Bot);
}
internal async Task MessageUpdated(DiscordClient sender, MessageUpdateEventArgs e)
{
if (e.MessageBefore?.Content != e.Message?.Content)
_ = this.CheckMessage(sender, e.Guild, e.Message).Add(this.Bot);
}
private async Task CheckMessage(DiscordClient sender, DiscordGuild guild, DiscordMessage e)
{
var prefix = guild.GetGuildPrefix(this.Bot);
if (e?.Content?.StartsWith(prefix) ?? false)
foreach (var command in sender.GetCommandsNext().RegisteredCommands)
if (e.Content.StartsWith($"{prefix}{command.Key}"))
return;
if (e.WebhookMessage || guild is null || e.Author?.Id == sender.CurrentUser.Id || (e.Author?.IsBot ?? true))
return;
if (!this.Bot.Guilds[guild.Id].PhishingDetection.DetectPhishing)
return;
var member = await guild.GetMemberAsync(e.Author.Id);
async Task CheckDb(Uri uri)
{
if (!this.Bot.Guilds[guild.Id].PhishingDetection.AbuseIpDbReports)
return;
IPAddress[] parsedIp;
try
{
parsedIp = await Dns.GetHostAddressesAsync(uri.Host);
}
catch (Exception)
{
return;
}
var query = await this.Bot.AbuseIpDbClient.QueryIp(parsedIp[0].ToString());
if (query.data.abuseConfidenceScore.HasValue && query.data.abuseConfidenceScore.Value > 60)
{
var report_fields = query.data.reports.Select(x => new DiscordEmbedField($"{x.reporterCountryCode.IsoCountryCodeToFlagEmoji()} {x.reporterId}{(x.reportedAt.HasValue ? $" {x.reportedAt.Value.ToTimestamp()}" : "")}", (x.comment.IsNullOrWhiteSpace() ? "No comment provided." : x.comment).FullSanitize().TruncateWithIndication(1000))).ToList();
DiscordEmbedBuilder embed = new()
{
Title = this.tKey.AbuseIpDbReport.Get(this.Bot.Guilds[guild.Id]),
Description = $"**{this.tKey.HostWasFoundInAbuseIpDb.Get(this.Bot.Guilds[guild.Id]).Build(new TVar("Host", $"`{uri.Host} ({parsedIp[0]})`"))}**\n" +
$"{(query.data.countryName.IsNullOrWhiteSpace() ? "" : $"**{this.tKey.ConfidenceOfAbuse.Get(this.Bot.Guilds[guild.Id])}**: {query.data.abuseConfidenceScore}%\n\n")}" +
$"{(query.data.countryName.IsNullOrWhiteSpace() ? "" : $"**{this.tKey.Country.Get(this.Bot.Guilds[guild.Id])}**: {query.data.countryCode.IsoCountryCodeToFlagEmoji()} {query.data.countryName}\n")}" +
$"{(query.data.isp.IsNullOrWhiteSpace() ? "" : $"**{this.tKey.ISP.Get(this.Bot.Guilds[guild.Id])}**: {query.data.isp}\n")}" +
$"{(query.data.domain.IsNullOrWhiteSpace() ? "" : $"**{this.tKey.DomainName.Get(this.Bot.Guilds[guild.Id])}**: {query.data.domain}\n")}",
Color = new DiscordColor("#FF0000"),
Thumbnail = new DiscordEmbedBuilder.EmbedThumbnail
{
Url = Resources.AbuseIpDbIcon
},
};
_ = embed.AddFields(report_fields.Take(2));
_ = e.RespondAsync(new DiscordMessageBuilder().WithEmbed(embed).AddComponents(new DiscordLinkButtonComponent($"https://www.abuseipdb.com/check/{parsedIp[0]}", this.tKey.OpenInBrowser.Get(this.Bot.Guilds[guild.Id]))));
}
}
var matches = RegexTemplates.Url.Matches(e.Content);
var parsedMatches = matches.Select(x => new UriBuilder(x.Value));
var parsedWords = e.Content.Split(" ", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
foreach (var url in this.Bot.PhishingHosts)
{
foreach (var word in parsedWords)
{
if (word.ToLower() == url.Key.ToLower())
{
_ = this.PunishMember(guild, member, e, url.Key);
return;
}
var reg = Regex.Match(word.ToLower(), @"([\S]*\.)?([\S]*)\.([\S]*)");
if (reg.Success && reg.Groups[1].Success)
{
var regex = new Regex(Regex.Escape(reg.Groups[1].Value));
if (regex.Replace(word.ToLower(), "", 1) == url.Key.ToLower())
{
_ = this.PunishMember(guild, member, e, url.Key);
return;
}
}
}
}
foreach (var match in parsedMatches)
{
if (match.Uri.ToString().Contains(''))
{
_ = this.PunishMember(guild, member, e, match.Uri.ToString());
return;
}
_ = CheckDb(match.Uri);
}
foreach (var url in this.Bot.PhishingHosts)
{
foreach (var match in parsedMatches)
{
if (match.Host.ToLower() == url.Key.ToLower())
{
_ = this.PunishMember(guild, member, e, url.Key);
return;
}
}
}
if (matches.Count > 0)
{
Dictionary<string, string> redirectUrls = new();
foreach (var match in matches.Cast<Match>())
{
try
{
var unshortenedUrl = await WebTools.UnshortenUrl(match.Value);
var parsedUri = new UriBuilder(unshortenedUrl);
_ = CheckDb(parsedUri.Uri);
if (unshortenedUrl != match.Value)
{
foreach (var url in this.Bot.PhishingHosts)
{
if (parsedUri.Host.ToLower() == url.Key.ToLower())
{
_ = this.PunishMember(guild, member, e, url.Key);
return;
}
}
if (!this.recentlyResolvedUrls.TryGetValue(unshortenedUrl, out var value) || value.AddSeconds(10) < DateTime.UtcNow)
redirectUrls.Add(match.Value, unshortenedUrl);
}
}
catch (DepthLimitReachedException)
{
if (this.Bot.Guilds[guild.Id].PhishingDetection.WarnOnRedirect)
_ = e.RespondAsync(embed: new DiscordEmbedBuilder
{
Title = $":no_entry: {this.tKey.RedirectDepthLimitError.Get(this.Bot.Guilds[guild.Id])}",
Color = EmbedColors.Error
});
}
catch (Exception ex) when (ex is TimeoutException ||
(ex is HttpRequestException && ex.Message.Contains("Cannot write more bytes")))
{
if (this.Bot.Guilds[guild.Id].PhishingDetection.WarnOnRedirect)
_ = e.RespondAsync(embed: new DiscordEmbedBuilder
{
Title = $":no_entry: {this.tKey.RedirectCheckTimeoutError.Get(this.Bot.Guilds[guild.Id])}",
Color = EmbedColors.Error
});
}
catch (Exception ex)
{
Log.Error(ex, "An exception occurred while trying to unshorten url '{url}'", match);
if (this.Bot.Guilds[guild.Id].PhishingDetection.WarnOnRedirect)
_ = e.RespondAsync(embed: new DiscordEmbedBuilder
{
Title = $":no_entry: {this.tKey.RedirectCheckTimeoutUnknownError.Get(this.Bot.Guilds[guild.Id])}",
Color = EmbedColors.Error
});
}
}
if (redirectUrls.Count > 0)
{
foreach (var b in redirectUrls)
if (!this.recentlyResolvedUrls.ContainsKey(b.Value))
this.recentlyResolvedUrls.Add(b.Value, DateTime.UtcNow);
else
this.recentlyResolvedUrls[b.Value] = DateTime.UtcNow;
if (this.Bot.Guilds[guild.Id].PhishingDetection.WarnOnRedirect)
_ = e.RespondAsync(embed: new DiscordEmbedBuilder
{
Title = $":warning: {this.tKey.FoundRedirects.Get(this.Bot.Guilds[guild.Id])}",
Description = $"`{string.Join("`\n`", redirectUrls.Select(x => x.Value))}`",
Color = EmbedColors.Warning
});
}
}
}
private async Task PunishMember(DiscordGuild guild, DiscordMember member, DiscordMessage e, string url)
{
if (!this.Bot.Guilds[guild.Id].PhishingDetection.DetectPhishing)
return;
switch (this.Bot.Guilds[guild.Id].PhishingDetection.PunishmentType)
{
case PhishingPunishmentType.Delete:
{
_ = e.DeleteAsync();
break;
}
case PhishingPunishmentType.Timeout:
{
_ = e.DeleteAsync();
_ = member.TimeoutAsync(this.Bot.Guilds[guild.Id].PhishingDetection.CustomPunishmentLength, this.Bot.Guilds[guild.Id].PhishingDetection.CustomPunishmentReason.Replace("%R", $"Detected malicious Url [{url}]"));
break;
}
case PhishingPunishmentType.Kick:
{
_ = e.DeleteAsync();
_ = member.RemoveAsync(this.Bot.Guilds[guild.Id].PhishingDetection.CustomPunishmentReason.Replace("%R", this.tKey.DetectedMaliciousHost.Get(this.Bot.Guilds[guild.Id]).Build(new TVar("Host", url))));
break;
}
case PhishingPunishmentType.SoftBan:
{
_ = e.DeleteAsync();
_ = member.BanAsync(7, this.Bot.Guilds[guild.Id].PhishingDetection.CustomPunishmentReason.Replace("%R", this.tKey.DetectedMaliciousHost.Get(this.Bot.Guilds[guild.Id]).Build(new TVar("Host", url))));
await Task.Delay(1000);
_ = member.UnbanAsync();
break;
}
case PhishingPunishmentType.Ban:
{
_ = e.DeleteAsync();
_ = member.BanAsync(7, this.Bot.Guilds[guild.Id].PhishingDetection.CustomPunishmentReason.Replace("%R", this.tKey.DetectedMaliciousHost.Get(this.Bot.Guilds[guild.Id]).Build(new TVar("Host", url))));
break;
}
}
}
private Dictionary<string, DateTime> recentlyResolvedUrls = new();
}