package net.essentialsx.discord.util; import club.minnced.discord.webhook.WebhookClient; import club.minnced.discord.webhook.send.AllowedMentions; import com.earth2me.essentials.utils.DownsampleUtil; import com.earth2me.essentials.utils.FormatUtil; import com.earth2me.essentials.utils.NumberUtil; import com.earth2me.essentials.utils.VersionUtil; import com.google.common.collect.ImmutableList; import net.dv8tion.jda.api.Permission; import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; import net.dv8tion.jda.api.entities.Message; import net.dv8tion.jda.api.entities.Role; import net.dv8tion.jda.api.entities.Webhook; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.essentialsx.api.v2.events.discord.DiscordMessageEvent; import net.essentialsx.api.v2.services.discord.MessageType; import net.essentialsx.discord.JDADiscordService; import okhttp3.OkHttpClient; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CopyOnWriteArrayList; public final class DiscordUtil { public final static String ADVANCED_RELAY_NAME = "EssX Advanced Relay"; public final static String CONSOLE_RELAY_NAME = "EssX Console Relay"; public final static List NO_GROUP_MENTIONS; public final static AllowedMentions ALL_MENTIONS_WEBHOOK = AllowedMentions.all(); public final static AllowedMentions NO_GROUP_MENTIONS_WEBHOOK = new AllowedMentions().withParseEveryone(false).withParseRoles(false).withParseUsers(true); public final static CopyOnWriteArrayList ACTIVE_WEBHOOKS = new CopyOnWriteArrayList<>(); static { final ImmutableList.Builder types = new ImmutableList.Builder<>(); types.add(Message.MentionType.USER); types.add(Message.MentionType.CHANNEL); types.add(Message.MentionType.EMOJI); NO_GROUP_MENTIONS = types.build(); } private DiscordUtil() { } /** * Creates a {@link WebhookClient}. * * @param id The id of the webhook. * @param token The token of the webhook. * @param client The http client of the webhook. * @return The {@link WebhookClient}. */ public static WrappedWebhookClient getWebhookClient(long id, String token, OkHttpClient client) { return new WrappedWebhookClient(id, token, client); } /** * Gets or creates a webhook with the given name in the given channel. * * @param channel The channel to search for/create webhooks in. * @param webhookName The name of the webhook to search for/create. * * @return A future which completes with the webhook by the given name in the given channel, or null * if the bot lacks the proper permissions. */ public static CompletableFuture getOrCreateWebhook(final TextChannel channel, final String webhookName) { if (!channel.getGuild().getSelfMember().hasPermission(channel, Permission.MANAGE_WEBHOOKS)) { return CompletableFuture.completedFuture(null); } final CompletableFuture future = new CompletableFuture<>(); channel.retrieveWebhooks().queue(webhooks -> { for (final Webhook webhook : webhooks) { if (webhook.getName().equals(webhookName) && webhook.getToken() != null) { ACTIVE_WEBHOOKS.addIfAbsent(webhook.getId()); future.complete(webhook); return; } } createWebhook(channel, webhookName).thenAccept(future::complete); }); return future; } /** * Cleans up unused webhooks from channels that no longer require an advanced relay. * * @param guild The guild which to preform the cleanup in. * @param webhookName The name of the webhook to scan for. */ @SuppressWarnings("unused") // :balloon: private static void cleanWebhooks(final Guild guild, String webhookName) { if (!guild.getSelfMember().hasPermission(Permission.MANAGE_WEBHOOKS)) { return; } guild.retrieveWebhooks().queue(webhooks -> { for (final Webhook webhook : webhooks) { if (webhook.getName().equalsIgnoreCase(webhookName) && !ACTIVE_WEBHOOKS.contains(webhook.getId())) { webhook.delete().reason("EssentialsX Discord: webhook cleanup").queue(); } } }); } /** * Creates a webhook with the given name in the given channel. * * @param channel The channel to search for webhooks in. * @param webhookName The name of the webhook to look for. * @return A future which completes with the webhook by the given name in the given channel or null if no permissions. */ public static CompletableFuture createWebhook(TextChannel channel, String webhookName) { if (!channel.getGuild().getSelfMember().hasPermission(channel, Permission.MANAGE_WEBHOOKS)) { return CompletableFuture.completedFuture(null); } final CompletableFuture future = new CompletableFuture<>(); channel.createWebhook(webhookName).queue(webhook -> { future.complete(webhook); ACTIVE_WEBHOOKS.addIfAbsent(webhook.getId()); }); return future; } /** * Gets the highest role of a given member or an empty string if the member has no roles. * * @param member The target member. * @return The highest role or blank string. */ public static String getRoleFormat(Member member) { final List roles = member == null ? null : member.getRoles(); if (roles == null || roles.isEmpty()) { return ""; } return roles.get(0).getName(); } /** * Gets the uppermost bukkit color code of a given member or an empty string if the server version is < 1.16. * * @param member The target member. * @return The bukkit color code or blank string. */ public static String getRoleColorFormat(Member member) { if (member == null || member.getColorRaw() == Role.DEFAULT_COLOR_RAW) { return ""; } final int rawColor = 0xff000000 | member.getColorRaw(); if (VersionUtil.getServerBukkitVersion().isHigherThanOrEqualTo(VersionUtil.v1_16_1_R01)) { // Essentials' FormatUtil allows us to not have to use bungee's chatcolor since bukkit's own one doesn't support rgb return FormatUtil.replaceFormat("&#" + Integer.toHexString(rawColor).substring(2)); } return FormatUtil.replaceFormat("&" + DownsampleUtil.nearestTo(rawColor)); } /** * Checks is the supplied user has at least one of the supplied roles. * * @param member The member to check. * @param roleDefinitions A list with either the name or id of roles. * @return true if member has role. */ public static boolean hasRoles(Member member, List roleDefinitions) { if (member.hasPermission(Permission.ADMINISTRATOR)) { return true; } final List roles = member.getRoles(); for (String roleDefinition : roleDefinitions) { roleDefinition = roleDefinition.trim(); final boolean id = NumberUtil.isNumeric(roleDefinition); if (roleDefinition.equals("*") || member.getId().equals(roleDefinition)) { return true; } for (final Role role : roles) { if (role.getId().equals(roleDefinition) || (!id && role.getName().equalsIgnoreCase(roleDefinition))) { return true; } } } return false; } public static String getAvatarUrl(final JDADiscordService jda, final Player player) { return jda.getSettings().getAvatarURL().replace("{uuid}", player.getUniqueId().toString()).replace("{name}", player.getName()); } public static void dispatchDiscordMessage(final JDADiscordService jda, final MessageType messageType, final String message, final boolean allowPing, final String avatarUrl, final String name, final UUID uuid) { if (jda.getPlugin().getSettings().getMessageChannel(messageType.getKey()).equalsIgnoreCase("none")) { return; } final DiscordMessageEvent event = new DiscordMessageEvent(messageType, FormatUtil.stripFormat(message), allowPing, avatarUrl, FormatUtil.stripFormat(name), uuid); // If the server is stopping, we cannot dispatch events. if (messageType == MessageType.DefaultTypes.SERVER_STOP) { jda.sendMessage(event, event.getMessage(), event.isAllowGroupMentions()); return; } if (Bukkit.getServer().isPrimaryThread()) { Bukkit.getPluginManager().callEvent(event); } else { Bukkit.getScheduler().runTask(jda.getPlugin(), () -> Bukkit.getPluginManager().callEvent(event)); } } }