diff --git a/src/main/java/net/dv8tion/jda/api/entities/Guild.java b/src/main/java/net/dv8tion/jda/api/entities/Guild.java index 10af96368b..26cf2d743d 100644 --- a/src/main/java/net/dv8tion/jda/api/entities/Guild.java +++ b/src/main/java/net/dv8tion/jda/api/entities/Guild.java @@ -1089,6 +1089,15 @@ default String getOwnerId() @Nonnull Timeout getAfkTimeout(); + /** + * The current guild {@link SecurityIncidents security incidents}. + *
Security incidents are used to temporarily disable features for the purpose of moderation. + * + * @return {@link SecurityIncidents}, or null if disabled + */ + @Nullable + SecurityIncidents getSecurityIncidents(); + /** * Used to determine if the provided {@link UserSnowflake} is a member of this Guild. * @@ -3501,6 +3510,34 @@ default AuditableRestAction prune(int days, @Nonnull Role... roles) @CheckReturnValue AuditableRestAction prune(int days, boolean wait, @Nonnull Role... roles); + /** + * Update the current guild {@link SecurityIncidents security incidents}. + *
Security incidents are used to temporarily disable features for the purpose of moderation. + * + *

Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by + * the returned {@link RestAction RestAction} include the following: + *

+ * + * @param incidents + * The new security incidents + * + * @throws IllegalArgumentException + * If null is provided + * @throws InsufficientPermissionException + * If the account doesn't have {@link net.dv8tion.jda.api.Permission#MANAGE_SERVER MANAGE_SERVER} Permission. + * + * @return {@link AuditableRestAction} + * + * @see SecurityIncidents#enabled(OffsetDateTime, OffsetDateTime) + * @see SecurityIncidents#disabled() + */ + @Nonnull + @CheckReturnValue + AuditableRestAction modifySecurityIncidents(@Nonnull SecurityIncidents incidents); + /** * Kicks the {@link UserSnowflake} from the {@link net.dv8tion.jda.api.entities.Guild Guild}. * diff --git a/src/main/java/net/dv8tion/jda/api/entities/SecurityIncidents.java b/src/main/java/net/dv8tion/jda/api/entities/SecurityIncidents.java new file mode 100644 index 0000000000..1a5bb76289 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/entities/SecurityIncidents.java @@ -0,0 +1,121 @@ +/* + * 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; + +import net.dv8tion.jda.internal.utils.Helpers; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.time.OffsetDateTime; +import java.util.Objects; + +/** + * The active security incidents in a {@link Guild}. + * + *

Security incidents are used to temporarily disable features for the purpose of moderation. + * + * @see #enabled(OffsetDateTime, OffsetDateTime) + * @see #disabled() + */ +public class SecurityIncidents +{ + private static final SecurityIncidents disabled = new SecurityIncidents(0, 0); + + private final long invitesDisabledUntil; + private final long directMessagesDisabledUntil; + + private SecurityIncidents(long invitesDisabledUntil, long directMessagesDisabledUntil) + { + this.invitesDisabledUntil = invitesDisabledUntil; + this.directMessagesDisabledUntil = directMessagesDisabledUntil; + } + + /** + * The time until when invites are paused. + * + * @return The time until invites are paused, or null if unpaused + */ + @Nullable + public OffsetDateTime getInvitesDisabledUntil() + { + return invitesDisabledUntil == 0 ? null : Helpers.toOffset(invitesDisabledUntil); + } + + /** + * The time until when direct messages are paused. + * + * @return The time until direct messages are paused, or null if unpaused + */ + @Nullable + public OffsetDateTime getDirectMessagesDisabledUntil() + { + return directMessagesDisabledUntil == 0 ? null : Helpers.toOffset(directMessagesDisabledUntil); + } + + /** + * Incidents state, which disables all active security incidents. + *
The resulting object is used with {@link Guild#modifySecurityIncidents(SecurityIncidents)} to update the active incidents of the guild. + * + * @return The new security incidents + */ + @Nonnull + public static SecurityIncidents disabled() + { + return disabled; + } + + /** + * Incidents state, which enables security incidents based on the provided deadlines. + *
The resulting object is used with {@link Guild#modifySecurityIncidents(SecurityIncidents)} to update the active incidents of the guild. + * + * @param invitesDisabledUntil + * The time until invites are paused + * @param directMessagesDisabledUntil + * The time until direct messages are paused + * + * @return The new security incidents + */ + @Nonnull + public static SecurityIncidents enabled( + @Nullable + OffsetDateTime invitesDisabledUntil, + @Nullable + OffsetDateTime directMessagesDisabledUntil + ) { + return new SecurityIncidents( + invitesDisabledUntil == null ? 0 : invitesDisabledUntil.toInstant().toEpochMilli(), + directMessagesDisabledUntil == null ? 0 : directMessagesDisabledUntil.toInstant().toEpochMilli() + ); + } + + @Override + public int hashCode() + { + return Objects.hash(invitesDisabledUntil, directMessagesDisabledUntil); + } + + @Override + public boolean equals(Object obj) + { + if (obj == this) + return true; + if (!(obj instanceof SecurityIncidents)) + return false; + SecurityIncidents other = (SecurityIncidents) obj; + return this.invitesDisabledUntil == other.invitesDisabledUntil && this.directMessagesDisabledUntil == other.directMessagesDisabledUntil; + } +} diff --git a/src/main/java/net/dv8tion/jda/api/events/guild/update/GuildUpdateSecurityIncidentsEvent.java b/src/main/java/net/dv8tion/jda/api/events/guild/update/GuildUpdateSecurityIncidentsEvent.java new file mode 100644 index 0000000000..506ac28dc2 --- /dev/null +++ b/src/main/java/net/dv8tion/jda/api/events/guild/update/GuildUpdateSecurityIncidentsEvent.java @@ -0,0 +1,63 @@ +/* + * 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.events.guild.update; + +import net.dv8tion.jda.api.JDA; +import net.dv8tion.jda.api.entities.Guild; +import net.dv8tion.jda.api.entities.SecurityIncidents; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +/** + * Indicates that the {@link SecurityIncidents} of a {@link Guild Guild} changed. + * + *

Can be used to detect when a guild pauses or unpauses invites. + * + *

Identifier: {@code security_incidents} + */ +public class GuildUpdateSecurityIncidentsEvent extends GenericGuildUpdateEvent +{ + public static final String IDENTIFIER = "security_incidents"; + + public GuildUpdateSecurityIncidentsEvent(@Nonnull JDA api, long responseNumber, @Nonnull Guild guild, @Nullable SecurityIncidents previous) + { + super(api, responseNumber, guild, previous, guild.getSecurityIncidents(), IDENTIFIER); + } + + /** + * The old security incidents, or null if disabled. + * + * @return The old incidents + */ + @Nullable + public SecurityIncidents getOldSecurityIncidents() + { + return getOldValue(); + } + + /** + * The new security incidents, or null if disabled. + * + * @return The new incidents + */ + @Nullable + public SecurityIncidents getNewSecurityIncidents() + { + return getNewValue(); + } +} diff --git a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java index aa26bce27c..8adf3d87e2 100644 --- a/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java +++ b/src/main/java/net/dv8tion/jda/api/hooks/ListenerAdapter.java @@ -256,6 +256,7 @@ public void onGuildUpdateSystemChannel(@Nonnull GuildUpdateSystemChannelEvent ev public void onGuildUpdateRulesChannel(@Nonnull GuildUpdateRulesChannelEvent event) {} public void onGuildUpdateCommunityUpdatesChannel(@Nonnull GuildUpdateCommunityUpdatesChannelEvent event) {} public void onGuildUpdateAfkTimeout(@Nonnull GuildUpdateAfkTimeoutEvent event) {} + public void onGuildUpdateSecurityIncidents(@Nonnull GuildUpdateSecurityIncidentsEvent event) {} public void onGuildUpdateExplicitContentLevel(@Nonnull GuildUpdateExplicitContentLevelEvent event) {} public void onGuildUpdateIcon(@Nonnull GuildUpdateIconEvent event) {} public void onGuildUpdateMFALevel(@Nonnull GuildUpdateMFALevelEvent event) {} diff --git a/src/main/java/net/dv8tion/jda/api/requests/Route.java b/src/main/java/net/dv8tion/jda/api/requests/Route.java index e28e1ca5e1..7535807239 100644 --- a/src/main/java/net/dv8tion/jda/api/requests/Route.java +++ b/src/main/java/net/dv8tion/jda/api/requests/Route.java @@ -136,8 +136,9 @@ public static class Guilds public static final Route DELETE_SCHEDULED_EVENT = new Route(DELETE, "guilds/{guild_id}/scheduled-events/{scheduled_event_id}"); public static final Route GET_SCHEDULED_EVENT_USERS = new Route(GET, "guilds/{guild_id}/scheduled-events/{scheduled_event_id}/users"); - public static final Route GET_WELCOME_SCREEN = new Route(GET, "guilds/{guild_id}/welcome-screen"); - public static final Route MODIFY_WELCOME_SCREEN = new Route(PATCH, "guilds/{guild_id}/welcome-screen"); + public static final Route GET_WELCOME_SCREEN = new Route(GET, "guilds/{guild_id}/welcome-screen"); + public static final Route MODIFY_WELCOME_SCREEN = new Route(PATCH, "guilds/{guild_id}/welcome-screen"); + public static final Route MODIFY_GUILD_INCIDENTS = new Route(PUT, "guilds/{guild_id}/incident-actions"); public static final Route CREATE_GUILD = new Route(POST, "guilds"); public static final Route DELETE_GUILD = new Route(POST, "guilds/{guild_id}/delete"); diff --git a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java index 09b06f2ad8..9b7116df59 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java @@ -248,6 +248,14 @@ private void createGuildStickerPass(GuildImpl guildObj, DataArray array) } } + public SecurityIncidents createSecurityIncidents(DataObject data) { + SecurityIncidents enabled = SecurityIncidents.enabled( + data.getOffsetDateTime("invites_disabled_until", null), + data.getOffsetDateTime("dms_disabled_until", null) + ); + return enabled.equals(SecurityIncidents.disabled()) ? null : enabled; + } + public GuildImpl createGuild(long guildId, DataObject guildJson, TLongObjectMap members, int memberCount) { final GuildImpl guildObj = new GuildImpl(getJDA(), guildId); @@ -258,6 +266,7 @@ public GuildImpl createGuild(long guildId, DataObject guildJson, TLongObjectMap< final String vanityCode = guildJson.getString("vanity_url_code", null); final String bannerId = guildJson.getString("banner", null); final String locale = guildJson.getString("preferred_locale", "en-US"); + final SecurityIncidents securityIncidents = guildJson.optObject("incidents_data").map(this::createSecurityIncidents).orElse(null); final DataArray roleArray = guildJson.getArray("roles"); final DataArray channelArray = guildJson.getArray("channels"); final DataArray threadArray = guildJson.getArray("threads"); @@ -294,6 +303,7 @@ public GuildImpl createGuild(long guildId, DataObject guildJson, TLongObjectMap< .setMaxPresences(maxPresences) .setOwnerId(ownerId) .setAfkTimeout(Guild.Timeout.fromKey(afkTimeout)) + .setSecurityIncidents(securityIncidents) .setVerificationLevel(VerificationLevel.fromKey(verificationLevel)) .setDefaultNotificationLevel(Guild.NotificationLevel.fromKey(notificationLevel)) .setExplicitContentLevel(Guild.ExplicitContentLevel.fromKey(explicitContentLevel)) diff --git a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java index 11ecc5bc7f..f91a62b5fc 100644 --- a/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java +++ b/src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java @@ -134,6 +134,7 @@ public class GuildImpl implements Guild private TextChannel rulesChannel; private TextChannel communityUpdatesChannel; private Role publicRole; + private SecurityIncidents securityIncidents; private VerificationLevel verificationLevel = VerificationLevel.UNKNOWN; private NotificationLevel defaultNotificationLevel = NotificationLevel.UNKNOWN; private MFALevel mfaLevel = MFALevel.UNKNOWN; @@ -778,6 +779,13 @@ public Timeout getAfkTimeout() return afkTimeout; } + @Nonnull + @Override + public SecurityIncidents getSecurityIncidents() + { + return securityIncidents; + } + @Override public boolean isMember(@Nonnull UserSnowflake user) { @@ -1583,6 +1591,20 @@ public AuditableRestAction prune(int days, boolean wait, @Nonnull Role. return new AuditableRestActionImpl<>(getJDA(), route, body, (response, request) -> response.getObject().getInt("pruned", 0)); } + @Nonnull + @Override + public AuditableRestAction modifySecurityIncidents(@Nonnull SecurityIncidents incidents) + { + Checks.notNull(incidents, "SecurityIncidents"); + checkPermission(Permission.MANAGE_SERVER); + + Route.CompiledRoute route = Route.Guilds.MODIFY_GUILD_INCIDENTS.compile(getId()); + DataObject body = DataObject.empty() + .put("invites_disabled_until", Objects.toString(incidents.getInvitesDisabledUntil(), null)) + .put("dms_disabled_until", Objects.toString(incidents.getDirectMessagesDisabledUntil(), null)); + return new AuditableRestActionImpl<>(api, route, body); + } + @Nonnull @Override public AuditableRestAction kick(@Nonnull UserSnowflake user) @@ -2230,6 +2252,12 @@ public GuildImpl setPublicRole(Role publicRole) return this; } + public GuildImpl setSecurityIncidents(SecurityIncidents incidents) + { + this.securityIncidents = incidents; + return this; + } + public GuildImpl setVerificationLevel(VerificationLevel level) { this.verificationLevel = level; diff --git a/src/main/java/net/dv8tion/jda/internal/handle/GuildUpdateHandler.java b/src/main/java/net/dv8tion/jda/internal/handle/GuildUpdateHandler.java index 15bedb0e28..a8c0ae383a 100644 --- a/src/main/java/net/dv8tion/jda/internal/handle/GuildUpdateHandler.java +++ b/src/main/java/net/dv8tion/jda/internal/handle/GuildUpdateHandler.java @@ -17,6 +17,7 @@ import net.dv8tion.jda.api.entities.Guild; import net.dv8tion.jda.api.entities.Member; +import net.dv8tion.jda.api.entities.SecurityIncidents; import net.dv8tion.jda.api.entities.channel.concrete.TextChannel; import net.dv8tion.jda.api.entities.channel.concrete.VoiceChannel; import net.dv8tion.jda.api.events.guild.update.*; @@ -74,6 +75,7 @@ protected Long handleInternally(DataObject content) String name = content.getString("name"); String iconId = content.getString("icon", null); String splashId = content.getString("splash", null); + SecurityIncidents securityIncidents = content.optObject("incidents_data").map(api.getEntityBuilder()::createSecurityIncidents).orElse(null); Guild.VerificationLevel verificationLevel = Guild.VerificationLevel.fromKey(content.getInt("verification_level")); Guild.NotificationLevel notificationLevel = Guild.NotificationLevel.fromKey(content.getInt("default_message_notifications")); Guild.MFALevel mfaLevel = Guild.MFALevel.fromKey(content.getInt("mfa_level")); @@ -304,6 +306,15 @@ protected Long handleInternally(DataObject content) getJDA(), responseNumber, guild, oldCommunityUpdatesChannel)); } + if (!Objects.equals(securityIncidents, guild.getSecurityIncidents())) + { + SecurityIncidents oldIncidents = guild.getSecurityIncidents(); + guild.setSecurityIncidents(securityIncidents); + api.handleEvent( + new GuildUpdateSecurityIncidentsEvent( + getJDA(), responseNumber, + guild, oldIncidents)); + } if (content.hasKey("nsfw_level") && nsfwLevel != guild.getNSFWLevel()) { Guild.NSFWLevel oldNSFWLevel = guild.getNSFWLevel();