add GrepLogCommand that is NOT skidded from any bot

technically some parts are still based off of HBot but at least it got a lot lower
This commit is contained in:
Chayapak 2024-10-22 15:17:35 +07:00
parent e8ef3adf3b
commit 41e57d04f2
5 changed files with 317 additions and 0 deletions

View file

@ -69,6 +69,7 @@ public class Bot {
public TPSPlugin tps; public TPSPlugin tps;
public EvalPlugin eval; public EvalPlugin eval;
public TrustedPlugin trusted; public TrustedPlugin trusted;
public GrepLogPlugin grepLog;
public BruhifyPlugin bruhify; public BruhifyPlugin bruhify;
public CloopPlugin cloop; public CloopPlugin cloop;
public ExploitsPlugin exploits; public ExploitsPlugin exploits;
@ -123,6 +124,7 @@ public class Bot {
this.tps = new TPSPlugin(this); this.tps = new TPSPlugin(this);
this.eval = new EvalPlugin(this); this.eval = new EvalPlugin(this);
this.trusted = new TrustedPlugin(this); this.trusted = new TrustedPlugin(this);
this.grepLog = new GrepLogPlugin(this);
this.bruhify = new BruhifyPlugin(this); this.bruhify = new BruhifyPlugin(this);
this.cloop = new CloopPlugin(this); this.cloop = new CloopPlugin(this);
this.exploits = new ExploitsPlugin(this); this.exploits = new ExploitsPlugin(this);

View file

@ -0,0 +1,93 @@
package me.chayapak1.chomens_bot.commands;
import me.chayapak1.chomens_bot.Bot;
import me.chayapak1.chomens_bot.command.Command;
import me.chayapak1.chomens_bot.command.CommandContext;
import me.chayapak1.chomens_bot.command.CommandException;
import me.chayapak1.chomens_bot.command.TrustLevel;
import me.chayapak1.chomens_bot.util.ColorUtilities;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
public class GrepLogCommand extends Command {
private Thread thread;
public GrepLogCommand() {
super(
"greplog",
"Queries the bot's logs",
new String[] { "<input>", "...-ignorecase...", "...-regex...", "stop" },
new String[] { "logquery", "findlog" },
TrustLevel.PUBLIC,
false
);
}
@Override
public Component execute(CommandContext context) throws CommandException {
final Bot bot = context.bot;
if (bot.discord.jda == null) throw new CommandException(Component.text("The bot's Discord integration has to be enabled to use the command."));
boolean ignoreCase = false;
boolean regex = false;
String firstInput = context.getString(false, true);
// run 2 times. for example `*greplog -ignorecase -regex test` will be both accepted
for (int i = 0; i < 2; i++) {
if (firstInput.equals("-ignorecase")) {
ignoreCase = true;
firstInput = context.getString(false, true);
} else if (firstInput.equals("-regex")) {
regex = true;
firstInput = context.getString(false, true);
}
}
// interesting code
final String input = (firstInput + " " + context.getString(true, false)).trim();
if (input.equals("stop")) {
if (thread == null) throw new CommandException(Component.text("There is no query process running"));
bot.grepLog.running = false;
thread.interrupt(); // ? should i interrupt it this way?
thread = null;
return Component.text("Stopped querying the logs").color(ColorUtilities.getColorByString(bot.config.colorPalette.defaultColor));
}
if (thread != null) throw new CommandException(Component.text("Another query is already running"));
context.sendOutput(
Component
.translatable("Started querying the logs for %s")
.color(ColorUtilities.getColorByString(bot.config.colorPalette.defaultColor))
.arguments(
Component
.text(input)
.color(ColorUtilities.getColorByString(bot.config.colorPalette.string))
)
);
final boolean finalIgnoreCase = ignoreCase;
final boolean finalRegex = regex;
thread = new Thread(() -> {
try {
bot.grepLog.search(context, input, finalIgnoreCase, finalRegex);
} catch (CommandException e) {
context.sendOutput(e.message.color(NamedTextColor.RED));
}
thread = null;
});
thread.start();
return null;
}
}

View file

@ -57,6 +57,7 @@ public class CommandHandlerPlugin {
registerCommand(new SeenCommand()); registerCommand(new SeenCommand());
registerCommand(new IPFilterCommand()); registerCommand(new IPFilterCommand());
registerCommand(new StopCommand()); registerCommand(new StopCommand());
registerCommand(new GrepLogCommand());
} }
public boolean disabled = false; public boolean disabled = false;

View file

@ -0,0 +1,168 @@
package me.chayapak1.chomens_bot.plugins;
import me.chayapak1.chomens_bot.Bot;
import me.chayapak1.chomens_bot.command.CommandContext;
import me.chayapak1.chomens_bot.command.CommandException;
import me.chayapak1.chomens_bot.util.ColorUtilities;
import me.chayapak1.chomens_bot.util.FileLoggerUtilities;
import me.chayapak1.chomens_bot.util.StringUtilities;
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
import net.dv8tion.jda.api.utils.FileUpload;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.event.HoverEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.NotDirectoryException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Comparator;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
public class GrepLogPlugin {
private final Bot bot;
private Pattern pattern;
private int count = 0;
public boolean running = false;
public GrepLogPlugin (Bot bot) {
this.bot = bot;
}
public void search (CommandContext context, String input, boolean ignoreCase, boolean regex) throws CommandException {
running = true;
try (final Stream<Path> files = Files.list(FileLoggerUtilities.logDirectory)) {
final Path[] fileList = files.toArray(Path[]::new);
Arrays.sort(fileList, Comparator.comparing(a -> a.getFileName().toString()));
final StringBuilder result = new StringBuilder();
for (Path filePath : fileList) {
if (!running) return;
if (count > 1_000_000) break;
final String fileName = filePath.getFileName().normalize().toString();
final String absolutePath = filePath.toAbsolutePath().normalize().toString();
if (fileName.endsWith(".txt.gz")) {
try (
final GZIPInputStream gzipInputStream = new GZIPInputStream(new FileInputStream(absolutePath));
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gzipInputStream, StandardCharsets.UTF_8))
) {
result.append(process(bufferedReader, input, ignoreCase, regex));
}
} else if (fileName.endsWith(".txt")) {
try (
final FileInputStream fileInputStream = new FileInputStream(absolutePath);
final BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fileInputStream, StandardCharsets.UTF_8))
) {
result.append(process(bufferedReader, input, ignoreCase, regex));
}
}
}
// ? should this be here? (see `process` function below)
pattern = null;
count = 0;
final String stringifiedResult = result.toString();
final long matches = stringifiedResult.lines().count();
if (matches == 0) throw new CommandException(Component.text("No matches has been found"));
final String channelId = bot.discord.servers.get(bot.host + ":" + bot.port);
final TextChannel logChannel = bot.discord.jda.getTextChannelById(channelId);
if (logChannel == null) return;
logChannel
.sendMessage("Greplog result:")
.addFiles(
FileUpload.fromData(
// as of the time writing this, discord has an 8 MB file size limit for bots
StringUtilities.truncateToFitUtf8ByteLength(stringifiedResult, 8 * 1000 * 1000).getBytes(),
String.format("result-%d.txt", System.currentTimeMillis() / 1000)
)
)
.queue(message -> {
final String url = message.getAttachments().get(0).getUrl();
final Component component = Component.translatable("Found %s matches for %s. You can see the results by clicking %s or in the Discord server.")
.color(ColorUtilities.getColorByString(bot.config.colorPalette.defaultColor))
.arguments(
Component.text(matches).color(ColorUtilities.getColorByString(bot.config.colorPalette.number)),
Component.text(input).color(ColorUtilities.getColorByString(bot.config.colorPalette.string)),
Component
.text("here")
.color(NamedTextColor.GREEN)
.hoverEvent(
HoverEvent.showText(
Component
.text("Click! :D")
.color(ColorUtilities.getColorByString(bot.config.colorPalette.secondary))
)
)
.clickEvent(
ClickEvent.openUrl(url)
)
);
context.sendOutput(component);
});
} catch (FileNotFoundException e) {
running = false;
throw new CommandException(Component.text("File not found"));
} catch (NotDirectoryException e) {
running = false;
throw new CommandException(Component.text("Logger directory is not a directory"));
} catch (PatternSyntaxException e) {
running = false;
throw new CommandException(Component.text("Pattern is invalid"));
} catch (IOException e) {
running = false;
throw new CommandException(Component.text("An I/O error has occurred"));
} catch (Exception e) {
e.printStackTrace();
}
running = false;
}
private StringBuilder process (BufferedReader bufferedReader, String input, boolean ignoreCase, boolean regex) throws IOException, PatternSyntaxException {
if (regex && pattern == null) {
if (ignoreCase) pattern = Pattern.compile(input, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS);
else pattern = Pattern.compile(input, Pattern.UNICODE_CHARACTER_CLASS);
}
final StringBuilder result = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
if (
(regex && pattern.matcher(line).find()) || // *greplog -regex text OR *greplog -ignorecase -regex text
(!ignoreCase && !regex && line.contains(input)) || // *greplog text
(ignoreCase && StringUtilities.containsIgnoreCase(line, input)) // *greplog -ignorecase
) {
result.append(line).append("\n");
count++;
}
}
return result;
}
}

View file

@ -0,0 +1,53 @@
package me.chayapak1.chomens_bot.util;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
public class StringUtilities {
// https://stackoverflow.com/a/35148974/18518424
public static String truncateToFitUtf8ByteLength(String s, int maxBytes) {
if (s == null) {
return null;
}
Charset charset = StandardCharsets.UTF_8;
CharsetDecoder decoder = charset.newDecoder();
byte[] sba = s.getBytes(charset);
if (sba.length <= maxBytes) {
return s;
}
// Ensure truncation by having byte buffer = maxBytes
ByteBuffer bb = ByteBuffer.wrap(sba, 0, maxBytes);
CharBuffer cb = CharBuffer.allocate(maxBytes);
// Ignore an incomplete character
decoder.onMalformedInput(CodingErrorAction.IGNORE);
decoder.decode(bb, cb, true);
decoder.flush(cb);
return new String(cb.array(), 0, cb.position());
}
// https://stackoverflow.com/a/25379180/18518424
public static boolean containsIgnoreCase(String src, String what) {
final int length = what.length();
if (length == 0)
return true; // Empty string is contained
final char firstLo = Character.toLowerCase(what.charAt(0));
final char firstUp = Character.toUpperCase(what.charAt(0));
for (int i = src.length() - length; i >= 0; i--) {
// Quick check before calling the more expensive regionMatches() method:
final char ch = src.charAt(i);
if (ch != firstLo && ch != firstUp)
continue;
if (src.regionMatches(true, i, what, 0, length))
return true;
}
return false;
}
}