net.dv8tion.jda.internal.requests.restaction.ChannelActionImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of JDA Show documentation
Show all versions of JDA Show documentation
Java wrapper for the popular chat & VOIP service: Discord https://discord.com
/*
* Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors
*
* 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 net.dv8tion.jda.internal.requests.restaction;
import gnu.trove.map.TLongObjectMap;
import gnu.trove.map.hash.TLongObjectHashMap;
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.Region;
import net.dv8tion.jda.api.entities.Guild;
import net.dv8tion.jda.api.entities.Member;
import net.dv8tion.jda.api.entities.channel.Channel;
import net.dv8tion.jda.api.entities.channel.ChannelType;
import net.dv8tion.jda.api.entities.channel.attribute.IPostContainer;
import net.dv8tion.jda.api.entities.channel.attribute.ISlowmodeChannel;
import net.dv8tion.jda.api.entities.channel.concrete.Category;
import net.dv8tion.jda.api.entities.channel.concrete.ForumChannel;
import net.dv8tion.jda.api.entities.channel.concrete.StageChannel;
import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel;
import net.dv8tion.jda.api.entities.channel.forums.BaseForumTag;
import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel;
import net.dv8tion.jda.api.entities.channel.middleman.StandardGuildMessageChannel;
import net.dv8tion.jda.api.entities.emoji.CustomEmoji;
import net.dv8tion.jda.api.entities.emoji.Emoji;
import net.dv8tion.jda.api.entities.emoji.UnicodeEmoji;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
import net.dv8tion.jda.api.requests.Route;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;
import net.dv8tion.jda.api.utils.data.DataArray;
import net.dv8tion.jda.api.utils.data.DataObject;
import net.dv8tion.jda.internal.entities.EntityBuilder;
import net.dv8tion.jda.internal.entities.GuildImpl;
import net.dv8tion.jda.internal.utils.ChannelUtil;
import net.dv8tion.jda.internal.utils.Checks;
import net.dv8tion.jda.internal.utils.PermissionUtil;
import okhttp3.RequestBody;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
public class ChannelActionImpl extends AuditableRestActionImpl implements ChannelAction
{
protected final TLongObjectMap overrides = new TLongObjectHashMap<>();
protected final Guild guild;
protected final Class clazz;
protected final ChannelType type;
// --all channels--
protected String name;
protected Category parent;
protected Integer position;
// --forum only--
protected List extends BaseForumTag> availableTags;
protected Emoji defaultReactionEmoji;
// --text/forum/voice only--
protected Integer slowmode = null;
protected Integer defaultThreadSlowmode = null;
// --text/forum/voice/news--
protected String topic = null;
protected Boolean nsfw = null;
// --voice only--
protected Integer userlimit = null;
// --audio only--
protected Integer bitrate = null;
protected Region region = null;
// --forum only--
protected Integer defaultLayout = null;
protected Integer defaultSortOrder = null;
public ChannelActionImpl(Class clazz, String name, Guild guild, ChannelType type)
{
super(guild.getJDA(), Route.Guilds.CREATE_CHANNEL.compile(guild.getId()));
this.clazz = clazz;
this.guild = guild;
this.type = type;
this.name = name;
}
@Nonnull
@Override
public ChannelActionImpl reason(@Nullable String reason)
{
return (ChannelActionImpl) super.reason(reason);
}
@Nonnull
@Override
public ChannelActionImpl setCheck(BooleanSupplier checks)
{
return (ChannelActionImpl) super.setCheck(checks);
}
@Nonnull
@Override
public ChannelActionImpl timeout(long timeout, @Nonnull TimeUnit unit)
{
return (ChannelActionImpl) super.timeout(timeout, unit);
}
@Nonnull
@Override
public ChannelActionImpl deadline(long timestamp)
{
return (ChannelActionImpl) super.deadline(timestamp);
}
@Nonnull
@Override
public Guild getGuild()
{
return guild;
}
@Nonnull
@Override
public ChannelType getType()
{
return type;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setName(@Nonnull String name)
{
Checks.notEmpty(name, "Name");
Checks.notLonger(name, Channel.MAX_NAME_LENGTH, "Name");
this.name = name;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setParent(Category category)
{
if (category != null)
{
Checks.check(category.getGuild().equals(guild), "Category is not from same guild!");
if (type == ChannelType.CATEGORY)
throw new UnsupportedOperationException("Cannot set a parent Category on a Category");
}
this.parent = category;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setPosition(Integer position)
{
Checks.check(position == null || position >= 0, "Position must be >= 0!");
this.position = position;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setTopic(String topic)
{
Checks.checkSupportedChannelTypes(ChannelUtil.TOPIC_SUPPORTED, type, "Topic");
if (topic != null)
{
if (ChannelUtil.POST_CONTAINERS.contains(type))
Checks.notLonger(topic, IPostContainer.MAX_POST_CONTAINER_TOPIC_LENGTH, "Topic");
else
Checks.notLonger(topic, StandardGuildMessageChannel.MAX_TOPIC_LENGTH, "Topic");
}
this.topic = topic;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setNSFW(boolean nsfw)
{
Checks.checkSupportedChannelTypes(ChannelUtil.NSFW_SUPPORTED, type, "NSFW (age-restricted)");
this.nsfw = nsfw;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setSlowmode(int slowmode)
{
Checks.checkSupportedChannelTypes(ChannelUtil.SLOWMODE_SUPPORTED, type, "Slowmode");
Checks.check(slowmode <= ISlowmodeChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode must be between 0 and %d (seconds)!", ISlowmodeChannel.MAX_SLOWMODE);
this.slowmode = slowmode;
return this;
}
@Nonnull
@Override
public ChannelAction setDefaultThreadSlowmode(int slowmode)
{
Checks.checkSupportedChannelTypes(ChannelUtil.THREAD_CONTAINERS, type, "Default Thread Slowmode");
Checks.check(slowmode <= ISlowmodeChannel.MAX_SLOWMODE && slowmode >= 0, "Slowmode must be between 0 and %d (seconds)!", ISlowmodeChannel.MAX_SLOWMODE);
this.defaultThreadSlowmode = slowmode;
return this;
}
@Nonnull
@Override
public ChannelAction setDefaultReaction(@Nullable Emoji emoji)
{
Checks.checkSupportedChannelTypes(ChannelUtil.POST_CONTAINERS, type, "Default Reaction");
this.defaultReactionEmoji = emoji;
return this;
}
@Nonnull
@Override
public ChannelAction setDefaultLayout(@Nonnull ForumChannel.Layout layout)
{
Checks.checkSupportedChannelTypes(EnumSet.of(ChannelType.FORUM), type, "Default Layout");
Checks.notNull(layout, "layout");
Checks.check(layout != ForumChannel.Layout.UNKNOWN, "Layout type cannot be UNKNOWN.");
this.defaultLayout = layout.getKey();
return this;
}
@Nonnull
@Override
public ChannelAction setDefaultSortOrder(@Nonnull IPostContainer.SortOrder sortOrder)
{
Checks.checkSupportedChannelTypes(ChannelUtil.POST_CONTAINERS, type, "Default Sort Order");
Checks.notNull(sortOrder, "SortOrder");
Checks.check(sortOrder != IPostContainer.SortOrder.UNKNOWN, "Sort Order cannot be UNKNOWN.");
this.defaultSortOrder = sortOrder.getKey();
return this;
}
@Nonnull
@Override
public ChannelAction setAvailableTags(@Nonnull List extends BaseForumTag> tags)
{
Checks.checkSupportedChannelTypes(ChannelUtil.POST_CONTAINERS, type, "Available Tags");
Checks.noneNull(tags, "Tags");
this.availableTags = new ArrayList<>(tags);
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl addMemberPermissionOverride(long userId, long allow, long deny)
{
return addOverride(userId, PermOverrideData.MEMBER_TYPE, allow, deny);
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl addRolePermissionOverride(long roleId, long allow, long deny)
{
return addOverride(roleId, PermOverrideData.ROLE_TYPE, allow, deny);
}
@Nonnull
@Override
public ChannelAction removePermissionOverride(long id)
{
overrides.remove(id);
return this;
}
@Nonnull
@Override
public ChannelAction clearPermissionOverrides()
{
overrides.clear();
return this;
}
@Nonnull
@Override
@SuppressWarnings("ResultOfMethodCallIgnored")
public ChannelAction syncPermissionOverrides()
{
if (parent == null)
throw new IllegalStateException("Cannot sync overrides without parent category! Use setParent(category) first!");
clearPermissionOverrides();
Member selfMember = getGuild().getSelfMember();
boolean canSetRoles = selfMember.hasPermission(parent, Permission.MANAGE_ROLES);
//You can only set MANAGE_ROLES if you have ADMINISTRATOR or MANAGE_PERMISSIONS as an override on the channel
// That is why we explicitly exclude it here!
// This is by far the most complex and weird permission logic in the entire API...
long botPerms = PermissionUtil.getEffectivePermission(selfMember) & ~Permission.MANAGE_PERMISSIONS.getRawValue();
parent.getRolePermissionOverrides().forEach(override -> {
long allow = override.getAllowedRaw();
long deny = override.getDeniedRaw();
if (!canSetRoles)
{
allow &= botPerms;
deny &= botPerms;
}
addRolePermissionOverride(override.getIdLong(), allow, deny);
});
parent.getMemberPermissionOverrides().forEach(override -> {
long allow = override.getAllowedRaw();
long deny = override.getDeniedRaw();
if (!canSetRoles)
{
allow &= botPerms;
deny &= botPerms;
}
addMemberPermissionOverride(override.getIdLong(), allow, deny);
});
return this;
}
private ChannelActionImpl addOverride(long targetId, int type, long allow, long deny)
{
Member selfMember = getGuild().getSelfMember();
boolean canSetRoles = selfMember.hasPermission(Permission.ADMINISTRATOR);
if (!canSetRoles && parent != null) // You can also set MANAGE_ROLES if you have it on the category (apparently?)
canSetRoles = selfMember.hasPermission(parent, Permission.MANAGE_ROLES);
if (!canSetRoles)
{
// Prevent permission escalation
//You can only set MANAGE_ROLES if you have ADMINISTRATOR or MANAGE_PERMISSIONS as an override on the channel
// That is why we explicitly exclude it here!
// This is by far the most complex and weird permission logic in the entire API...
long botPerms = PermissionUtil.getEffectivePermission(selfMember) & ~Permission.MANAGE_PERMISSIONS.getRawValue();
EnumSet missingPerms = Permission.getPermissions((allow | deny) & ~botPerms);
if (!missingPerms.isEmpty())
throw new InsufficientPermissionException(guild, Permission.MANAGE_PERMISSIONS, "You must have Permission.MANAGE_PERMISSIONS on the channel explicitly in order to set permissions you don't already have!");
}
overrides.put(targetId, new PermOverrideData(type, targetId, allow, deny));
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setBitrate(Integer bitrate)
{
if (!type.isAudio())
throw new UnsupportedOperationException("Can only set the bitrate for an Audio Channel!");
if (bitrate != null)
{
int maxBitrate = getGuild().getMaxBitrate();
if (bitrate < 8000)
throw new IllegalArgumentException("Bitrate must be greater than 8000.");
else if (bitrate > maxBitrate)
throw new IllegalArgumentException("Bitrate must be less than " + maxBitrate);
}
this.bitrate = bitrate;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setUserlimit(Integer userlimit)
{
if (userlimit != null)
{
Checks.notNegative(userlimit, "Userlimit");
if (type == ChannelType.VOICE)
Checks.check(userlimit <= VoiceChannel.MAX_USERLIMIT, "Userlimit may not be greater than %d for voice channels", VoiceChannel.MAX_USERLIMIT);
else if (type == ChannelType.STAGE)
Checks.check(userlimit <= StageChannel.MAX_USERLIMIT, "Userlimit may not be greater than %d for stage channels", StageChannel.MAX_USERLIMIT);
else
throw new IllegalStateException("Can only set userlimit on audio channels");
}
this.userlimit = userlimit;
return this;
}
@Nonnull
@Override
@CheckReturnValue
public ChannelActionImpl setRegion(@Nullable Region region)
{
if (!type.isAudio())
throw new UnsupportedOperationException("Can only set the region for AudioChannels!");
this.region = region;
return this;
}
@Override
protected RequestBody finalizeData()
{
DataObject object = DataObject.empty();
//All channel types
object.put("name", name);
object.put("type", type.getId());
object.put("permission_overwrites", DataArray.fromCollection(overrides.valueCollection()));
if (position != null)
object.put("position", position);
if (parent != null)
object.put("parent_id", parent.getId());
//Text and Forum
if (slowmode != null)
object.put("rate_limit_per_user", slowmode);
if (defaultThreadSlowmode != null)
object.put("default_thread_rate_limit_per_user", defaultThreadSlowmode);
//Text, Forum, and News
if (topic != null && !topic.isEmpty())
object.put("topic", topic);
if (nsfw != null)
object.put("nsfw", nsfw);
//Forum/Media only
if (defaultReactionEmoji instanceof CustomEmoji)
object.put("default_reaction_emoji", DataObject.empty().put("emoji_id", ((CustomEmoji) defaultReactionEmoji).getId()));
else if (defaultReactionEmoji instanceof UnicodeEmoji)
object.put("default_reaction_emoji", DataObject.empty().put("emoji_name", defaultReactionEmoji.getName()));
if (availableTags != null)
object.put("available_tags", DataArray.fromCollection(availableTags));
if (defaultSortOrder != null)
object.put("default_sort_order", defaultSortOrder);
//Forum only
if (defaultLayout != null)
object.put("default_forum_layout", defaultLayout);
//Voice only
if (userlimit != null)
object.put("user_limit", userlimit);
//Voice and Stage
if (bitrate != null)
object.put("bitrate", bitrate);
if (region != null)
object.put("rtc_region", region.getKey());
return getRequestBody(object);
}
@Override
protected void handleSuccess(Response response, Request request)
{
EntityBuilder builder = api.getEntityBuilder();
GuildChannel channel = builder.createGuildChannel((GuildImpl) guild, response.getObject());
if (channel == null)
request.onFailure(new IllegalStateException("Created channel of unknown type!"));
else
request.onSuccess(clazz.cast(channel));
}
}