diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..a0855a2
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+ com.jagrosh
+ Vortex
+ 2.0
+ jar
+
+
+ UTF-8
+ 1.8
+ 1.8
+
+
+
+
+ jcenter
+ jcenter-bintray
+ http://jcenter.bintray.com
+
+
+
+
+
+ net.dv8tion
+ JDA
+ 3.5.1_339
+
+
+ com.jagrosh
+ jda-utilities
+ 2.1
+ pom
+
+
+ com.jagrosh
+ EasySQL
+ 0.2
+
+
+ ch.qos.logback
+ logback-classic
+ 1.2.3
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/jagrosh/vortex/Action.java b/src/main/java/com/jagrosh/vortex/Action.java
new file mode 100644
index 0000000..2736375
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/Action.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public enum Action
+{
+ PARDON( "pardoned", "\uD83C\uDFF3", 14),
+ RAIDMODE( "", "\uD83D\uDD12", 13),
+ STRIKE( "", "\uD83D\uDEA9", 12),
+ UNMUTE( "unmuted", "\uD83D\uDD0A", 11),
+ UNBAN( "unbanned", "\uD83D\uDD27", 10),
+
+ BAN( "banned", "\uD83D\uDD28", 9),
+ TEMPBAN( "tempbanned", "\u23F2", 8),
+ SOFTBAN( "softbanned", "\uD83C\uDF4C", 7),
+ KICK( "kicked", "\uD83D\uDC62", 6),
+ MUTE( "muted", "\uD83D\uDD07", 5),
+ TEMPMUTE( "tempmuted", "\uD83E\uDD10", 4),
+ WARN( "warned", "\uD83D\uDDE3", 3),
+ CLEAN( "cleaned", "\uD83D\uDDD1", 2),
+ DELETE( "deleted", "\uD83D\uDDD1", 1),
+ NONE( "did not act", "\uD83D\uDE36", 0);
+
+ private final String verb;
+ private final String emoji;
+ private final int bit;
+
+ private Action(String verb, String emoji, int bit)
+ {
+ this.verb = verb;
+ this.emoji = emoji;
+ this.bit = bit;
+ }
+
+ public String getVerb()
+ {
+ return verb;
+ }
+
+ public String getEmoji()
+ {
+ return emoji;
+ }
+
+ public int getBit()
+ {
+ return bit;
+ }
+
+ public static Action fromBit(int bit)
+ {
+ for(Action a: values())
+ if(a.bit == bit)
+ return a;
+ return null;
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/Constants.java b/src/main/java/com/jagrosh/vortex/Constants.java
new file mode 100644
index 0000000..d87c91d
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/Constants.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex;
+
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class Constants {
+
+ public final static String PREFIX = "<<";
+ public final static String SUCCESS = "<:vSuccess:390202497827864597>";
+ public final static String WARNING = "<:vWarning:390208158699618306>";
+ public final static String ERROR = "<:vError:390229421228949504>";
+ public final static String LOADING = "";
+ public final static Permission[] PERMISSIONS = {Permission.ADMINISTRATOR, Permission.BAN_MEMBERS, Permission.KICK_MEMBERS, Permission.MANAGE_ROLES,
+ Permission.MANAGE_SERVER, Permission.MESSAGE_ADD_REACTION, Permission.MESSAGE_ATTACH_FILES, Permission.MESSAGE_READ,
+ Permission.MESSAGE_WRITE,Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_HISTORY, Permission.MESSAGE_EXT_EMOJI,
+ Permission.MESSAGE_MANAGE, Permission.VOICE_CONNECT, Permission.VOICE_MOVE_OTHERS, Permission.VOICE_DEAF_OTHERS,
+ Permission.VOICE_MUTE_OTHERS, Permission.NICKNAME_CHANGE, Permission.NICKNAME_MANAGE, Permission.VIEW_AUDIT_LOGS};
+ public final static String SERVER_INVITE = "https://discord.gg/0p9LSGoRLu6Pet0k";
+ public final static String BOT_INVITE = "https://discordapp.com/oauth2/authorize?client_id=240254129333731328&scope=bot&permissions="+Permission.getRaw(PERMISSIONS);
+ //public final static String NEED_MENTION = ERROR+" Please mention at least 1 %s!";
+ //public final static String NEED_X = ERROR+" Please include at least 1 %s";
+ public final static String OWNER_ID = "113156185389092864";
+}
diff --git a/src/main/java/com/jagrosh/vortex/Listener.java b/src/main/java/com/jagrosh/vortex/Listener.java
new file mode 100644
index 0000000..f508b34
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/Listener.java
@@ -0,0 +1,195 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import net.dv8tion.jda.core.JDA.ShardInfo;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.events.Event;
+import net.dv8tion.jda.core.events.ReadyEvent;
+import net.dv8tion.jda.core.events.guild.GuildBanEvent;
+import net.dv8tion.jda.core.events.guild.GuildUnbanEvent;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberJoinEvent;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberLeaveEvent;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberRoleAddEvent;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberRoleRemoveEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceJoinEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent;
+import net.dv8tion.jda.core.events.message.MessageBulkDeleteEvent;
+import net.dv8tion.jda.core.events.message.guild.GuildMessageDeleteEvent;
+import net.dv8tion.jda.core.events.message.guild.GuildMessageReceivedEvent;
+import net.dv8tion.jda.core.events.message.guild.GuildMessageUpdateEvent;
+import net.dv8tion.jda.core.events.user.UserNameUpdateEvent;
+import net.dv8tion.jda.core.hooks.EventListener;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class Listener implements EventListener
+{
+ private final static Logger LOG = LoggerFactory.getLogger("Listener");
+ private final Vortex vortex;
+
+ public Listener(Vortex vortex)
+ {
+ this.vortex = vortex;
+ }
+
+ @Override
+ public void onEvent(Event event)
+ {
+ if (event instanceof GuildMessageReceivedEvent)
+ {
+ Message m = ((GuildMessageReceivedEvent)event).getMessage();
+
+ if(!m.getAuthor().isBot()) // ignore bot messages
+ {
+ // Store the message
+ vortex.getMessageCache().putMessage(m);
+
+ // Run automod on the message
+ vortex.getAutoMod().performAutomod(m);
+ }
+ }
+ else if (event instanceof GuildMessageUpdateEvent)
+ {
+ Message m = ((GuildMessageUpdateEvent)event).getMessage();
+
+ if(!m.getAuthor().isBot()) // ignore bot edits
+ {
+ // Run automod on the message
+ vortex.getAutoMod().performAutomod(m);
+
+ // Store and log the edit
+ Message old = vortex.getMessageCache().putMessage(m);
+ vortex.getBasicLogger().logMessageEdit(m, old);
+ }
+ }
+ else if (event instanceof GuildMessageDeleteEvent)
+ {
+ GuildMessageDeleteEvent gevent = (GuildMessageDeleteEvent) event;
+
+ // Log the deletion
+ Message old = vortex.getMessageCache().pullMessage(gevent.getGuild(), gevent.getMessageIdLong());
+ vortex.getBasicLogger().logMessageDelete(old);
+ }
+ else if (event instanceof MessageBulkDeleteEvent)
+ {
+ MessageBulkDeleteEvent gevent = (MessageBulkDeleteEvent) event;
+
+ // Get the messages we had cached
+ List logged = gevent.getMessageIds().stream()
+ .map(id -> vortex.getMessageCache().pullMessage(gevent.getGuild(), Long.parseLong(id)))
+ .filter(m -> m!=null)
+ .collect(Collectors.toList());
+
+ // Log the deletion
+ vortex.getBasicLogger().logMessageBulkDelete(logged, gevent.getMessageIds().size(), gevent.getChannel());
+ }
+ else if (event instanceof GuildMemberJoinEvent)
+ {
+ GuildMemberJoinEvent gevent = (GuildMemberJoinEvent) event;
+
+ // Log the join
+ vortex.getBasicLogger().logGuildJoin(gevent);
+
+ // Perform automod on the newly-joined member
+ vortex.getAutoMod().memberJoin(gevent);
+ }
+ else if (event instanceof GuildMemberLeaveEvent)
+ {
+ GuildMemberLeaveEvent gmle = (GuildMemberLeaveEvent)event;
+
+ // Log the member leaving
+ vortex.getBasicLogger().logGuildLeave(gmle);
+
+ // Signal the modlogger because someone might have been kicked
+ vortex.getModLogger().setNeedUpdate(gmle.getGuild());
+ }
+ else if (event instanceof GuildBanEvent)
+ {
+ // Signal the modlogger because someone was banned
+ vortex.getModLogger().setNeedUpdate(((GuildBanEvent) event).getGuild());
+ }
+ else if (event instanceof GuildUnbanEvent)
+ {
+ // Signal the modlogger because someone was unbanned
+ vortex.getModLogger().setNeedUpdate(((GuildUnbanEvent) event).getGuild());
+ }
+ else if (event instanceof GuildMemberRoleAddEvent)
+ {
+ GuildMemberRoleAddEvent gmrae = (GuildMemberRoleAddEvent) event;
+
+ // Signal the modlogger if someone was muted
+ if(gmrae.getRoles().stream().anyMatch(r -> r.getName().equalsIgnoreCase("muted")))
+ vortex.getModLogger().setNeedUpdate(gmrae.getGuild());
+ }
+ else if (event instanceof GuildMemberRoleRemoveEvent)
+ {
+ GuildMemberRoleRemoveEvent gmrre = (GuildMemberRoleRemoveEvent) event;
+
+ // Signal the modlogger if someone was unmuted
+ if(gmrre.getRoles().stream().anyMatch(r -> r.getName().equalsIgnoreCase("muted")))
+ vortex.getModLogger().setNeedUpdate(gmrre.getGuild());
+ }
+ else if (event instanceof UserNameUpdateEvent)
+ {
+ // Log the name change
+ vortex.getBasicLogger().logNameChange((UserNameUpdateEvent)event);
+ }
+ else if (event instanceof GuildVoiceJoinEvent)
+ {
+ GuildVoiceJoinEvent gevent = (GuildVoiceJoinEvent)event;
+
+ // Log the voice join
+ if(!gevent.getMember().getUser().isBot()) // ignore bots
+ vortex.getBasicLogger().logVoiceJoin(gevent);
+ }
+ else if (event instanceof GuildVoiceMoveEvent)
+ {
+ GuildVoiceMoveEvent gevent = (GuildVoiceMoveEvent)event;
+
+ // Log the voice move
+ if(!gevent.getMember().getUser().isBot()) // ignore bots
+ vortex.getBasicLogger().logVoiceMove(gevent);
+ }
+ else if (event instanceof GuildVoiceLeaveEvent)
+ {
+ GuildVoiceLeaveEvent gevent = (GuildVoiceLeaveEvent)event;
+
+ // Log the voice leave
+ if(!gevent.getMember().getUser().isBot()) // ignore bots
+ vortex.getBasicLogger().logVoiceLeave(gevent);
+ }
+ else if (event instanceof ReadyEvent)
+ {
+ // Log the shard that has finished loading
+ ShardInfo si = event.getJDA().getShardInfo();
+ String shardinfo = si==null ? "1/1" : (si.getShardId()+1)+"/"+si.getShardTotal();
+ LOG.info("Shard "+shardinfo+" is ready.");
+ vortex.getLogWebhook().send("\uD83C\uDF00 Shard `"+shardinfo+"` has connected. Guilds: `"
+ +event.getJDA().getGuildCache().size()+"` Users: `"+event.getJDA().getUserCache().size()+"`");
+ vortex.getThreadpool().scheduleWithFixedDelay(() -> vortex.getDatabase().tempbans.checkUnbans(event.getJDA()), 0, 2, TimeUnit.MINUTES);
+ vortex.getThreadpool().scheduleWithFixedDelay(() -> vortex.getDatabase().tempmutes.checkUnmutes(event.getJDA()), 0, 45, TimeUnit.SECONDS);
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/Vortex.java b/src/main/java/com/jagrosh/vortex/Vortex.java
new file mode 100644
index 0000000..8b8cc50
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/Vortex.java
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.Command.Category;
+import com.jagrosh.vortex.commands.automod.AntimentionCmd;
+import com.jagrosh.vortex.commands.automod.AntiduplicateCmd;
+import com.jagrosh.vortex.commands.automod.AutoraidmodeCmd;
+import com.jagrosh.vortex.commands.settings.SettingsCmd;
+import com.jagrosh.vortex.commands.automod.AntiinviteCmd;
+import com.jagrosh.vortex.commands.automod.UnignoreCmd;
+import com.jagrosh.vortex.commands.automod.IgnoreCmd;
+import com.jagrosh.vortex.commands.general.*;
+import com.jagrosh.vortex.commands.moderation.*;
+import com.jagrosh.vortex.commands.owner.*;
+import com.jagrosh.vortex.commands.settings.*;
+import java.awt.Color;
+import java.nio.file.*;
+import java.util.List;
+import java.util.concurrent.Executors;
+import com.jagrosh.jdautilities.command.CommandClient;
+import com.jagrosh.jdautilities.command.CommandClientBuilder;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.waiter.EventWaiter;
+import com.jagrosh.jdautilities.examples.command.*;
+import com.jagrosh.vortex.automod.AutoMod;
+import com.jagrosh.vortex.automod.StrikeHandler;
+import java.util.concurrent.ScheduledExecutorService;
+import net.dv8tion.jda.core.OnlineStatus;
+import net.dv8tion.jda.core.entities.Game;
+import com.jagrosh.vortex.commands.*;
+import com.jagrosh.vortex.database.Database;
+import com.jagrosh.vortex.logging.BasicLogger;
+import com.jagrosh.vortex.logging.MessageCache;
+import com.jagrosh.vortex.logging.ModLogger;
+import com.jagrosh.vortex.logging.TextUploader;
+import com.jagrosh.vortex.utils.FormatUtil;
+import java.util.function.Consumer;
+import net.dv8tion.jda.bot.sharding.DefaultShardManagerBuilder;
+import net.dv8tion.jda.bot.sharding.ShardManager;
+import net.dv8tion.jda.core.entities.ChannelType;
+import net.dv8tion.jda.core.entities.User;
+import net.dv8tion.jda.core.exceptions.PermissionException;
+import net.dv8tion.jda.webhook.WebhookClient;
+import net.dv8tion.jda.webhook.WebhookClientBuilder;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class Vortex
+{
+ private final EventWaiter waiter;
+ private final ScheduledExecutorService threadpool;
+ private final Database database;
+ private final TextUploader uploader;
+ private final ShardManager shards;
+ private final ModLogger modlog;
+ private final BasicLogger basiclog;
+ private final MessageCache messages;
+ private final WebhookClient logwebhook;
+ private final AutoMod automod;
+ private final StrikeHandler strikehandler;
+
+ public Vortex() throws Exception
+ {
+ /**
+ * Tokens:
+ * 0 - bot token
+ * 1 - bots.discord.pw token
+ * 2 - other token 1 (unused)
+ * 3 - other token 2 (unused)
+ * 4 - database location
+ * 5 - database username
+ * 6 - database password
+ * 7 - log webhook url
+ * 8 - category id
+ */
+ List tokens = Files.readAllLines(Paths.get("config.txt"));
+ waiter = new EventWaiter();
+ threadpool = Executors.newScheduledThreadPool(30);
+ database = new Database(tokens.get(4), tokens.get(5), tokens.get(6));
+ uploader = new TextUploader(this, Long.parseLong(tokens.get(8)));
+ modlog = new ModLogger(this);
+ basiclog = new BasicLogger(this);
+ messages = new MessageCache();
+ logwebhook = new WebhookClientBuilder(tokens.get(7)).build();
+ automod = new AutoMod(this);
+ strikehandler = new StrikeHandler(this);
+
+ CommandClient client = new CommandClientBuilder()
+ .setPrefix(Constants.PREFIX)
+ .setGame(Game.watching("Type "+Constants.PREFIX+"help"))
+ .setOwnerId(Constants.OWNER_ID)
+ .setServerInvite(Constants.SERVER_INVITE)
+ .setEmojis(Constants.SUCCESS, Constants.WARNING, Constants.ERROR)
+ .setLinkedCacheSize(0)
+ .setGuildSettingsManager(database.settings)
+ .addCommands(
+ // General
+ new AboutCommand(Color.CYAN, "and I'm here to keep your Discord server safe and make moderating easy!",
+ new String[]{"Moderation commands","Configurable automoderation","Very easy setup"},Constants.PERMISSIONS),
+ new InviteCmd(),
+ new PingCommand(),
+ new ServerinfoCmd(),
+ new UserinfoCmd(),
+
+ // Moderation
+ new KickCmd(this),
+ new BanCmd(this),
+ new SoftbanCmd(this),
+ new CleanCmd(this),
+ new VoicemoveCmd(this),
+ new MuteCmd(this),
+ new UnmuteCmd(this),
+ new RaidCmd(this),
+ new StrikeCmd(this),
+ new PardonCmd(this),
+ new ReasonCmd(this),
+
+ // Settings
+ new SetupCmd(this),
+ new SetstrikesCmd(this),
+ new MessagelogCmd(this),
+ new ModlogCmd(this),
+ new ServerlogCmd(this),
+ new ModroleCmd(this),
+ new SettingsCmd(this),
+
+ // Automoderation
+ new AntiinviteCmd(this),
+ new AntimentionCmd(this),
+ new AntiduplicateCmd(this),
+ new AutoraidmodeCmd(this),
+ new IgnoreCmd(this),
+ new UnignoreCmd(this),
+
+ // Owner
+ new EvalCmd(this),
+ new DebugCmd(this)
+ )
+ .setHelpConsumer(event -> event.replyInDm(FormatUtil.formatHelp(event, this), m ->
+ {
+ if(event.isFromType(ChannelType.TEXT))
+ try
+ {
+ event.getMessage().addReaction(Constants.SUCCESS.replaceAll("", "$1:$2")).queue();
+ } catch(PermissionException ex) {}
+ }, t -> event.replyWarning("Help cannot be sent because you are blocking Direct Messages.")))
+ //.setDiscordBotsKey(tokens.get(1))
+ //.setCarbonitexKey(tokens.get(2))
+ //.setDiscordBotListKey(tokens.get(3))
+ .build();
+ shards = new DefaultShardManagerBuilder()
+ .setShardsTotal(2)
+ .setToken(tokens.get(0))
+ .addEventListeners(new Listener(this), client, waiter)
+ .setStatus(OnlineStatus.DO_NOT_DISTURB)
+ .setGame(Game.playing("loading..."))
+ .setBulkDeleteSplittingEnabled(false)
+ .build();
+
+ modlog.start();
+ }
+
+ public EventWaiter getEventWaiter()
+ {
+ return waiter;
+ }
+
+ public Database getDatabase()
+ {
+ return database;
+ }
+
+ public ScheduledExecutorService getThreadpool()
+ {
+ return threadpool;
+ }
+
+ public TextUploader getTextUploader()
+ {
+ return uploader;
+ }
+
+ public ShardManager getShardManager()
+ {
+ return shards;
+ }
+
+ public ModLogger getModLogger()
+ {
+ return modlog;
+ }
+
+ public BasicLogger getBasicLogger()
+ {
+ return basiclog;
+ }
+
+ public MessageCache getMessageCache()
+ {
+ return messages;
+ }
+
+ public WebhookClient getLogWebhook()
+ {
+ return logwebhook;
+ }
+
+ public AutoMod getAutoMod()
+ {
+ return automod;
+ }
+
+ public StrikeHandler getStrikeHandler()
+ {
+ return strikehandler;
+ }
+
+ /**
+ * @param args the command line arguments
+ * @throws java.lang.Exception
+ */
+ public static void main(String[] args) throws Exception
+ {
+ new Vortex();
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/automod/AutoMod.java b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java
new file mode 100644
index 0000000..317ac99
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java
@@ -0,0 +1,349 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.automod;
+
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.AutomodManager;
+import com.jagrosh.vortex.database.managers.AutomodManager.AutomodSettings;
+import com.jagrosh.vortex.utils.FixedCache;
+import com.jagrosh.vortex.utils.OtherUtil;
+import java.time.OffsetDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Guild.VerificationLevel;
+import net.dv8tion.jda.core.entities.Invite;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberJoinEvent;
+import net.dv8tion.jda.core.exceptions.PermissionException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class AutoMod
+{
+ private final static Pattern INVITES = Pattern.compile("discord\\s?(?:\\.\\s?gg|app\\s?.\\s?com\\s?\\/\\s?invite)\\s?\\/\\s?([A-Z0-9-]{2,18})",Pattern.CASE_INSENSITIVE);
+ private final static Pattern REF = Pattern.compile("");
+ private final static String CONDENSER = "(.+?)\\s*(\\1\\s*)+";
+ private final static Logger LOG = LoggerFactory.getLogger("AutoMod");
+
+ private final Vortex vortex;
+
+ private final FixedCache spams = new FixedCache<>(3000);
+ private final HashMap latestGuildJoin = new HashMap<>();
+
+ public AutoMod(Vortex vortex)
+ {
+ this.vortex = vortex;
+ }
+
+ public void enableRaidMode(Guild guild, Member moderator, OffsetDateTime now, String reason)
+ {
+ vortex.getDatabase().settings.enableRaidMode(guild);
+ if(guild.getVerificationLevel().getKey()120)
+ {
+ disableRaidMode(event.getGuild(), event.getGuild().getSelfMember(), now, "No recent join attempts");
+ }
+ // otherwise, boot 'em
+ else if(event.getGuild().getSelfMember().hasPermission(Permission.KICK_MEMBERS))
+ {
+ kicking = true;
+ }
+ }
+
+ // now, if we're not in raid mode, and auto mode is enabled
+ else if(ams.useAutoRaidMode())
+ {
+ // find the time that we should be looking after, and count the number of people that joined after that
+ OffsetDateTime min = event.getMember().getJoinDate().minusSeconds(ams.raidmodeTime);
+ long recent = event.getGuild().getMemberCache().stream().filter(m -> !m.getUser().isBot() && m.getJoinDate().isAfter(min)).count();
+ if(recent>=ams.raidmodeNumber)
+ {
+ enableRaidMode(event.getGuild(), event.getGuild().getSelfMember(), now, "Maximum join rate exceeded ("+ams.raidmodeNumber+"/"+ams.raidmodeTime+"s)");
+ kicking = true;
+ }
+ }
+
+ if(kicking)
+ {
+ OtherUtil.safeDM(event.getUser(),
+ "Sorry, **"+event.getGuild().getName()+"** is currently under lockdown. Please try joining again later. Sorry for the inconvenience.",
+ () -> event.getGuild().getController().kick(event.getMember(), "Anti-Raid Mode").queue());
+ }
+ else
+ {
+ if(vortex.getDatabase().tempmutes.isMuted(event.getMember()))
+ {
+ try
+ {
+ event.getGuild().getController().addSingleRoleToMember(event.getMember(), OtherUtil.getMutedRole(event.getGuild())).reason("Restoring Muted Role").queue();
+ } catch(Exception ex){}
+ }
+ }
+
+ latestGuildJoin.put(event.getGuild().getIdLong(), now);
+ }
+
+
+ private boolean shouldPerformAutomod(Member member, TextChannel channel)
+ {
+ // ignore users not in the guild
+ if(member==null || member.getGuild()==null)
+ return false;
+
+ // ignore bots
+ if(member.getUser().isBot())
+ return false;
+
+ // ignore users vortex cant interact with
+ if(!member.getGuild().getSelfMember().canInteract(member))
+ return false;
+
+ // ignore users that can kick
+ if(member.hasPermission(Permission.KICK_MEMBERS))
+ return false;
+
+ // ignore users that can ban
+ if(member.hasPermission(Permission.BAN_MEMBERS))
+ return false;
+
+ // ignore users that can manage server
+ if(member.hasPermission(Permission.MANAGE_SERVER))
+ return false;
+
+ // if a channel is specified, ignore users that can manage messages in that channel
+ if(channel!=null && member.hasPermission(channel, Permission.MESSAGE_MANAGE))
+ return false;
+
+ if(vortex.getDatabase().ignores.isIgnored(channel))
+ return false;
+
+ if(vortex.getDatabase().ignores.isIgnored(member))
+ return false;
+
+ return true;
+ }
+
+ public void performAutomod(Message message)
+ {
+ //simple automod
+
+ //ignore users with Manage Messages, Kick Members, Ban Members, Manage Server, or anyone the bot can't interact with
+ if(!shouldPerformAutomod(message.getMember(), message.getTextChannel()))
+ return;
+
+ //get the settings
+ AutomodSettings settings = vortex.getDatabase().automod.getSettings(message.getGuild());
+ if(settings==null)
+ return;
+
+ /*
+ check for automod actions
+ * AntiDuplicate - prevent repeated messages
+ * AntiMention - prevent mass-mention spammers
+ * AntiInvite - prevent invite links to other servers
+ */
+
+ boolean shouldDelete = false;
+
+ // anti-duplicate
+ if(settings.useAntiDuplicate() && (message.getTextChannel().getTopic()==null || !message.getTextChannel().getTopic().toLowerCase().contains("{spam}")))
+ {
+ String key = message.getAuthor().getId()+"|"+message.getGuild().getId();
+ String content = condensedContent(message);
+ DupeStatus status = spams.get(key);
+ if(status==null)
+ {
+ spams.put(key, new DupeStatus(content, latestTime(message)));
+ }
+ else
+ {
+ OffsetDateTime now = latestTime(message);
+ int offenses = status.update(content, now);
+
+ if(offenses==settings.dupeDeleteThresh)
+ purgeMessages(message.getGuild(), m -> m.getAuthor().getIdLong()==message.getAuthor().getIdLong() && m.getCreationTime().plusMinutes(2).isAfter(now));
+ else if(offenses>settings.dupeDeleteThresh)
+ shouldDelete = true;
+
+ if(offenses >= settings.dupeStrikeThresh)
+ vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(),
+ latestTime(message), message.getAuthor(), settings.dupeStrikes, "Duplicate messages");
+ }
+ }
+
+ // anti-mention (users)
+ if(settings.maxMentions>=AutomodManager.MENTION_MINIMUM)
+ {
+ long mentions = message.getMentionedUsers().stream().filter(u -> !u.isBot() && !u.equals(message.getAuthor())).count();
+ if(mentions > settings.maxMentions)
+ {
+ vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(), latestTime(message),
+ message.getAuthor(), (int)(mentions-settings.maxMentions), "Mentioning "+mentions+" users");
+ shouldDelete = true;
+ }
+ }
+
+ // anti-mention (roles)
+ if(settings.maxRoleMentions >= AutomodManager.ROLE_MENTION_MINIMUM)
+ {
+ long mentions = message.getMentionedRoles().size();
+ if(mentions > settings.maxRoleMentions)
+ {
+ vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(), latestTime(message),
+ message.getAuthor(), (int)(mentions-settings.maxRoleMentions), "Mentioning "+mentions+" roles");
+ shouldDelete = true;
+ }
+ }
+
+ // anti-invite
+ if(settings.inviteStrikes > 0 && (message.getTextChannel().getTopic()==null || !message.getTextChannel().getTopic().toLowerCase().contains("{invites}")))
+ {
+ List invites = new ArrayList<>();
+ Matcher m = INVITES.matcher(message.getContentRaw());
+ while(m.find())
+ invites.add(m.group(1));
+ LOG.trace("Found "+invites.size()+" invites.");
+ try{
+ for(String inviteCode : invites)
+ {
+ Invite invite = null;
+ try {
+ invite = Invite.resolve(message.getJDA(), inviteCode).complete();
+ } catch(Exception e) {}
+ if(invite==null || !invite.getGuild().getId().equals(message.getGuild().getId()))
+ {
+ vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(), latestTime(message),
+ message.getAuthor(), settings.inviteStrikes, "Advertising");
+ shouldDelete = true;
+ break;
+ }
+ }
+ }catch(PermissionException ex){}
+ }
+
+ if(shouldDelete)
+ {
+ try
+ {
+ message.delete().reason("Automod").queue(v->{}, f->{});
+ }catch(PermissionException e){}
+ }
+ }
+
+ private void purgeMessages(Guild guild, Predicate predicate)
+ {
+ vortex.getMessageCache().getMessages(guild, predicate).forEach(m ->
+ {
+ try
+ {
+ m.delete().queue();
+ }
+ catch(PermissionException ex) {}
+ });
+ }
+
+ private static OffsetDateTime latestTime(Message m)
+ {
+ return m.isEdited() ? m.getEditedTime() : m.getCreationTime();
+ }
+
+ private static String condensedContent(Message m)
+ {
+ StringBuilder sb = new StringBuilder(m.getContentRaw());
+ m.getAttachments().forEach(at -> sb.append("\n").append(at.getFileName()));
+ return sb.toString().trim().replaceAll(CONDENSER, "$1");
+ }
+
+ private class DupeStatus
+ {
+ private String content;
+ private OffsetDateTime time;
+ private int count;
+
+ private DupeStatus(String content, OffsetDateTime time)
+ {
+ this.content = content;
+ this.time = time;
+ count = 0;
+ }
+
+ private int update(String nextcontent, OffsetDateTime nexttime)
+ {
+ if(nextcontent.equals(content) && this.time.plusSeconds(30).isAfter(nexttime))
+ {
+ count++;
+ this.time = nexttime;
+ return count;
+ }
+ else
+ {
+ this.content = nextcontent;
+ this.time = nexttime;
+ count = 0;
+ return count;
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java b/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java
new file mode 100644
index 0000000..005f7c8
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.automod;
+
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.PunishmentManager.Punishment;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import com.jagrosh.vortex.utils.OtherUtil;
+import java.time.Instant;
+import java.time.OffsetDateTime;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.User;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class StrikeHandler
+{
+ private final static String STRIKE_FORMAT = Constants.WARNING+" You have received `%d` strikes in **%s** for: `%s`";
+ private final static String PARDON_FORMAT = Constants.SUCCESS+" You have been pardoned `%d` strikes in **%s** for: `%s`";
+ private final static String PUNISH_FORMAT = "\n%s You have been **%s** from **%s**";
+ private final static String PUNISH_FORMAT_TIME = "\n%s You have been **%s** for %s from **%s**";
+
+ private final Vortex vortex;
+
+ public StrikeHandler(Vortex vortex)
+ {
+ this.vortex = vortex;
+ }
+
+ public void pardonStrikes(Member moderator, OffsetDateTime nowo, long targetId, int number, String reason)
+ {
+ int[] counts = vortex.getDatabase().strikes.removeStrikes(moderator.getGuild(), targetId, number);
+ User user = vortex.getShardManager().getUserById(targetId);
+ if(user==null)
+ {
+ moderator.getJDA().retrieveUserById(targetId).queue(u -> vortex.getModLogger().postPardonCase(moderator, nowo, number, counts[0], counts[1], u, reason));
+ }
+ else
+ {
+ String dmmsg = String.format(PARDON_FORMAT, number, moderator.getGuild().getName(), reason);
+ vortex.getModLogger().postPardonCase(moderator, nowo, number, counts[0], counts[1], user, reason);
+ OtherUtil.safeDM(user, dmmsg, ()->{});
+ }
+ }
+
+ public void applyStrikes(Member moderator, OffsetDateTime nowo, User target, int number, String reason)
+ {
+ applyStrikes(moderator, nowo, target.getIdLong(), number, reason);
+ }
+
+ public void applyStrikes(Member moderator, OffsetDateTime nowo, long targetId, int number, String reason)
+ {
+ //reason = reason.length()>400 ? reason.substring(0, 400) : reason;
+ Instant now = nowo.toInstant();
+ int[] counts = vortex.getDatabase().strikes.addStrikes(moderator.getGuild(), targetId, number);
+ List punishments = vortex.getDatabase().actions.getPunishments(moderator.getGuild(), counts[0], counts[1]);
+ User user = vortex.getShardManager().getUserById(targetId);
+ String dmmsg = String.format(STRIKE_FORMAT, number, moderator.getGuild().getName(), reason);
+ if(punishments.isEmpty())
+ {
+ if(user==null)
+ {
+ moderator.getJDA().retrieveUserById(targetId).queue(u -> vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], u, reason));
+ }
+ else
+ {
+ vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], user, reason);
+ OtherUtil.safeDM(user, dmmsg, ()->{});
+ }
+ }
+ else
+ {
+ String notimeaudit = LogUtil.auditStrikeReasonFormat(moderator, 0, counts[1], reason);
+ if(punishments.stream().anyMatch(p -> p.action==Action.BAN))
+ {
+ OtherUtil.safeDM(user, dmmsg + punish(Action.BAN, moderator.getGuild()),
+ () -> moderator.getGuild().getController().ban(Long.toString(targetId), 7, notimeaudit).queue());
+ vortex.getDatabase().tempbans.clearBan(moderator.getGuild(), targetId);
+ return;
+ }
+ int muteDuration = 0;
+ int banDuration = 0;
+ for(Punishment p: punishments)
+ {
+ if(p.action==Action.MUTE)
+ muteDuration = Integer.MAX_VALUE;
+ else if(p.action==Action.TEMPMUTE && p.time>muteDuration)
+ muteDuration = p.time;
+ else if(p.action==Action.TEMPBAN && p.time>banDuration)
+ banDuration = p.time;
+ }
+ if(banDuration>0)
+ {
+ int finalBanDuration = banDuration;
+ OtherUtil.safeDM(user, dmmsg + punishTime(Action.TEMPBAN, moderator.getGuild(), banDuration),
+ () -> moderator.getGuild().getController().ban(Long.toString(targetId), 7, LogUtil.auditStrikeReasonFormat(moderator, finalBanDuration, counts[1], reason)).queue());
+ vortex.getDatabase().tempbans.setBan(moderator.getGuild(), targetId, now.plus(banDuration, ChronoUnit.MINUTES));
+ if(muteDuration>0)
+ vortex.getDatabase().tempmutes.setMute(moderator.getGuild(), targetId, muteTime(now, muteDuration));
+ return;
+ }
+ if(punishments.stream().anyMatch(p -> p.action==Action.SOFTBAN))
+ {
+ OtherUtil.safeDM(user, dmmsg + punish(Action.SOFTBAN, moderator.getGuild()),
+ () -> moderator.getGuild().getController().ban(Long.toString(targetId), 7, notimeaudit).queue(
+ s -> moderator.getGuild().getController().unban(Long.toString(targetId)).reason(notimeaudit).queueAfter(5, TimeUnit.SECONDS)));
+ if(muteDuration>0)
+ vortex.getDatabase().tempmutes.setMute(moderator.getGuild(), targetId, muteTime(now, muteDuration));
+ return;
+ }
+ if(punishments.stream().anyMatch(p -> p.action==Action.KICK))
+ {
+ if(user!=null && moderator.getGuild().isMember(user))
+ {
+ OtherUtil.safeDM(user, dmmsg + punish(Action.KICK, moderator.getGuild()),
+ () -> moderator.getGuild().getController().kick(Long.toString(targetId), notimeaudit).queue());
+ }
+ else
+ {
+ vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], user, reason);
+ }
+ if(muteDuration>0)
+ vortex.getDatabase().tempmutes.setMute(moderator.getGuild(), targetId, muteTime(now, muteDuration));
+ return;
+ }
+ if(muteDuration>0)
+ {
+ vortex.getDatabase().tempmutes.setMute(moderator.getGuild(), targetId, muteTime(now, muteDuration));
+ Role muted = moderator.getGuild().getRoles().stream().filter(r -> r.getName().equalsIgnoreCase("Muted")).findFirst().orElse(null);
+ Member mem = moderator.getGuild().getMemberById(targetId);
+ if(muted==null || mem==null)
+ {
+ vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], user, reason);
+ OtherUtil.safeDM(user, dmmsg, ()->{});
+ return;
+ }
+ if(mem.getRoles().contains(muted))
+ {
+ vortex.getModLogger().postPseudoCase(moderator, nowo,
+ muteDuration==Integer.MAX_VALUE ? Action.MUTE : Action.TEMPMUTE, user,
+ muteDuration==Integer.MAX_VALUE ? 0 : muteDuration, "["+counts[1]+" strikes] "+reason);
+ }
+ else
+ {
+ moderator.getGuild().getController().addSingleRoleToMember(mem, muted)
+ .reason(muteDuration==Integer.MAX_VALUE ? notimeaudit : LogUtil.auditStrikeReasonFormat(moderator, muteDuration, counts[1], reason))
+ .queue();
+ }
+ OtherUtil.safeDM(user, dmmsg + (muteDuration==Integer.MAX_VALUE ? punish(Action.MUTE, moderator.getGuild())
+ : punishTime(Action.TEMPMUTE, moderator.getGuild(), muteDuration)), ()->{});
+ }
+ }
+ }
+
+ private static String punish(Action action, Guild guild)
+ {
+ return String.format(PUNISH_FORMAT, action.getEmoji(), action.getVerb(), guild.getName());
+ }
+
+ private static String punishTime(Action action, Guild guild, int minutes)
+ {
+ return String.format(PUNISH_FORMAT_TIME, action.getEmoji(), action.getVerb(), FormatUtil.secondsToTime(minutes*60), guild.getName());
+ }
+
+ private static Instant muteTime(Instant now, int minutes)
+ {
+ return minutes==Integer.MAX_VALUE ? Instant.MAX : now.plus(minutes, ChronoUnit.MINUTES);
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/LogCommand.java b/src/main/java/com/jagrosh/vortex/commands/LogCommand.java
new file mode 100644
index 0000000..a229f7e
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/LogCommand.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.FormatUtil;
+import java.util.List;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public abstract class LogCommand extends Command
+{
+ public static Permission[] REQUIRED_PERMS = {Permission.MESSAGE_READ, Permission.MESSAGE_WRITE, Permission.MESSAGE_EMBED_LINKS, Permission.MESSAGE_HISTORY};
+ public static String REQUIRED_ERROR = "I am missing the necessary permissions (Read Messages, Send Messages, Read Message History, and Embed Links) in %s!";
+ protected final Vortex vortex;
+
+ public LogCommand(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ this.guildOnly = true;
+ this.arguments = "<#channel or OFF>";
+ this.category = new Category("Settings");
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty())
+ {
+ showCurrentChannel(event);
+ return;
+ }
+ if(event.getArgs().equalsIgnoreCase("off") || event.getArgs().equalsIgnoreCase("none"))
+ {
+ setLogChannel(event, null);
+ return;
+ }
+ List list = FinderUtil.findTextChannels(event.getArgs(), event.getGuild());
+ if(list.isEmpty())
+ {
+ event.replyError("I couldn't find any text channel called `"+event.getArgs()+"`.");
+ return;
+ }
+ if(list.size()>1)
+ {
+ event.reply(FormatUtil.listOfText(list, event.getArgs()));
+ return;
+ }
+
+ TextChannel tc = list.get(0);
+
+ if(!event.getSelfMember().hasPermission(tc, REQUIRED_PERMS))
+ {
+ event.replyError(String.format(REQUIRED_ERROR, tc.getAsMention()));
+ return;
+ }
+
+ setLogChannel(event, tc);
+ }
+
+ protected abstract void showCurrentChannel(CommandEvent event);
+
+ protected abstract void setLogChannel(CommandEvent event, TextChannel tc);
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/ModCommand.java b/src/main/java/com/jagrosh/vortex/commands/ModCommand.java
new file mode 100644
index 0000000..56e92ec
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/ModCommand.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.vortex.Vortex;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Role;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public abstract class ModCommand extends Command
+{
+ protected final Vortex vortex;
+
+ public ModCommand(Vortex vortex, Permission... altPerms)
+ {
+ this.vortex = vortex;
+ this.category = new Category("Moderation", event ->
+ {
+ if(event.getGuild()==null)
+ {
+ event.replyError("This command is not available in Direct Messages!");
+ return false;
+ }
+ Role modrole = vortex.getDatabase().settings.getSettings(event.getGuild()).getModeratorRole(event.getGuild());
+ if(modrole!=null && event.getMember().getRoles().contains(modrole))
+ return true;
+ return event.getMember().hasPermission(altPerms);
+ });
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/AntiduplicateCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/AntiduplicateCmd.java
new file mode 100644
index 0000000..a157993
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/AntiduplicateCmd.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Constants;
+import net.dv8tion.jda.core.Permission;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.PunishmentManager;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AntiduplicateCmd extends Command
+{
+ private final Vortex vortex;
+
+ public AntiduplicateCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "antiduplicate";
+ this.guildOnly = true;
+ this.category = new Category("AutoMod");
+ this.arguments = " [delete threshold] [strikes] or OFF";
+ this.help = "prevents duplicate messages";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty() || event.getArgs().equalsIgnoreCase("help"))
+ {
+ event.replySuccess("The Anti-Duplicate system prevents and punishes users for sending the same message repeatedly.\n"
+ + "Usage: `"+Constants.PREFIX+name+" "+arguments+"`\n"
+ + "`` - the number of duplicates at which strikes should start being assigned\n"
+ + "`[delete threshold]` - the number of duplicates at which a user's messages should start being deleted\n"
+ + "`[strikes]` - the number of strikes to assign on each duplicate after the strike threshold is met");
+ return;
+ }
+ if(event.getArgs().equalsIgnoreCase("off"))
+ {
+ vortex.getDatabase().automod.setDupeSettings(event.getGuild(), 0, 0, 0);
+ event.replySuccess("Anti-Duplicate has been disabled.");
+ return;
+ }
+ int strikeThreshold, deleteThreshold;
+ int strikes = 1;
+ String[] parts = event.getArgs().split("\\s+", 3);
+ try
+ {
+ strikeThreshold = Integer.parseInt(parts[0]);
+ }
+ catch(NumberFormatException ex)
+ {
+ event.replyError(" must be an integer!");
+ return;
+ }
+ if(parts.length==1)
+ deleteThreshold = strikeThreshold==1 || strikeThreshold==2 ? 1 : strikeThreshold-2;
+ else try
+ {
+ deleteThreshold = Integer.parseInt(parts[1]);
+ }
+ catch(NumberFormatException ex)
+ {
+ event.replyError("[delete threshold] must be an integer!");
+ return;
+ }
+ if(parts.length==3) try
+ {
+ strikes = Integer.parseInt(parts[2]);
+ }
+ catch(NumberFormatException ex)
+ {
+ event.replyError("[strikes] must be an integer!");
+ return;
+ }
+ if(strikeThreshold<=0 || deleteThreshold<=0 || strikes<=0)
+ {
+ vortex.getDatabase().automod.setDupeSettings(event.getGuild(), 0, 0, 0);
+ event.replySuccess("Anti-Duplicate has been disabled.");
+ return;
+ }
+ vortex.getDatabase().automod.setDupeSettings(event.getGuild(), strikes, deleteThreshold, strikeThreshold);
+ boolean also = vortex.getDatabase().actions.useDefaultSettings(event.getGuild());
+ event.replySuccess("Anti-Duplicate will now delete duplicates starting at duplicate **"+deleteThreshold
+ +"** and begin assigning **"+strikes+"** strikes for each duplicate after duplicate **"+strikeThreshold+"**."
+ +(also ? PunishmentManager.DEFAULT_SETUP_MESSAGE : ""));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/AntiinviteCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/AntiinviteCmd.java
new file mode 100644
index 0000000..2c6e69b
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/AntiinviteCmd.java
@@ -0,0 +1,67 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import net.dv8tion.jda.core.Permission;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.AutomodManager;
+import com.jagrosh.vortex.database.managers.PunishmentManager;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AntiinviteCmd extends Command
+{
+ private final Vortex vortex;
+
+ public AntiinviteCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "antiinvite";
+ this.guildOnly = true;
+ this.aliases = new String[]{"antinvite","anti-invite"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "sets strikes for posting invites";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty())
+ {
+ event.replyError("Please provide a number of strikes!");
+ return;
+ }
+ int numstrikes;
+ try
+ {
+ numstrikes = Integer.parseInt(event.getArgs());
+ }
+ catch(NumberFormatException ex)
+ {
+ if(event.getArgs().equalsIgnoreCase("none") || event.getArgs().equalsIgnoreCase("off"))
+ numstrikes = 0;
+ else
+ {
+ event.replyError("`"+event.getArgs()+"` is not a valid integer!");
+ return;
+ }
+ }
+ if(numstrikes<0 || numstrikes>AutomodManager.MAX_STRIKES)
+ {
+ event.replyError("The number of strikes must be between 0 and "+AutomodManager.MAX_STRIKES);
+ return;
+ }
+ vortex.getDatabase().automod.setInviteStrikes(event.getGuild(), numstrikes);
+ boolean also = vortex.getDatabase().actions.useDefaultSettings(event.getGuild());
+ event.replySuccess("Users will now receive `"+numstrikes+"` for posting invite links."+(also ? PunishmentManager.DEFAULT_SETUP_MESSAGE : ""));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/AntimentionCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/AntimentionCmd.java
new file mode 100644
index 0000000..b00ccac
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/AntimentionCmd.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import net.dv8tion.jda.core.Permission;
+import com.jagrosh.vortex.database.managers.AutomodManager;
+import com.jagrosh.vortex.database.managers.PunishmentManager;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AntimentionCmd extends Command
+{
+ private final Vortex vortex;
+
+ public AntimentionCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.guildOnly = true;
+ this.name = "maxmentions";
+ this.aliases = new String[]{"antimention","maxmention","mentionmax","mentionsmax"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "sets maximum number of mentions a user can send";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ this.children = new Command[]{new AntirolementionCmd()};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().equalsIgnoreCase("off") || event.getArgs().equalsIgnoreCase("none"))
+ {
+ vortex.getDatabase().automod.disableMaxMentions(event.getGuild());
+ event.replySuccess("Anti-Mention has been disabled.");
+ return;
+ }
+ else if(event.getArgs().isEmpty())
+ {
+ event.replyError("Please include an integer value or `OFF`");
+ return;
+ }
+ try
+ {
+ short num = Short.parseShort(event.getArgs());
+ if(num` must be a valid integer at least `"+AutomodManager.MENTION_MINIMUM+"`");
+ }
+ }
+
+ private class AntirolementionCmd extends Command
+ {
+ public AntirolementionCmd()
+ {
+ this.guildOnly = true;
+ this.name = "roles";
+ this.aliases = new String[]{"role"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "sets maximum number of unique role mentions a user can send";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty())
+ {
+ event.replyError("Please include an integer value or `OFF`");
+ return;
+ }
+ try
+ {
+ short num = Short.parseShort(event.getArgs());
+ if(num` must be a valid integer at least `"+AutomodManager.ROLE_MENTION_MINIMUM+"`");
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/AutoraidmodeCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/AutoraidmodeCmd.java
new file mode 100644
index 0000000..4ed3070
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/AutoraidmodeCmd.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AutoraidmodeCmd extends Command
+{
+ private final Vortex vortex;
+
+ public AutoraidmodeCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.guildOnly = true;
+ this.name = "autoraidmode";
+ this.aliases = new String[]{"autoraid","autoantiraid","autoantiraidmode"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "enables/disables auto-raidmode";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ this.botPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().equalsIgnoreCase("off"))
+ {
+ vortex.getDatabase().automod.setAutoRaidMode(event.getGuild(), 0, 0);
+ event.replySuccess("Auto-Anti-Raid mode has been disabled.");
+ return;
+ }
+ int joins;
+ int seconds;
+ if(event.getArgs().equalsIgnoreCase("on"))
+ {
+ joins = 10;
+ seconds = 10;
+ }
+ else if(!event.getArgs().matches("\\d{1,8}\\s*\\/\\s*\\d{1,8}"))
+ {
+ event.replyError("Valid options are `OFF`, `ON`, or `/`"
+ + "\nSetting to `OFF` means the bot will never automatically enable raid mode"
+ + "\nSetting to `ON` will use the recommended value of 10 joins per 10 seconds to rigger Anti-Raid mode"
+ + "\nSetting a customizable threshhold is possible; ex: `10/20` for 10 joins in 20 seconds"
+ + "\nFor more information, check out the wiki: ");
+ return;
+ }
+ else
+ {
+ String[] parts = event.getArgs().split("\\s*\\/\\s*");
+ joins = Integer.parseInt(parts[0]);
+ seconds = Integer.parseInt(parts[1]);
+ }
+ vortex.getDatabase().automod.setAutoRaidMode(event.getGuild(), joins, seconds);
+ event.replySuccess("Anti-Raid mode will be enabled automatically when there are `"+joins+"` in `"+seconds+"` seconds.");
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/IgnoreCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/IgnoreCmd.java
new file mode 100644
index 0000000..4294613
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/IgnoreCmd.java
@@ -0,0 +1,92 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import java.util.List;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.TextChannel;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.FormatUtil;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class IgnoreCmd extends Command
+{
+ private final Vortex vortex;
+
+ public IgnoreCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.guildOnly = true;
+ this.name = "ignore";
+ this.aliases = new String[]{"addignore","ignored","ignores"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "shows ignores, or sets automod to ignore a role or channel";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ if(event.getArgs().isEmpty())
+ {
+ EmbedBuilder ebuilder = new EmbedBuilder();
+ ebuilder.setColor(event.getSelfMember().getColor());
+ ebuilder.setTitle("Automod Ignores",null);
+ StringBuilder builder = new StringBuilder();
+ List roles = vortex.getDatabase().ignores.getIgnoredRoles(event.getGuild());
+ List channels = vortex.getDatabase().ignores.getIgnoredChannels(event.getGuild());
+ event.getGuild().getRoles().stream().forEach(r -> {
+ if(roles.contains(r))
+ builder.append("\n").append(r.getAsMention());
+ else if(!event.getSelfMember().canInteract(r))
+ builder.append("\n").append(r.getAsMention()).append(" [can't interact]");
+ else if(r.getPermissions().contains(Permission.ADMINISTRATOR)
+ || r.getPermissions().contains(Permission.MANAGE_SERVER)
+ || r.getPermissions().contains(Permission.BAN_MEMBERS)
+ || r.getPermissions().contains(Permission.KICK_MEMBERS)
+ || r.getPermissions().contains(Permission.MESSAGE_MANAGE))
+ builder.append("\n").append(r.getAsMention()).append(" [elevated perms]");
+ });
+ channels.forEach(c -> builder.append("\n").append(c.getAsMention()));
+ ebuilder.setDescription(builder.toString());
+ event.reply(ebuilder.build());
+ return;
+ }
+
+ String id = event.getArgs().replaceAll("<#(\\d{17,20})>", "$1");
+ TextChannel tc;
+ try {
+ tc = event.getGuild().getTextChannelById(id);
+ } catch(Exception e) {
+ tc = null;
+ }
+ if(tc!=null)
+ {
+ vortex.getDatabase().ignores.ignore(tc);
+ event.replySuccess("Automod is now ignoring channel <#"+tc.getId()+">");
+ return;
+ }
+
+ List roles = FinderUtil.findRoles(event.getArgs(), event.getGuild());
+ if(roles.isEmpty())
+ event.replyError("No roles or text channels found for `"+event.getArgs()+"`");
+ else if (roles.size()==1)
+ {
+ vortex.getDatabase().ignores.ignore(roles.get(0));
+ event.replySuccess("Automod is now ignoring role `"+roles.get(0).getName()+"`");
+ }
+ else
+ event.reply(FormatUtil.listOfRoles(roles, event.getArgs()));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/UnignoreCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/UnignoreCmd.java
new file mode 100644
index 0000000..6a4e287
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/automod/UnignoreCmd.java
@@ -0,0 +1,79 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.jagrosh.vortex.commands.automod;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import java.util.List;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.TextChannel;
+import com.jagrosh.vortex.utils.FormatUtil;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class UnignoreCmd extends Command {
+
+ private final Vortex vortex;
+
+ public UnignoreCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.guildOnly = true;
+ this.name = "unignore";
+ this.aliases = new String[]{"deignore","delignore","removeignore"};
+ this.category = new Category("AutoMod");
+ this.arguments = "";
+ this.help = "sets the automod to stop ignoring a role or channel";
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ if(event.getArgs().isEmpty())
+ {
+ event.replyWarning("Please include a #channel or role to stop ignoring!");
+ return;
+ }
+
+ String id = event.getArgs().replaceAll("<#(\\d{17,20})>", "$1");
+ TextChannel tc;
+ try {
+ tc = event.getGuild().getTextChannelById(id);
+ } catch(Exception e) {
+ tc = null;
+ }
+ if(tc!=null)
+ {
+ if(vortex.getDatabase().ignores.unignore(tc))
+ event.replySuccess("Automod is no longer ignoring channel <#"+tc.getId()+">");
+ else
+ event.replyError("Automod was not already ignoring <#"+tc.getId()+">!");
+ return;
+ }
+
+ List roles = FinderUtil.findRoles(event.getArgs(), event.getGuild());
+ if(roles.isEmpty())
+ event.replyError("No roles or text channels found for `"+event.getArgs()+"`");
+ else if (roles.size()==1)
+ {
+ if(vortex.getDatabase().ignores.unignore(roles.get(0)))
+ event.replySuccess("Automod is no longer ignoring role `"+roles.get(0).getName()+"`");
+ else
+ event.replyError("Automod was not ignoring role `"+roles.get(0).getName()+"`"
+ + "\n"+Constants.WARNING+" If this role is still listed when using `"+Constants.PREFIX+"ignore`:"
+ + "\n`[can't interact]` - the role is above "+event.getSelfUser().getName()+"'s highest role; try moving the '"+event.getSelfUser().getName()+"' role higher"
+ + "\n`[elevated perms]` - the role has one of the following permissions: Kick Members, Ban Members, Manage Server, Manage Messages, Administrator");
+ }
+ else
+ event.reply(FormatUtil.listOfRoles(roles, event.getArgs()));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/general/InviteCmd.java b/src/main/java/com/jagrosh/vortex/commands/general/InviteCmd.java
new file mode 100644
index 0000000..61c36d5
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/general/InviteCmd.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.general;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Constants;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class InviteCmd extends Command {
+
+ public InviteCmd()
+ {
+ this.name = "invite";
+ this.help = "shows how to invite the bot";
+ this.guildOnly = false;
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ event.reply("Hello. I am **"+event.getJDA().getSelfUser().getName()+"**, a simple moderation bot built by **jagrosh**#4824."
+ + "\nYou can add me to your server with the link below:"
+ + "\n\n\uD83D\uDD17 **<"+Constants.BOT_INVITE+">**"
+ + "\n\nFor help or suggestions, please join the support server: "+Constants.SERVER_INVITE);
+ }
+
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/general/ServerinfoCmd.java b/src/main/java/com/jagrosh/vortex/commands/general/ServerinfoCmd.java
new file mode 100644
index 0000000..56b979d
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/general/ServerinfoCmd.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.general;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.utils.FormatUtil;
+import java.time.format.DateTimeFormatter;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.OnlineStatus;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Guild;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class ServerinfoCmd extends Command
+{
+ private final String linestart = "\u25AB";
+ public ServerinfoCmd()
+ {
+ this.name = "serverinfo";
+ this.aliases = new String[]{"server","guildinfo"};
+ this.help = "shows server info";
+ this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ Guild guild = event.getGuild();
+ long onlineCount = guild.getMembers().stream().filter((u) -> (u.getOnlineStatus()!=OnlineStatus.OFFLINE)).count();
+ long botCount = guild.getMembers().stream().filter(m -> m.getUser().isBot()).count();
+ EmbedBuilder builder = new EmbedBuilder();
+ String title = FormatUtil.filterEveryone("\uD83D\uDDA5 Information about "+guild.getName()+":");
+ String verif;
+ switch(guild.getVerificationLevel()) {
+ case VERY_HIGH: verif = "┻━┻ミヽ(ಠ益ಠ)ノ彡┻━┻"; break;
+ case HIGH: verif = "(╯°□°)╯︵ ┻━┻"; break;
+ default: verif = guild.getVerificationLevel().name(); break;
+ }
+ String str = linestart+"ID: **"+guild.getId()+"**\n"
+ +linestart+"Owner: "+FormatUtil.formatUser(guild.getOwner().getUser())+"\n"
+ +linestart+"Location: **"+guild.getRegion().getName()+"**\n"
+ +linestart+"Creation: **"+guild.getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"**\n"
+ +linestart+"Users: **"+guild.getMembers().size()+"** ("+onlineCount+" online, "+botCount+" bots)\n"
+ +linestart+"Channels: **"+guild.getTextChannels().size()+"** Text, **"+guild.getVoiceChannels().size()+"** Voice\n"
+ +linestart+"Verification: **"+verif+"**";
+ if(guild.getSplashId()!=null)
+ {
+ builder.setImage(guild.getSplashUrl()+"?size=1024");
+ str += "\n<:partner:314068430556758017> **Discord Partner** <:partner:314068430556758017>";
+ }
+ if(guild.getIconUrl()!=null)
+ builder.setThumbnail(guild.getIconUrl());
+ builder.setColor(guild.getOwner().getColor());
+ builder.setDescription(str);
+ event.reply(new MessageBuilder().append(title).setEmbed(builder.build()).build());
+ }
+
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/general/UserinfoCmd.java b/src/main/java/com/jagrosh/vortex/commands/general/UserinfoCmd.java
new file mode 100644
index 0000000..c04e5bc
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/general/UserinfoCmd.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.general;
+
+import java.time.format.DateTimeFormatter;
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.OnlineStatus;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Game;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.User;
+import net.dv8tion.jda.core.utils.MiscUtil;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class UserinfoCmd extends Command
+{
+ private final String linestart = "\u25AB";
+ public UserinfoCmd()
+ {
+ this.name = "userinfo";
+ this.aliases = new String[]{"user","uinfo"};
+ this.help = "shows info on a user in the server";
+ this.arguments = "[user]";
+ this.guildOnly = true;
+ this.botPermissions = new Permission[]{Permission.MESSAGE_EMBED_LINKS};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ Member member;
+ if(event.getArgs().isEmpty())
+ {
+ member = event.getMember();
+ }
+ else
+ {
+ List found = FinderUtil.findMembers(event.getArgs(), event.getGuild());
+ if(found.isEmpty())
+ {
+ event.replyError("I couldn't find the user you were looking for!");
+ return;
+ }
+ else
+ {
+ member = found.get(0);
+ }
+ }
+ User user = member.getUser();
+ String title = (user.isBot() ? "\uD83E\uDD16":"\uD83D\uDC64")+" Information about **"+user.getName()+"** #"+user.getDiscriminator()+":";
+ String str = linestart+"Discord ID: **"+user.getId()+"**"+(user.getAvatarId()!=null && user.getAvatarId().startsWith("a_")?" <:nitro:314068430611415041>":"");
+ if(member.getNickname()!=null)
+ str+="\n"+linestart+"Nickname: **"+member.getNickname()+"**";
+ String roles="";
+ roles = member.getRoles().stream().map((rol) -> rol.getName()).filter((r) -> (!r.equalsIgnoreCase("@everyone"))).map((r) -> "`, `"+r).reduce(roles, String::concat);
+ if(roles.isEmpty())
+ roles="None";
+ else
+ roles=roles.substring(3)+"`";
+ str+="\n"+linestart+"Roles: "+roles;
+ str+="\n"+linestart+"Status: "+statusToEmote(member.getOnlineStatus(), member.getGame())+"**"+member.getOnlineStatus().name()+"**";
+ Game game = member.getGame();
+ if(game!=null)
+ str+=" ("+formatGame(game)+")";
+ str+="\n"+linestart+"Account Creation: **"+MiscUtil.getDateTimeString(MiscUtil.getCreationTime(user))+"**";
+
+ List joins = new ArrayList<>(event.getGuild().getMembers());
+ Collections.sort(joins, (Member a, Member b) -> a.getJoinDate().compareTo(b.getJoinDate()));
+ int index = joins.indexOf(member);
+ str+="\n"+linestart+"Guild Join Date: **"+member.getJoinDate().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "** `(#"+(index+1)+")`";
+ index-=3;
+ if(index<0)
+ index=0;
+ str+="\n"+linestart+"Join Order: ";
+ if(joins.get(index).equals(member))
+ str+="[**"+joins.get(index).getUser().getName()+"**]()";
+ else
+ str+=joins.get(index).getUser().getName();
+ for(int i=index+1;i=joins.size())
+ break;
+ Member m = joins.get(i);
+ String uname = m.getUser().getName();
+ if(m.equals(member))
+ uname="[**"+uname+"**]()";
+ str+=" > "+uname;
+ }
+
+ event.reply(new MessageBuilder()
+ .append(title)
+ .setEmbed(new EmbedBuilder()
+ .setDescription(str)
+ .setThumbnail(user.getEffectiveAvatarUrl())
+ .setColor(member.getColor()).build())
+ .build());
+ }
+
+ private static String statusToEmote(OnlineStatus status, Game game)
+ {
+ if(game!=null && game.getType()==Game.GameType.STREAMING && game.getUrl()!=null && Game.isValidStreamingUrl(game.getUrl()))
+ return "<:streaming:313956277132853248>";
+ switch(status) {
+ case ONLINE: return "<:online:313956277808005120>";
+ case IDLE: return "<:away:313956277220802560>";
+ case DO_NOT_DISTURB: return "<:dnd:313956276893646850>";
+ case INVISIBLE: return "<:invisible:313956277107556352>";
+ case OFFLINE: return "<:offline:313956277237710868>";
+ default: return "";
+ }
+ }
+
+ private static String formatGame(Game game)
+ {
+ String str;
+ switch(game.getType())
+ {
+ case STREAMING:
+ return "Streaming [*"+game.getName()+"*]("+game.getUrl()+")";
+ case LISTENING:
+ str="Listening to";
+ break;
+ case WATCHING:
+ str="Watching";
+ break;
+ case DEFAULT:
+ default:
+ str="Playing";
+ break;
+
+ }
+ return str+" *"+game.getName()+"*";
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/BanCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/BanCmd.java
new file mode 100644
index 0000000..f64a8d7
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/BanCmd.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.ArgsUtil.ResolvedArgs;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class BanCmd extends ModCommand
+{
+ public BanCmd(Vortex vortex)
+ {
+ super(vortex, Permission.BAN_MEMBERS);
+ this.name = "ban";
+ this.aliases = new String[]{"hackban","forceban"};
+ this.arguments = "<@users...> [time] [reason]";
+ this.help = "bans all provided users";
+ this.botPermissions = new Permission[]{Permission.BAN_MEMBERS};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ ResolvedArgs args = ArgsUtil.resolve(event.getArgs(), true, event.getGuild());
+ if(args.isEmpty())
+ {
+ event.replyError("Please include at least one user to ban (@mention or ID)!");
+ return;
+ }
+ int minutes = args.time/60;
+ if(minutes < 0)
+ {
+ event.replyError("Timed bans cannot be negative time!");
+ return;
+ }
+ String reason = LogUtil.auditReasonFormat(event.getMember(), minutes, args.reason);
+ StringBuilder builder = new StringBuilder();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to ban ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to ban ").append(FormatUtil.formatUser(m.getUser()));
+ else
+ args.ids.add(m.getUser().getIdLong());
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a user ID"));
+
+ args.users.forEach(u -> args.ids.add(u.getIdLong()));
+
+ if(args.ids.isEmpty())
+ {
+ event.reply(builder.toString());
+ return;
+ }
+
+ if(args.ids.size() > 5)
+ event.reactSuccess();
+
+ Instant unbanTime = Instant.now().plus(minutes, ChronoUnit.MINUTES);
+ for(int i=0; i
+ {
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully banned <@").append(id).append(">");
+ if(minutes>0)
+ vortex.getDatabase().tempbans.setBan(event.getGuild(), uid, unbanTime);
+ if(last)
+ event.reply(builder.toString());
+ }, failure ->
+ {
+ builder.append("\n").append(event.getClient().getError()).append(" Failed to ban <@").append(id).append(">");
+ if(last)
+ event.reply(builder.toString());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java
new file mode 100644
index 0000000..e7e5d8a
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.time.OffsetDateTime;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import com.jagrosh.vortex.database.managers.GuildSettingsDataManager.GuildSettings;
+import java.util.LinkedList;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.MessageHistory;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class CleanCmd extends ModCommand
+{
+ private final Pattern LINK_PATTERN = Pattern.compile("https?:\\/\\/.+");
+ private final Pattern QUOTES_PATTERN = Pattern.compile("\"(.*?)\"", Pattern.DOTALL);
+ private final Pattern CODE_PATTERN = Pattern.compile("`(.*?)`", Pattern.DOTALL);
+ private final Pattern MENTION_PATTERN = Pattern.compile("<@!?(\\d{17,22})>");
+ private final Pattern ID_PATTERN = Pattern.compile("(?:^|\\s)(\\d{17,22})(?:$|\\s)");
+ private final Pattern NUM_PATTERN = Pattern.compile("(?:^|\\s)(\\d{1,4})(?:$|\\s)");
+ private final String week2limit = " Note: Messages older than 2 weeks cannot be cleaned.";
+ private final String noparams = "**No valid cleaning paramaters included!**\n"
+ +"This command is to remove many messages quickly. Pinned messages are ignored. "
+ + "Messages can be filtered with various parameters. Mutliple arguments can be used, and "
+ + "the order of parameters does not matter. The following parameters are supported:\n"
+ + " `` - number of posts to delete; between 2 and 1000\n"
+ + " `bots` - cleans messages by bots\n"
+ + " `embeds` - cleans messages with embeds\n"
+ + " `links` - cleans messages containing links\n"
+ + " `images` - cleans messages with uploaded or embeded images or videos\n"
+ + " `@user` - cleans messages only from the provided user\n"
+ + " `userId` - cleans messages only from the provided user (via id)\n"
+ + " `\"quotes\"` - cleans messages containing the text in quotes\n"
+ + " `` `regex` `` - cleans messages that match the regex";
+
+ public CleanCmd(Vortex vortex)
+ {
+ super(vortex, Permission.MESSAGE_MANAGE, Permission.MESSAGE_HISTORY);
+ this.name = "clean";
+ this.arguments = "";
+ this.help = "cleans messages matching the given filters";
+ this.botPermissions = new Permission[]{Permission.MESSAGE_MANAGE, Permission.MESSAGE_HISTORY};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty() || event.getArgs().equalsIgnoreCase("help"))
+ {
+ event.replyWarning(noparams);
+ return;
+ }
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(event.getGuild()).getModLogChannel(event.getGuild());
+ if(modlog!=null && event.getChannel().getIdLong()==modlog.getIdLong())
+ {
+ event.replyWarning("This command cannot be used in the modlog!");
+ return;
+ }
+ int num = -1;
+ List quotes = new LinkedList<>();
+ String pattern = null;
+ List ids = new LinkedList<>();
+ String parameters = event.getArgs();
+
+ Matcher m = QUOTES_PATTERN.matcher(parameters);
+ while(m.find())
+ quotes.add(m.group(1).trim().toLowerCase());
+ parameters = parameters.replaceAll(QUOTES_PATTERN.pattern(), " ");
+
+ m = CODE_PATTERN.matcher(parameters);
+ if(m.find())
+ pattern= m.group(1);
+ parameters = parameters.replaceAll(CODE_PATTERN.pattern(), " ");
+
+ m = MENTION_PATTERN.matcher(parameters);
+ while(m.find())
+ ids.add(m.group(1));
+ parameters = parameters.replaceAll(MENTION_PATTERN.pattern(), " ");
+
+ m = ID_PATTERN.matcher(parameters);
+ while(m.find())
+ ids.add(m.group(1));
+ parameters = parameters.replaceAll(ID_PATTERN.pattern(), " ");
+
+ m = NUM_PATTERN.matcher(parameters);
+ if(m.find())
+ num = Integer.parseInt(m.group(1));
+ parameters = parameters.replaceAll(NUM_PATTERN.pattern(), " ").toLowerCase();
+
+ boolean bots = parameters.contains("bot");
+ boolean embeds = parameters.contains("embed");
+ boolean links = parameters.contains("link");
+ boolean images = parameters.contains("image");
+
+ boolean all = quotes.isEmpty() && pattern==null && ids.isEmpty() && !bots && !embeds && !links && !images;
+
+ if(num==-1)
+ {
+ if(all)
+ {
+ event.replyWarning(noparams);
+ return;
+ }
+ else
+ num=100;
+ }
+ if(num>1000 || num<2)
+ {
+ event.replyError("Number of messages must be between 2 and 1000");
+ return;
+ }
+
+ int val2 = num+1;
+ String p = pattern;
+ event.async(() -> {
+ int val = val2;
+ List msgs = new LinkedList<>();
+ MessageHistory mh = event.getChannel().getHistory();
+ OffsetDateTime earliest = event.getMessage().getCreationTime().minusHours(335);
+ while(val>100)
+ {
+ msgs.addAll(mh.retrievePast(100).complete());
+ val-=100;
+ if(msgs.get(msgs.size()-1).getCreationTime().isBefore(earliest))
+ {
+ val=0;
+ break;
+ }
+ }
+ if(val>0)
+ msgs.addAll(mh.retrievePast(val).complete());
+
+ msgs.remove(event.getMessage());
+ boolean week2 = false;
+ List del = new LinkedList<>();
+ for(Message msg : msgs)
+ {
+ if(msg.getCreationTime().isBefore(earliest))
+ {
+ week2 = true;
+ break;
+ }
+ if(all || ids.contains(msg.getAuthor().getId()) || (bots && msg.getAuthor().isBot()) || (embeds && !msg.getEmbeds().isEmpty())
+ || (links && LINK_PATTERN.matcher(msg.getContentRaw()).find()) || (images && hasImage(msg)))
+ {
+ del.add(msg);
+ continue;
+ }
+ String lowerContent = msg.getContentRaw().toLowerCase();
+ if(quotes.stream().anyMatch(quote -> lowerContent.contains(quote)))
+ {
+ del.add(msg);
+ continue;
+ }
+ try{
+ if(p!=null && msg.getContentRaw().matches(p))
+ del.add(msg);
+ }catch(Exception e){}
+ }
+
+ if(del.isEmpty())
+ {
+ event.replyWarning("There were no messages to clean!"+(week2?week2limit:""));
+ return;
+ }
+ try{
+ int index = 0;
+ while(index < del.size())
+ {
+ if(index+100>del.size())
+ if(index+1==del.size())
+ del.get(del.size()-1).delete().complete();
+ else
+ event.getTextChannel().deleteMessages(del.subList(index, del.size())).complete();
+ else
+ event.getTextChannel().deleteMessages(del.subList(index, index+100)).complete();
+ index+=100;
+ }
+ }catch(Exception e)
+ {
+ event.replyError("Failed to delete "+del.size()+" messages.");
+ return;
+ }
+ event.replySuccess("Cleaned **"+del.size()+"** messages."+(week2?week2limit:""));
+ if(vortex.getDatabase().settings.getSettings(event.getGuild()).getModLogChannel(event.getGuild())==null)
+ return;
+ final String params;
+ if(!all)
+ {
+ StringBuilder used = new StringBuilder();
+ if(bots) used.append(" bots");
+ if(embeds) used.append(" embeds");
+ if(links) used.append(" links");
+ if(images) used.append(" images");
+ if(p!=null) used.append(" `").append(p).append("`");
+ ids.forEach(id -> used.append(" ").append(id));
+ quotes.forEach(q -> used.append(" \"").append(q).append("\""));
+ params = used.toString().trim();
+ }
+ else
+ params = "all";
+ vortex.getTextUploader().upload(LogUtil.logMessages("Cleaned Messages", del), "CleanedMessages", (view, download) ->
+ {
+ vortex.getModLogger().postCleanCase(event.getMember(), event.getMessage().getCreationTime(), del.size(),
+ event.getTextChannel(), params, null, new EmbedBuilder().setColor(event.getSelfMember().getColor())
+ .appendDescription("[`\uD83D\uDCC4 View`]("+view+") | [`\uD83D\uDCE9 Download`]("+download+")").build());
+ });
+ });
+ }
+
+ private static boolean hasImage(Message message)
+ {
+ if(message.getAttachments().stream().anyMatch(a -> a.isImage()))
+ return true;
+ if(message.getEmbeds().stream().anyMatch(e -> e.getImage()!=null || e.getVideoInfo()!=null))
+ return true;
+ return false;
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/KickCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/KickCmd.java
new file mode 100644
index 0000000..9777828
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/KickCmd.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.util.LinkedList;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Member;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.util.List;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class KickCmd extends ModCommand
+{
+ public KickCmd(Vortex vortex)
+ {
+ super(vortex, Permission.KICK_MEMBERS);
+ this.name = "kick";
+ this.arguments = "<@users...> [reason]";
+ this.help = "kicks all provided users";
+ this.botPermissions = new Permission[]{Permission.KICK_MEMBERS};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ ArgsUtil.ResolvedArgs args = ArgsUtil.resolve(event.getArgs(), event.getGuild());
+ if(args.isEmpty())
+ {
+ event.replyError("Please include at least one user to kick (@mention or ID)!");
+ return;
+ }
+ String reason = LogUtil.auditReasonFormat(event.getMember(), args.reason);
+ StringBuilder builder = new StringBuilder();
+ List toKick = new LinkedList<>();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to kick ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to kick ").append(FormatUtil.formatUser(m.getUser()));
+ else
+ toKick.add(m);
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a member"));
+
+ args.users.forEach(u -> builder.append("\n").append(event.getClient().getWarning()).append("The user ").append(u.getAsMention()).append(" is not in this server."));
+
+ args.ids.forEach(id -> builder.append("\n").append(event.getClient().getWarning()).append("The user <@").append(id).append("> is not in this server."));
+
+ if(toKick.isEmpty())
+ {
+ event.reply(builder.toString());
+ return;
+ }
+
+ if(toKick.size() > 5)
+ event.reactSuccess();
+
+ for(int i=0; i
+ {
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully kicked ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ }, failure ->
+ {
+ builder.append("\n").append(event.getClient().getError()).append(" Failed to kick ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java
new file mode 100644
index 0000000..dc89ee6
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.util.List;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.LinkedList;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class MuteCmd extends ModCommand
+{
+ public MuteCmd(Vortex vortex)
+ {
+ super(vortex, Permission.MANAGE_ROLES);
+ this.name = "mute";
+ this.arguments = "<@users...> [reason]";
+ this.help = "applies a muted role to provided users";
+ this.botPermissions = new Permission[]{Permission.MANAGE_ROLES};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ Role muteRole = event.getGuild().getRoles().stream().filter(r -> r.getName().equalsIgnoreCase("muted")).findFirst().orElse(null);
+ if(muteRole == null)
+ {
+ event.replyError("No role called 'Muted' exists!");
+ return;
+ }
+ if(!event.getMember().canInteract(muteRole))
+ {
+ event.replyError("You do not have permissions to assign the '"+muteRole.getName()+"' role!");
+ return;
+ }
+ if(!event.getSelfMember().canInteract(muteRole))
+ {
+ event.reply(event.getClient().getError()+" I do not have permissions to assign the '"+muteRole.getName()+"' role!");
+ return;
+ }
+
+ ArgsUtil.ResolvedArgs args = ArgsUtil.resolve(event.getArgs(), true, event.getGuild());
+ if(args.isEmpty())
+ {
+ event.replyError("Please include at least one user to mute (@mention or ID)!");
+ return;
+ }
+ int minutes = args.time/60;
+ if(minutes < 0)
+ {
+ event.replyError("Timed bans cannot be negative time!");
+ return;
+ }
+ String reason = LogUtil.auditReasonFormat(event.getMember(), minutes, args.reason);
+ StringBuilder builder = new StringBuilder();
+ List toMute = new LinkedList<>();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to mute ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to mute ").append(FormatUtil.formatUser(m.getUser()));
+ else if(m.getRoles().contains(muteRole))
+ builder.append("\n").append(event.getClient().getError()).append(" ").append(FormatUtil.formatUser(m.getUser())).append(" is already muted!");
+ else
+ toMute.add(m);
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a member"));
+
+ args.users.forEach(u -> builder.append("\n").append(event.getClient().getWarning()).append("The user ").append(u.getAsMention()).append(" is not in this server."));
+
+ args.ids.forEach(id -> builder.append("\n").append(event.getClient().getWarning()).append("The user <@").append(id).append("> is not in this server."));
+
+ if(toMute.isEmpty())
+ {
+ event.reply(builder.toString());
+ return;
+ }
+
+ if(toMute.size() > 5)
+ event.reactSuccess();
+
+ Instant unmuteTime = Instant.now().plus(minutes, ChronoUnit.MINUTES);
+ for(int i=0; i
+ {
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully muted ").append(m.getUser().getAsMention());
+ if(minutes>0)
+ vortex.getDatabase().tempmutes.overrideMute(event.getGuild(), m.getUser().getIdLong(), unmuteTime);
+ else
+ vortex.getDatabase().tempmutes.setMute(event.getGuild(), m.getUser().getIdLong(), Instant.MAX);
+ if(last)
+ event.reply(builder.toString());
+ }, failure ->
+ {
+ builder.append("\n").append(event.getClient().getError()).append(" Failed to mute ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java
new file mode 100644
index 0000000..4cf4769
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.ArgsUtil.ResolvedArgs;
+import com.jagrosh.vortex.utils.FormatUtil;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class PardonCmd extends ModCommand
+{
+ public PardonCmd(Vortex vortex)
+ {
+ super(vortex, Permission.BAN_MEMBERS);
+ this.name = "pardon";
+ this.arguments = "[numstrikes] <@users...> ";
+ this.help = "removes strikes from users";
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ int numstrikes;
+ String[] parts = event.getArgs().split("\\s+", 2);
+ String str;
+ try
+ {
+ numstrikes = Integer.parseInt(parts[0]);
+ str = parts[1];
+ }
+ catch(NumberFormatException | ArrayIndexOutOfBoundsException ex)
+ {
+ numstrikes = 1;
+ str = event.getArgs();
+ }
+ if(numstrikes<1 || numstrikes>100)
+ {
+ event.replyError("Number of strikes must be between 1 and 100!");
+ return;
+ }
+ ResolvedArgs args = ArgsUtil.resolve(str, event.getGuild());
+ if(args.reason==null || args.reason.isEmpty())
+ {
+ event.replyError("Please provide a reason!");
+ return;
+ }
+ StringBuilder builder = new StringBuilder();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to interact with ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to interact with ").append(FormatUtil.formatUser(m.getUser()));
+ else
+ args.ids.add(m.getUser().getIdLong());
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a user ID"));
+
+ args.users.forEach(u -> args.ids.add(u.getIdLong()));
+
+ int fnumstrikes = numstrikes;
+
+ args.ids.forEach(id ->
+ {
+ vortex.getStrikeHandler().pardonStrikes(event.getMember(), event.getMessage().getCreationTime(), id, fnumstrikes, args.reason);
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully pardoned `").append(fnumstrikes).append("` strikes from <@").append(id).append(">");
+ });
+ event.reply(builder.toString());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/RaidCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/RaidCmd.java
new file mode 100644
index 0000000..3d40875
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/RaidCmd.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2018 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class RaidCmd extends ModCommand
+{
+ public RaidCmd(Vortex vortex)
+ {
+ super(vortex, Permission.MANAGE_SERVER, Permission.KICK_MEMBERS);
+ this.name = "raidmode";
+ this.aliases = new String[]{"raid","antiraidmode"};
+ this.arguments = "[ON|OFF] [reason]";
+ this.help = "views, enables, or disabled raidmode";
+ this.botPermissions = new Permission[]{Permission.MANAGE_SERVER, Permission.KICK_MEMBERS};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ boolean active = vortex.getDatabase().settings.getSettings(event.getGuild()).isInRaidMode();
+ String[] parts = event.getArgs().split("\\s+", 2);
+ if(parts[0].equalsIgnoreCase("off") || parts[0].equalsIgnoreCase("stop") || parts[0].equalsIgnoreCase("disable"))
+ {
+ if(active)
+ {
+ vortex.getAutoMod().disableRaidMode(event.getGuild(), event.getMember(), event.getMessage().getCreationTime(), parts.length>1 ? parts[1] : null);
+ event.replySuccess("Anti-Raid Mode has been disabled.");
+ }
+ else
+ event.replyError("Anti-Raid Mode is not currently enabled!");
+ }
+ else if (parts[0].equalsIgnoreCase("on") || parts[0].equalsIgnoreCase("start") || parts[0].equalsIgnoreCase("enable"))
+ {
+ if(!active)
+ {
+ vortex.getAutoMod().enableRaidMode(event.getGuild(), event.getMember(), event.getMessage().getCreationTime(), parts.length>1 ? parts[1] : null);
+ event.replySuccess("Anti-Raid Mode enabled. New members will be prevented from joining.");
+ }
+ else
+ event.replyError("Anti-Raid Mode is already enabled!");
+ }
+ else
+ {
+ event.replySuccess("Anti-Raid Mode is currently `"+(active ? "ACTIVE" : "NOT ACTIVE")+"`\n\n"
+ + "`"+Constants.PREFIX+name+" ON` to enable Anti-Raid Mode\n"
+ + "`"+Constants.PREFIX+name+" OFF` to disable Anti-Raid Mode");
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/ReasonCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/ReasonCmd.java
new file mode 100644
index 0000000..fc0063c
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/ReasonCmd.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ReasonCmd extends ModCommand
+{
+ public ReasonCmd(Vortex vortex)
+ {
+ super(vortex, Permission.BAN_MEMBERS);
+ this.name = "reason";
+ this.help = "updates a reason in the mod log";
+ this.arguments = "[case] ";
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+
+ int caseNum;
+ String[] parts = event.getArgs().split("\\s+", 2);
+ String str;
+ try
+ {
+ caseNum = Integer.parseInt(parts[0]);
+ str = parts.length==1 ? null : parts[1];
+ }
+ catch(NumberFormatException ex)
+ {
+ caseNum = -1;
+ str = event.getArgs();
+ }
+
+ if(caseNum<-1 || caseNum==0)
+ {
+ event.replyError("Case number must be a positive integer! The case number can be omitted to use the latest un-reasoned case.");
+ return;
+ }
+ if(str==null || str.isEmpty())
+ {
+ event.replyError("Please provide a reason!");
+ return;
+ }
+
+ String fstr = str;
+ int fcaseNum = caseNum;
+ event.async(() ->
+ {
+ int result = vortex.getModLogger().updateCase(event.getGuild(), fcaseNum, fstr);
+ switch (result)
+ {
+ case -1:
+ event.replyError("No modlog is set on this server!");
+ break;
+ case -2:
+ event.replyError("I am unable to Read, Write or retrieve History in the modlog!");
+ break;
+ case -3:
+ event.replyError("Case `"+fcaseNum+"` could not be found in the modlog!");
+ break;
+ case -4:
+ event.replyError("A recent case with no reason could not be found in the modlog!");
+ break;
+ default:
+ event.replySuccess("Updated case **"+result+"** in "+vortex.getDatabase().settings.getSettings(event.getGuild()).getModLogChannel(event.getGuild()).getAsMention());
+ break;
+ }
+ });
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/SoftbanCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/SoftbanCmd.java
new file mode 100644
index 0000000..55a1652
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/SoftbanCmd.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.util.LinkedList;
+import java.util.concurrent.TimeUnit;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Member;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.util.List;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class SoftbanCmd extends ModCommand
+{
+ public SoftbanCmd(Vortex vortex)
+ {
+ super(vortex, Permission.BAN_MEMBERS);
+ this.name = "softban";
+ this.arguments = "<@users...> [reason]";
+ this.help = "bans and unbans all provided users";
+ this.botPermissions = new Permission[]{Permission.BAN_MEMBERS};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ ArgsUtil.ResolvedArgs args = ArgsUtil.resolve(event.getArgs(), event.getGuild());
+ if(args.isEmpty())
+ {
+ event.replyError("Please include at least one user to softban (@mention or ID)!");
+ return;
+ }
+ String reason = LogUtil.auditReasonFormat(event.getMember(), args.reason);
+ StringBuilder builder = new StringBuilder();
+ List toSoftban = new LinkedList<>();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to softban ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to softban ").append(FormatUtil.formatUser(m.getUser()));
+ else
+ toSoftban.add(m);
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a member"));
+
+ args.users.forEach(u -> builder.append("\n").append(event.getClient().getWarning()).append("The user ").append(u.getAsMention()).append(" is not in this server."));
+
+ args.ids.forEach(id -> builder.append("\n").append(event.getClient().getWarning()).append("The user <@").append(id).append("> is not in this server."));
+
+ if(toSoftban.isEmpty())
+ {
+ event.reply(builder.toString());
+ return;
+ }
+
+ if(toSoftban.size() > 5)
+ event.reactSuccess();
+
+ for(int i=0; i
+ {
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully softbanned ").append(m.getUser().getAsMention());
+ event.getGuild().getController().unban(m.getUser().getId()).reason("Softban Unban").queueAfter(7, TimeUnit.SECONDS);
+ if(last)
+ event.reply(builder.toString());
+ }, failure ->
+ {
+ builder.append("\n").append(event.getClient().getError()).append(" Failed to softban ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java
new file mode 100644
index 0000000..721b4f4
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.ArgsUtil.ResolvedArgs;
+import com.jagrosh.vortex.utils.FormatUtil;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class StrikeCmd extends ModCommand
+{
+ public StrikeCmd(Vortex vortex)
+ {
+ super(vortex, Permission.BAN_MEMBERS);
+ this.name = "strike";
+ this.arguments = "[number] <@users...> ";
+ this.help = "applies strikes to users";
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ int numstrikes;
+ String[] parts = event.getArgs().split("\\s+", 2);
+ String str;
+ try
+ {
+ numstrikes = Integer.parseInt(parts[0]);
+ str = parts[1];
+ }
+ catch(NumberFormatException | ArrayIndexOutOfBoundsException ex)
+ {
+ numstrikes = 1;
+ str = event.getArgs();
+ }
+ if(numstrikes<1 || numstrikes>100)
+ {
+ event.replyError("Number of strikes must be between 1 and 100!");
+ return;
+ }
+ ResolvedArgs args = ArgsUtil.resolve(str, event.getGuild());
+ if(args.reason==null || args.reason.isEmpty())
+ {
+ event.replyError("Please provide a reason!");
+ return;
+ }
+ StringBuilder builder = new StringBuilder();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to interact with ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to interact with ").append(FormatUtil.formatUser(m.getUser()));
+ else
+ args.ids.add(m.getUser().getIdLong());
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a user ID"));
+
+ args.users.forEach(u -> args.ids.add(u.getIdLong()));
+
+ int fnumstrikes = numstrikes;
+
+ args.ids.forEach(id ->
+ {
+ vortex.getStrikeHandler().applyStrikes(event.getMember(), event.getMessage().getCreationTime(), id, fnumstrikes, args.reason);
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully gave `").append(fnumstrikes).append("` strikes to <@").append(id).append(">");
+ });
+ event.reply(builder.toString());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/UnmuteCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/UnmuteCmd.java
new file mode 100644
index 0000000..2bbe5da
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/UnmuteCmd.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.util.List;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+import com.jagrosh.vortex.utils.ArgsUtil;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.util.LinkedList;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class UnmuteCmd extends ModCommand
+{
+ public UnmuteCmd(Vortex vortex)
+ {
+ super(vortex, Permission.MANAGE_ROLES);
+ this.name = "unmute";
+ this.arguments = "<@users...> [reason]";
+ this.help = "removes a muted role from provided users";
+ this.botPermissions = new Permission[]{Permission.MANAGE_ROLES};
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ Role muteRole = event.getGuild().getRoles().stream().filter(r -> r.getName().equalsIgnoreCase("muted")).findFirst().orElse(null);
+ if(muteRole == null)
+ {
+ event.replyError("No role called 'Muted' exists!");
+ return;
+ }
+ if(!event.getMember().canInteract(muteRole))
+ {
+ event.replyError("You do not have permissions to assign the '"+muteRole.getName()+"' role!");
+ return;
+ }
+ if(!event.getSelfMember().canInteract(muteRole))
+ {
+ event.reply(event.getClient().getError()+" I do not have permissions to assign the '"+muteRole.getName()+"' role!");
+ return;
+ }
+
+ ArgsUtil.ResolvedArgs args = ArgsUtil.resolve(event.getArgs(), event.getGuild());
+ if(args.isEmpty())
+ {
+ event.replyError("Please include at least one user to unmute (@mention or ID)!");
+ return;
+ }
+ String reason = LogUtil.auditReasonFormat(event.getMember(), args.reason);
+ StringBuilder builder = new StringBuilder();
+ List toUnmute = new LinkedList<>();
+
+ args.members.forEach(m ->
+ {
+ if(!event.getMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" You do not have permission to unmute ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!event.getSelfMember().canInteract(m))
+ builder.append("\n").append(event.getClient().getError()).append(" I am unable to unmute ").append(FormatUtil.formatUser(m.getUser()));
+ else if(!m.getRoles().contains(muteRole))
+ builder.append("\n").append(event.getClient().getError()).append(" ").append(FormatUtil.formatUser(m.getUser())).append(" is not muted!");
+ else
+ toUnmute.add(m);
+ });
+
+ args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a member"));
+
+ args.users.forEach(u -> builder.append("\n").append(event.getClient().getWarning()).append("The user ").append(u.getAsMention()).append(" is not in this server."));
+
+ args.ids.forEach(id -> builder.append("\n").append(event.getClient().getWarning()).append("The user <@").append(id).append("> is not in this server."));
+
+ if(toUnmute.isEmpty())
+ {
+ event.reply(builder.toString());
+ return;
+ }
+
+ if(toUnmute.size() > 5)
+ event.reactSuccess();
+
+ for(int i=0; i
+ {
+ builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully unmuted ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ }, failure ->
+ {
+ builder.append("\n").append(event.getClient().getError()).append(" Failed to unmute ").append(m.getUser().getAsMention());
+ if(last)
+ event.reply(builder.toString());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java
new file mode 100644
index 0000000..36bda1c
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.moderation;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.ModCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.VoiceChannel;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent;
+import com.jagrosh.vortex.utils.FormatUtil;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class VoicemoveCmd extends ModCommand
+{
+ public VoicemoveCmd(Vortex vortex)
+ {
+ super(vortex, Permission.VOICE_MOVE_OTHERS);
+ this.name = "voicemove";
+ this.aliases = new String[]{"magnet"};
+ this.help = "mass-moves voice channel users";
+ this.arguments = "[channel]";
+ this.guildOnly = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getGuild().getSelfMember().getVoiceState().inVoiceChannel())
+ {
+ event.replyWarning("I am already in a voice channel; move me to drag all users.");
+ return;
+ }
+ if(event.getArgs().isEmpty() && !event.getMember().getVoiceState().inVoiceChannel())
+ {
+ event.replyError("You must specify a voice channel or be in a voice channel to connect");
+ return;
+ }
+ VoiceChannel vc;
+ if(!event.getArgs().isEmpty())
+ {
+ List list = FinderUtil.findVoiceChannels(event.getArgs(), event.getGuild());
+ if(list.isEmpty())
+ {
+ event.replyError("No voice channel found matching `"+event.getArgs()+"`!");
+ return;
+ }
+ if(list.size()>1)
+ {
+ event.reply(FormatUtil.listOfVoice(list, event.getArgs()));
+ return;
+ }
+ vc = list.get(0);
+ }
+ else
+ {
+ vc = event.getMember().getVoiceState().getChannel();
+ }
+ if(!event.getMember().hasPermission(Permission.VOICE_MOVE_OTHERS))
+ {
+ event.replyError("You don't have permission to move users out of **"+vc.getName()+"**!");
+ return;
+ }
+ if(!event.getSelfMember().hasPermission(Permission.VOICE_MOVE_OTHERS))
+ {
+ event.replyError("I don't have permission to move users out of **"+vc.getName()+"**!");
+ return;
+ }
+ try {
+ event.getGuild().getAudioManager().openAudioConnection(vc);
+ } catch(Exception e) {
+ event.replyWarning("I could not connect to **"+vc.getName()+"**");
+ return;
+ }
+ vortex.getEventWaiter().waitForEvent(GuildVoiceMoveEvent.class,
+ (GuildVoiceMoveEvent e) ->
+ e.getGuild().equals(event.getGuild()) && e.getMember().equals(event.getGuild().getSelfMember()),
+ (GuildVoiceMoveEvent e) -> {
+ event.getGuild().getAudioManager().closeAudioConnection();
+ e.getChannelLeft().getMembers().stream().forEach(m -> event.getGuild().getController().moveVoiceMember(m, e.getChannelJoined()).queue());
+ }, 1, TimeUnit.MINUTES, () -> {
+ event.getGuild().getAudioManager().closeAudioConnection();
+ event.replyWarning("You waited too long, "+event.getMember().getAsMention());
+ });
+ event.reply("\uD83C\uDF9B Now, move me and I'll drag users to a new voice channel.");
+ }
+
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/owner/DebugCmd.java b/src/main/java/com/jagrosh/vortex/commands/owner/DebugCmd.java
new file mode 100644
index 0000000..3fd0c64
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/owner/DebugCmd.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2018 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.owner;
+
+import java.time.OffsetDateTime;
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.FormatUtil;
+import java.time.temporal.ChronoUnit;
+import net.dv8tion.jda.core.JDA;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class DebugCmd extends Command
+{
+ private final OffsetDateTime start = OffsetDateTime.now();
+ private final Vortex vortex;
+
+ public DebugCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "debug";
+ this.help = "shows some debug stats ";
+ this.ownerCommand = true;
+ this.guildOnly = false;
+ this.hidden = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ long totalMb = Runtime.getRuntime().totalMemory()/(1024*1024);
+ long usedMb = (Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory())/(1024*1024);
+ StringBuilder sb = new StringBuilder("**"+event.getSelfUser().getName()+"** statistics:"
+ + "\nLast Startup: "+FormatUtil.secondsToTime(start.until(OffsetDateTime.now(), ChronoUnit.SECONDS))+" ago"
+ + "\nGuilds: **"+vortex.getShardManager().getGuildCache().size()+"**"
+ + "\nMemory: **"+usedMb+"**Mb / **"+totalMb+"**Mb"
+ + "\nAverage Ping: **"+vortex.getShardManager().getAveragePing()+"**ms"
+ + "\nShard Total: **"+vortex.getShardManager().getShardsTotal()+"**"
+ + "\nShard Connectivity: ```diff");
+ vortex.getShardManager().getShards().forEach(jda -> sb.append("\n").append(jda.getStatus()==JDA.Status.CONNECTED ? "+ " : "- ")
+ .append(jda.getShardInfo().getShardId()<10 ? "0" : "").append(jda.getShardInfo().getShardId()).append(": ").append(jda.getStatus()));
+ sb.append("\n```");
+ event.reply(sb.toString().trim());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/owner/EvalCmd.java b/src/main/java/com/jagrosh/vortex/commands/owner/EvalCmd.java
new file mode 100644
index 0000000..aeee4d4
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/owner/EvalCmd.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.owner;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class EvalCmd extends Command
+{
+ private final Vortex vortex;
+
+ public EvalCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "eval";
+ this.help = "evaluates nashorn code";
+ this.ownerCommand = true;
+ this.guildOnly = false;
+ this.hidden = true;
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ ScriptEngine se = new ScriptEngineManager().getEngineByName("Nashorn");
+ se.put("bot", vortex);
+ se.put("event", event);
+ se.put("jda", event.getJDA());
+ se.put("guild", event.getGuild());
+ se.put("channel", event.getChannel());
+ event.async(() ->
+ {
+ try
+ {
+ event.replySuccess("Evaluated Successfully:\n```\n"+se.eval(event.getArgs())+" ```");
+ }
+ catch(Exception e)
+ {
+ event.replyError("An exception was thrown:\n```\n"+e+" ```");
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/MessagelogCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/MessagelogCmd.java
new file mode 100644
index 0000000..529dbcc
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/MessagelogCmd.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.LogCommand;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class MessagelogCmd extends LogCommand
+{
+ public MessagelogCmd(Vortex vortex)
+ {
+ super(vortex);
+ this.name = "messagelog";
+ this.help = "sets channel to log message edits/deletes";
+ }
+
+ @Override
+ protected void showCurrentChannel(CommandEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getMessageLogChannel(event.getGuild());
+ if(tc==null)
+ event.replyWarning("Message Logs are not currently enabled on the server.");
+ else
+ event.replySuccess("Message Logs are currently being sent in "+tc.getAsMention()
+ +(event.getSelfMember().hasPermission(tc, REQUIRED_PERMS) ? "" : "\n"+event.getClient().getWarning()+String.format(REQUIRED_ERROR, tc.getAsMention())));
+ }
+
+ @Override
+ protected void setLogChannel(CommandEvent event, TextChannel tc)
+ {
+ vortex.getDatabase().settings.setMessageLogChannel(event.getGuild(), tc);
+ if(tc==null)
+ event.replySuccess("Message Logs will not be sent");
+ else
+ event.replySuccess("Message Logs will now be sent in "+tc.getAsMention());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/ModlogCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/ModlogCmd.java
new file mode 100644
index 0000000..989ae13
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/ModlogCmd.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.LogCommand;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ModlogCmd extends LogCommand
+{
+ public ModlogCmd(Vortex vortex)
+ {
+ super(vortex);
+ this.name = "modlog";
+ this.help = "sets channel to log moderation actions";
+ this.botPermissions = new Permission[]{Permission.VIEW_AUDIT_LOGS};
+ }
+
+ @Override
+ protected void showCurrentChannel(CommandEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getModLogChannel(event.getGuild());
+ if(tc==null)
+ event.replyWarning("Moderation Logs are not currently enabled on the server.");
+ else
+ event.replySuccess("Moderation Logs are currently being sent in "+tc.getAsMention()
+ +(event.getSelfMember().hasPermission(tc, REQUIRED_PERMS) ? "" : "\n"+event.getClient().getWarning()+String.format(REQUIRED_ERROR, tc.getAsMention())));
+ }
+
+ @Override
+ protected void setLogChannel(CommandEvent event, TextChannel tc)
+ {
+ vortex.getDatabase().settings.setModLogChannel(event.getGuild(), tc);
+ vortex.getModLogger().setNeedUpdate(event.getGuild());
+ if(tc==null)
+ event.replySuccess("Moderation Logs will not be sent");
+ else
+ event.replySuccess("Moderation Logs will now be sent in "+tc.getAsMention());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/ModroleCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/ModroleCmd.java
new file mode 100644
index 0000000..88f3e90
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/ModroleCmd.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.commons.utils.FinderUtil;
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.PunishmentManager;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.OtherUtil;
+import java.util.List;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Role;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ModroleCmd extends Command
+{
+ private final Vortex vortex;
+
+ public ModroleCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "modrole";
+ this.help = "sets the moderator role";
+ this.aliases = new String[]{"moderatorrole"};
+ this.arguments = "";
+ this.category = new Category("Settings");
+ this.guildOnly = true;
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ if(event.getArgs().isEmpty())
+ {
+ return;
+ }
+
+ else if(event.getArgs().equalsIgnoreCase("none"))
+ {
+ vortex.getDatabase().settings.setModeratorRole(event.getGuild(), null);
+ event.replySuccess("Moderation commands can now only be used by members that can perform the actions manually.");
+ return;
+ }
+
+ List roles = FinderUtil.findRoles(event.getArgs(), event.getGuild());
+ if(roles.isEmpty())
+ event.replyError("No roles found called `"+event.getArgs()+"`");
+ else if (roles.size()==1)
+ {
+ vortex.getDatabase().settings.setModeratorRole(event.getGuild(), roles.get(0));
+ event.replySuccess("Users with the `"+roles.get(0).getName()+"` role can now use all Moderation commands.");
+ }
+ else
+ event.reply(FormatUtil.listOfRoles(roles, event.getArgs()));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/ServerlogCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/ServerlogCmd.java
new file mode 100644
index 0000000..180740c
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/ServerlogCmd.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.commands.LogCommand;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ServerlogCmd extends LogCommand
+{
+ public ServerlogCmd(Vortex vortex)
+ {
+ super(vortex);
+ this.name = "serverlog";
+ this.help = "sets channel to log server activity";
+ }
+
+ @Override
+ protected void showCurrentChannel(CommandEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild());
+ if(tc==null)
+ event.replyWarning("Server Logs are not currently enabled on the server.");
+ else
+ event.replySuccess("Server Logs are currently being sent in "+tc.getAsMention()
+ +(event.getSelfMember().hasPermission(tc, REQUIRED_PERMS) ? "" : "\n"+event.getClient().getWarning()+String.format(REQUIRED_ERROR, tc.getAsMention())));
+ }
+
+ @Override
+ protected void setLogChannel(CommandEvent event, TextChannel tc)
+ {
+ vortex.getDatabase().settings.setServerLogChannel(event.getGuild(), tc);
+ if(tc==null)
+ event.replySuccess("Server Logs will not be sent");
+ else
+ event.replySuccess("Server Logs will now be sent in "+tc.getAsMention());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/SetstrikesCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/SetstrikesCmd.java
new file mode 100644
index 0000000..071d7d4
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/SetstrikesCmd.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.database.managers.PunishmentManager;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.OtherUtil;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class SetstrikesCmd extends Command
+{
+ private final static String SETTING_STRIKES = "\n\nUsage: `"+Constants.PREFIX+"setstrikes [time]`\n"
+ + "`` - the number of strikes at which to perform the action\n"
+ + "`` - the action, such as `None`, `Kick`, `Mute`, `Softban`, or `Ban`\n"
+ + "`[time]` - optional, the amount of time to keep the user muted or banned\n"
+ + "Do not include <> nor [] in your commands!";
+ private final Vortex vortex;
+
+ public SetstrikesCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "setstrikes";
+ this.help = "sets the number of strikes for a punishment";
+ this.aliases = new String[]{"setstrike"};
+ this.arguments = " [time]";
+ this.category = new Category("Settings");
+ this.guildOnly = true;
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ String[] parts = event.getArgs().split("\\s+", 3);
+ if(parts.length<2)
+ {
+ event.replyError("Please include a number of strikes and an action to perform!"+SETTING_STRIKES);
+ return;
+ }
+ int numstrikes;
+ try
+ {
+ numstrikes = Integer.parseInt(parts[0]);
+ }
+ catch(NumberFormatException ex)
+ {
+ event.replyError("`"+parts[0]+"` is not a valid integer!"+SETTING_STRIKES);
+ return;
+ }
+ if(numstrikes<1 || numstrikes>PunishmentManager.MAX_STRIKES)
+ {
+ event.replyError("`` must be between 1 and "+PunishmentManager.MAX_STRIKES+"!"+SETTING_STRIKES);
+ return;
+ }
+ String successMessage;
+ switch(parts[1].toLowerCase())
+ {
+ case "none":
+ {
+ vortex.getDatabase().actions.removeAction(event.getGuild(), numstrikes);
+ successMessage = "No action will be taken on `"+numstrikes+"` strikes.";
+ break;
+ }
+ case "tempmute":
+ case "temp-mute":
+ case "mute":
+ {
+ int minutes = parts.length>2 ? OtherUtil.parseTime(parts[2])/60 : 0;
+ if(minutes<0)
+ {
+ event.replyError("Temp-Mute time cannot be negative!");
+ return;
+ }
+ vortex.getDatabase().actions.setAction(event.getGuild(), numstrikes, Action.MUTE, minutes);
+ successMessage = "Users will now be `muted` "+(minutes>0 ? "for "+FormatUtil.secondsToTime(minutes*60)+" " : "")+"upon reaching `"+numstrikes+"` strikes.";
+ break;
+ }
+ case "kick":
+ {
+ vortex.getDatabase().actions.setAction(event.getGuild(), numstrikes, Action.KICK);
+ successMessage = "Users will now be `kicked` upon reaching `"+numstrikes+"` strikes.";
+ break;
+ }
+ case "softban":
+ {
+ vortex.getDatabase().actions.setAction(event.getGuild(), numstrikes, Action.SOFTBAN);
+ successMessage = "Users will now be `softbanned` upon reaching `"+numstrikes+"` strikes.";
+ break;
+ }
+ case "tempban":
+ case "temp-ban":
+ case "ban":
+ {
+ int minutes = parts.length>2 ? OtherUtil.parseTime(parts[2])/60 : 0;
+ if(minutes<0)
+ {
+ event.replyError("Temp-Ban time cannot be negative!");
+ return;
+ }
+ vortex.getDatabase().actions.setAction(event.getGuild(), numstrikes, Action.BAN, minutes);
+ successMessage = "Users will now be `banned` "+(minutes>0 ? "for "+FormatUtil.secondsToTime(minutes*60)+" " : "")+"upon reaching `"+numstrikes+"` strikes.";
+ break;
+ }
+ default:
+ {
+ event.replyError("`"+parts[1]+"` is not a valid action!"+SETTING_STRIKES);
+ return;
+ }
+ }
+ event.reply(new MessageBuilder()
+ .append(event.getClient().getSuccess())
+ .append(" ").append(FormatUtil.filterEveryone(successMessage))
+ .setEmbed(new EmbedBuilder().setColor(event.getSelfMember().getColor())
+ .addField(vortex.getDatabase().actions.getAllPunishmentsDisplay(event.getGuild()))
+ .build())
+ .build());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/SettingsCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/SettingsCmd.java
new file mode 100644
index 0000000..7ab38a0
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/SettingsCmd.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2018 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.vortex.Vortex;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.Permission;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class SettingsCmd extends Command
+{
+ private final Vortex vortex;
+
+ public SettingsCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "settings";
+ this.category = new Category("Settings");
+ this.help = "shows current settings";
+ this.guildOnly = true;
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ }
+
+ @Override
+ protected void execute(CommandEvent event) {
+ event.getChannel().sendMessage(new MessageBuilder()
+ .append("**"+event.getSelfUser().getName()+"** settings on **"+event.getGuild().getName()+"**:")
+ .setEmbed(new EmbedBuilder()
+ .addField(vortex.getDatabase().settings.getSettingsDisplay(event.getGuild()))
+ .addField(vortex.getDatabase().actions.getAllPunishmentsDisplay(event.getGuild()))
+ .addField(vortex.getDatabase().automod.getSettingsDisplay(event.getGuild()))
+ .setColor(event.getSelfMember().getColor())
+ .build()).build()).queue();
+ }
+
+}
diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java
new file mode 100644
index 0000000..d493214
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.commands.settings;
+
+import java.util.concurrent.TimeUnit;
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import com.jagrosh.jdautilities.menu.ButtonMenu;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.OtherUtil;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.PermissionOverride;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.entities.VoiceChannel;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class SetupCmd extends Command
+{
+ private final ButtonMenu.Builder buttons;
+ private final String MUTE = "\uD83D\uDD07";
+ private final String LOGS = "\uD83D\uDCDD";
+ private final String AUTOMOD = "\uD83E\uDD16";
+
+ private final String CANCEL = "\u274C";
+ private final String CONFIRM = "\u2611";
+
+ private final Vortex vortex;
+
+ public SetupCmd(Vortex vortex)
+ {
+ this.vortex = vortex;
+ this.name = "setup";
+ this.category = new Category("Settings");
+ this.help = "server setup";
+ this.guildOnly = true;
+ this.userPermissions = new Permission[]{Permission.MANAGE_SERVER};
+ this.botPermissions = new Permission[]{Permission.ADMINISTRATOR};
+ this.buttons = new ButtonMenu.Builder()
+ .setText(Constants.SUCCESS+" **Please select a setup option**:\n\n"
+ +MUTE+" 'Muted' Role\n"
+ +AUTOMOD+" Automod\n"
+ +CANCEL+" Cancel")
+ .setChoices(MUTE,AUTOMOD,CANCEL)
+ .setEventWaiter(vortex.getEventWaiter())
+ .setTimeout(1, TimeUnit.MINUTES)
+ .setFinalAction(m -> m.delete().queue())
+ ;
+ this.cooldown = 20;
+ this.cooldownScope = CooldownScope.GUILD;
+ }
+
+ @Override
+ protected void execute(CommandEvent event)
+ {
+ buttons.setAction(re ->
+ {
+ switch(re.getName())
+ {
+ case MUTE:
+ Role muted = OtherUtil.getMutedRole(event.getGuild());
+ String confirmation;
+ if(muted!=null)
+ {
+ if(!event.getSelfMember().canInteract(muted))
+ {
+ event.replyError("I cannot interact with the existing '"+muted.getName()+"' role. Please move my role(s) higher and then try again.");
+ return;
+ }
+ if(!event.getMember().canInteract(muted))
+ {
+ event.replyError("You do not have permission to interact with the existing '"+muted.getName()+"' role.");
+ return;
+ }
+ confirmation = "This will modify the existing '"+muted.getName()+"' role and assign it overrides in every channel.";
+ }
+ else
+ confirmation = "This will create a role called 'Muted' and assign it overrides in every channel.";
+ waitForConfirmation(event, confirmation, () -> setUpMutedRole(event, muted));
+ break;
+ case AUTOMOD:
+ break;
+ }
+ }).setUsers(event.getAuthor()).build().display(event.getChannel());
+ }
+
+ private void setUpMutedRole(CommandEvent event, Role role)
+ {
+ StringBuilder sb = new StringBuilder(Constants.SUCCESS+" Muted role setup started!\n");
+ event.reply(sb + Constants.LOADING+" Initializing role...", m -> event.async(() ->
+ {
+ try
+ {
+ Role mutedRole;
+ if(role==null)
+ {
+ mutedRole = event.getGuild().getController().createRole().setName("Muted").setPermissions().setColor(1).complete();
+ }
+ else
+ {
+ role.getManager().setPermissions().complete();
+ mutedRole = role;
+ }
+ sb.append(Constants.SUCCESS+" Role initialized!\n");
+ m.editMessage(sb + Constants.LOADING+" Making Category overrides...").complete();
+ PermissionOverride po;
+ for(net.dv8tion.jda.core.entities.Category cat: event.getGuild().getCategories())
+ {
+ po = cat.getPermissionOverride(mutedRole);
+ if(po==null)
+ cat.createPermissionOverride(mutedRole).setDeny(Permission.MESSAGE_WRITE, Permission.MESSAGE_ADD_REACTION, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK).complete();
+ else
+ po.getManager().deny(Permission.MESSAGE_WRITE, Permission.MESSAGE_ADD_REACTION, Permission.VOICE_CONNECT, Permission.VOICE_SPEAK).complete();
+ }
+ sb.append(Constants.SUCCESS+" Category overrides complete!\n");
+ m.editMessage(sb + Constants.LOADING + " Making Text Channel overrides...").complete();
+ for(TextChannel tc: event.getGuild().getTextChannels())
+ {
+ po = tc.getPermissionOverride(mutedRole);
+ if(po==null)
+ tc.createPermissionOverride(mutedRole).setDeny(Permission.MESSAGE_WRITE, Permission.MESSAGE_ADD_REACTION).complete();
+ else
+ po.getManager().deny(Permission.MESSAGE_WRITE, Permission.MESSAGE_ADD_REACTION).complete();
+ }
+ sb.append(Constants.SUCCESS+" Text Channel overrides complete!\n");
+ m.editMessage(sb + Constants.LOADING + " Making Voice Channel overrides...").complete();
+ for(VoiceChannel vc: event.getGuild().getVoiceChannels())
+ {
+ po = vc.getPermissionOverride(mutedRole);
+ if(po==null)
+ vc.createPermissionOverride(mutedRole).setDeny(Permission.VOICE_CONNECT, Permission.VOICE_SPEAK).complete();
+ else
+ po.getManager().deny(Permission.VOICE_CONNECT, Permission.VOICE_SPEAK).complete();
+ }
+ m.editMessage(sb + Constants.SUCCESS+" Voice Channel overrides complete!\n\n" + Constants.SUCCESS+" Muted role setup has completed!").queue();
+ }
+ catch(Exception ex)
+ {
+ m.editMessage(sb + Constants.ERROR+" An error occurred setting up the Muted role. Please check that I have the Administrator permission and that the Muted role is below my roles.").queue();
+ }
+ }));
+ }
+
+ private void waitForConfirmation(CommandEvent event, String message, Runnable confirm)
+ {
+ new ButtonMenu.Builder()
+ .setChoices(CONFIRM, CANCEL)
+ .setEventWaiter(vortex.getEventWaiter())
+ .setTimeout(1, TimeUnit.MINUTES)
+ .setText(Constants.WARNING+" "+message+"\n\n"+CONFIRM+" Continue\n"+CANCEL+" Cancel")
+ .setFinalAction(m -> m.delete().queue())
+ .setUsers(event.getAuthor())
+ .setAction(re ->
+ {
+ if(re.getName().equals(CONFIRM))
+ confirm.run();
+ }).build().display(event.getChannel());
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/jagrosh/vortex/database/Database.java b/src/main/java/com/jagrosh/vortex/database/Database.java
new file mode 100644
index 0000000..0f5dcc7
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/Database.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database;
+
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.vortex.database.managers.*;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class Database extends DatabaseConnector
+{
+ public final AutomodManager automod; // automod settings
+ public final GuildSettingsDataManager settings; // logs and other settings
+ public final IgnoreManager ignores; // ignored roles and channels
+ public final AuditCacheManager auditcache; // cache of latest audit logs
+ public final StrikeManager strikes; // strike counts for members
+ public final PunishmentManager actions; // strike punishment settings
+ public final TempMuteManager tempmutes;
+ public final TempBanManager tempbans;
+
+ public Database(String host, String user, String pass) throws Exception
+ {
+ super(host, user, pass);
+
+ automod = new AutomodManager(this);
+ settings = new GuildSettingsDataManager(this);
+ ignores = new IgnoreManager(this);
+ auditcache = new AuditCacheManager(this);
+ strikes = new StrikeManager(this);
+ actions = new PunishmentManager(this);
+ tempmutes = new TempMuteManager(this);
+ tempbans = new TempBanManager(this);
+
+ init();
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/AuditCacheManager.java b/src/main/java/com/jagrosh/vortex/database/managers/AuditCacheManager.java
new file mode 100644
index 0000000..d2748a6
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/AuditCacheManager.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.LongColumn;
+import java.util.LinkedList;
+import java.util.List;
+import net.dv8tion.jda.core.audit.AuditLogEntry;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AuditCacheManager extends DataManager
+{
+ public static final SQLColumn GUILD_ID = new LongColumn("GUILD_ID", false, 0L, true);
+ public static final SQLColumn OLD = new LongColumn("OLD", false, 0L);
+ public static final SQLColumn OLDER = new LongColumn("OLDER", false, 0L);
+ public static final SQLColumn OLDEST = new LongColumn("OLDEST", false, 0L);
+
+ public AuditCacheManager(DatabaseConnector connector)
+ {
+ super(connector, "AUDIT_CACHE");
+ }
+
+ public List filterUncheckedEntries(List list)
+ {
+ if(list.isEmpty())
+ return list;
+ long gid = list.get(0).getGuild().getIdLong();
+ return readWrite(selectAll(GUILD_ID.is(gid)), rs ->
+ {
+ LinkedList filtered = new LinkedList<>();
+ long old, older, oldest;
+ boolean found = rs.next();
+ if(found)
+ {
+ old = OLD.getValue(rs);
+ older = OLDER.getValue(rs);
+ oldest = OLDEST.getValue(rs);
+ }
+ else
+ {
+ old = 0;
+ older = 0;
+ oldest = 0;
+ rs.moveToInsertRow();
+ }
+
+ for(AuditLogEntry entry: list)
+ if(entry.getIdLong()>oldest && entry.getIdLong()!=older && entry.getIdLong()!=old)
+ filtered.add(0, entry);
+
+ OLD.updateValue(rs, list.get(0).getIdLong());
+ if(list.size()>1)
+ {
+ OLDER.updateValue(rs, list.get(1).getIdLong());
+ if(list.size()>2)
+ OLDEST.updateValue(rs, list.get(2).getIdLong());
+ else
+ OLDEST.updateValue(rs, list.get(1).getIdLong());
+ }
+ else
+ {
+ OLDER.updateValue(rs, list.get(0).getIdLong());
+ OLDEST.updateValue(rs, list.get(0).getIdLong());
+ }
+
+ if(found)
+ rs.updateRow();
+ else
+ {
+ GUILD_ID.updateValue(rs, gid);
+ rs.insertRow();
+ }
+
+ return filtered;
+ });
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java b/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java
new file mode 100644
index 0000000..c2c8953
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.*;
+import com.jagrosh.vortex.utils.FixedCache;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.MessageEmbed.Field;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class AutomodManager extends DataManager
+{
+ public final static int MAX_STRIKES = 100;
+ public final static int MENTION_MINIMUM = 7;
+ public final static int ROLE_MENTION_MINIMUM = 2;
+ private static final String SETTINGS_TITLE = "\uD83D\uDEE1 Automod Settings";
+
+ public final static SQLColumn GUILD_ID = new LongColumn("GUILD_ID",false,0L,true);
+
+ public final static SQLColumn MAX_MENTIONS = new IntegerColumn("MAX_MENTIONS", false, 0);
+ public final static SQLColumn MAX_ROLE_MENTIONS = new IntegerColumn("MAX_ROLE_MENTIONS", false, 0);
+
+ public final static SQLColumn RAIDMODE_NUMBER = new IntegerColumn("RAIDMODE_NUMBER", false, 0);
+ public final static SQLColumn RAIDMODE_TIME = new IntegerColumn("RAIDMODE_TIME", false, 0);
+
+ public final static SQLColumn INVITE_STRIKES = new IntegerColumn("INVITE_STRIKES", false, 0);
+ public final static SQLColumn REF_STRIKES = new IntegerColumn("REF_STRIKES", false, 0);
+
+ public final static SQLColumn DUPE_STRIKES = new IntegerColumn("DUPE_STRIKES", false, 0);
+ public final static SQLColumn DUPE_DELETE_THRESH = new IntegerColumn("DUPE_DELETE_THRESH", false, 0);
+ public final static SQLColumn DUPE_STRIKE_THRESH = new IntegerColumn("DUPE_STRIKES_THRESH", false, 0);
+
+ // Cache
+ private final FixedCache cache = new FixedCache<>(1000);
+ private final AutomodSettings blankSettings = new AutomodSettings();
+
+ public AutomodManager(DatabaseConnector connector)
+ {
+ super(connector, "AUTOMOD");
+ }
+
+ // Getters
+ public AutomodSettings getSettings(Guild guild)
+ {
+ if(cache.contains(guild.getIdLong()))
+ return cache.get(guild.getIdLong());
+ AutomodSettings settings = read(selectAll(GUILD_ID.is(guild.getIdLong())), rs -> rs.next() ? new AutomodSettings(rs) : blankSettings);
+ cache.put(guild.getIdLong(), settings);
+ return settings;
+ }
+
+ public Field getSettingsDisplay(Guild guild)
+ {
+ AutomodSettings settings = getSettings(guild);
+ return new Field(SETTINGS_TITLE,
+ "__Anti-Invite__\n" + (settings.inviteStrikes==0
+ ? "Disabled\n\n"
+ : "Strikes: **" + settings.inviteStrikes + "**\n\n")
+ + "__Anti-Duplicate__\n" + (settings.useAntiDuplicate()
+ ? "Delete Threshold: **" + settings.dupeDeleteThresh + "**\n" +
+ "Strike Threshold: **" + settings.dupeStrikeThresh + "**\n" +
+ "Strikes: **" + settings.dupeStrikes + "**\n\n"
+ : "Disabled\n\n")
+ + "__Anti-Mass-Mention__\n" + (settings.maxMentions==0 && settings.maxRoleMentions==0
+ ? "Disabled\n\n"
+ : "Max User Mentions: " + (settings.maxMentions==0 ? "None\n" : "**" + settings.maxMentions + "**\n") +
+ "Max Role Mentions: " + (settings.maxRoleMentions==0 ? "None\n\n" : "**" + settings.maxRoleMentions + "**\n\n"))
+ + "__Auto Anti-Raid Mode__\n" + (settings.useAutoRaidMode()
+ ? "**" + settings.raidmodeNumber + "** joins / **" + settings.raidmodeTime + "** seconds"
+ : "Disabled"), true);
+ }
+
+ // Setters
+ public void disableMaxMentions(Guild guild)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ MAX_MENTIONS.updateValue(rs, 0);
+ MAX_ROLE_MENTIONS.updateValue(rs, 0);
+ rs.updateRow();
+ }
+ });
+ }
+
+ public void setMaxMentions(Guild guild, int max)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ MAX_MENTIONS.updateValue(rs, max);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ MAX_MENTIONS.updateValue(rs, max);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setMaxRoleMentions(Guild guild, int max)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ MAX_ROLE_MENTIONS.updateValue(rs, max);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ MAX_ROLE_MENTIONS.updateValue(rs, max);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setAutoRaidMode(Guild guild, int number, int time)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ RAIDMODE_NUMBER.updateValue(rs, number);
+ RAIDMODE_TIME.updateValue(rs, time);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ RAIDMODE_NUMBER.updateValue(rs, number);
+ RAIDMODE_TIME.updateValue(rs, time);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setInviteStrikes(Guild guild, int strikes)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ INVITE_STRIKES.updateValue(rs, strikes);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ INVITE_STRIKES.updateValue(rs, strikes);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setRefStrikes(Guild guild, int strikes)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ REF_STRIKES.updateValue(rs, strikes);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ REF_STRIKES.updateValue(rs, strikes);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setDupeSettings(Guild guild, int strikes, int deleteThresh, int strikeThresh)
+ {
+ invalidateCache(guild);
+ readWrite(selectAll(GUILD_ID.is(guild.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ DUPE_STRIKES.updateValue(rs, strikes);
+ DUPE_DELETE_THRESH.updateValue(rs, deleteThresh);
+ DUPE_STRIKE_THRESH.updateValue(rs, strikeThresh);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ DUPE_STRIKES.updateValue(rs, strikes);
+ DUPE_DELETE_THRESH.updateValue(rs, deleteThresh);
+ DUPE_STRIKE_THRESH.updateValue(rs, strikeThresh);
+ rs.insertRow();
+ }
+ });
+ }
+
+ private void invalidateCache(Guild guild)
+ {
+ cache.pull(guild.getIdLong());
+ }
+
+ public class AutomodSettings
+ {
+ public final int maxMentions, maxRoleMentions;
+ public final int raidmodeNumber, raidmodeTime;
+ public final int inviteStrikes;
+ public final int refStrikes;
+ public final int dupeStrikes, dupeDeleteThresh, dupeStrikeThresh;
+
+ private AutomodSettings()
+ {
+ this.maxMentions = 0;
+ this.maxRoleMentions = 0;
+ this.raidmodeNumber = 0;
+ this.raidmodeTime = 0;
+ this.inviteStrikes = 0;
+ this.refStrikes = 0;
+ this.dupeStrikes = 0;
+ this.dupeDeleteThresh = 0;
+ this.dupeStrikeThresh = 0;
+ }
+
+ private AutomodSettings(int maxMentions, int maxRoleMentions, int raidmodeNumber, int raidmodeTime,
+ int inviteStrikes, int refStrikes, int dupeStrikes, int dupeDeleteThresh, int dupeStrikeThresh)
+ {
+ this.maxMentions = maxMentions;
+ this.maxRoleMentions = maxRoleMentions;
+ this.raidmodeNumber = raidmodeNumber;
+ this.raidmodeTime = raidmodeTime;
+ this.inviteStrikes = inviteStrikes;
+ this.refStrikes = refStrikes;
+ this.dupeStrikes = dupeStrikes;
+ this.dupeDeleteThresh = dupeDeleteThresh;
+ this.dupeStrikeThresh = dupeStrikeThresh;
+ }
+
+ private AutomodSettings(ResultSet rs) throws SQLException
+ {
+ this(MAX_MENTIONS.getValue(rs), MAX_ROLE_MENTIONS.getValue(rs), RAIDMODE_NUMBER.getValue(rs), RAIDMODE_TIME.getValue(rs),
+ INVITE_STRIKES.getValue(rs), REF_STRIKES.getValue(rs), DUPE_STRIKES.getValue(rs), DUPE_DELETE_THRESH.getValue(rs),
+ DUPE_STRIKE_THRESH.getValue(rs));
+ }
+
+ public boolean useAutoRaidMode()
+ {
+ return raidmodeNumber>1 && raidmodeTime>1;
+ }
+
+ public boolean useAntiDuplicate()
+ {
+ return dupeStrikes>0 && dupeDeleteThresh>0 && dupeStrikeThresh>0;
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java b/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java
new file mode 100644
index 0000000..3f176ba
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java
@@ -0,0 +1,291 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.*;
+import com.jagrosh.jdautilities.command.GuildSettingsManager;
+import com.jagrosh.jdautilities.command.GuildSettingsProvider;
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.utils.FixedCache;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.time.ZoneId;
+import java.util.Collection;
+import java.util.Collections;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Guild.VerificationLevel;
+import net.dv8tion.jda.core.entities.MessageEmbed.Field;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class GuildSettingsDataManager extends DataManager implements GuildSettingsManager
+{
+ public final static int PREFIX_MAX_LENGTH = 40;
+ private static final String SETTINGS_TITLE = "\uD83D\uDCCA Server Settings";
+
+ public final static SQLColumn GUILD_ID = new LongColumn("GUILD_ID",false,0L,true);
+ public final static SQLColumn MOD_ROLE_ID = new LongColumn("MOD_ROLE_ID",false,0L);
+
+ public final static SQLColumn MODLOG_ID = new LongColumn("MODLOG_ID",false,0L);
+ public final static SQLColumn SERVERLOG_ID = new LongColumn("SERVERLOG_ID",false,0L);
+ public final static SQLColumn MESSAGELOG_ID = new LongColumn("MESSAGELOG_ID",false,0L);
+
+ public final static SQLColumn PREFIX = new StringColumn("PREFIX", true, null, PREFIX_MAX_LENGTH);
+ public final static SQLColumn TIMEZONE = new StringColumn("TIMEZONE",true,null,32);
+
+ public final static SQLColumn RAIDMODE = new IntegerColumn("RAIDMODE",false,-2); // -2 = Raid Mode not activated, -1+ = Raid Mode active, level to set permission when finished
+
+ // Cache
+ private final FixedCache cache = new FixedCache<>(1000);
+ private final GuildSettings blankSettings = new GuildSettings();
+
+ public GuildSettingsDataManager(DatabaseConnector connector)
+ {
+ super(connector, "GUILD_SETTINGS");
+ }
+
+ // Getters
+ @Override
+ public GuildSettings getSettings(Guild guild)
+ {
+ if(cache.contains(guild.getIdLong()))
+ return cache.get(guild.getIdLong());
+ GuildSettings settings = read(selectAll(GUILD_ID.is(guild.getIdLong())), rs -> rs.next() ? new GuildSettings(rs) : blankSettings);
+ cache.put(guild.getIdLong(), settings);
+ return settings;
+ }
+
+ public Field getSettingsDisplay(Guild guild)
+ {
+ GuildSettings settings = getSettings(guild);
+ TextChannel modlog = settings.getModLogChannel(guild);
+ TextChannel serverlog = settings.getServerLogChannel(guild);
+ TextChannel messagelog = settings.getMessageLogChannel(guild);
+ Role modrole = settings.getModeratorRole(guild);
+ return new Field(SETTINGS_TITLE, "Prefix: `"+(settings.prefix==null ? Constants.PREFIX : settings.prefix)+"`"
+ + "\nMod Role: "+(modrole==null ? "None" : modrole.getAsMention())
+ + "\nMod Log: "+(modlog==null ? "None" : modlog.getAsMention())
+ + "\nMsg Log: "+(messagelog==null ? "None" : messagelog.getAsMention())
+ + "\nServer Log: "+(serverlog==null ? "None" : serverlog.getAsMention())
+ + "\nTimezone: **"+settings.timezone+"**", true);
+ }
+
+ // Setters
+ public void setModLogChannel(Guild guild, TextChannel tc)
+ {
+ invalidateCache(guild);
+ readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, MODLOG_ID), rs ->
+ {
+ if(rs.next())
+ {
+ MODLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ MODLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setServerLogChannel(Guild guild, TextChannel tc)
+ {
+ invalidateCache(guild);
+ readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, SERVERLOG_ID), rs ->
+ {
+ if(rs.next())
+ {
+ SERVERLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ SERVERLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setMessageLogChannel(Guild guild, TextChannel tc)
+ {
+ invalidateCache(guild);
+ readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, MESSAGELOG_ID), rs ->
+ {
+ if(rs.next())
+ {
+ MESSAGELOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ MESSAGELOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong());
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void setModeratorRole(Guild guild, Role role)
+ {
+ invalidateCache(guild);
+ readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, MOD_ROLE_ID), rs ->
+ {
+ if(rs.next())
+ {
+ MOD_ROLE_ID.updateValue(rs, role==null ? 0L : role.getIdLong());
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ MOD_ROLE_ID.updateValue(rs, role==null ? 0L : role.getIdLong());
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void enableRaidMode(Guild guild)
+ {
+ invalidateCache(guild);
+ readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, RAIDMODE), rs ->
+ {
+ if(rs.next())
+ {
+ RAIDMODE.updateValue(rs, guild.getVerificationLevel().getKey());
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ RAIDMODE.updateValue(rs, guild.getVerificationLevel().getKey());
+ rs.insertRow();
+ }
+ });
+ }
+
+ public VerificationLevel disableRaidMode(Guild guild)
+ {
+ invalidateCache(guild);
+ return readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, RAIDMODE), rs ->
+ {
+ VerificationLevel old = null;
+ if(rs.next())
+ {
+ old = VerificationLevel.fromKey(RAIDMODE.getValue(rs));
+ RAIDMODE.updateValue(rs, -2);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ RAIDMODE.updateValue(rs, -2);
+ rs.insertRow();
+ }
+ return old;
+ });
+ }
+
+ private void invalidateCache(Guild guild)
+ {
+ cache.pull(guild.getIdLong());
+ }
+
+ public class GuildSettings implements GuildSettingsProvider
+ {
+ private final long modRole, modlog, serverlog, messagelog;
+ private final String prefix;
+ private final ZoneId timezone;
+ private final int raidMode;
+
+ private GuildSettings()
+ {
+ this.modRole = 0;
+ this.modlog = 0;
+ this.serverlog = 0;
+ this.messagelog = 0;
+ this.prefix = null;
+ this.timezone = ZoneId.systemDefault();
+ this.raidMode = -2;
+ }
+
+ private GuildSettings(ResultSet rs) throws SQLException
+ {
+ this.modRole = MOD_ROLE_ID.getValue(rs);
+ this.modlog = MODLOG_ID.getValue(rs);
+ this.serverlog = SERVERLOG_ID.getValue(rs);
+ this.messagelog = MESSAGELOG_ID.getValue(rs);
+ this.prefix = PREFIX.getValue(rs);
+ String str = TIMEZONE.getValue(rs);
+ this.timezone = str==null ? ZoneId.systemDefault() : ZoneId.of(str);
+ this.raidMode = RAIDMODE.getValue(rs);
+ }
+
+ public Role getModeratorRole(Guild guild)
+ {
+ return guild.getRoleById(modRole);
+ }
+
+ public TextChannel getModLogChannel(Guild guild)
+ {
+ return guild.getTextChannelById(modlog);
+ }
+
+ public TextChannel getServerLogChannel(Guild guild)
+ {
+ return guild.getTextChannelById(serverlog);
+ }
+
+ public TextChannel getMessageLogChannel(Guild guild)
+ {
+ return guild.getTextChannelById(messagelog);
+ }
+
+ public ZoneId getTimezone()
+ {
+ return timezone;
+ }
+
+ @Override
+ public Collection getPrefixes()
+ {
+ if(prefix==null || prefix.isEmpty())
+ return null;
+ return Collections.singleton(prefix);
+ }
+
+ public boolean isInRaidMode()
+ {
+ return raidMode != -2;
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/IgnoreManager.java b/src/main/java/com/jagrosh/vortex/database/managers/IgnoreManager.java
new file mode 100644
index 0000000..6ffc990
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/IgnoreManager.java
@@ -0,0 +1,183 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.*;
+import com.jagrosh.vortex.utils.FixedCache;
+import java.sql.ResultSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class IgnoreManager extends DataManager
+{
+ public final static SQLColumn GUILD_ID = new LongColumn("GUILD_ID",false,0L);
+ public final static SQLColumn ENTITY_ID = new LongColumn("ENTITY_ID",false,0L,true);
+ public final static SQLColumn TYPE = new IntegerColumn("TYPE",false,0);
+
+ private final FixedCache> cache = new FixedCache<>(1000);
+
+ public IgnoreManager(DatabaseConnector connector)
+ {
+ super(connector, "IGNORED");
+ }
+
+ public boolean isIgnored(TextChannel tc)
+ {
+ return read(select(ENTITY_ID.is(tc.getIdLong()), ENTITY_ID), ResultSet::next);
+ }
+
+ public boolean isIgnored(Member member)
+ {
+ if(cache.contains(member.getGuild().getIdLong()))
+ {
+ for(long rid: cache.get(member.getGuild().getIdLong()))
+ if(member.getRoles().stream().anyMatch(r -> r.getIdLong()==rid))
+ return true;
+ return false;
+ }
+ return read(select(GUILD_ID.is(member.getGuild().getId())+" AND "+TYPE.is(Type.ROLE.ordinal()), ENTITY_ID), rs ->
+ {
+ while(rs.next())
+ {
+ long id = ENTITY_ID.getValue(rs);
+ if(member.getRoles().stream().anyMatch(r -> r.getIdLong()==id))
+ return true;
+ }
+ return false;
+ });
+ }
+
+ public boolean ignore(TextChannel tc)
+ {
+ return readWrite(selectAll(GUILD_ID.is(tc.getGuild().getIdLong())+" AND "+ENTITY_ID.is(tc.getIdLong())), rs ->
+ {
+ if(rs.next())
+ return false;
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, tc.getGuild().getIdLong());
+ ENTITY_ID.updateValue(rs, tc.getIdLong());
+ TYPE.updateValue(rs, Type.TEXT_CHANNEL.ordinal());
+ rs.insertRow();
+ return true;
+ });
+ }
+
+ public boolean ignore(Role role)
+ {
+ invalidateCache(role.getGuild());
+ return readWrite(selectAll(GUILD_ID.is(role.getGuild().getIdLong())+" AND "+ENTITY_ID.is(role.getIdLong())), rs ->
+ {
+ if(rs.next())
+ return false;
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, role.getGuild().getIdLong());
+ ENTITY_ID.updateValue(rs, role.getIdLong());
+ TYPE.updateValue(rs, Type.ROLE.ordinal());
+ rs.insertRow();
+ return true;
+ });
+ }
+
+ public List getIgnoredChannels(Guild guild)
+ {
+ return read(selectAll(GUILD_ID.is(guild.getIdLong())+" AND "+TYPE.is(Type.TEXT_CHANNEL.ordinal())), rs ->
+ {
+ List list = new LinkedList<>();
+ while(rs.next())
+ {
+ TextChannel tc = guild.getTextChannelById(ENTITY_ID.getValue(rs));
+ if(tc!=null)
+ list.add(tc);
+ }
+ return list;
+ });
+ }
+
+ public List getIgnoredRoles(Guild guild)
+ {
+ if(cache.contains(guild.getIdLong()))
+ {
+ List list = new LinkedList<>();
+ for(long rid: cache.get(guild.getIdLong()))
+ {
+ Role role = guild.getRoleById(rid);
+ if(role!=null)
+ list.add(role);
+ }
+ return list;
+ }
+ return read(selectAll(GUILD_ID.is(guild.getIdLong())+" AND "+TYPE.is(Type.ROLE.ordinal())), rs ->
+ {
+ List list = new LinkedList<>();
+ while(rs.next())
+ {
+ Role role = guild.getRoleById(ENTITY_ID.getValue(rs));
+ if(role!=null)
+ list.add(role);
+ }
+ return list;
+ });
+ }
+
+ public boolean unignore(TextChannel tc)
+ {
+ return readWrite(selectAll(GUILD_ID.is(tc.getGuild().getIdLong())+" AND "+ENTITY_ID.is(tc.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ rs.deleteRow();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ public boolean unignore(Role role)
+ {
+ invalidateCache(role.getGuild());
+ return readWrite(selectAll(GUILD_ID.is(role.getGuild().getIdLong())+" AND "+ENTITY_ID.is(role.getIdLong())), rs ->
+ {
+ if(rs.next())
+ {
+ rs.deleteRow();
+ return true;
+ }
+ return false;
+ });
+ }
+
+ private void invalidateCache(Guild guild)
+ {
+ cache.pull(guild.getIdLong());
+ }
+
+ private enum Type
+ {
+ TEXT_CHANNEL, ROLE
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java b/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java
new file mode 100644
index 0000000..e86960e
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.*;
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.utils.FormatUtil;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.stream.Collectors;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.MessageEmbed.Field;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class PunishmentManager extends DataManager
+{
+ public static final int MAX_STRIKES = 100;
+ private static final String STRIKES_TITLE = Action.STRIKE.getEmoji()+" Punishments";
+
+
+ public static final String DEFAULT_SETUP_MESSAGE = "\n" + Constants.WARNING + "It looks like you've set up some automoderation without assigning any punishments! "
+ + "I've gone ahead and set up some default punishments; you can see the settings with `" + Constants.PREFIX
+ + "settings` and set or change any punishments with the `" + Constants.PREFIX+"setstrikes` command!";
+ private static final int[] DEFAULT_STRIKE_COUNTS = {2, 3, 4, 5, 6, 7};
+ private static final Action[] DEFAULT_ACTIONS = {Action.TEMPMUTE, Action.TEMPMUTE, Action.KICK, Action.SOFTBAN, Action.TEMPBAN, Action.BAN};
+ private static final int[] DEFAULT_TIMES = {10, 120, 0, 0, 1440, 0};
+
+ public static final SQLColumn GUILD_ID = new LongColumn("GUILD_ID", false, 0L);
+ public static final SQLColumn NUM_STRIKES = new IntegerColumn("NUM_STRIKES", false, 0);
+ public static final SQLColumn ACTION = new IntegerColumn("ACTION", false, 0);
+ public static final SQLColumn TIME = new IntegerColumn("TIME", false, 0);
+
+ public PunishmentManager(DatabaseConnector connector)
+ {
+ super(connector, "ACTIONS");
+ }
+
+ @Override
+ protected String primaryKey()
+ {
+ return GUILD_ID+", "+NUM_STRIKES;
+ }
+
+ public boolean useDefaultSettings(Guild guild) // only activates if none set
+ {
+ return readWrite(selectAll(GUILD_ID.is(guild.getId())), rs ->
+ {
+ if(rs.next())
+ return false;
+ for(int i=0; i
+ {
+ if(rs.next())
+ rs.deleteRow();
+ });
+ }
+
+ public void setAction(Guild guild, int numStrikes, Action action)
+ {
+ setAction(guild, numStrikes, action, 0);
+ }
+
+ public void setAction(Guild guild, int numStrikes, Action action, int time)
+ {
+ Action act;
+ if(action==Action.MUTE && time>0)
+ act = Action.TEMPMUTE;
+ else if(action==Action.TEMPMUTE && time==0)
+ act = Action.MUTE;
+ else if(action==Action.BAN && time>0)
+ act = Action.TEMPBAN;
+ else if(action==Action.TEMPBAN && time==0)
+ act = Action.BAN;
+ else
+ act = action;
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+NUM_STRIKES.is(numStrikes)), rs ->
+ {
+ if(rs.next())
+ {
+ ACTION.updateValue(rs, act.getBit());
+ TIME.updateValue(rs, time);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ NUM_STRIKES.updateValue(rs, numStrikes);
+ ACTION.updateValue(rs, act.getBit());
+ TIME.updateValue(rs, time);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public List getAllPunishments(Guild guild)
+ {
+ return read(selectAll(GUILD_ID.is(guild.getId())), rs ->
+ {
+ List list = new LinkedList<>();
+ while(rs.next())
+ list.add(new Punishment(rs));
+ return list;
+ });
+ }
+
+ public List getPunishments(Guild guild, int from, int to)
+ {
+ List punishments = getAllPunishments(guild);
+ if(punishments.isEmpty())
+ return punishments;
+ List filtered = punishments.stream().filter(p -> p.numStrikes>from && p.numStrikes<=to).collect(Collectors.toList());
+ if(!filtered.isEmpty())
+ return filtered;
+ Punishment max = punishments.get(0);
+ for(Punishment p: punishments)
+ if(p.numStrikes>max.numStrikes)
+ max = p;
+ return Collections.singletonList(max);
+ }
+
+ public Field getAllPunishmentsDisplay(Guild guild)
+ {
+ List all = getAllPunishments(guild);
+ if(all.isEmpty())
+ return new Field(STRIKES_TITLE, "No strikes set!", true);
+ all.sort((a,b) -> a.numStrikes-b.numStrikes);
+ StringBuilder sb = new StringBuilder();
+ all.forEach(p -> sb.append("\n`").append(p.numStrikes).append(" ").append(Action.STRIKE.getEmoji()).append("`: **")
+ .append(FormatUtil.capitalize(p.action.name())).append("** ").append(p.action.getEmoji())
+ .append(p.time>0 ? " "+FormatUtil.secondsToTimeCompact(p.time*60) : ""));
+ return new Field(STRIKES_TITLE, sb.toString().trim(), true);
+ }
+
+ public class Punishment
+ {
+ public final Action action;
+ public final int numStrikes;
+ public final int time;
+
+ private Punishment(ResultSet rs) throws SQLException
+ {
+ this.action = Action.fromBit(ACTION.getValue(rs));
+ this.numStrikes = NUM_STRIKES.getValue(rs);
+ this.time = TIME.getValue(rs);
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/StrikeManager.java b/src/main/java/com/jagrosh/vortex/database/managers/StrikeManager.java
new file mode 100644
index 0000000..bc00962
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/StrikeManager.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.*;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class StrikeManager extends DataManager
+{
+ public final static SQLColumn GUILD_ID = new LongColumn("GUILD_ID", false, 0L);
+ public final static SQLColumn USER_ID = new LongColumn("USER_ID", false, 0L);
+ public final static SQLColumn STRIKES = new IntegerColumn("STRIKES", false, 0);
+
+ public StrikeManager(DatabaseConnector connector)
+ {
+ super(connector, "STRIKES");
+ }
+
+ @Override
+ protected String primaryKey()
+ {
+ return GUILD_ID+", "+USER_ID;
+ }
+
+ public int[] addStrikes(Member target, int strikes)
+ {
+ return addStrikes(target.getGuild(), target.getUser().getIdLong(), strikes);
+ }
+
+ public int[] addStrikes(Guild guild, long targetId, int strikes)
+ {
+ return readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(targetId)), rs ->
+ {
+ if(rs.next())
+ {
+ int current = STRIKES.getValue(rs);
+ STRIKES.updateValue(rs, current+strikes<0 ? 0 : current+strikes);
+ rs.updateRow();
+ return new int[]{current, current+strikes<0 ? 0 : current+strikes};
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ USER_ID.updateValue(rs, targetId);
+ STRIKES.updateValue(rs, strikes<0 ? 0 : strikes);
+ rs.insertRow();
+ return new int[]{0, strikes<0 ? 0 : strikes};
+ }
+ });
+ }
+
+ public int[] setStrikes(Member target, int strikes)
+ {
+ return setStrikes(target.getGuild(), target.getUser().getIdLong(), strikes);
+ }
+
+ public int[] setStrikes(Guild guild, long targetId, int strikes)
+ {
+ return readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(targetId)), rs ->
+ {
+ if(rs.next())
+ {
+ int current = STRIKES.getValue(rs);
+ STRIKES.updateValue(rs, strikes<0 ? 0 : strikes);
+ rs.updateRow();
+ return new int[]{current, strikes<0 ? 0 : strikes};
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ USER_ID.updateValue(rs, targetId);
+ STRIKES.updateValue(rs, strikes<0 ? 0 : strikes);
+ rs.insertRow();
+ return new int[]{0, strikes<0 ? 0 : strikes};
+ }
+ });
+ }
+
+ public int[] removeStrikes(Member target, int strikes)
+ {
+ return removeStrikes(target.getGuild(), target.getUser().getIdLong(), strikes);
+ }
+
+ public int[] removeStrikes(Guild guild, long targetId, int strikes)
+ {
+ return addStrikes(guild, targetId, -1*strikes);
+ }
+
+ public int getStrikes(Member target)
+ {
+ return getStrikes(target.getGuild(), target.getUser().getIdLong());
+ }
+
+ public int getStrikes(Guild guild, long targetId)
+ {
+ return read(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(targetId)), rs ->
+ {
+ if(rs.next())
+ return STRIKES.getValue(rs);
+ return 0;
+ });
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/TempBanManager.java b/src/main/java/com/jagrosh/vortex/database/managers/TempBanManager.java
new file mode 100644
index 0000000..ab27a41
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/TempBanManager.java
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.InstantColumn;
+import com.jagrosh.easysql.columns.LongColumn;
+import java.time.Instant;
+import net.dv8tion.jda.core.JDA;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Guild;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class TempBanManager extends DataManager
+{
+ public static final SQLColumn GUILD_ID = new LongColumn("GUILD_ID", false, 0);
+ public static final SQLColumn USER_ID = new LongColumn("USER_ID", false, 0);
+ public static final SQLColumn FINISH = new InstantColumn("FINISH", false, Instant.EPOCH);
+
+ public TempBanManager(DatabaseConnector connector)
+ {
+ super(connector, "TEMP_BANS");
+ }
+
+ @Override
+ protected String primaryKey()
+ {
+ return GUILD_ID+", "+USER_ID;
+ }
+
+ public void setBan(Guild guild, long userId, Instant finish)
+ {
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(userId)), rs ->
+ {
+ if(rs.next())
+ {
+ FINISH.updateValue(rs, finish);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ USER_ID.updateValue(rs, userId);
+ FINISH.updateValue(rs, finish);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void clearBan(Guild guild, long userId)
+ {
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(userId)), rs ->
+ {
+ if(rs.next())
+ rs.deleteRow();
+ });
+ }
+
+ public void checkUnbans(Guild guild)
+ {
+ if(!guild.isAvailable())
+ return;
+ if(!guild.getSelfMember().hasPermission(Permission.BAN_MEMBERS))
+ return;
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+FINISH.isLessThan(Instant.now().getEpochSecond())), rs ->
+ {
+ while(rs.next())
+ {
+ guild.getController().unban(Long.toString(USER_ID.getValue(rs))).reason("Temporary Ban Completed").queue(s->{}, f->{});
+ rs.deleteRow();
+ }
+ });
+ }
+
+ public void checkUnbans(JDA jda)
+ {
+ jda.getGuilds().forEach(g -> checkUnbans(g));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/database/managers/TempMuteManager.java b/src/main/java/com/jagrosh/vortex/database/managers/TempMuteManager.java
new file mode 100644
index 0000000..18745f4
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/database/managers/TempMuteManager.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.database.managers;
+
+import com.jagrosh.easysql.DataManager;
+import com.jagrosh.easysql.DatabaseConnector;
+import com.jagrosh.easysql.SQLColumn;
+import com.jagrosh.easysql.columns.InstantColumn;
+import com.jagrosh.easysql.columns.LongColumn;
+import java.sql.ResultSet;
+import java.time.Instant;
+import net.dv8tion.jda.core.JDA;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class TempMuteManager extends DataManager
+{
+ public static final SQLColumn GUILD_ID = new LongColumn("GUILD_ID", false, 0);
+ public static final SQLColumn USER_ID = new LongColumn("USER_ID", false, 0);
+ public static final SQLColumn FINISH = new InstantColumn("FINISH", false, Instant.EPOCH);
+
+ public TempMuteManager(DatabaseConnector connector)
+ {
+ super(connector, "TEMP_MUTES");
+ }
+
+ @Override
+ protected String primaryKey()
+ {
+ return GUILD_ID+", "+USER_ID;
+ }
+
+ public boolean isMuted(Member member)
+ {
+ return read(selectAll(GUILD_ID.is(member.getGuild().getId())+" AND "+USER_ID.is(member.getUser().getId())+" AND "+FINISH.isGreaterThan(Instant.now().getEpochSecond())),
+ rs -> {return rs.next();});
+ }
+
+ public void setMute(Guild guild, long userId, Instant finish)
+ {
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(userId)), rs ->
+ {
+ if(rs.next())
+ {
+ if(FINISH.getValue(rs).isBefore(finish))
+ {
+ FINISH.updateValue(rs, finish);
+ rs.updateRow();
+ }
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ USER_ID.updateValue(rs, userId);
+ FINISH.updateValue(rs, finish);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void overrideMute(Guild guild, long userId, Instant finish)
+ {
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+USER_ID.is(userId)), rs ->
+ {
+ if(rs.next())
+ {
+ FINISH.updateValue(rs, finish);
+ rs.updateRow();
+ }
+ else
+ {
+ rs.moveToInsertRow();
+ GUILD_ID.updateValue(rs, guild.getIdLong());
+ USER_ID.updateValue(rs, userId);
+ FINISH.updateValue(rs, finish);
+ rs.insertRow();
+ }
+ });
+ }
+
+ public void checkUnmutes(Guild guild)
+ {
+ if(!guild.isAvailable())
+ return;
+ if(!guild.getSelfMember().hasPermission(Permission.MANAGE_ROLES))
+ return;
+ Role muted = guild.getRoles().stream().filter(r -> r.getName().equalsIgnoreCase("Muted")).findFirst().orElse(null);
+ if(muted==null || !guild.getSelfMember().canInteract(muted))
+ return;
+ readWrite(selectAll(GUILD_ID.is(guild.getId())+" AND "+FINISH.isLessThan(Instant.now().getEpochSecond())), rs ->
+ {
+ while(rs.next())
+ {
+ Member m = guild.getMemberById(USER_ID.getValue(rs));
+ if(m!=null && m.getRoles().contains(muted))
+ guild.getController().removeSingleRoleFromMember(m, muted).reason("Temporary Mute Completed").queue();
+ rs.deleteRow();
+ }
+ });
+ }
+
+ public void checkUnmutes(JDA jda)
+ {
+ jda.getGuilds().forEach(g -> checkUnmutes(g));
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java b/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java
new file mode 100644
index 0000000..6987059
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.logging;
+
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import java.awt.Color;
+import java.time.OffsetDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.temporal.ChronoUnit;
+import java.util.List;
+import net.dv8tion.jda.core.EmbedBuilder;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.MessageEmbed;
+import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberJoinEvent;
+import net.dv8tion.jda.core.events.guild.member.GuildMemberLeaveEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceJoinEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceLeaveEvent;
+import net.dv8tion.jda.core.events.guild.voice.GuildVoiceMoveEvent;
+import net.dv8tion.jda.core.events.user.UserNameUpdateEvent;
+import net.dv8tion.jda.core.exceptions.PermissionException;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class BasicLogger
+{
+ private final Vortex vortex;
+
+ public BasicLogger(Vortex vortex)
+ {
+ this.vortex = vortex;
+ }
+
+ private void log(OffsetDateTime now, TextChannel tc, String emote, String message, MessageEmbed embed)
+ {
+ try
+ {
+ tc.sendMessage(new MessageBuilder()
+ .append(FormatUtil.filterEveryone(LogUtil.basiclogFormat(now, vortex.getDatabase().settings.getSettings(tc.getGuild()).getTimezone(), emote, message)))
+ .setEmbed(embed)
+ .build()).queue();
+ }
+ catch(PermissionException ex)
+ {
+
+ }
+ }
+
+ // Message Logs
+
+ public void logMessageEdit(Message newMessage, Message oldMessage)
+ {
+ if(oldMessage==null)
+ return;
+ TextChannel tc = vortex.getDatabase().settings.getSettings(newMessage.getGuild()).getMessageLogChannel(newMessage.getGuild());
+ if(tc==null)
+ return;
+ EmbedBuilder edit = new EmbedBuilder()
+ .setColor(Color.YELLOW)
+ .appendDescription("**From:** ")
+ .appendDescription(FormatUtil.formatMessage(oldMessage));
+ String newm = FormatUtil.formatMessage(newMessage);
+ if(edit.getDescriptionBuilder().length()+9+newm.length()>2048)
+ edit.addField("To:", newm.length()>1024 ? newm.substring(0,1016)+" (...)" : newm, false);
+ else
+ edit.appendDescription("\n**To:** "+newm);
+ log(newMessage.getEditedTime(), tc, "\u26A0",
+ FormatUtil.formatUser(newMessage.getAuthor())+" edited a message in "+newMessage.getTextChannel().getAsMention()+":", edit.build());
+ }
+
+ public void logMessageDelete(Message oldMessage)
+ {
+ if(oldMessage==null)
+ return;
+ TextChannel tc = vortex.getDatabase().settings.getSettings(oldMessage.getGuild()).getMessageLogChannel(oldMessage.getGuild());
+ if(tc==null)
+ return;
+ String formatted = FormatUtil.formatMessage(oldMessage);
+ if(formatted.isEmpty())
+ return;
+ EmbedBuilder delete = new EmbedBuilder()
+ .setColor(Color.RED)
+ .appendDescription(formatted);
+ log(OffsetDateTime.now(), tc, "\u274C",
+ FormatUtil.formatUser(oldMessage.getAuthor())+"'s message has been deleted from "+oldMessage.getTextChannel().getAsMention()+":", delete.build());
+ }
+
+ public void logMessageBulkDelete(List messages, int count, TextChannel text)
+ {
+ if(count==0)
+ return;
+ TextChannel tc = vortex.getDatabase().settings.getSettings(text.getGuild()).getMessageLogChannel(text.getGuild());
+ if(tc==null)
+ return;
+ vortex.getTextUploader().upload(LogUtil.logMessages("Deleted Messages", messages), "DeletedMessages", (view, download) ->
+ {
+ log(OffsetDateTime.now(), tc, "\uD83D\uDEAE", "**"+count+"** messages were deleted from "+text.getAsMention()+" (**"+messages.size()+"** logged):",
+ new EmbedBuilder().setColor(Color.RED.darker().darker())
+ .appendDescription("[`\uD83D\uDCC4 View`]("+view+") | [`\uD83D\uDCE9 Download`]("+download+")").build());
+ });
+ }
+
+
+ // Server Logs
+
+ public void logNameChange(UserNameUpdateEvent event)
+ {
+ OffsetDateTime now = OffsetDateTime.now();
+ event.getUser().getMutualGuilds().stream()
+ .map(guild -> vortex.getDatabase().settings.getSettings(guild).getServerLogChannel(guild))
+ .filter(tc -> tc!=null)
+ .forEachOrdered(tc ->
+ {
+ log(now, tc, "\uD83D\uDCDB",
+ "**"+event.getOldName()+"**#"+event.getOldDiscriminator()+" (ID:"+event.getUser().getId()+") has changed names to "+FormatUtil.formatUser(event.getUser()), null);
+ });
+ }
+
+ public void logGuildJoin(GuildMemberJoinEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild());
+ if(tc==null)
+ return;
+ OffsetDateTime now = OffsetDateTime.now();
+ long seconds = event.getUser().getCreationTime().until(now, ChronoUnit.SECONDS);
+ log(now, tc, "\uD83D\uDCE5", FormatUtil.formatFullUser(event.getUser())+" joined the server. "
+ +(seconds<16*60 ? "NEW" : "")
+ +"\nCreation: "+event.getUser().getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+" ("+FormatUtil.secondsToTimeCompact(seconds)+" ago)", null);
+ }
+
+ public void logGuildLeave(GuildMemberLeaveEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild());
+ if(tc==null)
+ return;
+ OffsetDateTime now = OffsetDateTime.now();
+ long seconds = event.getMember().getJoinDate().until(now, ChronoUnit.SECONDS);
+ StringBuilder rlist;
+ if(event.getMember().getRoles().isEmpty())
+ rlist = new StringBuilder();
+ else
+ {
+ rlist= new StringBuilder("\nRoles: `"+event.getMember().getRoles().get(0).getName());
+ for(int i=1; i",
+ FormatUtil.formatFullUser(event.getMember().getUser())+" has joined voice channel _"+event.getChannelJoined().getName()+"_", null);
+ }
+
+ public void logVoiceMove(GuildVoiceMoveEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild());
+ if(tc==null)
+ return;
+ log(OffsetDateTime.now(), tc, "<:voicechange:314043907992190987>",
+ FormatUtil.formatFullUser(event.getMember().getUser())+" has moved voice channels from _"+event.getChannelLeft().getName()+"_ to _"+event.getChannelJoined()+"_", null);
+ }
+
+ public void logVoiceLeave(GuildVoiceLeaveEvent event)
+ {
+ TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild());
+ if(tc==null)
+ return;
+ log(OffsetDateTime.now(), tc, "<:voiceleave:314044543609864193>",
+ FormatUtil.formatFullUser(event.getMember().getUser())+" has left voice channel _"+event.getChannelLeft().getName()+"_", null);
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/logging/MessageCache.java b/src/main/java/com/jagrosh/vortex/logging/MessageCache.java
new file mode 100644
index 0000000..94c8612
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/logging/MessageCache.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.logging;
+
+import com.jagrosh.vortex.utils.FixedCache;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Message;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class MessageCache
+{
+ private final static int SIZE = 1000;
+ private final HashMap> cache = new HashMap<>();
+
+ public Message putMessage(Message m)
+ {
+ if(!cache.containsKey(m.getGuild().getIdLong()))
+ cache.put(m.getGuild().getIdLong(), new FixedCache<>(SIZE));
+ return cache.get(m.getGuild().getIdLong()).put(m.getIdLong(), m);
+ }
+
+ public Message pullMessage(Guild guild, long messageId)
+ {
+ if(!cache.containsKey(guild.getIdLong()))
+ return null;
+ return cache.get(guild.getIdLong()).pull(messageId);
+ }
+
+ public List getMessages(Guild guild, Predicate predicate)
+ {
+ if(!cache.containsKey(guild.getIdLong()))
+ return Collections.EMPTY_LIST;
+ return cache.get(guild.getIdLong()).getValues().stream().filter(predicate).collect(Collectors.toList());
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/logging/ModLogger.java b/src/main/java/com/jagrosh/vortex/logging/ModLogger.java
new file mode 100644
index 0000000..967d3cb
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/logging/ModLogger.java
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.logging;
+
+import com.jagrosh.vortex.Action;
+import com.jagrosh.vortex.Vortex;
+import com.jagrosh.vortex.utils.FixedCache;
+import com.jagrosh.vortex.utils.FormatUtil;
+import com.jagrosh.vortex.utils.LogUtil;
+import com.jagrosh.vortex.utils.LogUtil.ParsedAuditReason;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import net.dv8tion.jda.core.MessageBuilder;
+import net.dv8tion.jda.core.Permission;
+import net.dv8tion.jda.core.audit.AuditLogChange;
+import net.dv8tion.jda.core.audit.AuditLogEntry;
+import net.dv8tion.jda.core.audit.AuditLogKey;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.MessageEmbed;
+import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.entities.User;
+import net.dv8tion.jda.core.exceptions.PermissionException;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ModLogger
+{
+ private final HashMap caseNum = new HashMap<>();
+ private final HashSet needsUpdate = new HashSet<>();
+ private final FixedCache banLogCache = new FixedCache<>(1000);
+ private final Vortex vortex;
+ private boolean isStarted = false;
+
+ public ModLogger(Vortex vortex)
+ {
+ this.vortex = vortex;
+ }
+
+ public void start()
+ {
+ if(isStarted)
+ return;
+ isStarted=true;
+ vortex.getThreadpool().scheduleWithFixedDelay(()->
+ {
+ Set toUpdate;
+ synchronized(needsUpdate)
+ {
+ toUpdate = new HashSet<>(needsUpdate);
+ needsUpdate.clear();
+ }
+ toUpdate.forEach(gid -> update(vortex.getShardManager().getGuildById(gid), 30));
+ }, 0, 2, TimeUnit.SECONDS);
+ }
+
+ public void setNeedUpdate(Guild guild)
+ {
+ if(vortex.getDatabase().settings.getSettings(guild).getModLogChannel(guild)==null)
+ return;
+ vortex.getThreadpool().schedule(() ->
+ {
+ synchronized(needsUpdate)
+ {
+ needsUpdate.add(guild.getIdLong());
+ }
+ }, 1, TimeUnit.SECONDS);
+ }
+
+ public int updateCase(Guild guild, int num, String reason)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(guild).getModLogChannel(guild);
+ if(modlog==null)
+ return -1;
+ else if(!modlog.canTalk() || !guild.getSelfMember().hasPermission(modlog, Permission.MESSAGE_HISTORY))
+ return -2;
+ List list = modlog.getHistory().retrievePast(100).complete();
+ Message m = null;
+ int thiscase = 0;
+ for(Message msg: list)
+ {
+ thiscase = LogUtil.isCase(msg, num);
+ if(thiscase!=0)
+ {
+ m = msg;
+ break;
+ }
+ }
+ if(m==null)
+ return num==-1 ? -4 : -3;
+ m.editMessage(m.getContentRaw().replaceAll("(?is)\n`\\[ Reason \\]` .+", "\n`[ Reason ]` "+reason)).queue();
+ return thiscase;
+ }
+
+ public void postCleanCase(Member moderator, OffsetDateTime now, int numMessages, TextChannel target, String criteria, String reason, MessageEmbed embed)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getModLogChannel(moderator.getGuild());
+ if(modlog==null || !modlog.canTalk())
+ return;
+ getCaseNumberAsync(modlog, i ->
+ {
+ try
+ {
+ modlog.sendMessage(new MessageBuilder()
+ .setEmbed(embed)
+ .append(FormatUtil.filterEveryone(LogUtil.modlogCleanFormat(now,
+ vortex.getDatabase().settings.getSettings(moderator.getGuild()).getTimezone(),
+ i, moderator.getUser(), numMessages, target, criteria, reason)))
+ .build()).queue();
+ }
+ catch(PermissionException ex) {}
+ });
+ }
+
+ public void postStrikeCase(Member moderator, OffsetDateTime now, int givenStrikes, int oldStrikes, int newStrikes, User target, String reason)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getModLogChannel(moderator.getGuild());
+ if(modlog==null || !modlog.canTalk())
+ return;
+ getCaseNumberAsync(modlog, i ->
+ {
+ modlog.sendMessage(FormatUtil.filterEveryone(LogUtil.modlogStrikeFormat(now,
+ vortex.getDatabase().settings.getSettings(moderator.getGuild()).getTimezone(), i,
+ moderator.getUser(), givenStrikes, oldStrikes, newStrikes, target, reason))).queue();
+ });
+ }
+
+ public void postPardonCase(Member moderator, OffsetDateTime now, int pardonedStrikes, int oldStrikes, int newStrikes, User target, String reason)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getModLogChannel(moderator.getGuild());
+ if(modlog==null || !modlog.canTalk())
+ return;
+ getCaseNumberAsync(modlog, i ->
+ {
+ modlog.sendMessage(FormatUtil.filterEveryone(LogUtil.modlogPardonFormat(now,
+ vortex.getDatabase().settings.getSettings(moderator.getGuild()).getTimezone(), i,
+ moderator.getUser(), pardonedStrikes, oldStrikes, newStrikes, target, reason))).queue();
+ });
+ }
+
+ public void postRaidmodeCase(Member moderator, OffsetDateTime now, boolean enabled, String reason)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getModLogChannel(moderator.getGuild());
+ if(modlog==null || !modlog.canTalk())
+ return;
+ getCaseNumberAsync(modlog, i ->
+ {
+
+ modlog.sendMessage(FormatUtil.filterEveryone(LogUtil.modlogRaidFormat(now,
+ vortex.getDatabase().settings.getSettings(moderator.getGuild()).getTimezone(), i,
+ moderator.getUser(), enabled, reason))).queue();
+ });
+ }
+
+ public void postPseudoCase(Member moderator, OffsetDateTime now, Action act, User target, int minutes, String reason)
+ {
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getModLogChannel(moderator.getGuild());
+ if(modlog==null || !modlog.canTalk())
+ return;
+ ZoneId timezone = vortex.getDatabase().settings.getSettings(moderator.getGuild()).getTimezone();
+ getCaseNumberAsync(modlog, i ->
+ {
+ modlog.sendMessage(FormatUtil.filterEveryone(minutes > 0 ?
+ LogUtil.modlogTimeFormat(now, timezone, i, moderator.getUser(), act, minutes, target, reason) :
+ LogUtil.modlogUserFormat(now, timezone, i, moderator.getUser(), act, target, reason))).queue();
+ });
+ }
+
+ // private methods
+
+ private void update(Guild guild, int limit) // not async
+ {
+ if(guild==null)
+ return;
+ TextChannel modlog = vortex.getDatabase().settings.getSettings(guild).getModLogChannel(guild);
+ if(modlog==null || !modlog.canTalk())
+ return;
+ try
+ {
+ List list = guild.getAuditLogs().cache(false).limit(limit).complete();
+ for(AuditLogEntry ale: vortex.getDatabase().auditcache.filterUncheckedEntries(list))
+ {
+ Action act = null;
+ switch(ale.getType())
+ {
+ case BAN: act = Action.BAN; break;
+ case KICK: act = Action.KICK; break;
+ case UNBAN: act = Action.UNBAN; break;
+ case MEMBER_ROLE_UPDATE:
+ AuditLogChange added = ale.getChangeByKey(AuditLogKey.MEMBER_ROLES_ADD);
+ if(added!=null)
+ {
+ if (((ArrayList>)added.getNewValue()).stream().anyMatch(hm -> hm.get("name").equals("Muted")))
+ {
+ act = Action.MUTE;
+ break;
+ }
+ }
+ AuditLogChange removed = ale.getChangeByKey(AuditLogKey.MEMBER_ROLES_REMOVE);
+ if(removed!=null)
+ {
+ if (((ArrayList>)removed.getNewValue()).stream().anyMatch(hm -> hm.get("name").equals("Muted")))
+ {
+ act = Action.UNMUTE;
+ break;
+ }
+ }
+ break;
+ }
+ if(act!=null)
+ {
+ User mod = ale.getUser();
+ String reason = ale.getReason()==null ? "" : ale.getReason();
+ int minutes = 0;
+ User target = vortex.getShardManager().getUserById(ale.getTargetIdLong());
+ if(target==null)
+ target = modlog.getJDA().retrieveUserById(ale.getTargetIdLong()).complete();
+ ZoneId timezone = vortex.getDatabase().settings.getSettings(guild).getTimezone();
+ if(mod.isBot())
+ {
+ ParsedAuditReason parsed = LogUtil.parse(guild, reason);
+ if(parsed!=null)
+ {
+ mod = parsed.moderator.getUser();
+ reason = parsed.reason;
+ minutes = parsed.minutes;
+ if(minutes>0)
+ {
+ if(act==Action.BAN)
+ act = Action.TEMPBAN;
+ else if(act==Action.MUTE)
+ act = Action.TEMPMUTE;
+ }
+ }
+ }
+ String banCacheKey = banCacheKey(ale, mod);
+ if(act==Action.UNBAN) // check for softban
+ {
+ Message banMsg = banLogCache.get(banCacheKey);
+ if(banMsg!=null && banMsg.getCreationTime().plusMinutes(2).isAfter(ale.getCreationTime()))
+ {
+ // This is a softban, because the user was banned by the same mod within the past 2 minutes
+ // We need to edit the existing modlog entry instead of making a new one
+ banMsg.editMessage(banMsg.getContentRaw()
+ .replaceFirst(Action.BAN.getEmoji(), Action.SOFTBAN.getEmoji())
+ .replaceFirst(Action.BAN.getVerb(), Action.SOFTBAN.getVerb())).queue();
+ return;
+ }
+ }
+ modlog.sendMessage(FormatUtil.filterEveryone(minutes > 0 ?
+ LogUtil.modlogTimeFormat(ale.getCreationTime(), timezone, getCaseNumber(modlog), mod, act, minutes, target, reason) :
+ LogUtil.modlogUserFormat(ale.getCreationTime(), timezone, getCaseNumber(modlog), mod, act, target, reason)))
+ .queue(act==Action.BAN ? m -> banLogCache.put(banCacheKey, m) : null);
+ }
+ }
+ }
+ catch(Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+
+ private int getCaseNumber(TextChannel tc) // not async
+ {
+ if(caseNum.containsKey(tc.getGuild().getIdLong()))
+ {
+ int num = caseNum.get(tc.getGuild().getIdLong());
+ caseNum.put(tc.getGuild().getIdLong(), num+1);
+ return num;
+ }
+ else
+ {
+ int num;
+ for(Message m: tc.getHistory().retrievePast(100).complete())
+ {
+ num = getCaseNumber(m);
+ if(num!=-1)
+ {
+ caseNum.put(tc.getGuild().getIdLong(), num+2);
+ return num+1;
+ }
+ }
+ caseNum.put(tc.getGuild().getIdLong(), 2);
+ return 1;
+ }
+ }
+
+ private void getCaseNumberAsync(TextChannel tc, Consumer result)
+ {
+ if(caseNum.containsKey(tc.getGuild().getIdLong()))
+ {
+ int num = caseNum.get(tc.getGuild().getIdLong());
+ caseNum.put(tc.getGuild().getIdLong(), num+1);
+ result.accept(num);
+ }
+ else
+ {
+ tc.getHistory().retrievePast(100).queue(list ->
+ {
+ int num;
+ for(Message m: list)
+ {
+ num = getCaseNumber(m);
+ if(num!=-1)
+ {
+ caseNum.put(tc.getGuild().getIdLong(), num+2);
+ result.accept(num);
+ return;
+ }
+ }
+ caseNum.put(tc.getGuild().getIdLong(), 2);
+ result.accept(1);
+ });
+ }
+ }
+
+ private static String banCacheKey(AuditLogEntry ale, User mod)
+ {
+ return ale.getGuild().getId()+"|"+ale.getTargetId()+"|"+mod.getId();
+ }
+
+ private static int getCaseNumber(Message m)
+ {
+ if(m.getAuthor().getIdLong()!=m.getJDA().getSelfUser().getIdLong())
+ return -1;
+ if(!m.getContentRaw().startsWith("`["))
+ return -1;
+ try
+ {
+ return Integer.parseInt(m.getContentRaw().substring(15, m.getContentRaw().indexOf("]` ",15)));
+ }
+ catch(Exception e)
+ {
+ return -1;
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/logging/TextUploader.java b/src/main/java/com/jagrosh/vortex/logging/TextUploader.java
new file mode 100644
index 0000000..627a917
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/logging/TextUploader.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2017 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.logging;
+
+import com.jagrosh.vortex.Vortex;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import net.dv8tion.jda.core.JDA;
+import net.dv8tion.jda.core.entities.Category;
+import net.dv8tion.jda.core.entities.TextChannel;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class TextUploader
+{
+ /**
+ * Alright, I'm going to write a bit here because I have a feeling that some
+ * people will wonder about this. The purpose of this class is to utilize
+ * Discord's persistent and encrypted message channels as storage for
+ * message logs.
+ */
+ private final Logger LOG = LoggerFactory.getLogger("Upload");
+ private final Vortex vortex;
+ private final long categoryId;
+ private Category category;
+ private int index = 0;
+
+ public TextUploader(Vortex vortex, long categoryId)
+ {
+ this.vortex = vortex;
+ this.categoryId = categoryId;
+ }
+
+ public void upload(String content, String filename, Result done)
+ {
+ if(category==null)
+ {
+ category = vortex.getShardManager().getCategoryById(categoryId);
+ if(category==null)
+ {
+ LOG.warn("Ignored upload due to unknown category");
+ return;
+ }
+ }
+ List list = category.getTextChannels();
+ list.get(index % list.size()).sendFile(content.getBytes(StandardCharsets.UTF_8), filename+".txt", null).queue(
+ m -> done.consume(
+ "http://txt.discord.website?txt="+m.getAttachments().get(0).getUrl().substring(m.getAttachments().get(0).getUrl().indexOf("s/")+2, m.getAttachments().get(0).getUrl().length()-4),
+ m.getAttachments().get(0).getUrl()),
+ f -> LOG.error("Failed to upload: "+f));
+ index++;
+ }
+
+
+ public static interface Result
+ {
+ public abstract void consume(String view, String download);
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/utils/ArgsUtil.java b/src/main/java/com/jagrosh/vortex/utils/ArgsUtil.java
new file mode 100644
index 0000000..b51cfbe
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/utils/ArgsUtil.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.utils;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.User;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class ArgsUtil
+{
+ private final static Pattern MENTION = Pattern.compile("^<@!?(\\d{17,19})>");
+ private final static Pattern BROKEN_MENTION = Pattern.compile("^@(\\S.{0,30}\\S)#(\\d{4})");
+ private final static Pattern ID = Pattern.compile("^(\\d{17,19})");
+ private final static String TIME_REGEX = "(?is)^((\\s*-?\\s*\\d+\\s*(d(ays?)?|h((ou)?rs?)?|m(in(ute)?s?)?|s(ec(ond)?s?)?)\\s*,?\\s*(and)?)*).*";
+
+ public static ResolvedArgs resolve(String args, Guild guild)
+ {
+ return resolve(args, false, guild);
+ }
+
+ public static ResolvedArgs resolve(String args, boolean allowTime, Guild guild)
+ {
+ List members = new LinkedList<>();
+ List users = new LinkedList<>();
+ List ids = new LinkedList<>();
+ List unresolved = new LinkedList<>();
+ Matcher mat;
+ User u;
+ long i;
+ boolean found = true;
+ while(!args.isEmpty() && found)
+ {
+ found = false;
+ mat = MENTION.matcher(args);
+ if(mat.find())
+ {
+ i = Long.parseLong(mat.group(1));
+ u = guild.getJDA().getUserById(i);
+ if(u==null)
+ ids.add(i);
+ else if(guild.isMember(u))
+ members.add(guild.getMember(u));
+ else
+ users.add(u);
+ args = args.substring(mat.group().length()).trim();
+ found = true;
+ continue;
+ }
+ mat = BROKEN_MENTION.matcher(args);
+ if(mat.find())
+ {
+ for(User user: guild.getJDA().getUserCache().asList())
+ {
+ if(user.getName().equals(mat.group(1)) && user.getDiscriminator().equals(mat.group(2)))
+ {
+ if(guild.isMember(user))
+ members.add(guild.getMember(user));
+ else
+ users.add(user);
+ found = true;
+ args = args.substring(mat.group().length()).trim();
+ break;
+ }
+ }
+ if(found)
+ continue;
+ }
+ mat = ID.matcher(args);
+ if(mat.find())
+ {
+ i = Long.parseLong(mat.group(1));
+ u = guild.getJDA().getUserById(i);
+ if(u==null)
+ ids.add(i);
+ else if(guild.isMember(u))
+ members.add(guild.getMember(u));
+ else
+ users.add(u);
+ args = args.substring(mat.group().length()).trim();
+ found = true;
+ }
+ }
+ int time = 0;
+ if(allowTime)
+ {
+ String timeString = args.replaceAll(TIME_REGEX, "$1");
+ if(!timeString.isEmpty())
+ {
+ args = args.substring(timeString.length()).trim();
+ time = OtherUtil.parseTime(timeString);
+ }
+ }
+ return new ResolvedArgs(members, users, ids, unresolved, time, args);
+ }
+
+ public static class ResolvedArgs
+ {
+ public final List members;
+ public final List users;
+ public final List ids;
+ public final List unresolved;
+ public final int time;
+ public final String reason;
+
+ private ResolvedArgs(List members, List users, List ids, List unresolved, int time, String reason)
+ {
+ this.members = members;
+ this.users = users;
+ this.ids = ids;
+ this.unresolved = unresolved;
+ this.time = time;
+ this.reason = reason;
+ }
+
+ public boolean isEmpty()
+ {
+ return members.isEmpty() && users.isEmpty() && ids.isEmpty() && unresolved.isEmpty();
+ }
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/utils/FixedCache.java b/src/main/java/com/jagrosh/vortex/utils/FixedCache.java
new file mode 100644
index 0000000..5baa43e
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/utils/FixedCache.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.utils;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ * @param key type
+ * @param cache item type
+ */
+public class FixedCache
+{
+ private final Map map;
+ private final K[] keys;
+ private int currIndex = 0;
+
+ public FixedCache(int size)
+ {
+ this.map = new HashMap<>();
+ if(size < 1)
+ throw new IllegalArgumentException("Cache size must be at least 1!");
+ this.keys = (K[]) new Object[size];
+ }
+
+ public V put(K key, V value)
+ {
+ if(map.containsKey(key))
+ {
+ return map.put(key, value);
+ }
+ if(keys[currIndex] != null)
+ {
+ map.remove(keys[currIndex]);
+ }
+ keys[currIndex] = key;
+ currIndex = (currIndex + 1) % keys.length;
+ return map.put(key, value);
+ }
+
+ public V pull(K key)
+ {
+ return map.remove(key);
+ }
+
+ public V get(K key)
+ {
+ return map.get(key);
+ }
+
+ public boolean contains(K key)
+ {
+ return map.containsKey(key);
+ }
+
+ public Collection getValues()
+ {
+ return map.values();
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java b/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java
new file mode 100644
index 0000000..a24aacb
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2016 John Grosh (jagrosh).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.utils;
+
+import com.jagrosh.jdautilities.command.Command;
+import com.jagrosh.jdautilities.command.CommandEvent;
+import java.util.List;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.User;
+import net.dv8tion.jda.core.entities.VoiceChannel;
+import com.jagrosh.vortex.Constants;
+import com.jagrosh.vortex.Vortex;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.TextChannel;
+
+/**
+ *
+ * @author John Grosh (jagrosh)
+ */
+public class FormatUtil {
+
+ private final static String MULTIPLE_FOUND = Constants.WARNING+" **Multiple %s found matching \"%s\":**";
+
+ public static String filterEveryone(String input)
+ {
+ return input.replace("@everyone","@\u0435veryone").replace("@here","@h\u0435re");
+ }
+
+ public static String formatMessage(Message m)
+ {
+ StringBuilder sb = new StringBuilder(m.getContentRaw());
+ m.getAttachments().forEach(att -> sb.append("\n").append(att.getUrl()));
+ return sb.toString();
+ }
+
+ public static String formatUser(User user)
+ {
+ return filterEveryone("**"+user.getName()+"**#"+user.getDiscriminator());
+ }
+
+ public static String formatFullUser(User user)
+ {
+ return filterEveryone("**"+user.getName()+"**#"+user.getDiscriminator()+" (ID:"+user.getId()+")");
+ }
+
+ public static String capitalize(String input)
+ {
+ if(input==null || input.isEmpty())
+ return "";
+ if(input.length()==1)
+ return input.toUpperCase();
+ return Character.toUpperCase(input.charAt(0))+input.substring(1).toLowerCase();
+ }
+
+ public static String listOfVoice(List list, String query)
+ {
+ String out = String.format(MULTIPLE_FOUND, "voice channels", query);
+ for(int i=0; i<6 && i6)
+ out+="\n**And "+(list.size()-6)+" more...**";
+ return out;
+ }
+
+ public static String listOfRoles(List list, String query)
+ {
+ String out = String.format(MULTIPLE_FOUND, "roles", query);
+ for(int i=0; i<6 && i6)
+ out+="\n**And "+(list.size()-6)+" more...**";
+ return out;
+ }
+
+ public static String listOfText(List list, String query)
+ {
+ String out = String.format(MULTIPLE_FOUND, "text channels", query);
+ for(int i=0; i<6 && i6)
+ out+="\n**And "+(list.size()-6)+" more...**";
+ return out;
+ }
+
+ public static String secondsToTime(long timeseconds)
+ {
+ StringBuilder builder = new StringBuilder();
+ int years = (int)(timeseconds / (60*60*24*365));
+ if(years>0)
+ {
+ builder.append("**").append(years).append("** years, ");
+ timeseconds = timeseconds % (60*60*24*365);
+ }
+ int weeks = (int)(timeseconds / (60*60*24*365));
+ if(weeks>0)
+ {
+ builder.append("**").append(weeks).append("** weeks, ");
+ timeseconds = timeseconds % (60*60*24*7);
+ }
+ int days = (int)(timeseconds / (60*60*24));
+ if(days>0)
+ {
+ builder.append("**").append(days).append("** days, ");
+ timeseconds = timeseconds % (60*60*24);
+ }
+ int hours = (int)(timeseconds / (60*60));
+ if(hours>0)
+ {
+ builder.append("**").append(hours).append("** hours, ");
+ timeseconds = timeseconds % (60*60);
+ }
+ int minutes = (int)(timeseconds / (60));
+ if(minutes>0)
+ {
+ builder.append("**").append(minutes).append("** minutes, ");
+ timeseconds = timeseconds % (60);
+ }
+ if(timeseconds>0)
+ builder.append("**").append(timeseconds).append("** seconds");
+ String str = builder.toString();
+ if(str.endsWith(", "))
+ str = str.substring(0,str.length()-2);
+ if(str.isEmpty())
+ str="**No time**";
+ return str;
+ }
+
+ public static String secondsToTimeCompact(long timeseconds)
+ {
+ StringBuilder builder = new StringBuilder();
+ int years = (int)(timeseconds / (60*60*24*365));
+ if(years>0)
+ {
+ builder.append("**").append(years).append("**y ");
+ timeseconds = timeseconds % (60*60*24*365);
+ }
+ int weeks = (int)(timeseconds / (60*60*24*365));
+ if(weeks>0)
+ {
+ builder.append("**").append(weeks).append("**w ");
+ timeseconds = timeseconds % (60*60*24*7);
+ }
+ int days = (int)(timeseconds / (60*60*24));
+ if(days>0)
+ {
+ builder.append("**").append(days).append("**d ");
+ timeseconds = timeseconds % (60*60*24);
+ }
+ int hours = (int)(timeseconds / (60*60));
+ if(hours>0)
+ {
+ builder.append("**").append(hours).append("**h ");
+ timeseconds = timeseconds % (60*60);
+ }
+ int minutes = (int)(timeseconds / (60));
+ if(minutes>0)
+ {
+ builder.append("**").append(minutes).append("**m ");
+ timeseconds = timeseconds % (60);
+ }
+ if(timeseconds>0)
+ builder.append("**").append(timeseconds).append("**s");
+ String str = builder.toString();
+ if(str.endsWith(", "))
+ str = str.substring(0,str.length()-2);
+ if(str.isEmpty())
+ str="**No time**";
+ return str;
+ }
+
+ public static String formatHelp(CommandEvent event, Vortex vortex)
+ {
+ StringBuilder builder = new StringBuilder("**"+event.getSelfUser().getName()+"** commands:\n");
+ Command.Category category = null;
+ for(Command command : event.getClient().getCommands())
+ {
+ if(!command.isHidden() && (!command.isOwnerCommand() || event.isOwner()))
+ {
+ if(category==null)
+ {
+ if(command.getCategory()!=null)
+ {
+ category = command.getCategory();
+ builder.append("\n\n __").append(category==null ? "No Category" : category.getName()).append("__:\n");
+ }
+ }
+ else
+ {
+ if(command.getCategory()==null || !command.getCategory().getName().equals(category.getName()))
+ {
+ category = command.getCategory();
+ builder.append("\n\n __").append(category==null ? "No Category" : category.getName()).append("__:\n");
+ }
+ }
+ builder.append("\n`").append(event.getClient().getTextualPrefix()).append(event.getClient().getPrefix()==null?" ":"").append(command.getName())
+ .append(command.getArguments()==null ? "`" : " "+command.getArguments()+"`")
+ .append(" - ").append(command.getHelp());
+ }
+ }
+ User owner = vortex.getShardManager().getUserById(event.getClient().getOwnerIdLong());
+ if(owner!=null)
+ {
+ builder.append("\n\nFor additional help, contact **").append(owner.getName()).append("**#").append(owner.getDiscriminator());
+ if(event.getClient().getServerInvite()!=null)
+ builder.append(" or join ").append(event.getClient().getServerInvite());
+ }
+ return builder.toString();
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/utils/LogUtil.java b/src/main/java/com/jagrosh/vortex/utils/LogUtil.java
new file mode 100644
index 0000000..89e9c91
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/utils/LogUtil.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.utils;
+
+import com.jagrosh.vortex.Action;
+import java.time.OffsetDateTime;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Message;
+import net.dv8tion.jda.core.entities.TextChannel;
+import net.dv8tion.jda.core.entities.User;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class LogUtil
+{
+ // Constants
+ private final static String NO_REASON = "[no reason specified]";
+
+ // Channel logging formats
+ private final static String LOG_TIME = "`[%s]`";
+ private final static String MODLOG_CASE = " `[%d]`";
+ private final static String EMOJI = " %s";
+ private final static String ACTION = " %s";
+ private final static String MODERATOR = " **%s**#%s";
+ private final static String TARGET_USER = " **%s**#%s (ID:%s)";
+ private final static String REASON = "\n`[ Reason ]` %s";
+
+ private final static String MODLOG_USER_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + ACTION + TARGET_USER + REASON;
+ private final static String MODLOG_TIME_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + ACTION + TARGET_USER + " for %s" + REASON;
+ private final static String MODLOG_CLEAN_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + ACTION + " `%d` messages in %s\n`[Criteria]` %s" + REASON;
+ private final static String MODLOG_STRIKE_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + " gave `%d` strikes `[%d ➡ %d]` to" + TARGET_USER + REASON;
+ private final static String MODLOG_PARDON_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + " pardoned `%d` strikes `[%d ➡ %d]` from" + TARGET_USER + REASON;
+ private final static String MODLOG_RAID_FORMAT = LOG_TIME + MODLOG_CASE + EMOJI + MODERATOR + " `%s` anti-raid mode" + REASON;
+
+ private final static String BASICLOG_FORMAT = LOG_TIME + EMOJI + " %s";
+
+ // Modlog methods
+ public static String modlogUserFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, Action action, User target, String reason)
+ {
+ return String.format(MODLOG_USER_FORMAT, timeF(time, zone), caseNum, action.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), action.getVerb(), target.getName(), target.getDiscriminator(), target.getId(),
+ reasonF(reason));
+ }
+
+ public static String modlogTimeFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, Action action, int minutes, User target, String reason)
+ {
+ return String.format(MODLOG_TIME_FORMAT, timeF(time, zone), caseNum, action.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), action.getVerb(), target.getName(), target.getDiscriminator(), target.getId(),
+ FormatUtil.secondsToTime(minutes*60), reasonF(reason));
+ }
+
+ public static String modlogCleanFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, int numMessages, TextChannel channel, String criteria, String reason)
+ {
+ return String.format(MODLOG_CLEAN_FORMAT, timeF(time, zone), caseNum, Action.CLEAN.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), Action.CLEAN.getVerb(), numMessages, channel.getAsMention(), criteria, reasonF(reason));
+ }
+
+ public static String modlogStrikeFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, int givenStrikes, int oldStrikes, int newStrikes, User target, String reason)
+ {
+ return String.format(MODLOG_STRIKE_FORMAT, timeF(time, zone), caseNum, Action.STRIKE.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), givenStrikes, oldStrikes, newStrikes, target.getName(), target.getDiscriminator(), target.getId(),
+ reasonF(reason));
+ }
+
+ public static String modlogPardonFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, int pardonedStrikes, int oldStrikes, int newStrikes, User target, String reason)
+ {
+ return String.format(MODLOG_PARDON_FORMAT, timeF(time, zone), caseNum, Action.PARDON.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), pardonedStrikes, oldStrikes, newStrikes, target.getName(), target.getDiscriminator(), target.getId(),
+ reasonF(reason));
+ }
+
+ public static String modlogRaidFormat(OffsetDateTime time, ZoneId zone, int caseNum, User moderator, boolean enabled, String reason)
+ {
+ return String.format(MODLOG_RAID_FORMAT, timeF(time, zone), caseNum, Action.RAIDMODE.getEmoji(), moderator.getName(),
+ moderator.getDiscriminator(), enabled ? "ENABLED" : "DISABLED", reasonF(reason));
+ }
+
+ public static int isCase(Message m, int caseNum)
+ {
+ if(m.getAuthor().getIdLong()!=m.getJDA().getSelfUser().getIdLong())
+ return 0;
+ String match = "(?is)`\\[.{8}\\]` `\\["+(caseNum==-1 ? "(\\d+)" : caseNum)+"\\]` .+";
+ if(m.getContentRaw().matches(match))
+ return caseNum==-1 ? Integer.parseInt(m.getContentRaw().replaceAll(match, "$1")) : caseNum;
+ return 0;
+ }
+
+ // Basiclog methods
+ public static String basiclogFormat(OffsetDateTime time, ZoneId zone, String emoji, String content)
+ {
+ return String.format(BASICLOG_FORMAT, timeF(time, zone), emoji, content);
+ }
+
+ public static String logMessages(String title, List messages)
+ {
+ StringBuilder sb = new StringBuilder("-- "+title+" --");
+ Message m;
+ for(int i=messages.size()-1; i>=0; i--)
+ {
+ m = messages.get(i);
+ sb.append("\r\n\r\n[")
+ .append(m.getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME))
+ .append("] ").append(m.getAuthor().getName()).append("#").append(m.getAuthor().getDiscriminator())
+ .append(" : ").append(m.getContentRaw());
+ m.getAttachments().forEach(att -> sb.append("\n").append(att.getUrl()));
+ }
+ return sb.toString().trim();
+ }
+
+ // Audit logging formats
+ private final static String A_MOD = "%s#%s";
+ private final static String A_TIME = " (%dm)";
+ private final static String A_REASON = ": %s";
+
+ private final static String AUDIT_BASIC_FORMAT = A_MOD + A_REASON;
+ private final static String AUDIT_TIMED_FORMAT = A_MOD + A_TIME + A_REASON;
+ private final static Pattern AUDIT_BASIC_PATTERN = Pattern.compile("^(\\S.{0,32}\\S)#(\\d{4}): (.*)$");
+ private final static Pattern AUDIT_TIMED_PATTERN = Pattern.compile("^(\\S.{0,32}\\S)#(\\d{4}) \\((\\d{1,9})m\\): (.*)$");
+
+ // Auditlog methods
+ public static String auditStrikeReasonFormat(Member moderator, int minutes, int strikes, String reason)
+ {
+ return auditReasonFormat(moderator, minutes, "["+strikes+" strikes] "+reason);
+ }
+
+ public static String auditReasonFormat(Member moderator, String reason)
+ {
+ return limit512(String.format(AUDIT_BASIC_FORMAT, moderator.getUser().getName(), moderator.getUser().getDiscriminator(), reasonF(reason)));
+ }
+
+ public static String auditReasonFormat(Member moderator, int minutes, String reason)
+ {
+ if(minutes<=0)
+ return auditReasonFormat(moderator, reason);
+ return limit512(String.format(AUDIT_TIMED_FORMAT, moderator.getUser().getName(), moderator.getUser().getDiscriminator(), minutes, reasonF(reason)));
+ }
+
+ public static ParsedAuditReason parse(Guild guild, String reason)
+ {
+ if(reason==null || reason.isEmpty())
+ return null;
+ try
+ {
+ // first try the timed pattern
+ Matcher m = AUDIT_TIMED_PATTERN.matcher(reason);
+ if(m.find())
+ {
+ Member mem = OtherUtil.findMember(m.group(1), m.group(2), guild);
+ if(mem==null)
+ return null;
+ Integer minutes = Integer.parseInt(m.group(3));
+ return new ParsedAuditReason(mem, minutes, reasonF(m.group(4)));
+ }
+
+ // next try the basic pattern
+ m = AUDIT_BASIC_PATTERN.matcher(reason);
+ if(m.find())
+ {
+ Member mem = OtherUtil.findMember(m.group(1), m.group(2), guild);
+ if(mem==null)
+ return null;
+ return new ParsedAuditReason(mem, 0, reasonF(m.group(3)));
+ }
+
+ // we got nothin
+ return null;
+ }
+ catch(Exception e)
+ {
+ return null;
+ }
+ }
+
+ public static class ParsedAuditReason
+ {
+ public final Member moderator;
+ public final int minutes;
+ public final String reason;
+
+ private ParsedAuditReason(Member moderator, int minutes, String reason)
+ {
+ this.moderator = moderator;
+ this.minutes = minutes;
+ this.reason = reason;
+ }
+ }
+
+
+ // Private methods
+ private static String reasonF(String reason)
+ {
+ return reason==null || reason.isEmpty() ? NO_REASON : reason;
+ }
+
+ private static String timeF(OffsetDateTime time, ZoneId zone)
+ {
+ return time.atZoneSameInstant(zone).format(DateTimeFormatter.ISO_LOCAL_TIME).substring(0,8);
+ }
+
+ private static String limit512(String input)
+ {
+ if(input.length()<512)
+ return input;
+ return input.substring(0, 512);
+ }
+}
diff --git a/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java b/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java
new file mode 100644
index 0000000..5b6d666
--- /dev/null
+++ b/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2018 John Grosh (john.a.grosh@gmail.com).
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.jagrosh.vortex.utils;
+
+import net.dv8tion.jda.core.entities.Guild;
+import net.dv8tion.jda.core.entities.Member;
+import net.dv8tion.jda.core.entities.Role;
+import net.dv8tion.jda.core.entities.User;
+
+/**
+ *
+ * @author John Grosh (john.a.grosh@gmail.com)
+ */
+public class OtherUtil
+{
+ public static Role getMutedRole(Guild guild)
+ {
+ return guild.getRoles().stream().filter(r -> r.getName().equalsIgnoreCase("Muted")).findFirst().orElse(null);
+ }
+
+ public static void safeDM(User user, String message, Runnable then)
+ {
+ if(user==null)
+ then.run();
+ else
+ user.openPrivateChannel()
+ .queue(pc -> pc.sendMessage(message).queue(s->then.run(),
+ f->then.run()), f->then.run());
+ }
+
+ public static Member findMember(String username, String discriminator, Guild guild)
+ {
+ return guild.getMembers().stream().filter(m -> m.getUser().getName().equals(username) && m.getUser().getDiscriminator().equals(discriminator)).findAny().orElse(null);
+ }
+
+ public static int parseTime(String timestr)
+ {
+ timestr = timestr.replaceAll("(?i)(\\s|,|and)","")
+ .replaceAll("(?is)(-?\\d+|[a-z]+)", "$1 ")
+ .trim();
+ String[] vals = timestr.split("\\s+");
+ int timeinseconds = 0;
+ try
+ {
+ for(int j=0; j
+
+
+
+
+
+
+
+
+ %nopex[%d{HH:mm:ss}] [%level] [%logger{0}]: %msg%n%ex
+
+
+
+
+
+
+
+
+