Compare commits
2 commits
e56fc19ab3
...
41e57d04f2
Author | SHA1 | Date | |
---|---|---|---|
41e57d04f2 | |||
e8ef3adf3b |
6 changed files with 327 additions and 18 deletions
|
@ -69,6 +69,7 @@ public class Bot {
|
|||
public TPSPlugin tps;
|
||||
public EvalPlugin eval;
|
||||
public TrustedPlugin trusted;
|
||||
public GrepLogPlugin grepLog;
|
||||
public BruhifyPlugin bruhify;
|
||||
public CloopPlugin cloop;
|
||||
public ExploitsPlugin exploits;
|
||||
|
@ -123,6 +124,7 @@ public class Bot {
|
|||
this.tps = new TPSPlugin(this);
|
||||
this.eval = new EvalPlugin(this);
|
||||
this.trusted = new TrustedPlugin(this);
|
||||
this.grepLog = new GrepLogPlugin(this);
|
||||
this.bruhify = new BruhifyPlugin(this);
|
||||
this.cloop = new CloopPlugin(this);
|
||||
this.exploits = new ExploitsPlugin(this);
|
||||
|
|
|
@ -47,7 +47,8 @@ public class FilterCommand extends Command {
|
|||
|
||||
String action = context.getString(false, true);
|
||||
|
||||
// this is a mess
|
||||
// run 2 times. for example `*filter -ignorecase -regex add test` will be both accepted
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (action.equals("-ignorecase")) {
|
||||
ignoreCase = true;
|
||||
action = context.getString(false, true);
|
||||
|
@ -55,13 +56,6 @@ public class FilterCommand extends Command {
|
|||
regex = true;
|
||||
action = context.getString(false, true);
|
||||
}
|
||||
|
||||
if (action.equals("-ignorecase")) {
|
||||
ignoreCase = true;
|
||||
action = context.getString(false, true);
|
||||
} else if (action.equals("-regex")) {
|
||||
regex = true;
|
||||
action = context.getString(false, true);
|
||||
}
|
||||
|
||||
final Gson gson = new Gson();
|
||||
|
@ -128,9 +122,7 @@ public class FilterCommand extends Command {
|
|||
Component.join(JoinConfiguration.newlines(), filtersComponents)
|
||||
);
|
||||
}
|
||||
default -> {
|
||||
throw new CommandException(Component.text("Invalid action"));
|
||||
}
|
||||
default -> throw new CommandException(Component.text("Invalid action"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ public class CommandHandlerPlugin {
|
|||
registerCommand(new SeenCommand());
|
||||
registerCommand(new IPFilterCommand());
|
||||
registerCommand(new StopCommand());
|
||||
registerCommand(new GrepLogCommand());
|
||||
}
|
||||
|
||||
public boolean disabled = false;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue