sx.blah.discord.handle.impl.obj.Guild.java Source code

Java tutorial

Introduction

Here is the source code for sx.blah.discord.handle.impl.obj.Guild.java

Source

/*
 *     This file is part of Discord4J.
 *
 *     Discord4J is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU Lesser General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     Discord4J is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU Lesser General Public License for more details.
 *
 *     You should have received a copy of the GNU Lesser General Public License
 *     along with Discord4J.  If not, see <http://www.gnu.org/licenses/>.
 */

package sx.blah.discord.handle.impl.obj;

import com.fasterxml.jackson.core.JsonProcessingException;
import sx.blah.discord.Discord4J;
import sx.blah.discord.api.IDiscordClient;
import sx.blah.discord.api.IShard;
import sx.blah.discord.api.internal.DiscordClientImpl;
import sx.blah.discord.api.internal.DiscordEndpoints;
import sx.blah.discord.api.internal.DiscordUtils;
import sx.blah.discord.api.internal.json.objects.*;
import sx.blah.discord.api.internal.json.requests.ChannelCreateRequest;
import sx.blah.discord.api.internal.json.requests.GuildEditRequest;
import sx.blah.discord.api.internal.json.requests.MemberEditRequest;
import sx.blah.discord.api.internal.json.requests.ReorderRolesRequest;
import sx.blah.discord.api.internal.json.responses.PruneResponse;
import sx.blah.discord.handle.audio.IAudioManager;
import sx.blah.discord.handle.audio.impl.AudioManager;
import sx.blah.discord.handle.impl.events.WebhookCreateEvent;
import sx.blah.discord.handle.impl.events.WebhookDeleteEvent;
import sx.blah.discord.handle.impl.events.WebhookUpdateEvent;
import sx.blah.discord.handle.obj.*;
import sx.blah.discord.util.*;
import sx.blah.discord.util.cache.Cache;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Function;
import java.util.stream.Collectors;

public class Guild implements IGuild {
    /**
     * All text channels in the guild.
     */
    public final Cache<IChannel> channels;

    /**
     * All voice channels in the guild.
     */
    public final Cache<IVoiceChannel> voiceChannels;

    /**
     * All users connected to the guild.
     */
    public final Cache<IUser> users;

    /**
     * The joined timetamps for users.
     */
    public final Cache<TimeStampHolder> joinTimes;

    /**
     * The name of the guild.
     */
    protected volatile String name;

    /**
     * The ID of this guild.
     */
    protected final long id;

    /**
     * The location of the guild icon
     */
    protected volatile String icon;

    /**
     * The url pointing to the guild icon
     */
    protected volatile String iconURL;

    /**
     * The user id for the owner of the guild
     */
    protected volatile long ownerID;

    /**
     * The roles the guild contains.
     */
    public final Cache<IRole> roles;

    /**
     * The channel where those who are afk are moved to.
     */
    protected volatile long afkChannel;
    /**
     * The time in seconds for a user to be idle to be determined as "afk".
     */
    protected volatile int afkTimeout;

    /**
     * The region this guild is located in.
     */
    protected volatile String regionID;

    /**
     * The verification level of this guild
     */
    protected volatile VerificationLevel verification;

    /**
     * This guild's audio manager.
     */
    protected volatile AudioManager audioManager;

    /**
     * The client that created this object.
     */
    protected final IDiscordClient client;

    /**
     * The shard this object belongs to.
     */
    private final IShard shard;

    /**
     * The list of emojis.
     */
    public final Cache<IEmoji> emojis;

    /**
     * The total number of members in this guild
     */
    private int totalMemberCount;

    /**
     * The ID of the voice channel that the bot is connecting to in this guild.
     * This is 0 if a voice connection has already been established in this guild or none was ever attempted.
     */
    public long connectingVoiceChannelID;

    public Guild(IShard shard, String name, long id, String icon, long ownerID, long afkChannel, int afkTimeout,
            String region, int verification) {
        this(shard, name, id, icon, ownerID, afkChannel, afkTimeout, region, verification,
                new Cache<>((DiscordClientImpl) shard.getClient(), IRole.class),
                new Cache<>((DiscordClientImpl) shard.getClient(), IChannel.class),
                new Cache<>((DiscordClientImpl) shard.getClient(), IVoiceChannel.class),
                new Cache<>((DiscordClientImpl) shard.getClient(), IUser.class),
                new Cache<>((DiscordClientImpl) shard.getClient(), TimeStampHolder.class));
    }

    public Guild(IShard shard, String name, long id, String icon, long ownerID, long afkChannel, int afkTimeout,
            String region, int verification, Cache<IRole> roles, Cache<IChannel> channels,
            Cache<IVoiceChannel> voiceChannels, Cache<IUser> users, Cache<TimeStampHolder> joinTimes) {
        this.shard = shard;
        this.client = shard.getClient();
        this.name = name;
        this.voiceChannels = voiceChannels;
        this.channels = channels;
        this.users = users;
        this.id = id;
        this.icon = icon;
        this.joinTimes = joinTimes;
        this.iconURL = String.format(DiscordEndpoints.ICONS, this.id, this.icon);
        this.ownerID = ownerID;
        this.roles = roles;
        this.afkChannel = afkChannel;
        this.afkTimeout = afkTimeout;
        this.regionID = region;
        this.verification = VerificationLevel.get(verification);
        this.audioManager = new AudioManager(this);
        this.emojis = new Cache<>((DiscordClientImpl) client, IEmoji.class);
    }

    @Override
    public long getOwnerLongID() {
        return ownerID;
    }

    @Override
    public IUser getOwner() {
        return client.getUserByID(ownerID);
    }

    /**
     * Sets the CACHED owner id.
     *
     * @param id The user if of the new owner.
     */
    public void setOwnerID(long id) {
        ownerID = id;
    }

    @Override
    public String getIcon() {
        return icon;
    }

    @Override
    public String getIconURL() {
        return iconURL;
    }

    /**
     * Sets the CACHED icon id for the guild.
     *
     * @param icon The icon id.
     */
    public void setIcon(String icon) {
        this.icon = icon;
        this.iconURL = String.format(DiscordEndpoints.ICONS, getStringID(), this.icon);
    }

    @Override
    public List<IChannel> getChannels() {
        LinkedList<IChannel> list = new LinkedList<>(channels.values());
        list.sort((c1, c2) -> {
            int originalPos1 = ((Channel) c1).position;
            int originalPos2 = ((Channel) c2).position;
            if (originalPos1 == originalPos2) {
                return c2.getCreationDate().compareTo(c1.getCreationDate());
            } else {
                return originalPos1 - originalPos2;
            }
        });
        return list;
    }

    @Override
    public IChannel getChannelByID(long id) {
        return channels.get(id);
    }

    @Override
    public List<IUser> getUsers() {
        return new LinkedList<>(users.values());
    }

    @Override
    public IUser getUserByID(long id) {
        if (users == null)
            return null;

        IUser user = users.get(id);

        if (user == null) {
            if (client.getOurUser() != null && id == client.getOurUser().getLongID())
                user = client.getOurUser();
            else if (id == ownerID)
                user = getOwner();
        }

        return user;
    }

    @Override
    public List<IChannel> getChannelsByName(String name) {
        return channels.stream().filter(channel -> channel.getName().equals(name)).collect(Collectors.toList());
    }

    @Override
    public List<IVoiceChannel> getVoiceChannelsByName(String name) {
        return voiceChannels.stream().filter(channel -> channel.getName().equals(name))
                .collect(Collectors.toList());
    }

    @Override
    public List<IUser> getUsersByName(String name) {
        return getUsersByName(name, true);
    }

    @Override
    public List<IUser> getUsersByName(String name, boolean includeNicknames) {
        return users.stream()
                .filter(u -> includeNicknames ? u.getDisplayName(this).equals(name) : u.getName().equals(name))
                .collect(Collectors.toList());
    }

    @Override
    public List<IUser> getUsersByRole(IRole role) {
        return users.stream().filter(user -> user.getRolesForGuild(this).contains(role))
                .collect(Collectors.toList());
    }

    @Override
    public String getName() {
        return name;
    }

    /**
     * Sets the CACHED name of the guild.
     *
     * @param name The name.
     */
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public long getLongID() {
        return id;
    }

    @Override
    public List<IRole> getRoles() {
        LinkedList<IRole> list = new LinkedList<>(roles.values());
        list.sort((r1, r2) -> {
            int originalPos1 = ((Role) r1).position;
            int originalPos2 = ((Role) r2).position;
            if (originalPos1 == originalPos2) {
                return r2.getCreationDate().compareTo(r1.getCreationDate());
            } else {
                return originalPos1 - originalPos2;
            }
        });
        return list;
    }

    @Override
    public List<IRole> getRolesForUser(IUser user) {
        return user.getRolesForGuild(this);
    }

    @Override
    public IRole getRoleByID(long id) {
        return roles.get(id);
    }

    @Override
    public List<IRole> getRolesByName(String name) {
        return roles.stream().filter(role -> role.getName().equals(name)).collect(Collectors.toList());
    }

    @Override
    public List<IVoiceChannel> getVoiceChannels() {
        LinkedList<IVoiceChannel> list = new LinkedList<>(voiceChannels.values());
        list.sort((c1, c2) -> {
            int originalPos1 = ((Channel) c1).position;
            int originalPos2 = ((Channel) c2).position;
            if (originalPos1 == originalPos2) {
                return c2.getCreationDate().compareTo(c1.getCreationDate());
            } else {
                return originalPos1 - originalPos2;
            }
        });
        return list;
    }

    @Override
    public IVoiceChannel getVoiceChannelByID(long id) {
        return voiceChannels.get(id);
    }

    @Override
    public IVoiceChannel getAFKChannel() {
        if (afkChannel == 0)
            return null;

        return getVoiceChannelByID(afkChannel);
    }

    @Override
    public IVoiceChannel getConnectedVoiceChannel() {
        return client.getConnectedVoiceChannels().stream().filter(voiceChannels::containsValue).findFirst()
                .orElse(null);
    }

    @Override
    public int getAFKTimeout() {
        return afkTimeout;
    }

    public void setAFKChannel(long id) {
        this.afkChannel = id;
    }

    public void setAfkTimeout(int timeout) {
        this.afkTimeout = timeout;
    }

    @Override
    public IRole createRole() {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_ROLES);

        RoleObject response = ((DiscordClientImpl) client).REQUESTS.POST
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/roles", RoleObject.class);
        return DiscordUtils.getRoleFromJSON(this, response);
    }

    @Override
    public List<IUser> getBannedUsers() {
        return getBans().stream().map(Ban::getUser).collect(Collectors.toList());
    }

    @Override
    public List<Ban> getBans() {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.BAN);

        BanObject[] bans = ((DiscordClientImpl) client).REQUESTS.GET
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/bans", BanObject[].class);

        return Arrays.stream(bans)
                .map(b -> new Ban(this, DiscordUtils.getUserFromJSON(getShard(), b.user), b.reason))
                .collect(Collectors.toList());
    }

    @Override
    public void banUser(IUser user) {
        banUser(user, null, 0);
    }

    @Override
    public void banUser(IUser user, int deleteMessagesForDays) {
        banUser(user, null, deleteMessagesForDays);
    }

    @Override
    public void banUser(IUser user, String reason) {
        banUser(user, reason, 0);
    }

    @Override
    public void banUser(IUser user, String reason, int deleteMessagesForDays) {
        banUser(user.getLongID(), reason, deleteMessagesForDays);
    }

    @Override
    public void banUser(long userID) {
        banUser(userID, null, 0);
    }

    @Override
    public void banUser(long userID, int deleteMessagesForDays) {
        banUser(userID, null, deleteMessagesForDays);
    }

    @Override
    public void banUser(long userID, String reason) {
        banUser(userID, reason, 0);
    }

    @Override
    public void banUser(long userID, String reason, int deleteMessagesForDays) {
        IUser user = getUserByID(userID);
        if (getUserByID(userID) == null) {
            PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.BAN);
        } else {
            PermissionUtils.requireHierarchicalPermissions(this, client.getOurUser(), getRolesForUser(user),
                    Permissions.BAN);
        }
        if (reason != null && reason.length() > Ban.MAX_REASON_LENGTH) {
            throw new IllegalArgumentException("Reason length cannot be more than " + Ban.MAX_REASON_LENGTH);
        }
        try {
            ((DiscordClientImpl) client).REQUESTS.PUT.makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/bans/"
                    + Long.toUnsignedString(userID) + "?delete-message-days=" + deleteMessagesForDays
                    + (reason == null ? "" : ("&reason=" + URLEncoder.encode(reason, "UTF-8"))));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void pardonUser(long userID) {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.BAN);
        ((DiscordClientImpl) client).REQUESTS.DELETE
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/bans/" + Long.toUnsignedString(userID));
    }

    @Override
    public void kickUser(IUser user) {
        kickUser(user, null);
    }

    @Override
    public void kickUser(IUser user, String reason) {
        PermissionUtils.requireHierarchicalPermissions(this, client.getOurUser(), getRolesForUser(user),
                Permissions.KICK);
        if (reason != null && reason.length() > Ban.MAX_REASON_LENGTH) {
            throw new IllegalArgumentException("Reason length cannot be more than " + Ban.MAX_REASON_LENGTH);
        }
        try {
            ((DiscordClientImpl) client).REQUESTS.DELETE
                    .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/members/" + user.getStringID()
                            + (reason == null ? "" : ("?reason=" + URLEncoder.encode(reason, "UTF-8"))));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void editUserRoles(IUser user, IRole[] roles) {
        PermissionUtils.requireHierarchicalPermissions(this, client.getOurUser(), Arrays.asList(roles),
                Permissions.MANAGE_ROLES);

        try {
            ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(
                    DiscordEndpoints.GUILDS + getStringID() + "/members/" + user.getStringID(),
                    DiscordUtils.MAPPER_NO_NULLS
                            .writeValueAsString(new MemberEditRequest.Builder().roles(roles).build()));
        } catch (JsonProcessingException e) {
            Discord4J.LOGGER.error(LogMarkers.HANDLE, "Discord4J Internal Exception", e);
        }

    }

    @Override
    public void setDeafenUser(IUser user, boolean deafen) {
        PermissionUtils.requireHierarchicalPermissions(this, client.getOurUser(), getRolesForUser(user),
                Permissions.VOICE_DEAFEN_MEMBERS);

        try {
            ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(
                    DiscordEndpoints.GUILDS + getStringID() + "/members/" + user.getStringID(),
                    DiscordUtils.MAPPER_NO_NULLS
                            .writeValueAsString(new MemberEditRequest.Builder().deafen(deafen).build()));
        } catch (JsonProcessingException e) {
            Discord4J.LOGGER.error(LogMarkers.HANDLE, "Discord4J Internal Exception", e);
        }
    }

    @Override
    public void setMuteUser(IUser user, boolean mute) {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.VOICE_MUTE_MEMBERS);

        try {
            ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(
                    DiscordEndpoints.GUILDS + getStringID() + "/members/" + user.getStringID(),
                    DiscordUtils.MAPPER_NO_NULLS
                            .writeValueAsString(new MemberEditRequest.Builder().mute(mute).build()));
        } catch (JsonProcessingException e) {
            Discord4J.LOGGER.error(LogMarkers.HANDLE, "Discord4J Internal Exception", e);
        }
    }

    @Override
    public void setUserNickname(IUser user, String nick) {
        boolean isSelf = user.equals(client.getOurUser());
        if (isSelf) {
            PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.CHANGE_NICKNAME);
        } else {
            PermissionUtils.requireHierarchicalPermissions(this, client.getOurUser(), getRolesForUser(user),
                    Permissions.MANAGE_NICKNAMES);
        }

        try {
            ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(
                    DiscordEndpoints.GUILDS + getStringID() + "/members/"
                            + (isSelf ? "@me/nick" : user.getStringID()),
                    DiscordUtils.MAPPER_NO_NULLS.writeValueAsString(
                            new MemberEditRequest.Builder().nick(nick == null ? "" : nick).build()));
        } catch (JsonProcessingException e) {
            Discord4J.LOGGER.error(LogMarkers.HANDLE, "Discord4J Internal Exception", e);
        }
    }

    @Override
    public void edit(String name, IRegion region, VerificationLevel level, Image icon, IVoiceChannel afkChannel,
            int afkTimeout) {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_SERVER);

        if (name == null || name.length() < 2 || name.length() > 100)
            throw new IllegalArgumentException("Guild name must be between 2 and 100 characters!");
        if (region == null)
            throw new IllegalArgumentException("Region must not be null.");
        if (level == null)
            throw new IllegalArgumentException("Verification level must not be null.");
        if (icon == null)
            throw new IllegalArgumentException("Icon must not be null.");
        if (afkChannel != null && !getVoiceChannels().contains(afkChannel))
            throw new IllegalArgumentException("Invalid AFK voice channel.");
        if (afkTimeout != 60 && afkTimeout != 300 && afkTimeout != 900 && afkTimeout != 1800 && afkTimeout != 3600)
            throw new IllegalArgumentException("AFK timeout must be one of (60, 300, 900, 1800, 3600).");

        ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(DiscordEndpoints.GUILDS + getStringID(),
                new GuildEditRequest(name, region.getID(), level.ordinal(), icon.getData(),
                        afkChannel == null ? null : afkChannel.getStringID(), afkTimeout));
    }

    @Override
    public void changeName(String name) {
        edit(name, getRegion(), getVerificationLevel(), this::getIcon, getAFKChannel(), getAFKTimeout());
    }

    @Override
    public void changeRegion(IRegion region) {
        edit(getName(), region, getVerificationLevel(), this::getIcon, getAFKChannel(), getAFKTimeout());
    }

    @Override
    public void changeVerificationLevel(VerificationLevel verificationLevel) {
        edit(getName(), getRegion(), verificationLevel, this::getIcon, getAFKChannel(), getAFKTimeout());
    }

    @Override
    public void changeIcon(Image icon) {
        edit(getName(), getRegion(), getVerificationLevel(), icon, getAFKChannel(), getAFKTimeout());
    }

    @Override
    public void changeAFKChannel(IVoiceChannel afkChannel) {
        edit(getName(), getRegion(), getVerificationLevel(), this::getIcon, afkChannel, getAFKTimeout());
    }

    @Override
    public void changeAFKTimeout(int timeout) {
        edit(getName(), getRegion(), getVerificationLevel(), this::getIcon, getAFKChannel(), timeout);
    }

    @Override
    @Deprecated
    public void deleteGuild() {
        if (ownerID != client.getOurUser().getLongID())
            throw new MissingPermissionsException("You must be the guild owner to delete guilds!",
                    EnumSet.noneOf(Permissions.class));

        ((DiscordClientImpl) client).REQUESTS.DELETE.makeRequest(DiscordEndpoints.GUILDS + getStringID());
    }

    @Override
    @Deprecated
    public void leaveGuild() {
        if (ownerID == client.getOurUser().getLongID())
            throw new DiscordException("Guild owners cannot leave their own guilds! Use deleteGuild() instead.");

        ((DiscordClientImpl) client).REQUESTS.DELETE
                .makeRequest(DiscordEndpoints.USERS + "@me/guilds/" + getStringID());
    }

    @Override
    public void leave() {
        ((DiscordClientImpl) client).REQUESTS.DELETE
                .makeRequest(DiscordEndpoints.USERS + "@me/guilds/" + getStringID());
    }

    @Override
    public IChannel createChannel(String name) {
        shard.checkReady("create channel");
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_CHANNELS);

        if (name == null || name.length() < 2 || name.length() > 100)
            throw new DiscordException("Channel name can only be between 2 and 100 characters!");

        ChannelObject response = ((DiscordClientImpl) client).REQUESTS.POST.makeRequest(
                DiscordEndpoints.GUILDS + getStringID() + "/channels", new ChannelCreateRequest(name, "text"),
                ChannelObject.class);

        IChannel channel = DiscordUtils.getChannelFromJSON(this, response);
        channels.put(channel);

        return channel;
    }

    @Override
    public IVoiceChannel createVoiceChannel(String name) {
        getShard().checkReady("create voice channel");
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_CHANNELS);

        if (name == null || name.length() < 2 || name.length() > 100)
            throw new DiscordException("Channel name can only be between 2 and 100 characters!");

        ChannelObject response = ((DiscordClientImpl) client).REQUESTS.POST.makeRequest(
                DiscordEndpoints.GUILDS + getStringID() + "/channels", new ChannelCreateRequest(name, "voice"),
                ChannelObject.class);

        IVoiceChannel channel = DiscordUtils.getVoiceChannelFromJSON(this, response);
        channels.put(channel);

        return channel;
    }

    @Override
    public IRegion getRegion() {
        return client.getRegionByID(regionID);
    }

    /**
     * CACHES the region for this guild.
     *
     * @param regionID The region.
     */
    public void setRegion(String regionID) {
        this.regionID = regionID;
    }

    @Override
    public VerificationLevel getVerificationLevel() {
        return verification;
    }

    /**
     * CACHES the verification for this guild.
     *
     * @param verification The verification level.
     */
    public void setVerificationLevel(int verification) {
        this.verification = VerificationLevel.get(verification);
    }

    @Override
    public IRole getEveryoneRole() {
        return getRoleByID(this.id);
    }

    @Override
    public IChannel getGeneralChannel() {
        return getChannelByID(this.id);
    }

    @Override
    public List<IInvite> getInvites() {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_SERVER);
        ExtendedInviteObject[] response = ((DiscordClientImpl) client).REQUESTS.GET
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/invites", ExtendedInviteObject[].class);

        List<IInvite> invites = new ArrayList<>();
        for (ExtendedInviteObject inviteResponse : response)
            invites.add(DiscordUtils.getInviteFromJSON(client, inviteResponse));

        return invites;
    }

    @Override
    public List<IExtendedInvite> getExtendedInvites() {
        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_SERVER);
        ExtendedInviteObject[] response = ((DiscordClientImpl) client).REQUESTS.GET
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/invites", ExtendedInviteObject[].class);

        List<IExtendedInvite> invites = new ArrayList<>();
        for (ExtendedInviteObject inviteResponse : response)
            invites.add(DiscordUtils.getExtendedInviteFromJSON(client, inviteResponse));

        return invites;
    }

    @Override
    public void reorderRoles(IRole... rolesInOrder) {
        if (rolesInOrder.length != getRoles().size())
            throw new DiscordException(
                    "The number of roles to reorder does not equal the number of available roles!");

        PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_ROLES);

        int usersHighest = getRolesForUser(client.getOurUser()).stream().map(IRole::getPosition)
                .max(Comparator.comparing(Function.identity())).orElse(-1);

        ReorderRolesRequest[] request = new ReorderRolesRequest[roles.size()];

        for (int i = 0; i < roles.size(); i++) {
            IRole role = rolesInOrder[i];

            int newPosition = role.getPosition();
            int oldPosition = getRoleByID(role.getLongID()).getPosition();
            if (newPosition != oldPosition && oldPosition >= usersHighest) { // If the position was changed and the user doesn't have permission to change it.
                throw new MissingPermissionsException(
                        "Cannot edit the position of a role higher than or equal to your own.",
                        EnumSet.noneOf(Permissions.class));
            } else {
                request[i] = new ReorderRolesRequest(role.getStringID(), i);
            }
        }

        ((DiscordClientImpl) client).REQUESTS.PATCH.makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/roles",
                request);
    }

    @Override
    public int getUsersToBePruned(int days) {
        PruneResponse response = ((DiscordClientImpl) client).REQUESTS.GET
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/prune?days=" + days, PruneResponse.class);
        return response.pruned;
    }

    @Override
    public int pruneUsers(int days) {
        PruneResponse response = ((DiscordClientImpl) client).REQUESTS.POST
                .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/prune?days=" + days, PruneResponse.class);
        return response.pruned;
    }

    @Override
    public boolean isDeleted() {
        return getClient().getGuildByID(id) != this;
    }

    @Override
    public IAudioManager getAudioManager() {
        return audioManager;
    }

    @Override
    public LocalDateTime getJoinTimeForUser(IUser user) {
        if (!joinTimes.containsKey(user.getLongID()))
            throw new DiscordException("Cannot find user " + user.getDisplayName(this) + " in this guild!");

        return joinTimes.get(user.getLongID()).getObject();
    }

    @Override
    public IMessage getMessageByID(long id) {
        IMessage message = channels.stream().map(IChannel::getMessageHistory).flatMap(List::stream)
                .filter(msg -> msg.getLongID() == id).findAny().orElse(null);

        if (message == null) {
            Collection<IChannel> toCheck = channels.stream().filter(it -> {
                EnumSet<Permissions> perms = it.getModifiedPermissions(client.getOurUser());
                return perms.contains(Permissions.READ_MESSAGE_HISTORY)
                        && perms.contains(Permissions.READ_MESSAGES);
            }).collect(Collectors.toSet());
            for (IChannel channel : toCheck) {
                message = channel.getMessageByID(id);
                if (message != null)
                    return message;
            }
        }

        return message;
    }

    @Override
    public IDiscordClient getClient() {
        return client;
    }

    @Override
    public IShard getShard() {
        return shard;
    }

    @Override
    public IGuild copy() {
        return new Guild(shard, name, id, icon, ownerID, afkChannel, afkTimeout, regionID, verification.ordinal(),
                roles.copy(), channels.copy(), voiceChannels.copy(), users.copy(), joinTimes.copy());
    }

    @Override
    public List<IEmoji> getEmojis() {
        return new LinkedList<>(emojis.values());
    }

    @Override
    public IEmoji getEmojiByID(long id) {
        return emojis.get(id);
    }

    @Override
    public IEmoji getEmojiByName(String name) {
        return emojis.stream().filter(emoji -> emoji.getName().equals(name)).findFirst().orElse(null);
    }

    @Override
    public IWebhook getWebhookByID(long id) {
        return channels.stream().map(channel -> channel.getWebhookByID(id)).filter(Objects::nonNull).findAny()
                .orElse(null);
    }

    @Override
    public List<IWebhook> getWebhooksByName(String name) {
        return channels.stream().map(IChannel::getWebhooks).flatMap(List::stream)
                .filter(hook -> hook.getDefaultName().equals(name)).collect(Collectors.toList());
    }

    @Override
    public List<IWebhook> getWebhooks() {
        return channels.stream().map(IChannel::getWebhooks).flatMap(List::stream).collect(Collectors.toList());
    }

    public void loadWebhooks() {
        try {
            PermissionUtils.requirePermissions(this, client.getOurUser(), Permissions.MANAGE_WEBHOOKS);
        } catch (MissingPermissionsException ignored) {
            return;
        }

        RequestBuffer.request(() -> {
            try {
                List<IWebhook> oldList = getWebhooks().stream().map(IWebhook::copy)
                        .collect(Collectors.toCollection(CopyOnWriteArrayList::new));

                WebhookObject[] response = ((DiscordClientImpl) client).REQUESTS.GET
                        .makeRequest(DiscordEndpoints.GUILDS + getStringID() + "/webhooks", WebhookObject[].class);

                if (response != null) {
                    for (WebhookObject webhookObject : response) {
                        Channel channel = (Channel) getChannelByID(
                                Long.parseUnsignedLong(webhookObject.channel_id));
                        long webhookId = Long.parseUnsignedLong(webhookObject.id);
                        if (getWebhookByID(webhookId) == null) {
                            IWebhook newWebhook = DiscordUtils.getWebhookFromJSON(channel, webhookObject);
                            client.getDispatcher().dispatch(new WebhookCreateEvent(newWebhook));
                            channel.webhooks.put(newWebhook);
                        } else {
                            IWebhook toUpdate = channel.getWebhookByID(webhookId);
                            IWebhook oldWebhook = toUpdate.copy();
                            toUpdate = DiscordUtils.getWebhookFromJSON(channel, webhookObject);
                            if (!oldWebhook.getDefaultName().equals(toUpdate.getDefaultName())
                                    || !String.valueOf(oldWebhook.getDefaultAvatar())
                                            .equals(String.valueOf(toUpdate.getDefaultAvatar())))
                                client.getDispatcher().dispatch(new WebhookUpdateEvent(oldWebhook, toUpdate));

                            oldList.remove(oldWebhook);
                        }
                    }
                }

                oldList.forEach(webhook -> {
                    ((Channel) webhook.getChannel()).webhooks.remove(webhook);
                    client.getDispatcher().dispatch(new WebhookDeleteEvent(webhook));
                });
            } catch (Exception e) {
                Discord4J.LOGGER.warn(LogMarkers.HANDLE, "Discord4J Internal Exception", e);
            }
        });
    }

    @Override
    public int getTotalMemberCount() {
        return totalMemberCount;
    }

    public void setTotalMemberCount(int totalMemberCount) {
        this.totalMemberCount = totalMemberCount;
    }

    @Override
    public String toString() {
        return super.toString();
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public boolean equals(Object other) {
        return DiscordUtils.equals(this, other);
    }

    public static class TimeStampHolder extends IDLinkedObjectWrapper<LocalDateTime> {

        public TimeStampHolder(long id, LocalDateTime obj) {
            super(id, obj);
        }
    }
}