diff --git a/images/NoAvatar.png b/images/NoAvatar.png new file mode 100644 index 0000000..fb9b1ea --- /dev/null +++ b/images/NoAvatar.png Binary files differ diff --git a/lists/copypastas.txt b/lists/copypastas.txt new file mode 100644 index 0000000..b6c9de4 --- /dev/null +++ b/lists/copypastas.txt @@ -0,0 +1 @@ +cooldog will never be cool | \ No newline at end of file diff --git a/lists/referral_domains.txt b/lists/referral_domains.txt new file mode 100644 index 0000000..cc645e5 --- /dev/null +++ b/lists/referral_domains.txt @@ -0,0 +1,58 @@ +// Referral Domain List +// This list contains referral sites + +// IP Loggers +iplogger.com +blasze.tk +ps3cfw.com + +// Grabify (IP Loggers) +grabify.link +bmwforum.co +leancoding.co +quickmessage.io +spottyfly.com +spötify.com +stopify.co +yoütu.be +yoütübe.co +yoütübe.com +xda-developers.io +starbucksiswrong.com +starbucks.bio +starbucksisbadforyou.com +bucks.as +discörd.com +minecräft.com +shört.co +cyberh1.xyz + +// Ad sites +adf.ly +sh.st +ceesty.com +linkshrink.net +adexchangecloud.com +cur.lv +getcryptotab.com +udmoney.club +btcclicks.com +bc.vc +adfoc.us +linkbucks.com + +// Referral Sites +wn.nr + +// Scams +fortnite-special.com +fortnite-vbucks.net +fortnite-vbucks.de +fortnite-vbuck.com +free-steam-code.com +steamquests.com +steampromote.com +steamsummer.com +steamreward.com +steamspecial.com +easter-event.com \ No newline at end of file diff --git a/lists/safe_domains.txt b/lists/safe_domains.txt new file mode 100644 index 0000000..40379ee --- /dev/null +++ b/lists/safe_domains.txt @@ -0,0 +1,80 @@ +// Safe Domain List +// This list contains URLs that are considered non-redirecting URLs + +// Discord links +discord.gg +dis.gd +discordapp.net +discordapp.com +discordcdn.com + +// Video & Stream Hosts +youtube.com +youtu.be +ytimg.com +twitch.tv +plays.tv +streamable.com + +// Audio Hosts +bandcamp.com +soundcloud.com +spotify.com +genius.com + +// Image Hosts +imgur.com +imgur.com +giphy.com +deviantart.net +deviantart.com +puu.sh +gfycat.com +prntscr.com +prnt.sc +gyazo.com +instagram.com +9gag.com +twimg.com +tenor.co + +// Development +github.com +githubusercontent.com +hastebin.com +pastebin.com +trello.com + +// Social Media +twitter.com +reddit.com +redd.it +tumblr.com +patreon.com +blogspot.com +facebook.com + +// Wikis +wikia.com +wikipedia.org + +// Shopping +amazon.com +ebay.com +etsy.com + +// Games +steamcommunity.com +steampowered.com +challonge.com +humblebundle.com +nintendo.com +roblox.com + +// Misc +google.com +googleusercontent.com +speedtest.net +myanimelist.net +xkcd.com +knowyourmeme.com \ No newline at end of file diff --git a/src/main/java/com/jagrosh/vortex/Constants.java b/src/main/java/com/jagrosh/vortex/Constants.java index a36bbbd..98db000 100644 --- a/src/main/java/com/jagrosh/vortex/Constants.java +++ b/src/main/java/com/jagrosh/vortex/Constants.java @@ -15,7 +15,6 @@ */ package com.jagrosh.vortex; -import com.jagrosh.jdautilities.command.Command.Category; import java.time.OffsetDateTime; import net.dv8tion.jda.core.Permission; @@ -43,6 +42,7 @@ public final static String BOT_INVITE = "https://discordapp.com/oauth2/authorize?client_id=169463754382114816&scope=bot&permissions="+Permission.getRaw(PERMISSIONS); public final static String OWNER_ID = "113156185389092864"; public final static String DONATION_LINK = "https://patreon.com/jagrosh"; + public final static String NEED_PRO = WARNING + " Sorry, this feature requires Vortex Pro. Vortex Pro is not currently available."; public final static class Wiki { @@ -52,26 +52,5 @@ public final static String LOG_TIMEZONE = WIKI_BASE + "/Log-Timezone"; public final static String RAID_MODE = WIKI_BASE + "/Raid-Mode"; public final static String COMMANDS = WIKI_BASE + "/Commands"; - - public final static class Shortened - { - public final static String GENERAL_COMMANDS = "https://git.io/vArzg"; - public final static String MODERATOR_COMMANDS = "https://git.io/vArza"; - public final static String SETTINGS_COMMANDS = "https://git.io/vArzV"; - public final static String AUTOMOD_COMMANDS = "https://git.io/vArzr"; - - public static String fromCategory(Category category) - { - if(category==null) - return GENERAL_COMMANDS; - switch(category.getName().toLowerCase()) - { - case "moderation": return MODERATOR_COMMANDS; - case "settings": return SETTINGS_COMMANDS; - case "automod": return AUTOMOD_COMMANDS; - default: return GENERAL_COMMANDS; - } - } - } } } diff --git a/src/main/java/com/jagrosh/vortex/Listener.java b/src/main/java/com/jagrosh/vortex/Listener.java index 05d6226..53c81dc 100644 --- a/src/main/java/com/jagrosh/vortex/Listener.java +++ b/src/main/java/com/jagrosh/vortex/Listener.java @@ -35,6 +35,7 @@ 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.UserAvatarUpdateEvent; import net.dv8tion.jda.core.events.user.UserNameUpdateEvent; import net.dv8tion.jda.core.hooks.EventListener; import org.slf4j.Logger; @@ -161,6 +162,14 @@ // Log the name change vortex.getBasicLogger().logNameChange((UserNameUpdateEvent)event); } + else if (event instanceof UserAvatarUpdateEvent) + { + UserAvatarUpdateEvent uaue = (UserAvatarUpdateEvent)event; + + // Log the avatar change + if(!uaue.getUser().isBot()) + vortex.getBasicLogger().logAvatarChange(uaue); + } else if (event instanceof GuildVoiceJoinEvent) { GuildVoiceJoinEvent gevent = (GuildVoiceJoinEvent)event; diff --git a/src/main/java/com/jagrosh/vortex/Vortex.java b/src/main/java/com/jagrosh/vortex/Vortex.java index 29c2b0d..6b2184c 100644 --- a/src/main/java/com/jagrosh/vortex/Vortex.java +++ b/src/main/java/com/jagrosh/vortex/Vortex.java @@ -15,9 +15,11 @@ */ package com.jagrosh.vortex; +import com.jagrosh.vortex.commands.tools.LookupCmd; import com.jagrosh.vortex.commands.automod.*; import com.jagrosh.vortex.commands.general.*; import com.jagrosh.vortex.commands.moderation.*; +import com.jagrosh.vortex.commands.tools.*; import com.jagrosh.vortex.commands.owner.*; import com.jagrosh.vortex.commands.settings.*; import java.awt.Color; @@ -30,6 +32,7 @@ import com.jagrosh.jdautilities.examples.command.*; import com.jagrosh.vortex.automod.AutoMod; import com.jagrosh.vortex.automod.StrikeHandler; +import com.jagrosh.vortex.commands.CommandExceptionListener; import java.util.concurrent.ScheduledExecutorService; import net.dv8tion.jda.core.OnlineStatus; import net.dv8tion.jda.core.entities.Game; @@ -80,7 +83,7 @@ */ List tokens = Files.readAllLines(Paths.get("config.txt")); waiter = new EventWaiter(); - threadpool = Executors.newScheduledThreadPool(30); + threadpool = Executors.newScheduledThreadPool(40); 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); @@ -92,17 +95,17 @@ CommandClient client = new CommandClientBuilder() .setPrefix(Constants.PREFIX) - .setGame(Game.watching("Type "+Constants.PREFIX+"help | Vortex Beta (Formerly Spectra Beta)")) + .setGame(Game.watching("Type "+Constants.PREFIX+"help | Vortex 2.0 Beta")) .setOwnerId(Constants.OWNER_ID) .setServerInvite(Constants.SERVER_INVITE) .setEmojis(Constants.SUCCESS, Constants.WARNING, Constants.ERROR) .setLinkedCacheSize(0) .setGuildSettingsManager(database.settings) + .setListener(new CommandExceptionListener()) .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 LookupCmd(this), new PingCommand(), new ServerinfoCmd(), new UserinfoCmd(), @@ -127,6 +130,8 @@ new MessagelogCmd(this), new ModlogCmd(this), new ServerlogCmd(this), + new VoicelogCmd(this), + new AvatarlogCmd(this), new TimezoneCmd(this), new ModroleCmd(this), new PrefixCmd(this), @@ -139,9 +144,15 @@ new MaxlinesCmd(this), new MaxmentionsCmd(this), new AntiduplicateCmd(this), + new ResolvelinksCmd(this), new AutoraidmodeCmd(this), new IgnoreCmd(this), new UnignoreCmd(this), + + // Tools + new DehoistCmd(), + new InvitepruneCmd(this), + new LookupCmd(this), // Owner new EvalCmd(this), @@ -166,6 +177,7 @@ .setStatus(OnlineStatus.DO_NOT_DISTURB) .setGame(Game.playing("loading...")) .setBulkDeleteSplittingEnabled(false) + .setRequestTimeoutRetry(true) .build(); modlog.start(); @@ -226,6 +238,15 @@ return strikehandler; } + public void cleanPremium() + { + database.premium.cleanPremiumList().forEach((gid) -> + { + database.automod.setResolveUrls(gid, false); + database.settings.setAvatarLogChannel(gid, null); + }); + } + /** * @param args the command line arguments * @throws java.lang.Exception diff --git a/src/main/java/com/jagrosh/vortex/automod/AutoMod.java b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java index 4e7cde7..322a165 100644 --- a/src/main/java/com/jagrosh/vortex/automod/AutoMod.java +++ b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java @@ -20,10 +20,12 @@ import com.jagrosh.vortex.database.managers.AutomodManager.AutomodSettings; import com.jagrosh.vortex.utils.FixedCache; import com.jagrosh.vortex.utils.OtherUtil; +import com.jagrosh.vortex.utils.URLResolver; import java.time.OffsetDateTime; import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; import java.util.function.Predicate; import java.util.regex.Matcher; @@ -47,12 +49,24 @@ 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("https?:\\/\\/(\\S+[?&]ref=|wn.nr\\/)\\S+", Pattern.CASE_INSENSITIVE); + + private final static String[] REF_LINK_LIST = OtherUtil.readLines("referral_domains"); + private final static Pattern REF = Pattern.compile("https?:\\/\\/(?:(?:[a-z0-9-_]+\\.)?(?:" + + (String.join("|", REF_LINK_LIST).replace(".", "\\.")) + + ")[/?]|\\S+[?&]ref=|\\S+\\/ref\\/)\\S+", Pattern.CASE_INSENSITIVE); + + private final static Pattern LINK = Pattern.compile("https?:\\/\\/\\S+", Pattern.CASE_INSENSITIVE); + private final static String INVITE_LINK = "https?:\\/\\/discord(?:app\\.com\\/invite|\\.gg)\\/(\\S+)"; + private final static String REF_LINK = REF.pattern(); + private final static String CONDENSER = "(.+?)\\s*(\\1\\s*)+"; private final static Logger LOG = LoggerFactory.getLogger("AutoMod"); + public final static String RESTORE_MUTE_ROLE_AUDIT = "Restoring Muted Role"; private final Vortex vortex; + private final URLResolver urlResolver = new URLResolver(); + private final InviteResolver inviteResolver = new InviteResolver(); private final FixedCache spams = new FixedCache<>(3000); private final HashMap latestGuildJoin = new HashMap<>(); @@ -128,7 +142,7 @@ if(kicking) { OtherUtil.safeDM(event.getUser(), - "Sorry, **"+event.getGuild().getName()+"** is currently under lockdown. Please try joining again later. Sorry for the inconvenience.", () -> + "Sorry, **"+event.getGuild().getName()+"** is currently under lockdown. Please try joining again later. Sorry for the inconvenience.", true, () -> { try { @@ -142,7 +156,7 @@ { try { - event.getGuild().getController().addSingleRoleToMember(event.getMember(), OtherUtil.getMutedRole(event.getGuild())).reason("Restoring Muted Role").queue(); + event.getGuild().getController().addSingleRoleToMember(event.getMember(), OtherUtil.getMutedRole(event.getGuild())).reason(RESTORE_MUTE_ROLE_AUDIT).queue(); } catch(Exception ex){} } } @@ -258,7 +272,7 @@ int count = message.getContentRaw().split("\n").length; if(count > settings.maxLines) { - strikeTotal += count-settings.maxLines; + strikeTotal += Math.ceil((double)(count-settings.maxLines)/settings.maxLines); reason.append(", Message contained ").append(count).append(" newlines"); shouldDelete = true; } @@ -299,11 +313,8 @@ 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())) + long gid = inviteResolver.resolve(message.getJDA(), inviteCode); + if(gid != message.getGuild().getIdLong()) { strikeTotal += settings.inviteStrikes; reason.append(", Advertising"); @@ -314,6 +325,7 @@ }catch(PermissionException ex){} } + // delete the message if applicable if(shouldDelete) { try @@ -322,11 +334,58 @@ }catch(PermissionException e){} } + // assign strikes if necessary if(strikeTotal>0) { vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(), latestTime(message), message.getAuthor(), strikeTotal, reason.toString().substring(2)); } + + // now, lets resolve links, but async + if(!shouldDelete && settings.resolveUrls && (settings.inviteStrikes>0 || settings.refStrikes>0)) + { + List links = new LinkedList<>(); + Matcher m = LINK.matcher(message.getContentRaw()); + while(m.find()) + links.add(m.group()); + if(!links.isEmpty()) + vortex.getThreadpool().execute(() -> + { + boolean containsInvite = false; + boolean containsRef = false; + String llink = null; + List redirects = null; + for(String link: links) + { + llink = link; + redirects = urlResolver.findRedirects(link); + for(String resolved: redirects) + { + if(settings.inviteStrikes>0 && resolved.matches(INVITE_LINK)) + { + if(inviteResolver.resolve(message.getJDA(), resolved.replaceAll(INVITE_LINK, "$1")) != message.getGuild().getIdLong()) + containsInvite = true; + } + if(settings.refStrikes>0 && resolved.matches(REF_LINK)) + containsRef = true; + } + if((containsInvite || settings.inviteStrikes<1) && (containsRef || settings.refStrikes<1)) + break; + } + int rstrikeTotal = (containsInvite ? settings.inviteStrikes : 0) + (containsRef ? settings.refStrikes : 0); + if(rstrikeTotal > 0) + { + vortex.getBasicLogger().logRedirectPath(message, llink, redirects); + String rreason = ((containsInvite ? ", Advertising (Resolved Link)" : "") + (containsRef ? ", Referral Link (Resolved Link)" : "")).substring(2); + try + { + message.delete().reason("Automod").queue(v->{}, f->{}); + }catch(PermissionException e){} + vortex.getStrikeHandler().applyStrikes(message.getGuild().getSelfMember(), + latestTime(message), message.getAuthor(), rstrikeTotal, rreason); + } + }); + } } private void purgeMessages(Guild guild, Predicate predicate) diff --git a/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java b/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java new file mode 100644 index 0000000..a5e8af7 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java @@ -0,0 +1,46 @@ +/* + * 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.utils.FixedCache; +import net.dv8tion.jda.core.JDA; +import net.dv8tion.jda.core.entities.Invite; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class InviteResolver +{ + private final FixedCache cached = new FixedCache<>(200); + + public long resolve(JDA jda, String code) + { + if(cached.contains(code)) + return cached.get(code); + try + { + Invite i = Invite.resolve(jda, code).complete(); + cached.put(code, i.getGuild().getIdLong()); + return i.getGuild().getIdLong(); + } + catch(Exception ex) + { + cached.put(code, 0L); + return 0L; + } + } +} diff --git a/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java b/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java index 9074033..2b1ab44 100644 --- a/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java +++ b/src/main/java/com/jagrosh/vortex/automod/StrikeHandler.java @@ -62,12 +62,13 @@ { 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, ()->{}); + OtherUtil.safeDM(user, dmmsg, moderator.getGuild().getMemberById(targetId)!=null, ()->{}); } } public void applyStrikes(Member moderator, OffsetDateTime nowo, User target, int number, String reason) { + boolean shouldDM = moderator.getGuild().getMemberById(target.getIdLong())!=null; Instant now = nowo.toInstant(); int[] counts = vortex.getDatabase().strikes.addStrikes(moderator.getGuild(), target.getIdLong(), number); List punishments = vortex.getDatabase().actions.getPunishments(moderator.getGuild(), counts[0], counts[1]); @@ -75,14 +76,14 @@ if(punishments.isEmpty()) { vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], target, reason); - OtherUtil.safeDM(target, dmmsg, ()->{}); + OtherUtil.safeDM(target, dmmsg, shouldDM, ()->{}); } else { String notimeaudit = LogUtil.auditStrikeReasonFormat(moderator, 0, counts[0], counts[1], reason); if(punishments.stream().anyMatch(p -> p.action==Action.BAN)) { - OtherUtil.safeDM(target, dmmsg + punish(Action.BAN, moderator.getGuild()), + OtherUtil.safeDM(target, dmmsg + punish(Action.BAN, moderator.getGuild()), shouldDM, () -> moderator.getGuild().getController().ban(target, 7, notimeaudit).queue()); vortex.getDatabase().tempbans.clearBan(moderator.getGuild(), target.getIdLong()); return; @@ -101,7 +102,7 @@ if(banDuration>0) { int finalBanDuration = banDuration; - OtherUtil.safeDM(target, dmmsg + punishTime(Action.TEMPBAN, moderator.getGuild(), banDuration), + OtherUtil.safeDM(target, dmmsg + punishTime(Action.TEMPBAN, moderator.getGuild(), banDuration), shouldDM, () -> moderator.getGuild().getController().ban(target, 7, LogUtil.auditStrikeReasonFormat(moderator, finalBanDuration, counts[0], counts[1], reason)).queue()); vortex.getDatabase().tempbans.setBan(moderator.getGuild(), target.getIdLong(), now.plus(banDuration, ChronoUnit.MINUTES)); if(muteDuration>0) @@ -110,9 +111,9 @@ } if(punishments.stream().anyMatch(p -> p.action==Action.SOFTBAN)) { - OtherUtil.safeDM(target, dmmsg + punish(Action.SOFTBAN, moderator.getGuild()), + OtherUtil.safeDM(target, dmmsg + punish(Action.SOFTBAN, moderator.getGuild()), shouldDM, () -> moderator.getGuild().getController().ban(target, 7, notimeaudit).queue( - s -> moderator.getGuild().getController().unban(target).reason(notimeaudit).queueAfter(1, TimeUnit.SECONDS))); + s -> moderator.getGuild().getController().unban(target).reason(notimeaudit).queueAfter(4, TimeUnit.SECONDS))); if(muteDuration>0) vortex.getDatabase().tempmutes.setMute(moderator.getGuild(), target.getIdLong(), muteTime(now, muteDuration)); return; @@ -121,7 +122,7 @@ { if(moderator.getGuild().isMember(target)) { - OtherUtil.safeDM(target, dmmsg + punish(Action.KICK, moderator.getGuild()), + OtherUtil.safeDM(target, dmmsg + punish(Action.KICK, moderator.getGuild()), shouldDM, () -> moderator.getGuild().getController().kick(target.getId(), notimeaudit).queue()); } else @@ -140,7 +141,7 @@ if(muted==null || mem==null || !moderator.getGuild().getSelfMember().canInteract(muted)) { vortex.getModLogger().postStrikeCase(moderator, nowo, number, counts[0], counts[1], target, reason); - OtherUtil.safeDM(target, dmmsg, ()->{}); + OtherUtil.safeDM(target, dmmsg, shouldDM, ()->{}); return; } if(mem.getRoles().contains(muted)) @@ -156,7 +157,7 @@ .queue(); } OtherUtil.safeDM(target, dmmsg + (muteDuration==Integer.MAX_VALUE ? punish(Action.MUTE, moderator.getGuild()) - : punishTime(Action.TEMPMUTE, moderator.getGuild(), muteDuration)), ()->{}); + : punishTime(Action.TEMPMUTE, moderator.getGuild(), muteDuration)), shouldDM, ()->{}); } } } diff --git a/src/main/java/com/jagrosh/vortex/commands/CommandExceptionListener.java b/src/main/java/com/jagrosh/vortex/commands/CommandExceptionListener.java new file mode 100644 index 0000000..85ffdc2 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/CommandExceptionListener.java @@ -0,0 +1,54 @@ +/* + * 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.command.CommandListener; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class CommandExceptionListener implements CommandListener +{ + @Override + public void onCommandException(CommandEvent event, Command command, Throwable throwable) + { + if (throwable instanceof CommandErrorException) + event.replyError(throwable.getMessage()); + else if (throwable instanceof CommandWarningException) + event.replyWarning(throwable.getMessage()); + else + throwable.printStackTrace(); + } + + public static class CommandErrorException extends RuntimeException + { + public CommandErrorException(String message) + { + super(message); + } + } + + public static class CommandWarningException extends RuntimeException + { + public CommandWarningException(String message) + { + super(message); + } + } +} diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/MaxlinesCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/MaxlinesCmd.java index 8aa45bd..528959a 100644 --- a/src/main/java/com/jagrosh/vortex/commands/automod/MaxlinesCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/automod/MaxlinesCmd.java @@ -9,7 +9,6 @@ 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; /** @@ -62,6 +61,7 @@ } vortex.getDatabase().automod.setMaxLines(event.getGuild(), maxlines); boolean also = vortex.getDatabase().actions.useDefaultSettings(event.getGuild()); - event.replySuccess("Messages longer than `"+maxlines+"` lines will now be automatically deleted, and users will receive strikes for every additional line."+(also ? PunishmentManager.DEFAULT_SETUP_MESSAGE : "")); + event.replySuccess("Messages longer than `"+maxlines+"` lines will now be automatically deleted, " + + "and users will receive strikes for every additional multiple of up to `"+maxlines+"` lines."+(also ? PunishmentManager.DEFAULT_SETUP_MESSAGE : "")); } } diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/ResolvelinksCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/ResolvelinksCmd.java new file mode 100644 index 0000000..e62588c --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/automod/ResolvelinksCmd.java @@ -0,0 +1,64 @@ +/* + * 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 com.jagrosh.vortex.database.managers.PremiumManager; +import net.dv8tion.jda.core.Permission; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class ResolvelinksCmd extends Command +{ + private final Vortex vortex; + private final static String DESCRIPTION = "When link resolving is enabled, all links will be checked for redirects when performing automod functions."; + + public ResolvelinksCmd(Vortex vortex) + { + this.vortex = vortex; + this.name = "resolvelinks"; + this.guildOnly = true; + this.category = new Category("AutoMod"); + this.arguments = ""; + this.help = "resolve redirect urls"; + this.userPermissions = new Permission[]{Permission.MANAGE_SERVER}; + } + + @Override + protected void execute(CommandEvent event) + { + if(event.getArgs().equalsIgnoreCase("on") || event.getArgs().equalsIgnoreCase("off")) + { + if(vortex.getDatabase().premium.getPremiumInfo(event.getGuild()).level.isAtLeast(PremiumManager.Level.PRO)) + { + vortex.getDatabase().automod.setResolveUrls(event.getGuild(), event.getArgs().equalsIgnoreCase("on")); + event.replySuccess("Link Resolving has been turned `"+event.getArgs().toUpperCase()+"`"); + } + else + event.reply(Constants.NEED_PRO); + } + else + { + event.replyWarning(DESCRIPTION+"\nValid options are `ON` and `OFF`"); + } + + } +} diff --git a/src/main/java/com/jagrosh/vortex/commands/general/LookupCmd.java b/src/main/java/com/jagrosh/vortex/commands/general/LookupCmd.java deleted file mode 100644 index cfe2cb9..0000000 --- a/src/main/java/com/jagrosh/vortex/commands/general/LookupCmd.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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://wwwidget.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.Vortex; -import java.time.format.DateTimeFormatter; -import net.dv8tion.jda.core.EmbedBuilder; -import net.dv8tion.jda.core.MessageBuilder; -import net.dv8tion.jda.core.entities.ChannelType; -import net.dv8tion.jda.core.entities.Invite; -import net.dv8tion.jda.core.entities.User; -import net.dv8tion.jda.core.exceptions.RateLimitedException; -import net.dv8tion.jda.core.utils.MiscUtil; -import net.dv8tion.jda.core.utils.WidgetUtil; -import net.dv8tion.jda.core.utils.WidgetUtil.Widget; - -/** - * - * @author John Grosh (john.a.grosh@gmail.com) - */ -public class LookupCmd extends Command -{ - private final Vortex vortex; - private final String linestart = "\u25AB"; - - public LookupCmd(Vortex vortex) - { - this.vortex = vortex; - this.name = "lookup"; - this.arguments = ""; - this.help = "finds information about a user or server"; - this.cooldown = 5; - } - - @Override - protected void execute(CommandEvent event) - { - if(event.getArgs().isEmpty()) - { - event.replyError("Please provide a User ID, Server ID, or Invite Code\n" - + "This command provides information about a user or server. " - + "All of the information provided is information that Discord makes publically-available."); - return; - } - event.getChannel().sendTyping().queue(); - event.async(() -> - { - try - { - long id = Long.parseLong(event.getArgs()); - User u = vortex.getShardManager().getUserById(id); - if(u==null) try - { - u = event.getJDA().retrieveUserById(id).complete(); - } - catch(Exception ex) {} - if(u!=null) - { - String text = (u.isBot()?"<:botTag:230105988211015680>":"\uD83D\uDC64")+" Information about **"+u.getName()+"**#"+u.getDiscriminator()+":"; - EmbedBuilder eb = new EmbedBuilder(); - eb.setThumbnail(u.getEffectiveAvatarUrl()); - String str = linestart+"Discord ID: **"+u.getId()+"**"; - if(u.getAvatarId()!=null && u.getAvatarId().startsWith("a_")) - str+= " <:nitro:314068430611415041>"; - str+="\n"+linestart+"Account Creation: **"+MiscUtil.getDateTimeString(u.getCreationTime())+"**"; - eb.setDescription(str); - event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); - return; - } - - Widget widget = WidgetUtil.getWidget(id); - if(widget!=null) - { - if(!widget.isAvailable()) - { - event.replySuccess("Guild with ID `"+id+"` found; no further information found."); - return; - } - Invite inv = null; - if(widget.getInviteCode()!=null) - { - try - { - inv = Invite.resolve(event.getJDA(), widget.getInviteCode()).complete(); - } - catch(Exception ex){} - } - String text = "\uD83D\uDDA5 Information about **"+widget.getName()+"**:"; - EmbedBuilder eb = new EmbedBuilder(); - String str = linestart+"ID: **"+widget.getId()+"**\n" - +linestart+"Creation: **"+widget.getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"**\n" - +linestart+"Channels: **"+widget.getVoiceChannels().size()+"** Voice\n" - +linestart+"Users: **"+widget.getMembers().size()+"** online\n"; - if(inv!=null) - { - eb.setThumbnail(inv.getGuild().getIconUrl()); - str+=linestart+"Invite: **"+inv.getCode()+"** "+(inv.getChannel().getType()==ChannelType.TEXT - ? "#"+inv.getChannel().getName() : inv.getChannel().getName())+" (ID:"+inv.getChannel().getId()+")"; - if(inv.getGuild().getSplashId()!=null) - { - str+="\n\n<:partner:314068430556758017> **Discord Partner** <:partner:314068430556758017>"; - eb.setImage(inv.getGuild().getSplashUrl()+"?size=1024"); - } - } - eb.setDescription(str); - event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); - return; - } - - } - catch(NumberFormatException ex) - { - } - catch(RateLimitedException ex) - { - event.reactWarning(); - return; - } - catch(Exception ex) - { - ex.printStackTrace(); - } - String code = event.getArgs().substring(event.getArgs().indexOf("/")+1); - Invite inv = null; - try - { - inv = Invite.resolve(event.getJDA(), code).complete(); - } - catch(Exception ex){} - if(inv==null) - { - event.replyError("No users, guilds, or invites found."); - return; - } - Widget widget = null; - try - { - widget = WidgetUtil.getWidget(inv.getGuild().getIdLong()); - } - catch(RateLimitedException ex) {} - String text = "\uD83D\uDDA5 Information about Invite Code **"+code+"**:"; - EmbedBuilder eb = new EmbedBuilder(); - eb.setThumbnail(inv.getGuild().getIconUrl()); - String str = linestart+"Guild: **"+inv.getGuild().getName()+"**\n" - + linestart+"Channel: **"+(inv.getChannel().getType()==ChannelType.TEXT?"#":"")+inv.getChannel().getName()+"** (ID:"+inv.getChannel().getId()+")\n" - + linestart+"Inviter: "+(inv.getInviter()==null?"N/A":"**"+inv.getInviter().getName()+"**#"+inv.getInviter().getDiscriminator()+" (ID:"+inv.getInviter().getId()+")"); - eb.setDescription(str); - str = linestart+"ID: **"+inv.getGuild().getId()+"**\n" - +linestart+"Creation: **"+inv.getGuild().getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"**\n"; - if(widget!=null) - str += linestart+"Channels: **"+widget.getVoiceChannels().size()+"** Voice\n" - +linestart+"Users: **"+widget.getMembers().size()+"** online"; - if(inv.getGuild().getSplashId()!=null) - { - str+="\n\n<:partner:314068430556758017> **Discord Partner** <:partner:314068430556758017>"; - eb.setImage(inv.getGuild().getSplashUrl()+"?size=1024"); - } - eb.addField("Guild Info", str, false); - event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); - }); - } -} diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java index fd17c8e..8d8630c 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/CleanCmd.java @@ -21,6 +21,9 @@ import java.util.regex.Pattern; import com.jagrosh.jdautilities.command.CommandEvent; import com.jagrosh.vortex.Vortex; +import com.jagrosh.vortex.commands.CommandExceptionListener; +import com.jagrosh.vortex.commands.CommandExceptionListener.CommandErrorException; +import com.jagrosh.vortex.commands.CommandExceptionListener.CommandWarningException; import com.jagrosh.vortex.commands.ModCommand; import java.util.LinkedList; import net.dv8tion.jda.core.Permission; @@ -28,6 +31,7 @@ import net.dv8tion.jda.core.entities.MessageHistory; import com.jagrosh.vortex.utils.LogUtil; import net.dv8tion.jda.core.EmbedBuilder; +import net.dv8tion.jda.core.entities.EmbedType; import net.dv8tion.jda.core.entities.TextChannel; /** @@ -37,7 +41,7 @@ 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 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("\\b(\\d{17,22})\\b"); @@ -61,6 +65,7 @@ { super(vortex, Permission.MESSAGE_MANAGE, Permission.MESSAGE_HISTORY); this.name = "clean"; + this.aliases = new String[]{"clear"}; this.arguments = ""; this.help = "cleans messages matching filters"; this.botPermissions = new Permission[]{Permission.MESSAGE_MANAGE, Permission.MESSAGE_HISTORY}; @@ -71,16 +76,10 @@ protected void execute(CommandEvent event) { if(event.getArgs().isEmpty() || event.getArgs().equalsIgnoreCase("help")) - { - event.replyWarning(noparams); - return; - } + throw new CommandWarningException(noparams); 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; - } + throw new CommandWarningException("This command cannot be used in the modlog!"); int num = -1; List quotes = new LinkedList<>(); String pattern = null; @@ -130,10 +129,7 @@ num=100; } if(num>1000 || num<2) - { - event.replyError("Number of messages must be between 2 and 1000"); - return; - } + throw new CommandErrorException("Number of messages must be between 2 and 1000"); int val2 = num+1; String p = pattern; @@ -239,6 +235,8 @@ { if(message.getAttachments().stream().anyMatch(a -> a.isImage())) return true; + if(message.getEmbeds().stream().anyMatch(e -> e.getType()==EmbedType.IMAGE)) + 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/MuteCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java index 91e7d24..6786713 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/MuteCmd.java @@ -95,9 +95,9 @@ 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.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.")); + 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()) { diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java index 9512f9e..4eda900 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/PardonCmd.java @@ -93,9 +93,16 @@ 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(event.getJDA().getUserById(id)==null ? "<@"+id+">" : FormatUtil.formatUser(event.getJDA().getUserById(id))); + String user = event.getJDA().getUserById(id)==null ? "<@"+id+">" : FormatUtil.formatUser(event.getJDA().getUserById(id)); + int strikes = vortex.getDatabase().strikes.getStrikes(event.getGuild(), id); + if(strikes==0) + builder.append("\n").append(event.getClient().getWarning()).append(" ").append(user).append(" has no strikes."); + else + { + strikes = fnumstrikes { builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully softbanned ").append(FormatUtil.formatUser(m.getUser())); - event.getGuild().getController().unban(m.getUser().getId()).reason(unbanreason).queueAfter(1, TimeUnit.SECONDS); + event.getGuild().getController().unban(m.getUser().getId()).reason(unbanreason).queueAfter(4, TimeUnit.SECONDS); if(last) event.reply(builder.toString()); }, failure -> diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java index c69bb25..46a9f57 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/StrikeCmd.java @@ -108,7 +108,11 @@ { try { - args.users.add(event.getJDA().retrieveUserById(id).complete()); + User u = event.getJDA().retrieveUserById(id).complete(); + if(u==null) + builder.append("\n").append(event.getClient().getError()).append(" `").append(id).append("` is not a valid user ID."); + else + args.users.add(u); } catch(Exception ex) { diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/AvatarlogCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/AvatarlogCmd.java new file mode 100644 index 0000000..4d5126e --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/settings/AvatarlogCmd.java @@ -0,0 +1,63 @@ +/* + * 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.Constants; +import com.jagrosh.vortex.Vortex; +import com.jagrosh.vortex.commands.LogCommand; +import com.jagrosh.vortex.database.managers.PremiumManager; +import net.dv8tion.jda.core.entities.TextChannel; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class AvatarlogCmd extends LogCommand +{ + public AvatarlogCmd(Vortex vortex) + { + super(vortex); + this.name = "avatarlog"; + this.help = "sets channel to log avatar changes"; + } + + @Override + protected void showCurrentChannel(CommandEvent event) + { + TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getAvatarLogChannel(event.getGuild()); + if(tc==null) + event.replyWarning("Avatar Logs are not currently enabled on the server. Please include a channel name."); + else + event.replySuccess("Avatar 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) + { + if(vortex.getDatabase().premium.getPremiumInfo(event.getGuild()).level.isAtLeast(PremiumManager.Level.PRO)) + { + vortex.getDatabase().settings.setAvatarLogChannel(event.getGuild(), tc); + if(tc==null) + event.replySuccess("Avatar Logs will not be sent"); + else + event.replySuccess("Avatar Logs will now be sent in "+tc.getAsMention()); + } + else + event.reply(Constants.NEED_PRO); + } +} diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java index 4c6f2c8..54732e0 100644 --- a/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/settings/SetupCmd.java @@ -61,7 +61,7 @@ .setChoices(MUTE,CANCEL) .setEventWaiter(vortex.getEventWaiter()) .setTimeout(1, TimeUnit.MINUTES) - .setFinalAction(m -> m.delete().queue()) + .setFinalAction(m -> m.delete().queue(s->{}, f->{})) ; this.cooldown = 20; this.cooldownScope = CooldownScope.GUILD; @@ -166,7 +166,7 @@ .setEventWaiter(vortex.getEventWaiter()) .setTimeout(1, TimeUnit.MINUTES) .setText(Constants.WARNING+" "+message+"\n\n"+CONFIRM+" Continue\n"+CANCEL+" Cancel") - .setFinalAction(m -> m.delete().queue()) + .setFinalAction(m -> m.delete().queue(s->{}, f->{})) .setUsers(event.getAuthor()) .setAction(re -> { diff --git a/src/main/java/com/jagrosh/vortex/commands/settings/VoicelogCmd.java b/src/main/java/com/jagrosh/vortex/commands/settings/VoicelogCmd.java new file mode 100644 index 0000000..b104a74 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/settings/VoicelogCmd.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 VoicelogCmd extends LogCommand +{ + public VoicelogCmd(Vortex vortex) + { + super(vortex); + this.name = "voicelog"; + this.help = "sets channel to log voice changes"; + } + + @Override + protected void showCurrentChannel(CommandEvent event) + { + TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getVoiceLogChannel(event.getGuild()); + if(tc==null) + event.replyWarning("Voice Logs are not currently enabled on the server. Please include a channel name."); + else + event.replySuccess("Voice 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.setVoiceLogChannel(event.getGuild(), tc); + if(tc==null) + event.replySuccess("Voice Logs will not be sent"); + else + event.replySuccess("Voice Logs will now be sent in "+tc.getAsMention()); + } +} diff --git a/src/main/java/com/jagrosh/vortex/commands/tools/DehoistCmd.java b/src/main/java/com/jagrosh/vortex/commands/tools/DehoistCmd.java new file mode 100644 index 0000000..82b9ef3 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/tools/DehoistCmd.java @@ -0,0 +1,84 @@ +/* + * 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.tools; + +import com.jagrosh.jdautilities.command.Command; +import com.jagrosh.jdautilities.command.CommandEvent; +import com.jagrosh.vortex.commands.CommandExceptionListener.CommandErrorException; +import java.util.List; +import java.util.stream.Collectors; +import net.dv8tion.jda.core.Permission; +import net.dv8tion.jda.core.entities.Member; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class DehoistCmd extends Command +{ + private final char[] valid = {'!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/'}; + private final String validJoined; + private final String dehoistChar = "\uD82F\uDCA2"; + + public DehoistCmd() + { + this.name = "dehoist"; + this.arguments = "[symbol]"; + this.help = "modifies users' nicknames to prevent using ascii characters as a hoist"; + this.category = new Category("Tools"); + this.botPermissions = new Permission[]{Permission.NICKNAME_MANAGE}; + this.userPermissions = new Permission[]{Permission.NICKNAME_MANAGE}; + this.guildOnly = true; + this.cooldown = 10; + StringBuilder sb = new StringBuilder().append("`").append(valid[0]); + for(int i=1; i toDehoist = event.getGuild().getMembers().stream() + .filter(m -> event.getSelfMember().canInteract(m) && m.getEffectiveName().charAt(0)<=symbol) + .collect(Collectors.toList()); + event.getChannel().sendTyping().queue(); + toDehoist.forEach(m -> + { + String newname = dehoistChar+m.getEffectiveName(); + if(newname.length()>32) + newname = newname.substring(0,32); + event.getGuild().getController().setNickname(m, newname).queue(); + }); + event.replySuccess("Dehoisting `"+toDehoist.size()+"` members with names starting with `"+symbol+"` or higher."); + } + +} diff --git a/src/main/java/com/jagrosh/vortex/commands/tools/InvitepruneCmd.java b/src/main/java/com/jagrosh/vortex/commands/tools/InvitepruneCmd.java new file mode 100644 index 0000000..233e732 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/tools/InvitepruneCmd.java @@ -0,0 +1,102 @@ +/* + * 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.tools; + +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.commands.CommandExceptionListener.CommandErrorException; +import com.jagrosh.vortex.commands.CommandExceptionListener.CommandWarningException; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import net.dv8tion.jda.core.Permission; +import net.dv8tion.jda.core.entities.Invite; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class InvitepruneCmd extends Command +{ + private final Vortex vortex; + private final String CANCEL = "\u274C"; + private final String CONFIRM = "\u2611"; + + public InvitepruneCmd(Vortex vortex) + { + this.vortex = vortex; + this.name = "inviteprune"; + this.arguments = "[max uses]"; + this.help = "deletes invites with up to a certain number of uses"; + this.category = new Category("Tools"); + this.userPermissions = new Permission[]{Permission.MANAGE_SERVER}; + this.botPermissions = new Permission[]{Permission.MANAGE_SERVER}; + this.guildOnly = true; + this.cooldown = 10; + } + + @Override + protected void execute(CommandEvent event) + { + int uses; + if(event.getArgs().isEmpty()) + uses = 1; + else try + { + uses = Integer.parseInt(event.getArgs()); + } + catch(NumberFormatException ex) + { + throw new CommandErrorException("`"+event.getArgs()+"` is not a valid integer!"); + } + if(uses<0 || uses>50) + throw new CommandWarningException("Maximum uses must be at least 0 and no larger than 50"); + if(uses>10) + waitForConfirmation(event, "This will delete all invites with "+uses+" or fewer uses.", () -> pruneInvites(uses, event)); + else + pruneInvites(uses, event); + } + + private void pruneInvites(int uses, CommandEvent event) + { + event.getChannel().sendTyping().queue(); + event.getGuild().getInvites().queue(list -> + { + List toPrune = list.stream().filter(i -> i.getUses()<=uses).collect(Collectors.toList()); + toPrune.forEach(i -> i.delete().queue()); + event.replySuccess("Deleted `"+toPrune.size()+"` invites with `"+uses+"` or fewer uses."); + }); + } + + 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(s->{}, f->{})) + .setUsers(event.getAuthor()) + .setAction(re -> + { + if(re.getName().equals(CONFIRM)) + confirm.run(); + }).build().display(event.getChannel()); + } +} diff --git a/src/main/java/com/jagrosh/vortex/commands/tools/LookupCmd.java b/src/main/java/com/jagrosh/vortex/commands/tools/LookupCmd.java new file mode 100644 index 0000000..0834b93 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/tools/LookupCmd.java @@ -0,0 +1,178 @@ +/* + * 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://wwwidget.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.tools; + +import com.jagrosh.jdautilities.command.Command; +import com.jagrosh.jdautilities.command.CommandEvent; +import com.jagrosh.vortex.Vortex; +import java.time.format.DateTimeFormatter; +import net.dv8tion.jda.core.EmbedBuilder; +import net.dv8tion.jda.core.MessageBuilder; +import net.dv8tion.jda.core.entities.ChannelType; +import net.dv8tion.jda.core.entities.Invite; +import net.dv8tion.jda.core.entities.User; +import net.dv8tion.jda.core.exceptions.RateLimitedException; +import net.dv8tion.jda.core.utils.MiscUtil; +import net.dv8tion.jda.core.utils.WidgetUtil; +import net.dv8tion.jda.core.utils.WidgetUtil.Widget; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class LookupCmd extends Command +{ + private final Vortex vortex; + private final String linestart = "\u25AB"; + + public LookupCmd(Vortex vortex) + { + this.vortex = vortex; + this.name = "lookup"; + this.arguments = ""; + this.help = "finds information about a user or server"; + this.cooldown = 5; + this.category = new Category("Tools"); + } + + @Override + protected void execute(CommandEvent event) + { + if(event.getArgs().isEmpty()) + { + event.replyError("Please provide a User ID, Server ID, or Invite Code\n" + + "This command provides information about a user or server. " + + "All of the information provided is information that Discord makes publically-available."); + return; + } + event.getChannel().sendTyping().queue(); + event.async(() -> + { + try + { + long id = Long.parseLong(event.getArgs()); + User u = vortex.getShardManager().getUserById(id); + if(u==null) try + { + u = event.getJDA().retrieveUserById(id).complete(); + } + catch(Exception ex) {} + if(u!=null) + { + String text = (u.isBot()?"<:botTag:230105988211015680>":"\uD83D\uDC64")+" Information about **"+u.getName()+"**#"+u.getDiscriminator()+":"; + EmbedBuilder eb = new EmbedBuilder(); + eb.setThumbnail(u.getEffectiveAvatarUrl()); + String str = linestart+"Discord ID: **"+u.getId()+"**"; + if(u.getAvatarId()!=null && u.getAvatarId().startsWith("a_")) + str+= " <:nitro:314068430611415041>"; + str+="\n"+linestart+"Account Creation: **"+MiscUtil.getDateTimeString(u.getCreationTime())+"**"; + eb.setDescription(str); + event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); + return; + } + + Widget widget = WidgetUtil.getWidget(id); + if(widget!=null) + { + if(!widget.isAvailable()) + { + event.replySuccess("Guild with ID `"+id+"` found; no further information found."); + return; + } + Invite inv = null; + if(widget.getInviteCode()!=null) + { + try + { + inv = Invite.resolve(event.getJDA(), widget.getInviteCode()).complete(); + } + catch(Exception ex){} + } + String text = "\uD83D\uDDA5 Information about **"+widget.getName()+"**:"; + EmbedBuilder eb = new EmbedBuilder(); + String str = linestart+"ID: **"+widget.getId()+"**\n" + +linestart+"Creation: **"+widget.getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"**\n" + +linestart+"Channels: **"+widget.getVoiceChannels().size()+"** Voice\n" + +linestart+"Users: **"+widget.getMembers().size()+"** online\n"; + if(inv!=null) + { + eb.setThumbnail(inv.getGuild().getIconUrl()); + str+=linestart+"Invite: **"+inv.getCode()+"** "+(inv.getChannel().getType()==ChannelType.TEXT + ? "#"+inv.getChannel().getName() : inv.getChannel().getName())+" (ID:"+inv.getChannel().getId()+")"; + if(inv.getGuild().getSplashId()!=null) + { + str+="\n\n<:partner:314068430556758017> **Discord Partner** <:partner:314068430556758017>"; + eb.setImage(inv.getGuild().getSplashUrl()+"?size=1024"); + } + } + eb.setDescription(str); + event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); + return; + } + + } + catch(NumberFormatException ex) + { + } + catch(RateLimitedException ex) + { + event.reactWarning(); + return; + } + catch(Exception ex) + { + ex.printStackTrace(); + } + String code = event.getArgs().substring(event.getArgs().indexOf("/")+1); + Invite inv = null; + try + { + inv = Invite.resolve(event.getJDA(), code).complete(); + } + catch(Exception ex){} + if(inv==null) + { + event.replyError("No users, guilds, or invites found."); + return; + } + Widget widget = null; + try + { + widget = WidgetUtil.getWidget(inv.getGuild().getIdLong()); + } + catch(RateLimitedException ex) {} + String text = "\uD83D\uDDA5 Information about Invite Code **"+code+"**:"; + EmbedBuilder eb = new EmbedBuilder(); + eb.setThumbnail(inv.getGuild().getIconUrl()); + String str = linestart+"Guild: **"+inv.getGuild().getName()+"**\n" + + linestart+"Channel: **"+(inv.getChannel().getType()==ChannelType.TEXT?"#":"")+inv.getChannel().getName()+"** (ID:"+inv.getChannel().getId()+")\n" + + linestart+"Inviter: "+(inv.getInviter()==null?"N/A":"**"+inv.getInviter().getName()+"**#"+inv.getInviter().getDiscriminator()+" (ID:"+inv.getInviter().getId()+")"); + eb.setDescription(str); + str = linestart+"ID: **"+inv.getGuild().getId()+"**\n" + +linestart+"Creation: **"+inv.getGuild().getCreationTime().format(DateTimeFormatter.RFC_1123_DATE_TIME)+"**\n"; + if(widget!=null) + str += linestart+"Channels: **"+widget.getVoiceChannels().size()+"** Voice\n" + +linestart+"Users: **"+widget.getMembers().size()+"** online"; + if(inv.getGuild().getSplashId()!=null) + { + str+="\n\n<:partner:314068430556758017> **Discord Partner** <:partner:314068430556758017>"; + eb.setImage(inv.getGuild().getSplashUrl()+"?size=1024"); + } + eb.addField("Guild Info", str, false); + event.reply(new MessageBuilder().append(text).setEmbed(eb.build()).build()); + }); + } +} diff --git a/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java b/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java index 67c2d65..61ccd0a 100644 --- a/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java +++ b/src/main/java/com/jagrosh/vortex/database/managers/AutomodManager.java @@ -19,6 +19,7 @@ import com.jagrosh.easysql.DatabaseConnector; import com.jagrosh.easysql.SQLColumn; import com.jagrosh.easysql.columns.*; +import com.jagrosh.vortex.Action; import com.jagrosh.vortex.utils.FixedCache; import java.sql.ResultSet; import java.sql.SQLException; @@ -38,6 +39,8 @@ public final static SQLColumn GUILD_ID = new LongColumn("GUILD_ID",false,0L,true); + public final static SQLColumn RESOLVE_URLS = new BooleanColumn("RESOLVE_URLS", false, false); + 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 MAX_LINES = new IntegerColumn("MAX_LINES", false, 0); @@ -78,25 +81,26 @@ return new Field(SETTINGS_TITLE, "__Anti-Advertisement__\n" + (settings.inviteStrikes==0 && settings.refStrikes==0 ? "Disabled\n\n" - : "Invite Link Strikes: **" + settings.inviteStrikes + "**\n" + - "Referral Link Strikes: **" + settings.refStrikes + "**\n\n") + : "Invite Links: `" + settings.inviteStrikes + " " + Action.STRIKE.getEmoji() + "`\n" + + "Referral Links: `" + settings.refStrikes + " " + Action.STRIKE.getEmoji() + "`\n" + + "Resolve Links: `" + (settings.resolveUrls ? "ON" : "OFF") + "`\n\n") + "__Anti-Duplicate__\n" + (settings.useAntiDuplicate() - ? "Strike Threshold: **" + settings.dupeStrikeThresh + "**\n" + - "Delete Threshold: **" + settings.dupeDeleteThresh + "**\n" + - "Strikes: **" + settings.dupeStrikes + "**\n\n" + ? "Strike Threshold: `" + settings.dupeStrikeThresh + "`\n" + + "Delete Threshold: `" + settings.dupeDeleteThresh + "`\n" + + "Strikes: `" + settings.dupeStrikes + " " + Action.STRIKE.getEmoji() + "`\n\n" : "Disabled\n\n") + "__Maximum Mentions__\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")) + : "Max User Mentions: " + (settings.maxMentions==0 ? "None\n" : "`" + settings.maxMentions + "`\n") + + "Max Role Mentions: " + (settings.maxRoleMentions==0 ? "None\n\n" : "`" + settings.maxRoleMentions + "`\n\n")) + "__Spam Prevention__\n" + (settings.maxLines==0 && settings.copypastaStrikes==0 ? "Disabled\n\n" - : "Max Lines / Message: "+(settings.maxLines==0 ? "Disabled\n" : "**"+settings.maxLines+"**\n") + - "Copypasta Strikes: **" + settings.copypastaStrikes + "**\n\n") + : "Max Lines / Message: "+(settings.maxLines==0 ? "Disabled\n" : "`"+settings.maxLines+"`\n") + + "Copypasta: `" + settings.copypastaStrikes + " " + Action.STRIKE.getEmoji() + "`\n\n") + "__Auto Anti-Raid Mode__\n" + (settings.useAutoRaidMode() - ? "**" + settings.raidmodeNumber + "** joins / **" + settings.raidmodeTime + "** seconds\n" + ? "`" + settings.raidmodeNumber + "` joins / `" + settings.raidmodeTime + "` seconds\n" : "Disabled\n") - + "\u200B", true); + /*+ "\u200B"*/, true); } // Setters @@ -114,6 +118,31 @@ }); } + public void setResolveUrls(Guild guild, boolean value) + { + setResolveUrls(guild.getIdLong(), value); + } + + public void setResolveUrls(long guildId, boolean value) + { + invalidateCache(guildId); + readWrite(selectAll(GUILD_ID.is(guildId)), rs -> + { + if(rs.next()) + { + RESOLVE_URLS.updateValue(rs, value); + rs.updateRow(); + } + else + { + rs.moveToInsertRow(); + GUILD_ID.updateValue(rs, guildId); + RESOLVE_URLS.updateValue(rs, value); + rs.insertRow(); + } + }); + } + public void setMaxMentions(Guild guild, int max) { invalidateCache(guild); @@ -282,11 +311,17 @@ private void invalidateCache(Guild guild) { - cache.pull(guild.getIdLong()); + invalidateCache(guild.getIdLong()); + } + + private void invalidateCache(long guildId) + { + cache.pull(guildId); } public class AutomodSettings { + public final boolean resolveUrls; public final int maxMentions, maxRoleMentions; public final int maxLines; public final int raidmodeNumber, raidmodeTime; @@ -295,6 +330,7 @@ private AutomodSettings() { + this.resolveUrls = false; this.maxMentions = 0; this.maxRoleMentions = 0; this.maxLines = 0; @@ -310,6 +346,7 @@ private AutomodSettings(ResultSet rs) throws SQLException { + this.resolveUrls = RESOLVE_URLS.getValue(rs); this.maxMentions = MAX_MENTIONS.getValue(rs); this.maxRoleMentions = MAX_ROLE_MENTIONS.getValue(rs); this.maxLines = MAX_LINES.getValue(rs); diff --git a/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java b/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java index a0870ef..42a6b9f 100644 --- a/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java +++ b/src/main/java/com/jagrosh/vortex/database/managers/GuildSettingsDataManager.java @@ -21,7 +21,6 @@ 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; @@ -50,6 +49,8 @@ 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 VOICELOG_ID = new LongColumn("VOICELOG_ID",false,0L); + public final static SQLColumn AVATARLOG_ID = new LongColumn("AVATARLOG_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); @@ -82,11 +83,15 @@ TextChannel modlog = settings.getModLogChannel(guild); TextChannel serverlog = settings.getServerLogChannel(guild); TextChannel messagelog = settings.getMessageLogChannel(guild); + TextChannel voicelog = settings.getVoiceLogChannel(guild); + TextChannel avylog = settings.getAvatarLogChannel(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()) + + "\nMessage Log: "+(messagelog==null ? "None" : messagelog.getAsMention()) + + "\nVoice Log: "+(voicelog==null ? "None" : voicelog.getAsMention()) + + "\nAvatar Log: "+(avylog==null ? "None" : avylog.getAsMention()) + "\nServer Log: "+(serverlog==null ? "None" : serverlog.getAsMention()) + "\nTimezone: **"+settings.timezone+"**\n\u200B", true); } @@ -152,6 +157,51 @@ }); } + public void setVoiceLogChannel(Guild guild, TextChannel tc) + { + invalidateCache(guild); + readWrite(select(GUILD_ID.is(guild.getIdLong()), GUILD_ID, VOICELOG_ID), rs -> + { + if(rs.next()) + { + VOICELOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong()); + rs.updateRow(); + } + else + { + rs.moveToInsertRow(); + GUILD_ID.updateValue(rs, guild.getIdLong()); + VOICELOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong()); + rs.insertRow(); + } + }); + } + + public void setAvatarLogChannel(Guild guild, TextChannel tc) + { + setAvatarLogChannel(guild.getIdLong(), tc); + } + + public void setAvatarLogChannel(long guildId, TextChannel tc) + { + invalidateCache(guildId); + readWrite(select(GUILD_ID.is(guildId), GUILD_ID, AVATARLOG_ID), rs -> + { + if(rs.next()) + { + AVATARLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong()); + rs.updateRow(); + } + else + { + rs.moveToInsertRow(); + GUILD_ID.updateValue(rs, guildId); + AVATARLOG_ID.updateValue(rs, tc==null ? 0L : tc.getIdLong()); + rs.insertRow(); + } + }); + } + public void setModeratorRole(Guild guild, Role role) { invalidateCache(guild); @@ -257,12 +307,17 @@ private void invalidateCache(Guild guild) { - cache.pull(guild.getIdLong()); + invalidateCache(guild.getIdLong()); + } + + private void invalidateCache(long guildId) + { + cache.pull(guildId); } public class GuildSettings implements GuildSettingsProvider { - private final long modRole, modlog, serverlog, messagelog; + private final long modRole, modlog, serverlog, messagelog, voicelog, avatarlog; private final String prefix; private final ZoneId timezone; private final int raidMode; @@ -273,6 +328,8 @@ this.modlog = 0; this.serverlog = 0; this.messagelog = 0; + this.voicelog = 0; + this.avatarlog = 0; this.prefix = null; this.timezone = ZoneId.systemDefault(); this.raidMode = -2; @@ -284,6 +341,8 @@ this.modlog = MODLOG_ID.getValue(rs); this.serverlog = SERVERLOG_ID.getValue(rs); this.messagelog = MESSAGELOG_ID.getValue(rs); + this.voicelog = VOICELOG_ID.getValue(rs); + this.avatarlog = AVATARLOG_ID.getValue(rs); this.prefix = PREFIX.getValue(rs); String str = TIMEZONE.getValue(rs); this.timezone = str==null ? ZoneId.systemDefault() : ZoneId.of(str); @@ -310,6 +369,16 @@ return guild.getTextChannelById(messagelog); } + public TextChannel getVoiceLogChannel(Guild guild) + { + return guild.getTextChannelById(voicelog); + } + + public TextChannel getAvatarLogChannel(Guild guild) + { + return guild.getTextChannelById(avatarlog); + } + public ZoneId getTimezone() { return timezone; diff --git a/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java b/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java index 61573f9..29da0fe 100644 --- a/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java +++ b/src/main/java/com/jagrosh/vortex/database/managers/PunishmentManager.java @@ -44,9 +44,9 @@ 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}; + private static final int[] DEFAULT_STRIKE_COUNTS = {2, 3, 4, 5, 6, 7, 8}; + private static final Action[] DEFAULT_ACTIONS = {Action.TEMPMUTE, Action.TEMPMUTE, Action.KICK, Action.SOFTBAN, Action.TEMPMUTE, Action.TEMPBAN, Action.BAN}; + private static final int[] DEFAULT_TIMES = {10, 120, 0, 0, 1440, 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); diff --git a/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java b/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java index 1209161..c329bee 100644 --- a/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java +++ b/src/main/java/com/jagrosh/vortex/logging/BasicLogger.java @@ -15,14 +15,17 @@ */ package com.jagrosh.vortex.logging; +import com.jagrosh.vortex.utils.AvatarUtil; import com.jagrosh.vortex.Vortex; import com.jagrosh.vortex.utils.FormatUtil; import com.jagrosh.vortex.utils.LogUtil; import java.awt.Color; +import java.awt.image.BufferedImage; import java.time.OffsetDateTime; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.List; +import java.util.stream.Collectors; import net.dv8tion.jda.core.EmbedBuilder; import net.dv8tion.jda.core.MessageBuilder; import net.dv8tion.jda.core.entities.Message; @@ -33,6 +36,7 @@ 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.UserAvatarUpdateEvent; import net.dv8tion.jda.core.events.user.UserNameUpdateEvent; import net.dv8tion.jda.core.exceptions.PermissionException; @@ -64,6 +68,20 @@ } } + private void logFile(OffsetDateTime now, TextChannel tc, String emote, String message, byte[] file, String filename) + { + try + { + tc.sendFile(file, filename, new MessageBuilder() + .append(FormatUtil.filterEveryone(LogUtil.basiclogFormat(now, vortex.getDatabase().settings.getSettings(tc.getGuild()).getTimezone(), emote, message))) + .build()).queue(); + } + catch(PermissionException ex) + { + + } + } + // Message Logs public void logMessageEdit(Message newMessage, Message oldMessage) @@ -114,7 +132,19 @@ return; if(messages.isEmpty()) { - log(OffsetDateTime.now(), tc, "\uD83D\uDEAE", "**"+count+"** messages were deleted from "+text.getAsMention()+" (**"+messages.size()+"** logged)", null); + //log(OffsetDateTime.now(), tc, "\uD83D\uDEAE", "**"+count+"** messages were deleted from "+text.getAsMention()+" (**"+messages.size()+"** logged)", null); + return; + } + if(messages.size()==1) + { + String formatted = FormatUtil.formatMessage(messages.get(0)); + if(formatted.isEmpty()) + return; + EmbedBuilder delete = new EmbedBuilder() + .setColor(Color.RED) + .appendDescription(formatted); + log(OffsetDateTime.now(), tc, "\u274C", + FormatUtil.formatFullUser(messages.get(0).getAuthor())+"'s message has been deleted from "+messages.get(0).getTextChannel().getAsMention()+":", delete.build()); return; } vortex.getTextUploader().upload(LogUtil.logMessages("Deleted Messages", messages), "DeletedMessages", (view, download) -> @@ -125,6 +155,19 @@ }); } + public void logRedirectPath(Message message, String link, List redirects) + { + TextChannel tc = vortex.getDatabase().settings.getSettings(message.getGuild()).getMessageLogChannel(message.getGuild()); + if(tc==null) + return; + StringBuilder sb = new StringBuilder("\uD83D\uDD37 **"+link+"**"); + for(int i=0; i", @@ -186,19 +232,41 @@ public void logVoiceMove(GuildVoiceMoveEvent event) { - TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild()); + TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getVoiceLogChannel(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); + FormatUtil.formatFullUser(event.getMember().getUser())+" has moved voice channels from _"+event.getChannelLeft().getName()+"_ to _"+event.getChannelJoined().getName()+"_", null); } public void logVoiceLeave(GuildVoiceLeaveEvent event) { - TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getServerLogChannel(event.getGuild()); + TextChannel tc = vortex.getDatabase().settings.getSettings(event.getGuild()).getVoiceLogChannel(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); } + + + // Avatar Logs + + public void logAvatarChange(UserAvatarUpdateEvent event) + { + List logs = event.getUser().getMutualGuilds().stream() + .map(guild -> vortex.getDatabase().settings.getSettings(guild).getAvatarLogChannel(guild)) + .filter(tc -> tc!=null) + .collect(Collectors.toList()); + if(logs.isEmpty()) + return; + OffsetDateTime now = OffsetDateTime.now(); + vortex.getThreadpool().execute(() -> + { + byte[] im = AvatarUtil.makeAvatarImage(event.getUser(), event.getPreviousAvatarUrl(), event.getPreviousAvatarId()); + if(im!=null) + logs.forEach(tc -> logFile(now, tc, "\uD83D\uDDBC", FormatUtil.formatFullUser(event.getUser())+" has changed avatars" + +(event.getUser().getAvatarId()!=null && event.getUser().getAvatarId().startsWith("a_") ? " <:gif:314068430624129039>" : "") + +":", im, "AvatarChange.png")); + }); + } } diff --git a/src/main/java/com/jagrosh/vortex/logging/MessageCache.java b/src/main/java/com/jagrosh/vortex/logging/MessageCache.java index 94c8612..c38ac1a 100644 --- a/src/main/java/com/jagrosh/vortex/logging/MessageCache.java +++ b/src/main/java/com/jagrosh/vortex/logging/MessageCache.java @@ -22,7 +22,6 @@ 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; /** diff --git a/src/main/java/com/jagrosh/vortex/logging/ModLogger.java b/src/main/java/com/jagrosh/vortex/logging/ModLogger.java index 249fb94..4c4733a 100644 --- a/src/main/java/com/jagrosh/vortex/logging/ModLogger.java +++ b/src/main/java/com/jagrosh/vortex/logging/ModLogger.java @@ -17,6 +17,7 @@ import com.jagrosh.vortex.Action; import com.jagrosh.vortex.Vortex; +import com.jagrosh.vortex.automod.AutoMod; import com.jagrosh.vortex.utils.FixedCache; import com.jagrosh.vortex.utils.FormatUtil; import com.jagrosh.vortex.utils.LogUtil; @@ -196,7 +197,7 @@ if(guild==null) return; TextChannel modlog = vortex.getDatabase().settings.getSettings(guild).getModLogChannel(guild); - if(modlog==null || !modlog.canTalk()) + if(modlog==null || !modlog.canTalk() || !modlog.getGuild().getSelfMember().hasPermission(Permission.VIEW_AUDIT_LOGS)) return; try { @@ -233,6 +234,8 @@ if(act!=null) { User mod = ale.getUser(); + if(ale.getJDA().getSelfUser().equals(mod) && act==Action.MUTE && AutoMod.RESTORE_MUTE_ROLE_AUDIT.equals(ale.getReason())) + return; // restoring muted role shouldn't trigger a log entry String reason = ale.getReason()==null ? "" : ale.getReason(); int minutes = 0; User target = vortex.getShardManager().getUserById(ale.getTargetIdLong()); diff --git a/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java b/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java index 868158c..3563b79 100644 --- a/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java +++ b/src/main/java/com/jagrosh/vortex/utils/FormatUtil.java @@ -24,6 +24,8 @@ import com.jagrosh.vortex.Constants; import com.jagrosh.vortex.Vortex; import java.awt.Color; +import java.util.Collections; +import java.util.stream.Collectors; import net.dv8tion.jda.core.EmbedBuilder; import net.dv8tion.jda.core.MessageBuilder; import net.dv8tion.jda.core.entities.Member; @@ -37,6 +39,7 @@ public class FormatUtil { private final static String MULTIPLE_FOUND = Constants.WARNING+" **Multiple %s found matching \"%s\":**"; + private final static String CMD_EMOJI = "\uD83D\uDCDC"; public static String filterEveryone(String input) { @@ -210,43 +213,50 @@ EmbedBuilder builder = new EmbedBuilder() .setColor(event.getGuild()==null ? Color.LIGHT_GRAY : event.getSelfMember().getColor()); - StringBuilder sb = new StringBuilder(); - Command.Category category = null; - for(Command command : event.getClient().getCommands()) + List commandsInCategory; + String content; + if(event.getArgs().isEmpty()) { - if(!command.isHidden() && (!command.isOwnerCommand() || event.isOwner())) - { - if(category==null) - { - if(command.getCategory()!=null) - { - builder.addField("General Commands", sb.toString()+"\u200B", true); - sb = new StringBuilder(); - category = command.getCategory(); - } - } - else - { - if(command.getCategory()==null || !command.getCategory().getName().equals(category.getName())) - { - builder.addField(category.getName()+" Commands", sb.toString()+"\u200B", true); - sb = new StringBuilder(); - category = command.getCategory(); - } - } - sb.append("[`").append(event.getClient().getTextualPrefix()).append(event.getClient().getPrefix()==null?" ":"").append(command.getName()) - .append(command.getArguments()==null ? "" : " "+command.getArguments()+"").append("`](") - .append(Constants.Wiki.Shortened.fromCategory(command.getCategory())).append(") - ").append(command.getHelp()).append("\n"); - } + commandsInCategory = Collections.EMPTY_LIST; + content = Constants.SUCCESS+" **"+event.getSelfUser().getName()+"** Commands Categories:"; } - builder.addField(category==null ? "General Commands" : category.getName()+" Commands", sb.toString()+"\u200B", true); + else + { + commandsInCategory = event.getClient().getCommands().stream().filter(cmd -> + { + if(cmd.isHidden() || cmd.isOwnerCommand()) + return false; + if(cmd.getCategory()==null) + return event.getArgs().equalsIgnoreCase("general"); + return cmd.getCategory().getName().equalsIgnoreCase(event.getArgs()); + }).collect(Collectors.toList()); + if(commandsInCategory.isEmpty()) + content = Constants.WARNING+" No Category `"+event.getArgs()+"` found."; + else + content = Constants.SUCCESS+" **"+event.getSelfUser().getName()+"** " + +(commandsInCategory.get(0).getCategory()==null ? "General" : commandsInCategory.get(0).getCategory().getName()) + +" Commands:"; + } + + if(commandsInCategory.isEmpty()) + { + builder.addField(CMD_EMOJI+" General Commands", "[`"+Constants.PREFIX+"help general`]("+Constants.Wiki.COMMANDS+"#-general-commands)\n\u200B", false); + event.getClient().getCommands().stream().filter(cmd -> cmd.getCategory()!=null).map(cmd -> cmd.getCategory().getName()).distinct() + .forEach(cat -> builder.addField(CMD_EMOJI+" "+cat+" Commands", "[`"+Constants.PREFIX+"help "+cat.toLowerCase()+"`](" + +Constants.Wiki.COMMANDS+"#-"+cat.toLowerCase()+"-commands)\n\u200B", false)); + } + else + { + commandsInCategory.forEach(cmd -> builder.addField(Constants.PREFIX+cmd.getName()+(cmd.getArguments()==null ? "" : " "+cmd.getArguments()), + "["+cmd.getHelp()+"]("+Constants.Wiki.COMMANDS+"#-"+(cmd.getCategory()==null?"general":cmd.getCategory().getName().toLowerCase())+"-commands)\n\u200B", false)); + } builder.addField("Additional Help", "\uD83D\uDD17 ["+event.getSelfUser().getName()+" Wiki]("+Constants.Wiki.WIKI_BASE+")\n" + "<:discord:314003252830011395> [Support Server]("+event.getClient().getServerInvite()+")\n" + "\uD83D\uDCDC [Full Command Reference]("+Constants.Wiki.COMMANDS+")\n" + "<:patreon:417455429145329665> [Donations]("+Constants.DONATION_LINK+")", false); - return new MessageBuilder().append(Constants.SUCCESS+" **"+event.getSelfUser().getName()+"** Commands:").setEmbed(builder.build()).build(); + return new MessageBuilder().append(content).setEmbed(builder.build()).build(); } } diff --git a/src/main/java/com/jagrosh/vortex/utils/LogUtil.java b/src/main/java/com/jagrosh/vortex/utils/LogUtil.java index 846129e..79c1024 100644 --- a/src/main/java/com/jagrosh/vortex/utils/LogUtil.java +++ b/src/main/java/com/jagrosh/vortex/utils/LogUtil.java @@ -114,7 +114,9 @@ public static String logMessages(String title, List messages) { - StringBuilder sb = new StringBuilder("-- "+title+" --"); + TextChannel deltc = messages.get(0).getTextChannel(); + Guild delg = messages.get(0).getGuild(); + StringBuilder sb = new StringBuilder("-- "+title+" -- #"+deltc.getName()+" ("+deltc.getId()+") -- "+delg.getName()+" ("+delg.getId()+") --"); Message m; for(int i=messages.size()-1; i>=0; i--) { @@ -122,7 +124,7 @@ 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()); + .append(" (").append(m.getAuthor().getId()).append(") : ").append(m.getContentRaw()); m.getAttachments().forEach(att -> sb.append("\n").append(att.getUrl())); } return sb.toString().trim(); diff --git a/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java b/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java index d556599..58b4564 100644 --- a/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java +++ b/src/main/java/com/jagrosh/vortex/utils/OtherUtil.java @@ -15,11 +15,18 @@ */ package com.jagrosh.vortex.utils; +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.stream.Collectors; import net.dv8tion.jda.core.Region; 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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * @@ -27,19 +34,24 @@ */ public class OtherUtil { + private final static Logger LOG = LoggerFactory.getLogger(OtherUtil.class); + 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) + public static void safeDM(User user, String message, boolean shouldDM, Runnable then) { - if(user==null) + if(user==null || !shouldDM) then.run(); - else + else try + { user.openPrivateChannel() .queue(pc -> pc.sendMessage(message).queue(s->then.run(), f->then.run()), f->then.run()); + } + catch(Exception ex){} } public static Member findMember(String username, String discriminator, Guild guild) @@ -75,6 +87,25 @@ return timeinseconds; } + public static String[] readLines(String filename) + { + try + { + List values = Files.readAllLines(Paths.get("lists" + File.separator + filename + ".txt")).stream() + .map(str -> str.replace("\uFEFF", "").trim()).filter(str -> !str.isEmpty() && !str.startsWith("//")).collect(Collectors.toList()); + String[] list = new String[values.size()]; + for(int i=0; i