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 + + + + + + + + +