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

Add support for guild security incidents #2577

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/Guild.java
Original file line number Diff line number Diff line change
Expand Up @@ -1089,6 +1089,15 @@ default String getOwnerId()
@Nonnull
Timeout getAfkTimeout();

/**
* The current guild {@link SecurityIncidents security incidents}.
* <br>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.
*
Expand Down Expand Up @@ -3501,6 +3510,34 @@ default AuditableRestAction<Integer> prune(int days, @Nonnull Role... roles)
@CheckReturnValue
AuditableRestAction<Integer> prune(int days, boolean wait, @Nonnull Role... roles);

/**
* Update the current guild {@link SecurityIncidents security incidents}.
* <br>Security incidents are used to temporarily disable features for the purpose of moderation.
*
* <p>Possible {@link net.dv8tion.jda.api.requests.ErrorResponse ErrorResponses} caused by
* the returned {@link RestAction RestAction} include the following:
* <ul>
* <li>{@link net.dv8tion.jda.api.requests.ErrorResponse#INVALID_FORM_BODY}
* <br>If one of the provided timestamps is too far into the future</li>
* </ul>
Comment on lines +3517 to +3522
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we check for this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We might in the future, but since they still haven't merged docs it might be bad to check limits.

*
* @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<Void> modifySecurityIncidents(@Nonnull SecurityIncidents incidents);

/**
* Kicks the {@link UserSnowflake} from the {@link net.dv8tion.jda.api.entities.Guild Guild}.
*
Expand Down
121 changes: 121 additions & 0 deletions src/main/java/net/dv8tion/jda/api/entities/SecurityIncidents.java
Original file line number Diff line number Diff line change
@@ -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}.
*
* <p>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.
* <br>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.
* <br>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;
}
}
Original file line number Diff line number Diff line change
@@ -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.
*
* <p>Can be used to detect when a guild pauses or unpauses invites.
*
* <p>Identifier: {@code security_incidents}
*/
public class GuildUpdateSecurityIncidentsEvent extends GenericGuildUpdateEvent<SecurityIncidents>
{
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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {}
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/net/dv8tion/jda/api/requests/Route.java
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/EntityBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<DataObject> members, int memberCount)
{
final GuildImpl guildObj = new GuildImpl(getJDA(), guildId);
Expand All @@ -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");
Expand Down Expand Up @@ -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))
Expand Down
28 changes: 28 additions & 0 deletions src/main/java/net/dv8tion/jda/internal/entities/GuildImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -778,6 +779,13 @@ public Timeout getAfkTimeout()
return afkTimeout;
}

@Nonnull
@Override
public SecurityIncidents getSecurityIncidents()
{
return securityIncidents;
}

@Override
public boolean isMember(@Nonnull UserSnowflake user)
{
Expand Down Expand Up @@ -1583,6 +1591,20 @@ public AuditableRestAction<Integer> prune(int days, boolean wait, @Nonnull Role.
return new AuditableRestActionImpl<>(getJDA(), route, body, (response, request) -> response.getObject().getInt("pruned", 0));
}

@Nonnull
@Override
public AuditableRestAction<Void> 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<Void> kick(@Nonnull UserSnowflake user)
Expand Down Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*;
Expand Down Expand Up @@ -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"));
Expand Down Expand Up @@ -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();
Expand Down
Loading