|
17 | 17 | package net.dv8tion.jda.api.interactions.commands;
|
18 | 18 |
|
19 | 19 | import net.dv8tion.jda.api.JDA;
|
| 20 | +import net.dv8tion.jda.api.Permission; |
20 | 21 | import net.dv8tion.jda.api.entities.Guild;
|
| 22 | +import net.dv8tion.jda.api.entities.Member; |
| 23 | +import net.dv8tion.jda.api.entities.Role; |
21 | 24 | import net.dv8tion.jda.api.entities.SelfUser;
|
| 25 | +import net.dv8tion.jda.api.entities.channel.middleman.GuildChannel; |
22 | 26 | import net.dv8tion.jda.api.interactions.commands.privileges.IntegrationPrivilege;
|
23 | 27 | import net.dv8tion.jda.internal.utils.Checks;
|
| 28 | +import net.dv8tion.jda.internal.utils.PermissionUtil; |
24 | 29 | import org.jetbrains.annotations.Unmodifiable;
|
25 | 30 |
|
26 | 31 | import javax.annotation.Nonnull;
|
27 | 32 | import javax.annotation.Nullable;
|
28 |
| -import java.util.Collections; |
29 |
| -import java.util.List; |
30 |
| -import java.util.Map; |
| 33 | +import java.util.*; |
| 34 | +import java.util.function.Predicate; |
| 35 | +import java.util.stream.Collectors; |
31 | 36 |
|
32 | 37 | /**
|
33 | 38 | * A PrivilegeConfig is the collection of moderator defined {@link IntegrationPrivilege privileges} set on a specific application and its commands
|
@@ -144,4 +149,199 @@ public Map<String, List<IntegrationPrivilege>> getAsMap()
|
144 | 149 | {
|
145 | 150 | return privileges;
|
146 | 151 | }
|
| 152 | + |
| 153 | + /** |
| 154 | + * Determines whether the {@link Command} can be run by the {@link Member} in the {@link GuildChannel}. |
| 155 | + * |
| 156 | + * <p>This will always return {@code true} for guild {@link Member#isOwner() Owners} and {@link Permission#ADMINISTRATOR Administrators}. |
| 157 | + * |
| 158 | + * <p>Implements <a href="https://discord.com/assets/6da3bd6082744a5eca59bb032c890092.svg" target="_blank">Discord's flow chart for command permissions logic</a>. |
| 159 | + * |
| 160 | + * @param channel |
| 161 | + * The channel in which the command would run in |
| 162 | + * @param member |
| 163 | + * The member which would run the command |
| 164 | + * @param command |
| 165 | + * The command which should be tested for |
| 166 | + * |
| 167 | + * @return {@code true} if the command can be run by the specified member, in the guild channel, {@code false} otherwise. |
| 168 | + */ |
| 169 | + public boolean canMemberRun(@Nonnull GuildChannel channel, @Nonnull Member member, @Nonnull Command command) |
| 170 | + { |
| 171 | + Checks.notNull(channel, "Channel"); |
| 172 | + Checks.notNull(member, "Member"); |
| 173 | + Checks.notNull(command, "Command"); |
| 174 | + if (member.hasPermission(channel, Permission.ADMINISTRATOR)) |
| 175 | + return true; |
| 176 | + return isCommandAllowedInChannel(channel, member, command); |
| 177 | + } |
| 178 | + |
| 179 | + private boolean isCommandAllowedInChannel(GuildChannel channel, Member member, Command command) |
| 180 | + { |
| 181 | + final IntegrationPrivilege commandChannelPermissions = findPrivilege(getCommandPrivileges(command), matchingChannel(channel)); |
| 182 | + if (commandChannelPermissions != null) |
| 183 | + { |
| 184 | + if (commandChannelPermissions.isDisabled()) |
| 185 | + return false; |
| 186 | + return userOrRolePermission(channel, member, command); |
| 187 | + } |
| 188 | + else |
| 189 | + return isCommandAllowedInAllChannels(channel, member, command); |
| 190 | + } |
| 191 | + |
| 192 | + private boolean isCommandAllowedInAllChannels(GuildChannel channel, Member member, Command command) |
| 193 | + { |
| 194 | + final IntegrationPrivilege commandAllChannelsPermissions = findPrivilege(getCommandPrivileges(command), IntegrationPrivilege::targetsAllChannels); |
| 195 | + |
| 196 | + if (commandAllChannelsPermissions != null) |
| 197 | + { |
| 198 | + if (commandAllChannelsPermissions.isEnabled()) |
| 199 | + return userOrRolePermission(channel, member, command); |
| 200 | + return false; |
| 201 | + } |
| 202 | + else |
| 203 | + return isAppAllowedInChannel(channel, member, command); |
| 204 | + } |
| 205 | + |
| 206 | + private boolean userOrRolePermission(GuildChannel channel, Member member, Command command) |
| 207 | + { |
| 208 | + final List<IntegrationPrivilege> commandPrivileges = getCommandPrivileges(command); |
| 209 | + final IntegrationPrivilege commandUserPermissions = findPrivilege(commandPrivileges, matchingMember(member)); |
| 210 | + if (commandUserPermissions != null) |
| 211 | + return commandUserPermissions.isEnabled(); |
| 212 | + else |
| 213 | + { |
| 214 | + // If there's a role override, then at least one needs to be enabled |
| 215 | + // If there's no role override, check @everyone |
| 216 | + final List<IntegrationPrivilege> commandRolePermissionList = member.getRoles().stream() |
| 217 | + .map(r -> findPrivilege(commandPrivileges, matchingRole(r))) |
| 218 | + .filter(Objects::nonNull) |
| 219 | + .collect(Collectors.toList()); |
| 220 | + if (commandRolePermissionList.isEmpty()) |
| 221 | + return commandEveryonePermission(channel, member, command); |
| 222 | + |
| 223 | + for (IntegrationPrivilege integrationPrivilege : commandRolePermissionList) |
| 224 | + { |
| 225 | + if (integrationPrivilege.isEnabled()) |
| 226 | + return true; |
| 227 | + } |
| 228 | + return false; |
| 229 | + } |
| 230 | + } |
| 231 | + |
| 232 | + private boolean commandEveryonePermission(GuildChannel channel, Member member, Command command) |
| 233 | + { |
| 234 | + final IntegrationPrivilege commandEveryonePermissions = findPrivilege(getCommandPrivileges(command), matchingRole(channel.getGuild().getPublicRole())); |
| 235 | + if (commandEveryonePermissions != null) |
| 236 | + return commandEveryonePermissions.isEnabled(); |
| 237 | + return appLevelUserOrRolePermission(channel, member, command); |
| 238 | + } |
| 239 | + |
| 240 | + private boolean appLevelUserOrRolePermission(GuildChannel channel, Member member, Command command) |
| 241 | + { |
| 242 | + final List<IntegrationPrivilege> applicationPrivileges = getApplicationPrivileges(); |
| 243 | + final IntegrationPrivilege appUserPermissions = findPrivilege(applicationPrivileges, matchingMember(member)); |
| 244 | + if (appUserPermissions != null) |
| 245 | + { |
| 246 | + if (appUserPermissions.isEnabled()) |
| 247 | + return hasDefaultMemberPermissions(channel, member, command); |
| 248 | + } |
| 249 | + else |
| 250 | + { |
| 251 | + // If there's a role override, then at least one needs to be enabled |
| 252 | + // If there's no role override, check @everyone |
| 253 | + final List<IntegrationPrivilege> commandRolePermissionList = member.getRoles().stream() |
| 254 | + .map(r -> findPrivilege(applicationPrivileges, matchingRole(r))) |
| 255 | + .filter(Objects::nonNull) |
| 256 | + .collect(Collectors.toList()); |
| 257 | + if (commandRolePermissionList.isEmpty()) |
| 258 | + return isAppAllowingEveryone(channel, member, command); |
| 259 | + |
| 260 | + for (IntegrationPrivilege integrationPrivilege : commandRolePermissionList) |
| 261 | + { |
| 262 | + if (integrationPrivilege.isEnabled()) |
| 263 | + return hasDefaultMemberPermissions(channel, member, command); |
| 264 | + } |
| 265 | + } |
| 266 | + return false; |
| 267 | + } |
| 268 | + |
| 269 | + private boolean isAppAllowingEveryone(GuildChannel channel, Member member, Command command) |
| 270 | + { |
| 271 | + final IntegrationPrivilege commandEveryonePermissions = findPrivilege(getCommandPrivileges(command), matchingRole(channel.getGuild().getPublicRole())); |
| 272 | + if (commandEveryonePermissions != null) |
| 273 | + { |
| 274 | + if (commandEveryonePermissions.isEnabled()) |
| 275 | + return hasDefaultMemberPermissions(channel, member, command); |
| 276 | + return false; |
| 277 | + } |
| 278 | + return appLevelUserOrRolePermission(channel, member, command); |
| 279 | + } |
| 280 | + |
| 281 | + private boolean hasDefaultMemberPermissions(GuildChannel channel, Member member, Command command) |
| 282 | + { |
| 283 | + final Long rawPermissions = command.getDefaultPermissions().getPermissionsRaw(); |
| 284 | + // No permissions requires |
| 285 | + if (rawPermissions == null) |
| 286 | + return true; |
| 287 | + // Admins only, already checked in [[PrivilegeConfig#canMemberRun]] |
| 288 | + if (rawPermissions == 0) |
| 289 | + return false; |
| 290 | + return ((PermissionUtil.getEffectivePermission(channel, member) & rawPermissions) == rawPermissions); |
| 291 | + } |
| 292 | + |
| 293 | + private boolean isAppAllowedInChannel(GuildChannel channel, Member member, Command command) |
| 294 | + { |
| 295 | + final IntegrationPrivilege appChannelPermissions = findPrivilege(getApplicationPrivileges(), matchingChannel(channel)); |
| 296 | + if (appChannelPermissions != null) |
| 297 | + { |
| 298 | + if (appChannelPermissions.isEnabled()) |
| 299 | + return userOrRolePermission(channel, member, command); |
| 300 | + return false; |
| 301 | + } |
| 302 | + else |
| 303 | + return isAppAllowedInAllChannels(channel, member, command); |
| 304 | + } |
| 305 | + |
| 306 | + private boolean isAppAllowedInAllChannels(GuildChannel channel, Member member, Command command) |
| 307 | + { |
| 308 | + final IntegrationPrivilege appChannelPermissions = findPrivilege(getApplicationPrivileges(), IntegrationPrivilege::targetsAllChannels); |
| 309 | + if (appChannelPermissions != null) |
| 310 | + { |
| 311 | + if (appChannelPermissions.isEnabled()) |
| 312 | + return userOrRolePermission(channel, member, command); |
| 313 | + return false; |
| 314 | + } |
| 315 | + else |
| 316 | + return userOrRolePermission(channel, member, command); |
| 317 | + } |
| 318 | + |
| 319 | + @Nullable |
| 320 | + private static IntegrationPrivilege findPrivilege(@Nullable Collection<IntegrationPrivilege> privileges, @Nonnull Predicate<IntegrationPrivilege> predicate) |
| 321 | + { |
| 322 | + if (privileges == null) |
| 323 | + return null; |
| 324 | + return privileges.stream() |
| 325 | + .filter(predicate) |
| 326 | + .findAny() |
| 327 | + .orElse(null); |
| 328 | + } |
| 329 | + |
| 330 | + @Nonnull |
| 331 | + private static Predicate<IntegrationPrivilege> matchingChannel(@Nonnull GuildChannel channel) |
| 332 | + { |
| 333 | + return p -> p.getType() == IntegrationPrivilege.Type.CHANNEL && p.getIdLong() == channel.getIdLong(); |
| 334 | + } |
| 335 | + |
| 336 | + @Nonnull |
| 337 | + private static Predicate<IntegrationPrivilege> matchingMember(@Nonnull Member member) |
| 338 | + { |
| 339 | + return p -> p.getType() == IntegrationPrivilege.Type.USER && p.getIdLong() == member.getIdLong(); |
| 340 | + } |
| 341 | + |
| 342 | + @Nonnull |
| 343 | + private static Predicate<IntegrationPrivilege> matchingRole(@Nonnull Role role) |
| 344 | + { |
| 345 | + return p -> p.getType() == IntegrationPrivilege.Type.ROLE && p.getIdLong() == role.getIdLong(); |
| 346 | + } |
147 | 347 | }
|
0 commit comments