ProjectMakoto/ProjectMakoto/Events/TokenLeakEvents.cs

110 lines
4.2 KiB
C#

// 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
using Octokit;
namespace ProjectMakoto.Events;
internal sealed class TokenLeakEvents(Bot bot) : RequiresTranslation(bot)
{
Translations.events.tokenDetection tKey
=> this.Bot.LoadedTranslations.Events.TokenDetection;
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);
}
internal 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)
return;
if (!this.Bot.Guilds[guild.Id].TokenLeakDetection.DetectTokens)
return;
var matchCollection = RegexTemplates.Token.Matches(e.Content);
if (!matchCollection.IsNotNullAndNotEmpty())
return;
var filtered_matches = matchCollection.GroupBy(x => x.Value).Select<IGrouping<string, Match>, Match>(x => x.First());
_ = e.DeleteAsync();
var InvalidateCount = 0;
foreach (var token in filtered_matches)
{
var botId = token.Groups["botid"].Value!;
DiscordUser? botUser = null;
try { botUser = await this.GetBotInfo(sender, botId); } catch { }
if (botUser is null)
{
Log.Debug("Not uploading detected token, no bot user was fetched.");
continue;
}
var owner = this.Bot.status.LoadedConfig.Secrets.Github.TokenLeakRepoOwner;
var repo = this.Bot.status.LoadedConfig.Secrets.Github.TokenLeakRepo;
var seconds = (long)DateTime.UtcNow.Subtract(DateTime.MinValue).TotalSeconds;
if (this.Bot.TokenInvalidator.SearchForString(token.Value).Item1)
{
Log.Debug("Not uploading detected token, token already present in repository.");
continue;
}
var fileName = $"token_leak_{e.Author.Id}_{guild.Id}_{e.Channel.Id}_{seconds}.md";
var content = $"## Token of {botUser?.Id.ToString() ?? "unknown"} (Owner {e.Author.Id})\n\nBot {token}";
_ = await this.Bot.GithubClient.Repository.Content.CreateFile(owner, repo, $"automatic/{fileName}", new CreateFileRequest("Upload token to invalidate", content, "main"));
InvalidateCount++;
}
if (InvalidateCount > 0)
_ = this.Bot.TokenInvalidator.Pull();
var s = (InvalidateCount > 1 ? "s" : "");
_ = e.Channel.SendMessageAsync(new DiscordMessageBuilder().AddEmbed(
new DiscordEmbedBuilder()
.WithColor(EmbedColors.Error)
.WithAuthor(sender.CurrentUser.GetUsername(), null, sender.CurrentUser.AvatarUrl)
.WithDescription(this.tKey.TokenInvalidated.Get(this.Bot.Guilds[e.Guild.Id]).Build(true, false, new TVar("Count", filtered_matches.Count()))))
.WithContent(e.Author.Mention));
}
private async Task<DiscordUser> GetBotInfo(DiscordClient client, string botId)
{
var ulongId = Convert.ToUInt64(Base64Decode(botId + "=="));
var bot = await client.GetUserAsync(ulongId);
return bot;
}
public static string Base64Decode(string base64)
{
var base64Bytes = Convert.FromBase64String(base64);
return Encoding.UTF8.GetString(base64Bytes);
}
}