Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle text in voice channels #2072

Merged
merged 28 commits into from Jul 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
11f3378
Start work on text in voice channels
MinnDevelopment Mar 27, 2022
dbb697a
Add missing getVoiceChannel where applicable
MinnDevelopment Mar 27, 2022
b3b7647
Implement getter
MinnDevelopment Mar 27, 2022
0f3268b
Handle permissions for text in voice
MinnDevelopment Mar 27, 2022
cf2104f
Call setter for last_message_id on channel creation
MinnDevelopment Mar 28, 2022
30141fc
Add checks for unexpected message origins
MinnDevelopment Mar 28, 2022
622c58c
Fix typo
MinnDevelopment Mar 28, 2022
b0ba362
Merge remote-tracking branch 'origin/master' into feature/text-in-voice
MinnDevelopment Apr 29, 2022
4dfdd74
Update permission check
MinnDevelopment Jul 3, 2022
ff56052
Merge remote-tracking branch 'origin/master' into feature/text-in-voice
MinnDevelopment Jul 3, 2022
4dc7258
Update some more permission checks
MinnDevelopment Jul 3, 2022
62ba46f
Handle nsfw
MinnDevelopment Jul 3, 2022
c38a697
Remove redundant acces checks
MinnDevelopment Jul 3, 2022
c16ae79
Add asVoiceChannel to unions
MinnDevelopment Jul 3, 2022
35de117
Abstract isNSFW and rename to isAgeRestricted
MinnDevelopment Jul 3, 2022
f854aac
Change back to nsfw for now
MinnDevelopment Jul 3, 2022
ff212bf
Improve perm checks
MinnDevelopment Jul 8, 2022
3a2b6b9
Move age restriction into attribute package
MinnDevelopment Jul 8, 2022
a976c5f
Fix union docs
MinnDevelopment Jul 8, 2022
5d1bba7
Remove outdated todo
MinnDevelopment Jul 8, 2022
ccfbae8
Move some implementations
MinnDevelopment Jul 8, 2022
95c7965
Mention that connect is related to text in voice
MinnDevelopment Jul 8, 2022
10c4dce
Improve perm checks in MessageReference
MinnDevelopment Jul 9, 2022
67f990b
Consistency
MinnDevelopment Jul 9, 2022
61f6294
Improve usage of permission container
MinnDevelopment Jul 9, 2022
f6e42bc
Remove outdated todo
MinnDevelopment Jul 9, 2022
6ba32a1
Cleanup invite
MinnDevelopment Jul 9, 2022
c6edd68
Fix NPE
MinnDevelopment Jul 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
3 changes: 1 addition & 2 deletions src/main/java/net/dv8tion/jda/api/audit/AuditLogKey.java
Expand Up @@ -244,8 +244,7 @@ public enum AuditLogKey
CHANNEL_USER_LIMIT("user_limit"),

/**
* Change of the {@link net.dv8tion.jda.api.entities.TextChannel#isNSFW() TextChannel.isNSFW()} value.
* <br>Only for {@link net.dv8tion.jda.api.entities.ChannelType#TEXT ChannelType.TEXT}
* Change of the {@link net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel#isNSFW() IAgeRestrictedChannel.isNSFW()} value.
*
* <p>Expected type: <b>Boolean</b>
*/
Expand Down
Expand Up @@ -135,6 +135,7 @@ public boolean isMessage()
switch (this)
{
case TEXT:
case VOICE:
case NEWS:
case PRIVATE:
case GROUP:
Expand Down
Expand Up @@ -173,7 +173,7 @@ public interface IPermissionHolder extends ISnowflake
default boolean hasAccess(@Nonnull GuildChannel channel)
{
Checks.notNull(channel, "Channel");
return channel.getType() == ChannelType.VOICE || channel.getType() == ChannelType.STAGE
return channel.getType().isAudio()
? hasPermission(channel, Permission.VOICE_CONNECT, Permission.VIEW_CHANNEL)
: hasPermission(channel, Permission.VIEW_CHANNEL);
}
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Message.java
Expand Up @@ -1414,6 +1414,8 @@ default MessageAction reply(@Nonnull byte[] data, @Nonnull String name, @Nonnull
*
* @throws java.lang.UnsupportedOperationException
* If this is a Data Message (output of {@link MessageBuilder MessageBuilder})
* @throws MissingAccessException
* If the currently logged in account does not have {@link Member#hasAccess(GuildChannel) access} in this channel.
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If this Message was not sent by the currently logged in account, the Message was sent in a
* {@link GuildChannel GuildChannel}, and the currently logged in account
Expand Down Expand Up @@ -1928,6 +1930,8 @@ default MessageAction reply(@Nonnull byte[] data, @Nonnull String name, @Nonnull
* <li>If the channel is not a {@link NewsChannel}.</li>
* <li>If the message is ephemeral.</li>
* </ul>
* @throws MissingAccessException
* If the currently logged in account does not have {@link Member#hasAccess(GuildChannel) access} in this channel.
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If the currently logged in account does not have
* {@link Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} in this channel
Expand Down
28 changes: 12 additions & 16 deletions src/main/java/net/dv8tion/jda/api/entities/MessageHistory.java
Expand Up @@ -20,7 +20,6 @@
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.exceptions.MissingAccessException;
import net.dv8tion.jda.api.requests.Request;
import net.dv8tion.jda.api.requests.Response;
import net.dv8tion.jda.api.requests.RestAction;
Expand Down Expand Up @@ -72,15 +71,13 @@ public MessageHistory(@Nonnull MessageChannel channel)
Checks.notNull(channel, "Channel");
this.channel = channel;

//TODO-v5: Fix permissions here.
if (channel instanceof TextChannel)
if (channel instanceof GuildChannel)
{
TextChannel tc = (TextChannel) channel;
Member selfMember = tc.getGuild().getSelfMember();
if (!selfMember.hasAccess(tc))
throw new MissingAccessException(tc, Permission.VIEW_CHANNEL);
if (!selfMember.hasPermission(tc, Permission.MESSAGE_HISTORY))
throw new InsufficientPermissionException(tc, Permission.MESSAGE_HISTORY);
GuildChannel guildChannel = (GuildChannel) channel;
Member selfMember = guildChannel.getGuild().getSelfMember();
Checks.checkAccess(selfMember, guildChannel);
if (!selfMember.hasPermission(guildChannel, Permission.MESSAGE_HISTORY))
throw new InsufficientPermissionException(guildChannel, Permission.MESSAGE_HISTORY);
}
}

Expand Down Expand Up @@ -534,14 +531,13 @@ private static void checkArguments(MessageChannel channel, String messageId)
{
Checks.isSnowflake(messageId, "Message ID");
Checks.notNull(channel, "Channel");
if (channel.getType() == ChannelType.TEXT)
if (channel instanceof GuildChannel)
{
TextChannel t = (TextChannel) channel;
Member selfMember = t.getGuild().getSelfMember();
if (!selfMember.hasAccess(t))
throw new MissingAccessException(t, Permission.VIEW_CHANNEL);
if (!selfMember.hasPermission(t, Permission.MESSAGE_HISTORY))
throw new InsufficientPermissionException(t, Permission.MESSAGE_HISTORY);
GuildChannel guildChannel = (GuildChannel) channel;
Member selfMember = guildChannel.getGuild().getSelfMember();
Checks.checkAccess(selfMember, guildChannel);
if (!selfMember.hasPermission(guildChannel, Permission.MESSAGE_HISTORY))
throw new InsufficientPermissionException(guildChannel, Permission.MESSAGE_HISTORY);
}
}

Expand Down
Expand Up @@ -33,7 +33,6 @@

import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Objects;

/**
Expand Down Expand Up @@ -329,9 +328,9 @@ public RestAction<Void> removeReaction(@Nonnull User user)
if (!channel.getType().isGuild())
throw new PermissionException("Unable to remove Reaction of other user in non-guild channels!");

IPermissionContainer permChannel = getGuildChannel().getPermissionContainer();
if (!permChannel.getGuild().getSelfMember().hasPermission(permChannel, Permission.MESSAGE_MANAGE))
throw new InsufficientPermissionException(permChannel, Permission.MESSAGE_MANAGE);
GuildChannel guildChannel = (GuildChannel) channel;
if (!guildChannel.getGuild().getSelfMember().hasPermission(guildChannel, Permission.MESSAGE_MANAGE))
throw new InsufficientPermissionException(guildChannel, Permission.MESSAGE_MANAGE);
}

String code = EncodingUtil.encodeReaction(emoji.getAsReactionCode());
Expand Down
23 changes: 11 additions & 12 deletions src/main/java/net/dv8tion/jda/api/entities/MessageReference.java
Expand Up @@ -19,12 +19,12 @@
import net.dv8tion.jda.api.Permission;
import net.dv8tion.jda.api.entities.channel.unions.MessageChannelUnion;
import net.dv8tion.jda.api.exceptions.InsufficientPermissionException;
import net.dv8tion.jda.api.exceptions.MissingAccessException;
import net.dv8tion.jda.api.requests.RestAction;
import net.dv8tion.jda.internal.JDAImpl;
import net.dv8tion.jda.internal.requests.CompletedRestAction;
import net.dv8tion.jda.internal.requests.RestActionImpl;
import net.dv8tion.jda.internal.requests.Route;
import net.dv8tion.jda.internal.utils.Checks;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
Expand Down Expand Up @@ -54,7 +54,7 @@ public MessageReference(long messageId, long channelId, long guildId, @Nullable
if (guildId == 0L)
this.channel = api.getPrivateChannelById(channelId);
else
this.channel = (MessageChannel) api.getGuildChannelById(channelId);
this.channel = api.getChannelById(MessageChannel.class, channelId);

this.guild = api.getGuildById(guildId); // is null if guildId = 0 anyway

Expand Down Expand Up @@ -84,9 +84,10 @@ public MessageReference(long messageId, long channelId, long guildId, @Nullable
* </ul>
*
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If this reference refers to a {@link net.dv8tion.jda.api.entities.TextChannel TextChannel} and the logged in account does not have
* If this reference refers to a {@link net.dv8tion.jda.api.entities.GuildChannel GuildChannel} and the logged in account does not have
* <ul>
* <li>{@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL}</li>
* <li>{@link net.dv8tion.jda.api.Permission#VOICE_CONNECT Permission.VOICE_CONNECT} (applicable if {@code getChannel().getType().isAudio()})</li>
* <li>{@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY}</li>
* </ul>
*
Expand Down Expand Up @@ -127,9 +128,10 @@ public RestAction<Message> resolve()
* Whether to update the already stored message
*
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If this reference refers to a {@link net.dv8tion.jda.api.entities.TextChannel TextChannel} and the logged in account does not have
* If this reference refers to a {@link net.dv8tion.jda.api.entities.GuildChannel GuildChannel} and the logged in account does not have
* <ul>
* <li>{@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL}</li>
* <li>{@link net.dv8tion.jda.api.Permission#VOICE_CONNECT Permission.VOICE_CONNECT} (applicable if {@code getChannel().getType().isAudio()})</li>
* <li>{@link net.dv8tion.jda.api.Permission#MESSAGE_HISTORY Permission.MESSAGE_HISTORY}</li>
* </ul>
*
Expand Down Expand Up @@ -284,16 +286,13 @@ public JDA getJDA()

private void checkPermission(Permission permission)
{
if (guild == null || !(channel instanceof IPermissionContainer)) return;
if (guild == null || !(channel instanceof GuildChannel)) return;

Member selfMember = guild.getSelfMember();
GuildChannel guildChannel = (GuildChannel) channel;


IPermissionContainer permChannel = (IPermissionContainer) channel;

if (!selfMember.hasAccess(permChannel))
throw new MissingAccessException(permChannel, Permission.VIEW_CHANNEL);
if (!selfMember.hasPermission(permChannel, permission))
throw new InsufficientPermissionException(permChannel, permission);
Checks.checkAccess(selfMember, guildChannel);
if (!selfMember.hasPermission(guildChannel, permission))
throw new InsufficientPermissionException(guildChannel, permission);
}
}
10 changes: 6 additions & 4 deletions src/main/java/net/dv8tion/jda/api/entities/NewsChannel.java
Expand Up @@ -107,6 +107,8 @@ default RestAction<Webhook.WebhookReference> follow(long targetChannelId)
* @param targetChannel
* The target channel
*
* @throws MissingAccessException
* If the currently logged in account does not have {@link Member#hasAccess(GuildChannel) access} in the <b>target channel</b>.
* @throws InsufficientPermissionException
* If the currently logged in account does not have {@link Permission#MANAGE_WEBHOOKS} in the <b>target channel</b>.
* @throws IllegalArgumentException
Expand All @@ -122,8 +124,7 @@ default RestAction<Webhook.WebhookReference> follow(@Nonnull TextChannel targetC
{
Checks.notNull(targetChannel, "Target Channel");
Member selfMember = targetChannel.getGuild().getSelfMember();
if (!selfMember.hasAccess(targetChannel))
throw new MissingAccessException(targetChannel, Permission.VIEW_CHANNEL);
Checks.checkAccess(selfMember, targetChannel);
if (!selfMember.hasPermission(targetChannel, Permission.MANAGE_WEBHOOKS))
throw new InsufficientPermissionException(targetChannel, Permission.MANAGE_WEBHOOKS);
return follow(targetChannel.getId());
Expand Down Expand Up @@ -160,6 +161,8 @@ default RestAction<Webhook.WebhookReference> follow(@Nonnull TextChannel targetC
*
* @throws java.lang.IllegalArgumentException
* If provided {@code messageId} is {@code null} or empty.
* @throws MissingAccessException
* If the currently logged in account does not have {@link Member#hasAccess(GuildChannel) access} in this channel.
* @throws net.dv8tion.jda.api.exceptions.InsufficientPermissionException
* If the currently logged in account does not have
* {@link net.dv8tion.jda.api.Permission#VIEW_CHANNEL Permission.VIEW_CHANNEL} in this channel.
Expand All @@ -173,8 +176,7 @@ default RestAction<Webhook.WebhookReference> follow(@Nonnull TextChannel targetC
default RestAction<Message> crosspostMessageById(@Nonnull String messageId)
{
Checks.isSnowflake(messageId);
if (!getGuild().getSelfMember().hasAccess(this))
throw new MissingAccessException(this, Permission.VIEW_CHANNEL);
Checks.checkAccess(getGuild().getSelfMember(), this);
Route.CompiledRoute route = Route.Messages.CROSSPOST_MESSAGE.compile(getId(), messageId);
return new RestActionImpl<>(getJDA(), route,
(response, request) -> request.getJDA().getEntityBuilder().createMessageWithChannel(response.getObject(), this, false));
Expand Down
Expand Up @@ -16,6 +16,7 @@

package net.dv8tion.jda.api.entities;

import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel;
import net.dv8tion.jda.api.managers.channel.middleman.StandardGuildMessageChannelManager;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;

Expand All @@ -35,28 +36,21 @@
* @see NewsChannel
* @see StandardGuildChannel
*/
public interface StandardGuildMessageChannel extends StandardGuildChannel, GuildMessageChannel, IThreadContainer, IWebhookContainer
public interface StandardGuildMessageChannel extends StandardGuildChannel, GuildMessageChannel, IThreadContainer, IWebhookContainer, IAgeRestrictedChannel
{
@Nonnull
@Override
StandardGuildMessageChannelManager<?, ?> getManager();

/**
* The topic set for this TextChannel.
* The topic set for this channel.
* <br>If no topic has been set, this returns null.
*
* @return Possibly-null String containing the topic of this TextChannel.
* @return Possibly-null String containing the topic of this channel.
*/
@Nullable
String getTopic();

/**
* Whether or not this channel is considered as "NSFW" (Not-Safe-For-Work)
*
* @return True, If this TextChannel is considered NSFW by the official Discord Client
*/
boolean isNSFW();

@Override
@Nonnull
@CheckReturnValue
Expand Down
3 changes: 2 additions & 1 deletion src/main/java/net/dv8tion/jda/api/entities/VoiceChannel.java
Expand Up @@ -16,6 +16,7 @@
package net.dv8tion.jda.api.entities;

import net.dv8tion.jda.api.JDA;
import net.dv8tion.jda.api.entities.channel.attribute.IAgeRestrictedChannel;
import net.dv8tion.jda.api.managers.channel.concrete.VoiceChannelManager;
import net.dv8tion.jda.api.requests.restaction.ChannelAction;

Expand All @@ -39,7 +40,7 @@
* @see JDA#getVoiceChannelsByName(String, boolean)
* @see JDA#getVoiceChannelById(long)
*/
public interface VoiceChannel extends AudioChannel, StandardGuildChannel
public interface VoiceChannel extends AudioChannel, StandardGuildChannel, GuildMessageChannel, IWebhookContainer, IAgeRestrictedChannel
{
/**
* The maximum amount of {@link net.dv8tion.jda.api.entities.Member Members} that can be in this
Expand Down
@@ -0,0 +1,33 @@
/*
* 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.api.entities.channel.attribute;

import net.dv8tion.jda.api.entities.GuildChannel;

/**
* Channels which can be set to age-restricted.
* <br>These channels only allow users with a verified mature age to participate.
*/
public interface IAgeRestrictedChannel extends GuildChannel
{
/**
* Whether this channel is considered as age-restricted, also known as NSFW (Not-Safe-For-Work)
*
* @return True, If this channel is age-restricted by the official Discord Client
*/
boolean isNSFW();
}
Expand Up @@ -100,7 +100,27 @@ public interface GuildMessageChannelUnion extends GuildMessageChannel
@Nonnull
ThreadChannel asThreadChannel();

//TODO: Add asVoiceChannel once TiV is launched
/**
* Casts this union to a {@link VoiceChannel}.
* This method exists for developer discoverability.
*
* Note: This is effectively equivalent to using the cast operator:
* <pre><code>
* //These are the same!
* VoiceChannel channel = union.asVoiceChannel();
* VoiceChannel channel2 = (VoiceChannel) union;
* </code></pre>
*
* You can use {@link #getType()} to see if the channel is of type {@link ChannelType#VOICE} to validate
* whether you can call this method in addition to normal instanceof checks: <code>channel instanceof VoiceChannel</code>
*
* @throws IllegalStateException
* If the channel represented by this union is not actually a {@link VoiceChannel}.
*
* @return The channel as a {@link VoiceChannel}
*/
@Nonnull
VoiceChannel asVoiceChannel();

/**
* Casts this union to a {@link IThreadContainer}.
Expand Down
Expand Up @@ -97,8 +97,31 @@ public interface IWebhookContainerUnion extends IWebhookContainer
*
* @return The channel as a {@link IThreadContainer}
*/
@Nonnull
IThreadContainer asThreadContainer();

/**
* Casts this union to a {@link VoiceChannel}.
* This method exists for developer discoverability.
*
* Note: This is effectively equivalent to using the cast operator:
* <pre><code>
* //These are the same!
* VoiceChannel channel = union.asVoiceChannel();
* VoiceChannel channel2 = (VoiceChannel) union;
* </code></pre>
*
* You can use {@link #getType()} to see if the channel is of type {@link ChannelType#VOICE} to validate
* whether you can call this method in addition to normal instanceof checks: <code>channel instanceof VoiceChannel</code>
*
* @throws IllegalStateException
* If the channel represented by this union is not actually a {@link VoiceChannel}.
*
* @return The channel as a {@link VoiceChannel}
*/
@Nonnull
VoiceChannel asVoiceChannel();

/**
* Casts this union to a {@link GuildMessageChannel}.
* <br>This works for the following channel types represented by this union:
Expand Down