Working Prototype
This commit is contained in:
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
|||||||
@@ -13,8 +13,16 @@ dependencies {
|
|||||||
testImplementation(platform("org.junit:junit-bom:6.0.0"))
|
testImplementation(platform("org.junit:junit-bom:6.0.0"))
|
||||||
testImplementation("org.junit.jupiter:junit-jupiter")
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
implementation("net.dv8tion:JDA:6.4.1")
|
||||||
|
implementation("com.google.code.gson:gson:2.7")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.test {
|
tasks.test {
|
||||||
useJUnitPlatform()
|
useJUnitPlatform()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tasks.jar {
|
||||||
|
manifest {
|
||||||
|
attributes["Main-Class"] = "me.trouper.antispambot.Init"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package me.trouper;
|
|
||||||
|
|
||||||
//TIP To <b>Run</b> code, press <shortcut actionId="Run"/> or
|
|
||||||
// click the <icon src="AllIcons.Actions.Execute"/> icon in the gutter.
|
|
||||||
public class Main {
|
|
||||||
static void main() {
|
|
||||||
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
|
|
||||||
// to see how IntelliJ IDEA suggests fixing it.
|
|
||||||
IO.println(String.format("Hello and welcome!"));
|
|
||||||
|
|
||||||
for (int i = 1; i <= 5; i++) {
|
|
||||||
//TIP Press <shortcut actionId="Debug"/> to start debugging your code. We have set one <icon src="AllIcons.Debugger.Db_set_breakpoint"/> breakpoint
|
|
||||||
// for you, but you can always add more by pressing <shortcut actionId="ToggleLineBreakpoint"/>.
|
|
||||||
IO.println("i = " + i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
15
src/main/java/me/trouper/antispambot/Context.java
Normal file
15
src/main/java/me/trouper/antispambot/Context.java
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package me.trouper.antispambot;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.bot.AntiSpamBot;
|
||||||
|
import me.trouper.antispambot.data.Config;
|
||||||
|
|
||||||
|
public interface Context {
|
||||||
|
|
||||||
|
public default AntiSpamBot getBot() {
|
||||||
|
return Init.getBot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public default Config getConfig() {
|
||||||
|
return getBot().getConfig();
|
||||||
|
}
|
||||||
|
}
|
||||||
25
src/main/java/me/trouper/antispambot/Init.java
Normal file
25
src/main/java/me/trouper/antispambot/Init.java
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package me.trouper.antispambot;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.bot.AntiSpamBot;
|
||||||
|
|
||||||
|
public class Init {
|
||||||
|
|
||||||
|
private static AntiSpamBot bot;
|
||||||
|
|
||||||
|
static void main() {
|
||||||
|
try {
|
||||||
|
bot = new AntiSpamBot(System.getenv("TOKEN"));
|
||||||
|
} catch (InterruptedException ex) {
|
||||||
|
System.err.println("Failed to start AntiSpamBot: ");
|
||||||
|
ex.printStackTrace();
|
||||||
|
System.exit(-1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bot.startBot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static AntiSpamBot getBot() {
|
||||||
|
return bot;
|
||||||
|
}
|
||||||
|
}
|
||||||
122
src/main/java/me/trouper/antispambot/bot/AntiSpamBot.java
Normal file
122
src/main/java/me/trouper/antispambot/bot/AntiSpamBot.java
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
package me.trouper.antispambot.bot;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.bot.commands.CleanseCommand;
|
||||||
|
import me.trouper.antispambot.bot.events.HoneyPotListener;
|
||||||
|
import me.trouper.antispambot.data.Config;
|
||||||
|
import me.trouper.antispambot.data.JsonSerializable;
|
||||||
|
import net.dv8tion.jda.api.EmbedBuilder;
|
||||||
|
import net.dv8tion.jda.api.JDA;
|
||||||
|
import net.dv8tion.jda.api.JDABuilder;
|
||||||
|
import net.dv8tion.jda.api.entities.Guild;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.Role;
|
||||||
|
import net.dv8tion.jda.api.entities.User;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.Commands;
|
||||||
|
import net.dv8tion.jda.api.interactions.commands.build.SlashCommandData;
|
||||||
|
import net.dv8tion.jda.api.requests.GatewayIntent;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class AntiSpamBot {
|
||||||
|
private final String CONFIG_FILE_NAME = "config.json";
|
||||||
|
private File configFile;
|
||||||
|
private Guild guild;
|
||||||
|
private TextChannel channel;
|
||||||
|
private Role modRole;
|
||||||
|
private Config config;
|
||||||
|
private final JDA jda;
|
||||||
|
|
||||||
|
public AntiSpamBot(String token) throws InterruptedException {
|
||||||
|
this.jda = JDABuilder.create(token, GatewayIntent.getIntents(GatewayIntent.ALL_INTENTS))
|
||||||
|
.addEventListeners(
|
||||||
|
new HoneyPotListener(),
|
||||||
|
new CleanseCommand()
|
||||||
|
)
|
||||||
|
.build()
|
||||||
|
.awaitReady();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void startBot() {
|
||||||
|
jda.updateCommands().addCommands(
|
||||||
|
Commands.slash("cleanse","Ban all users in this channel who sent 4 attachments at once.")
|
||||||
|
).queue();
|
||||||
|
|
||||||
|
configFile = new File(CONFIG_FILE_NAME);
|
||||||
|
config = JsonSerializable.load(configFile,Config.class,new Config());
|
||||||
|
|
||||||
|
guild = jda.getGuildById(config.targetServerID);
|
||||||
|
if (guild == null) throw new RuntimeException("The bot could not find the target server (" + config.targetServerID + ").");
|
||||||
|
|
||||||
|
channel = guild.getTextChannelById(config.targetChanelID);
|
||||||
|
if (channel == null) throw new RuntimeException("The bot could not find the target channel (" + config.targetChanelID + ").");
|
||||||
|
modRole = guild.getRoleById(config.moderatorRoleID);
|
||||||
|
if (modRole == null) throw new RuntimeException("The bot could not find the moderator role (" + config.moderatorRoleID + ").");
|
||||||
|
|
||||||
|
verifyChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyChannel() {
|
||||||
|
channel.getHistory().retrievePast(20).submit().thenAccept((messages -> {
|
||||||
|
channel.deleteMessages(messages).queue();
|
||||||
|
}));
|
||||||
|
channel.sendMessageEmbeds(new EmbedBuilder()
|
||||||
|
.setTitle("Notice")
|
||||||
|
.setDescription("Sending a message in this channel will result in an immediate ban.")
|
||||||
|
.build()).queue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void banNotice(Member member) {
|
||||||
|
User user = member.getUser();
|
||||||
|
user.openPrivateChannel().submit().thenAccept(privateChannel -> {
|
||||||
|
privateChannel.sendMessageEmbeds(new EmbedBuilder()
|
||||||
|
.setTitle("Notice of Hacked Account")
|
||||||
|
.setDescription("""
|
||||||
|
ClickCrystals.xyz has detected your account as being compromised.
|
||||||
|
A 3rd party actor has been using it to spam a Crypto Scam/Token Stealer
|
||||||
|
in all of the servers you are a member of. If you still have access to
|
||||||
|
your account...
|
||||||
|
|
||||||
|
**CHANGE YOUR PASSWORD __IMMEDIATLY__**
|
||||||
|
|
||||||
|
Doing so will refresh your token, kicking the malicious actors out.
|
||||||
|
Do *not* download, run apps, or paste console scripts from any unestablished sources.
|
||||||
|
|
||||||
|
If you believe this notice to be in error, you may rejoin the ClickCrystals
|
||||||
|
discord server after verifing your account's security.
|
||||||
|
|
||||||
|
Thank you for your cooperation.
|
||||||
|
""")
|
||||||
|
.build()).setContent("!!! OPEN DM IMMEDIATELY !!!").submit().thenAccept((message)->{
|
||||||
|
member.ban(3, TimeUnit.DAYS).submit().thenAccept((v)->{
|
||||||
|
guild.unban(user).queue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public Config getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public JDA getJda() {
|
||||||
|
return jda;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getConfigFile() {
|
||||||
|
return configFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Guild getGuild() {
|
||||||
|
return guild;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TextChannel getChannel() {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Role getModRole() {
|
||||||
|
return modRole;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
package me.trouper.antispambot.bot.commands;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.Context;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.entities.Message;
|
||||||
|
import net.dv8tion.jda.api.entities.Role;
|
||||||
|
import net.dv8tion.jda.api.entities.channel.concrete.TextChannel;
|
||||||
|
import net.dv8tion.jda.api.events.interaction.command.SlashCommandInteractionEvent;
|
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
|
public class CleanseCommand extends ListenerAdapter implements Context {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSlashCommandInteraction(SlashCommandInteractionEvent event) {
|
||||||
|
if (!event.isFromGuild() || event.getGuild().getIdLong() != getConfig().targetServerID) return;
|
||||||
|
if (!event.getInteraction().getName().equals("cleanse")) return;
|
||||||
|
|
||||||
|
Member member = event.getMember();
|
||||||
|
if (member == null) return;
|
||||||
|
|
||||||
|
if (!member.getRoles().contains(getBot().getModRole())) {
|
||||||
|
event.reply("You do not have permission to use this command.").setEphemeral(true).queue();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
event.reply("Scanning channel...").submit().thenAccept(reply -> {
|
||||||
|
TextChannel target = event.getChannel().asTextChannel();
|
||||||
|
|
||||||
|
target.getHistory().retrievePast(20).submit().thenAccept(messages -> {
|
||||||
|
reply.editOriginal("Retrieved 20 messages...").queue();
|
||||||
|
|
||||||
|
AtomicInteger count = new AtomicInteger();
|
||||||
|
|
||||||
|
messages.stream()
|
||||||
|
.filter(message -> {
|
||||||
|
List<Message.Attachment> jpgs = message.getAttachments().stream()
|
||||||
|
.filter(a -> "jpg".equals(a.getFileExtension()))
|
||||||
|
.toList();
|
||||||
|
return jpgs.size() == 4;
|
||||||
|
})
|
||||||
|
.forEach(message -> {
|
||||||
|
Member suspect = message.getMember();
|
||||||
|
if (suspect == null) return;
|
||||||
|
getBot().banNotice(suspect);
|
||||||
|
count.incrementAndGet();
|
||||||
|
});
|
||||||
|
|
||||||
|
reply.editOriginal("Done cleansing compromised accounts. Banned: " + count.get()).queue();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package me.trouper.antispambot.bot.events;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.Context;
|
||||||
|
import net.dv8tion.jda.api.EmbedBuilder;
|
||||||
|
import net.dv8tion.jda.api.entities.Member;
|
||||||
|
import net.dv8tion.jda.api.events.message.MessageReceivedEvent;
|
||||||
|
import net.dv8tion.jda.api.hooks.ListenerAdapter;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
public class HoneyPotListener extends ListenerAdapter implements Context {
|
||||||
|
@Override
|
||||||
|
public void onMessageReceived(MessageReceivedEvent event) {
|
||||||
|
if (!event.isFromGuild() || event.getGuild().getIdLong() != getConfig().targetServerID) return;
|
||||||
|
if (event.getChannel().getIdLong() != getConfig().targetChanelID) return;
|
||||||
|
|
||||||
|
Member member = event.getMember();
|
||||||
|
if (member == null) return;
|
||||||
|
if (member.getUser().isBot()) return;
|
||||||
|
|
||||||
|
event.getMessage().delete().queue();
|
||||||
|
getBot().banNotice(member);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/main/java/me/trouper/antispambot/data/Config.java
Normal file
17
src/main/java/me/trouper/antispambot/data/Config.java
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
package me.trouper.antispambot.data;
|
||||||
|
|
||||||
|
import me.trouper.antispambot.Context;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
public class Config implements JsonSerializable, Context {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public File getFile() {
|
||||||
|
return getBot().getConfigFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
public long targetServerID = 1095079504516493404L;
|
||||||
|
public long targetChanelID = 1494468792775086172L;
|
||||||
|
public long moderatorRoleID = 1100292475932917841L;
|
||||||
|
}
|
||||||
166
src/main/java/me/trouper/antispambot/data/JsonSerializable.java
Normal file
166
src/main/java/me/trouper/antispambot/data/JsonSerializable.java
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
package me.trouper.antispambot.data;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.google.gson.JsonElement;
|
||||||
|
import com.google.gson.JsonObject;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is taken from ImproperIssues (ItziSpyder on GitHub) don't touch it unless its broken somehow.
|
||||||
|
*/
|
||||||
|
public interface JsonSerializable<T> {
|
||||||
|
|
||||||
|
Gson gson = new GsonBuilder().setPrettyPrinting().serializeNulls().setLenient().create();
|
||||||
|
File getFile();
|
||||||
|
|
||||||
|
default String serialize(boolean pretty) {
|
||||||
|
Gson gson;
|
||||||
|
if (pretty) {
|
||||||
|
gson = new GsonBuilder().setPrettyPrinting().setLenient().create();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
gson = new Gson();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String json = gson.toJson(this);
|
||||||
|
if (json == null) {
|
||||||
|
throw new IllegalStateException("json parse failed for " + this.getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return "{}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
default T deserialize(String json) {
|
||||||
|
try {
|
||||||
|
JsonSerializable<?> v = gson.fromJson(json, this.getClass());
|
||||||
|
if (v == null) {
|
||||||
|
throw new IllegalStateException("json parse failed");
|
||||||
|
}
|
||||||
|
return (T)v;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default JsonObject getJson() {
|
||||||
|
return gson.toJsonTree(this).getAsJsonObject();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a json element given the specified member path
|
||||||
|
* @param path Path separated by a period . between each member name
|
||||||
|
* @return the JsonElement at the end of the path, otherwise null
|
||||||
|
*/
|
||||||
|
default JsonElement get(String path) {
|
||||||
|
JsonElement root = gson.toJsonTree(this);
|
||||||
|
JsonElement json = root;
|
||||||
|
|
||||||
|
for (String memberName : path.split("\\.")) {
|
||||||
|
JsonElement e = json.getAsJsonObject().get(memberName);
|
||||||
|
if (e != null)
|
||||||
|
json = e;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return json == root ? null : json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a json element given the specified member path
|
||||||
|
* @param path Path separated by a period . between each member name
|
||||||
|
*/
|
||||||
|
default boolean set(String path, Object obj) {
|
||||||
|
JsonElement root = gson.toJsonTree(this);
|
||||||
|
JsonElement json = root;
|
||||||
|
String[] paths = path.split("\\.");
|
||||||
|
|
||||||
|
if (paths.length == 0)
|
||||||
|
return false;
|
||||||
|
if (paths.length == 1) {
|
||||||
|
root.getAsJsonObject().add(path, gson.toJsonTree(obj));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < paths.length - 1; i++) {
|
||||||
|
JsonElement e = json.getAsJsonObject().get(paths[i]);
|
||||||
|
if (e != null)
|
||||||
|
json = e;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json != root) {
|
||||||
|
json.getAsJsonObject().add(paths[paths.length - 1], gson.toJsonTree(obj));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void save() {
|
||||||
|
String json = serialize(true);
|
||||||
|
File f = getFile();
|
||||||
|
|
||||||
|
if (validate(f)) {
|
||||||
|
try {
|
||||||
|
FileWriter fw = new FileWriter(f);
|
||||||
|
BufferedWriter bw = new BufferedWriter(fw);
|
||||||
|
bw.write(json);
|
||||||
|
bw.close();
|
||||||
|
} catch (Exception ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default <O> O getOrDef(O val, O def) {
|
||||||
|
return val != null ? val : def;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends JsonSerializable<?>> T load(File file, Class<T> jsonSerializable, T fallback) {
|
||||||
|
if (validate(file)) {
|
||||||
|
try {
|
||||||
|
FileReader fr = new FileReader(file);
|
||||||
|
BufferedReader br = new BufferedReader(fr);
|
||||||
|
T t = gson.fromJson(br, jsonSerializable);
|
||||||
|
|
||||||
|
if (t == null) {
|
||||||
|
throw new IllegalStateException("json parse failed!");
|
||||||
|
}
|
||||||
|
|
||||||
|
return t;
|
||||||
|
} catch (Throwable ex) {
|
||||||
|
ex.printStackTrace();
|
||||||
|
System.out.println("The above stacktrace is expected. Falling back to default json file.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T extends JsonSerializable<?>> T load(String path, Class<T> jsonSerializable, T fallback) {
|
||||||
|
return load(new File(path), jsonSerializable, fallback);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean validate(File file) {
|
||||||
|
try {
|
||||||
|
if (!file.getParentFile().exists())
|
||||||
|
if (!file.getParentFile().mkdirs())
|
||||||
|
return false;
|
||||||
|
if (!file.exists())
|
||||||
|
if (!file.createNewFile())
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user