diff --git a/pom.xml b/pom.xml index 2cc3470..e8a7bdd 100644 --- a/pom.xml +++ b/pom.xml @@ -14,9 +14,8 @@ - dv8tion - m2-dv8tion - https://m2.dv8tion.net/releases + jitpack.io + https://jitpack.io jcenter @@ -26,11 +25,11 @@ - - net.dv8tion - JDA - 4.3.0_307 - + + com.github.jagrosh + JDA + vortex-SNAPSHOT + com.jagrosh jda-utilities diff --git a/src/main/java/com/jagrosh/vortex/Listener.java b/src/main/java/com/jagrosh/vortex/Listener.java index 60ef3b8..e4fad8e 100644 --- a/src/main/java/com/jagrosh/vortex/Listener.java +++ b/src/main/java/com/jagrosh/vortex/Listener.java @@ -30,6 +30,7 @@ import net.dv8tion.jda.api.events.guild.GuildUnbanEvent; import net.dv8tion.jda.api.events.guild.member.*; import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateNicknameEvent; +import net.dv8tion.jda.api.events.guild.member.update.GuildMemberUpdateTimeOutEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceJoinEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceLeaveEvent; import net.dv8tion.jda.api.events.guild.voice.GuildVoiceMoveEvent; @@ -215,6 +216,11 @@ { vortex.getDatabase().tempslowmodes.clearSlowmode(((TextChannelUpdateSlowmodeEvent) event).getChannel()); } + else if (event instanceof GuildMemberUpdateTimeOutEvent) + { + // Signal the modlogger because someone was timed out + vortex.getModLogger().setNeedUpdate(((GuildMemberUpdateTimeOutEvent) event).getGuild()); + } else if (event instanceof GuildJoinEvent) { Guild guild = ((GuildJoinEvent)event).getGuild(); diff --git a/src/main/java/com/jagrosh/vortex/Vortex.java b/src/main/java/com/jagrosh/vortex/Vortex.java index 39032d6..7a6c890 100644 --- a/src/main/java/com/jagrosh/vortex/Vortex.java +++ b/src/main/java/com/jagrosh/vortex/Vortex.java @@ -80,7 +80,7 @@ System.setProperty("config.file", System.getProperty("config.file", "application.conf")); config = ConfigFactory.load(); waiter = new EventWaiter(Executors.newSingleThreadScheduledExecutor(r -> new Thread(r, "eventwaiter")), false); - threadpool = Executors.newScheduledThreadPool(100, r -> new Thread(r, "vortex")); + threadpool = Executors.newScheduledThreadPool(400, r -> new Thread(r, "vortex")); database = new Database(config.getString("database.host"), config.getString("database.username"), config.getString("database.password")); @@ -109,7 +109,7 @@ new InviteCmd(), new PingCommand(), new RoleinfoCommand(), - new ServerinfoCommand(), + new ServerinfoCmd(), new UserinfoCmd(), // Moderation @@ -140,6 +140,7 @@ new AvatarlogCmd(this), new TimezoneCmd(this), new ModroleCmd(this), + new MuteroleCmd(this), new PrefixCmd(this), new SettingsCmd(this), @@ -203,6 +204,7 @@ threadpool.scheduleWithFixedDelay(() -> database.tempbans.checkUnbans(shards), 0, 2, TimeUnit.MINUTES); threadpool.scheduleWithFixedDelay(() -> database.tempmutes.checkUnmutes(shards, database.settings), 0, 45, TimeUnit.SECONDS); threadpool.scheduleWithFixedDelay(() -> database.tempslowmodes.checkSlowmode(shards), 0, 45, TimeUnit.SECONDS); + threadpool.scheduleWithFixedDelay(() -> database.premium.checkPremiumSubscriptions(shards), 5, 10, TimeUnit.HOURS); } diff --git a/src/main/java/com/jagrosh/vortex/automod/AutoMod.java b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java index 037df40..17faaa7 100644 --- a/src/main/java/com/jagrosh/vortex/automod/AutoMod.java +++ b/src/main/java/com/jagrosh/vortex/automod/AutoMod.java @@ -485,8 +485,8 @@ LOG.trace("Found "+invites.size()+" invites."); for(String inviteCode : invites) { - LOG.info("Resolving invite in " + message.getGuild().getId() + ": " + inviteCode); long gid = inviteResolver.resolve(inviteCode, message.getJDA()); + LOG.info("Resolving invite in " + message.getGuild().getId() + ": " + inviteCode + " -> " + gid); if(gid != message.getGuild().getIdLong() && !inviteWhitelist.contains(gid)) { currentStatus.addStrikes(settings.inviteStrikes); diff --git a/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java b/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java index 5d9d135..3b81262 100644 --- a/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java +++ b/src/main/java/com/jagrosh/vortex/automod/InviteResolver.java @@ -16,8 +16,19 @@ package com.jagrosh.vortex.automod; import com.jagrosh.vortex.utils.FixedCache; +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.Collections; import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.ChannelType; +import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Invite; +import net.dv8tion.jda.api.utils.MiscUtil; +import net.dv8tion.jda.internal.entities.InviteImpl; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.Response; +import org.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -27,19 +38,46 @@ */ public class InviteResolver { + private final static String[] BASE_URLS = {"discordapp.com", "ptb.discordapp.com", "canary.discordapp.com", + /*"discord.com",*/ "ptb.discord.com", "canary.discord.com", "discord.co", "watchanimeattheoffice.com", "discord.new"}; private final Logger log = LoggerFactory.getLogger(InviteResolver.class); private final FixedCache cached = new FixedCache<>(5000); + private final OkHttpClient client = new OkHttpClient.Builder().build(); + + public Invite resolveFull(String code, JDA jda) + { + return resolveFull(code, jda.getToken()); + } + + public Invite resolveFull(String code, String token) + { + log.debug("Attempting to resolve " + code); + try + { + return getInvite(code, token); + } + catch(Exception ex) + { + return null; + } + } public long resolve(String code, JDA jda) { + return resolve(code, jda.getToken()); + } + + public long resolve(String code, String token) + { log.debug("Attempting to resolve " + code); if(cached.contains(code)) return cached.get(code); try { - Invite i = Invite.resolve(jda, code).complete(false); - cached.put(code, i.getGuild().getIdLong()); - return i.getGuild().getIdLong(); + long guildId = getInviteGuild(code, token); + //long guildId = Invite.resolve(jda, code).complete(false).getGuild().getIdLong(); + cached.put(code, guildId); + return guildId; } catch(Exception ex) { @@ -47,4 +85,69 @@ return 0L; } } + + private long getInviteGuild(String code, String token) throws IOException + { + JSONObject json = getInviteJSON(code, token); + if(json != null) + return MiscUtil.parseSnowflake(json.getJSONObject("guild").getString("id")); + return 0L; + } + + private InviteImpl getInvite(String code, String token) throws IOException + { + JSONObject json = getInviteJSON(code, token); + if(json != null) + { + Invite.Channel channel; + if(json.has("channel")) + { + JSONObject c = json.getJSONObject("channel"); + channel = new InviteImpl.ChannelImpl(c.optLong("id"), c.optString("name"), c.optInt("type") == 0 ? ChannelType.TEXT : ChannelType.UNKNOWN); + } + else + channel = null; + Invite.Guild guild; + if(json.has("guild")) + { + JSONObject g = json.getJSONObject("guild"); + guild = new InviteImpl.GuildImpl(g.optLong("id"), g.optString("icon"), g.optString("name"), g.optString("splash"), Guild.VerificationLevel.UNKNOWN, -1, -1, Collections.emptySet()); + } + else + guild = null; + return new InviteImpl(null, code, false, null, 0, 0, false, OffsetDateTime.now(), 0, channel, guild, null, null, Invite.InviteType.GUILD); + } + return null; + } + + private JSONObject getInviteJSON(String code, String token) throws IOException + { + Response res = client.newCall(new Request.Builder().get() + .addHeader("Authorization", token) + .addHeader("User-Agent", "DiscordBot (VortexInviteResolver, 1.0)") + .url("https://" + rand(BASE_URLS) + "/api/invites/" + code) + .build()).execute(); + if(res.isSuccessful()) + { + return new JSONObject(res.body().string()); + } + + // if it failed, log why (usually unknown invite or cloudflare issue) + try + { + String message = new JSONObject(res.body().string()).getString("message"); + log.warn(String.format("Unsuccessful resolving '%s': %s", code, message)); + } + catch(Exception ex) + { + log.warn(String.format("Unsuccessful resolving '%s' | Headers: %s | Body: %s", code, + res.headers().toMultimap().toString(), res.body() == null ? null : res.body().string())); + } + return null; + } + + private T rand(T[] items) + { + return items[(int)(Math.random() * items.length)]; + } } diff --git a/src/main/java/com/jagrosh/vortex/commands/automod/AntieveryoneCmd.java b/src/main/java/com/jagrosh/vortex/commands/automod/AntieveryoneCmd.java index 84d7465..43105b0 100644 --- a/src/main/java/com/jagrosh/vortex/commands/automod/AntieveryoneCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/automod/AntieveryoneCmd.java @@ -74,6 +74,9 @@ vortex.getDatabase().automod.setEveryoneStrikes(event.getGuild(), numstrikes); event.replySuccess("Users will now receive `"+numstrikes+"` strikes for attempting to ping @\u0435veryone/here. " // cyrillic e - + "This also considers pingable roles called 'everyone' and 'here'. This will not affect users that actually "); + + "This also considers pingable roles called 'everyone' and 'here'. This will not affect users that actually " + + "have permission to ping everyone. Pinging oneself or bots do not count towards this total. Duplicate " + + "pings in the same message are also not counted towards this total (pinging the same person 3 times only " + + "counts as 1 ping)."); } } 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..0ef3c49 --- /dev/null +++ b/src/main/java/com/jagrosh/vortex/commands/general/ServerinfoCmd.java @@ -0,0 +1,80 @@ +/* + * Copyright 2022 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.general; + +import com.jagrosh.jdautilities.command.Command; +import com.jagrosh.jdautilities.command.CommandEvent; +import java.time.format.DateTimeFormatter; +import net.dv8tion.jda.api.*; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.Member; + +/** + * + * @author John Grosh (john.a.grosh@gmail.com) + */ +public class ServerinfoCmd extends Command +{ + private final static String LINESTART = "\u25AB"; // â–« + private final static String GUILD_EMOJI = "\uD83D\uDDA5"; // 🖥 + private final static String NO_REGION = "\u2754"; // â�” + + 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(); + Member owner = guild.getOwner(); + 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 = (GUILD_EMOJI + " Information about **" + guild.getName() + "**:") + .replace("@everyone", "@\u0435veryone") // cyrillic e + .replace("@here", "@h\u0435re") // cyrillic e + .replace("discord.gg/", "dis\u0441ord.gg/"); // cyrillic c; + String str = LINESTART + "ID: **" + guild.getId() + "**\n" + + LINESTART + "Owner: " + (owner == null ? "Unknown" : "**" + owner.getUser().getName() + "**#" + owner.getUser().getDiscriminator()) + "\n"; + if(!guild.getVoiceChannels().isEmpty()) + { + Region reg = guild.getVoiceChannels().get(0).getRegion(); + str += LINESTART + "Location: " + (reg.getEmoji() == null || reg.getEmoji().isEmpty() ? NO_REGION : reg.getEmoji()) + " **" + reg.getName() + "**\n"; + } + str += LINESTART + "Creation: **" + guild.getTimeCreated().format(DateTimeFormatter.RFC_1123_DATE_TIME) + "**\n" + + LINESTART + "Users: **" + guild.getMemberCache().size() + "** (" + onlineCount + " online, " + botCount + " bots)\n" + + LINESTART + "Channels: **" + guild.getTextChannelCache().size() + "** Text, **" + guild.getVoiceChannelCache().size() + "** Voice, **" + guild.getCategoryCache().size() + "** Categories\n" + + LINESTART + "Verification: **" + guild.getVerificationLevel().name() + "**"; + if(!guild.getFeatures().isEmpty()) + str += "\n" + LINESTART + "Features: **" + String.join("**, **", guild.getFeatures()) + "**"; + if(guild.getSplashId() != null) + { + builder.setImage(guild.getSplashUrl() + "?size=1024"); + str += "\n" + LINESTART + "Splash: "; + } + if(guild.getIconUrl()!=null) + builder.setThumbnail(guild.getIconUrl()); + builder.setColor(owner == null ? null : owner.getColor()); + builder.setDescription(str); + event.reply(new MessageBuilder().append(title).setEmbed(builder.build()).build()); + } +} \ No newline at end of file diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/UnbanCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/UnbanCmd.java index 85c10b0..6d8b88d 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/UnbanCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/UnbanCmd.java @@ -25,7 +25,7 @@ import com.jagrosh.vortex.utils.LogUtil; import java.util.LinkedList; import java.util.List; -import net.dv8tion.jda.api.entities.Guild.Ban; +//import net.dv8tion.jda.api.entities.Guild.Ban; /** * @@ -54,25 +54,25 @@ String reason = LogUtil.auditReasonFormat(event.getMember(), args.reason); StringBuilder builder = new StringBuilder(); - event.getGuild().retrieveBanList().queue(list -> - { - List toUnban = new LinkedList<>(); - args.members.forEach(m -> args.users.add(m.getUser())); + //event.getGuild().retrieveBanList().queue(list -> + //{ + List toUnban = new LinkedList<>(); + args.members.forEach(m -> builder.append("\n").append(event.getClient().getError()).append(" ").append(FormatUtil.formatUser(m.getUser())).append(" is not banned!")); args.users.forEach(u -> { - Ban ban = list.stream().filter(b -> b.getUser().getIdLong()==u.getIdLong()).findFirst().orElse(null); - if(ban==null) - builder.append("\n").append(event.getClient().getError()).append(" ").append(FormatUtil.formatUser(u)).append(" is not banned!"); - else - toUnban.add(ban); + //Ban ban = list.stream().filter(b -> b.getUser().getIdLong()==u.getIdLong()).findFirst().orElse(null); + //if(ban==null) + // builder.append("\n").append(event.getClient().getError()).append(" ").append(FormatUtil.formatUser(u)).append(" is not banned!"); + //else + toUnban.add(u.getIdLong()); }); args.ids.forEach(id -> { - Ban ban = list.stream().filter(b -> b.getUser().getIdLong()==id).findFirst().orElse(null); - if(ban==null) - builder.append("\n").append(event.getClient().getError()).append(" <@").append(id).append("> is not banned!"); - else - toUnban.add(ban); + //Ban ban = list.stream().filter(b -> b.getUser().getIdLong()==id).findFirst().orElse(null); + //if(ban==null) + // builder.append("\n").append(event.getClient().getError()).append(" <@").append(id).append("> is not banned!"); + //else + toUnban.add(id); }); args.unresolved.forEach(un -> builder.append("\n").append(event.getClient().getWarning()).append(" Could not resolve `").append(un).append("` to a user ID")); @@ -87,21 +87,21 @@ for(int i=0; i + event.getGuild().unban(Long.toString(ban)).reason(reason).queue(success -> { - builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully unbanned ").append(FormatUtil.formatUser(ban.getUser())); + builder.append("\n").append(event.getClient().getSuccess()).append(" Successfully unbanned <@").append(ban).append(">"); if(last) event.reply(builder.toString()); }, f -> { - builder.append("\n").append(event.getClient().getError()).append(" Failed to unban ").append(FormatUtil.formatUser(ban.getUser())); + builder.append("\n").append(event.getClient().getError()).append(" Failed to unban <@").append(ban).append(">"); if(last) event.reply(builder.toString()); }); } - }, f -> event.replyError("Failed to retreive the ban list.")); + //}, f -> event.replyError("Failed to retreive the ban list.")); } } diff --git a/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java b/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java index ae181d8..6a9df0e 100644 --- a/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/moderation/VoicemoveCmd.java @@ -39,6 +39,8 @@ this.aliases = new String[]{"magnet"}; this.help = "mass-moves voice channel users"; this.arguments = "[channel]"; + this.cooldown = 20; + this.cooldownScope = CooldownScope.GUILD; } @Override @@ -91,6 +93,7 @@ catch(Exception e) { event.replyWarning(FormatUtil.filterEveryone("I could not connect to **"+vc.getName()+"**")); + event.getGuild().getAudioManager().closeAudioConnection(); return; } vortex.getEventWaiter().waitForEvent(GuildVoiceMoveEvent.class, @@ -104,6 +107,8 @@ 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."); // 🎛 + // failsafe to leave voice channel + event.getClient().getScheduleExecutor().schedule(() -> event.getGuild().getAudioManager().closeAudioConnection(), 2, TimeUnit.MINUTES); } } diff --git a/src/main/java/com/jagrosh/vortex/commands/owner/PremiumCmd.java b/src/main/java/com/jagrosh/vortex/commands/owner/PremiumCmd.java index fd04a01..cc6c565 100644 --- a/src/main/java/com/jagrosh/vortex/commands/owner/PremiumCmd.java +++ b/src/main/java/com/jagrosh/vortex/commands/owner/PremiumCmd.java @@ -24,6 +24,7 @@ import java.time.temporal.ChronoUnit; import java.util.stream.Collectors; import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.User; /** * @@ -37,48 +38,121 @@ { this.vortex = vortex; this.name = "premium"; - this.help = "gives premium"; - this.arguments = "