Browse Source

Guilduser timeouts and MODERATE_MEMBERS permission (#2003)

Co-Authored-By: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>
tags/3.1.0
Quin Lynch GitHub 3 years ago
parent
commit
144741e7c4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 157 additions and 11 deletions
  1. +5
    -2
      src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs
  2. +12
    -6
      src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs
  3. +10
    -1
      src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs
  4. +28
    -0
      src/Discord.Net.Core/Entities/Users/IGuildUser.cs
  5. +2
    -0
      src/Discord.Net.Rest/API/Common/GuildMember.cs
  6. +3
    -0
      src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs
  7. +21
    -0
      src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs
  8. +8
    -0
      src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs
  9. +28
    -1
      src/Discord.Net.Rest/Entities/Users/UserHelper.cs
  10. +20
    -1
      src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs
  11. +8
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs
  12. +10
    -0
      src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs
  13. +2
    -0
      test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs

+ 5
- 2
src/Discord.Net.Core/Entities/Permissions/GuildPermission.cs View File

@@ -214,7 +214,10 @@ namespace Discord
/// <summary>
/// Allows for launching activities (applications with the EMBEDDED flag) in a voice channel.
/// </summary>
StartEmbeddedActivities = 0x80_00_00_00_00

StartEmbeddedActivities = 0x80_00_00_00_00,
/// <summary>
/// Allows for timing out users.
/// </summary>
ModerateMembers = 0x01_00_00_00_00_00
}
}

+ 12
- 6
src/Discord.Net.Core/Entities/Permissions/GuildPermissions.cs View File

@@ -102,7 +102,8 @@ namespace Discord
public bool SendMessagesInThreads => Permissions.GetValue(RawValue, GuildPermission.SendMessagesInThreads);
/// <summary> If <c>true</c>, a user launch application activities in voice channels in this guild. </summary>
public bool StartEmbeddedActivities => Permissions.GetValue(RawValue, GuildPermission.StartEmbeddedActivities);

/// <summary> If <c>true</c>, a user can timeout other users in this guild.</summary>
public bool ModerateMembers => Permissions.GetValue(RawValue, GuildPermission.ModerateMembers);
/// <summary> Creates a new <see cref="GuildPermissions"/> with the provided packed value. </summary>
public GuildPermissions(ulong rawValue) { RawValue = rawValue; }

@@ -149,7 +150,8 @@ namespace Discord
bool? createPrivateThreads = null,
bool? useExternalStickers = null,
bool? sendMessagesInThreads = null,
bool? startEmbeddedActivities = null)
bool? startEmbeddedActivities = null,
bool? moderateMembers = null)
{
ulong value = initialValue;

@@ -193,6 +195,7 @@ namespace Discord
Permissions.SetValue(ref value, useExternalStickers, GuildPermission.UseExternalStickers);
Permissions.SetValue(ref value, sendMessagesInThreads, GuildPermission.SendMessagesInThreads);
Permissions.SetValue(ref value, startEmbeddedActivities, GuildPermission.StartEmbeddedActivities);
Permissions.SetValue(ref value, moderateMembers, GuildPermission.ModerateMembers);

RawValue = value;
}
@@ -238,7 +241,8 @@ namespace Discord
bool createPrivateThreads = false,
bool useExternalStickers = false,
bool sendMessagesInThreads = false,
bool startEmbeddedActivities = false)
bool startEmbeddedActivities = false,
bool moderateMembers = false)
: this(0,
createInstantInvite: createInstantInvite,
manageRoles: manageRoles,
@@ -279,7 +283,8 @@ namespace Discord
createPrivateThreads: createPrivateThreads,
useExternalStickers: useExternalStickers,
sendMessagesInThreads: sendMessagesInThreads,
startEmbeddedActivities: startEmbeddedActivities)
startEmbeddedActivities: startEmbeddedActivities,
moderateMembers: moderateMembers)
{ }

/// <summary> Creates a new <see cref="GuildPermissions"/> from this one, changing the provided non-null permissions. </summary>
@@ -323,13 +328,14 @@ namespace Discord
bool? createPrivateThreads = null,
bool? useExternalStickers = null,
bool? sendMessagesInThreads = null,
bool? startEmbeddedActivities = null)
bool? startEmbeddedActivities = null,
bool? moderateMembers = null)
=> new GuildPermissions(RawValue, createInstantInvite, kickMembers, banMembers, administrator, manageChannels, manageGuild, addReactions,
viewAuditLog, viewGuildInsights, viewChannel, sendMessages, sendTTSMessages, manageMessages, embedLinks, attachFiles,
readMessageHistory, mentionEveryone, useExternalEmojis, connect, speak, muteMembers, deafenMembers, moveMembers,
useVoiceActivation, prioritySpeaker, stream, changeNickname, manageNicknames, manageRoles, manageWebhooks, manageEmojisAndStickers,
useApplicationCommands, requestToSpeak, manageEvents, manageThreads, createPublicThreads, createPrivateThreads, useExternalStickers, sendMessagesInThreads,
startEmbeddedActivities);
startEmbeddedActivities, moderateMembers);

/// <summary>
/// Returns a value that indicates if a specific <see cref="GuildPermission"/> is enabled


+ 10
- 1
src/Discord.Net.Core/Entities/Users/GuildUserProperties.cs View File

@@ -1,3 +1,4 @@
using System;
using System.Collections.Generic;

namespace Discord
@@ -72,6 +73,14 @@ namespace Discord
/// <remarks>
/// This user MUST already be in a <see cref="IVoiceChannel"/> for this to work.
/// </remarks>
public Optional<ulong> ChannelId { get; set; } // TODO: v3 breaking change, change ChannelId to ulong? to allow for kicking users from voice
public Optional<ulong?> ChannelId { get; set; }

/// <summary>
/// Sets a timestamp how long a user should be timed out for.
/// </summary>
/// <remarks>
/// <see cref="null"/> or a time in the past to clear a currently existing timeout.
/// </remarks>
public Optional<DateTimeOffset?> TimedOutUntil { get; set; }
}
}

+ 28
- 0
src/Discord.Net.Core/Entities/Users/IGuildUser.cs View File

@@ -85,6 +85,17 @@ namespace Discord
/// </summary>
int Hierarchy { get; }

/// <summary>
/// Gets the date and time that indicates if and for how long a user has been timed out.
/// </summary>
/// <remarks>
/// <see cref="null"/> or a timestamp in the past if the user is not timed out.
/// </remarks>
/// <returns>
/// A <see cref="DateTimeOffset"/> indicating how long the user will be timed out for.
/// </returns>
DateTimeOffset? TimedOutUntil { get; }

/// <summary>
/// Gets the level permissions granted to this user to a given channel.
/// </summary>
@@ -211,5 +222,22 @@ namespace Discord
/// A task that represents the asynchronous role removal operation.
/// </returns>
Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null);
/// <summary>
/// Sets a timeout based on provided <see cref="TimeSpan"/> to this user in the guild.
/// </summary>
/// <param name="span">The <see cref="TimeSpan"/> indicating how long a user should be timed out for.</param>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous timeout creation operation.
/// </returns>
Task SetTimeOutAsync(TimeSpan span, RequestOptions options = null);
/// <summary>
/// Removes the current timeout from the user in this guild if one exists.
/// </summary>
/// <param name="options">The options to be used when sending the request.</param>
/// <returns>
/// A task that represents the asynchronous timeout removal operation.
/// </returns>
Task RemoveTimeOutAsync(RequestOptions options = null);
}
}

+ 2
- 0
src/Discord.Net.Rest/API/Common/GuildMember.cs View File

@@ -23,5 +23,7 @@ namespace Discord.API
public Optional<bool> Pending { get; set; }
[JsonProperty("premium_since")]
public Optional<DateTimeOffset?> PremiumSince { get; set; }
[JsonProperty("communication_disabled_until")]
public Optional<DateTimeOffset?> TimedOutUntil { get; set; }
}
}

+ 3
- 0
src/Discord.Net.Rest/API/Rest/ModifyGuildMemberParams.cs View File

@@ -1,4 +1,5 @@
using Newtonsoft.Json;
using System;

namespace Discord.API.Rest
{
@@ -15,5 +16,7 @@ namespace Discord.API.Rest
public Optional<ulong[]> RoleIds { get; set; }
[JsonProperty("channel_id")]
public Optional<ulong?> ChannelId { get; set; }
[JsonProperty("communication_disabled_until")]
public Optional<DateTimeOffset?> TimedOutUntil { get; set; }
}
}

+ 21
- 0
src/Discord.Net.Rest/Entities/Users/RestGuildUser.cs View File

@@ -16,6 +16,7 @@ namespace Discord.Rest
{
#region RestGuildUser
private long? _premiumSinceTicks;
private long? _timedOutTicks;
private long? _joinedAtTicks;
private ImmutableArray<ulong> _roleIds;

@@ -47,6 +48,18 @@ namespace Discord.Rest
}
}

/// <inheritdoc />
public DateTimeOffset? TimedOutUntil
{
get
{
if (!_timedOutTicks.HasValue || _timedOutTicks.Value < 0)
return null;
else
return DateTimeUtils.FromTicks(_timedOutTicks);
}
}

/// <inheritdoc />
/// <exception cref="InvalidOperationException" accessor="get">Resolving permissions requires the parent guild to be downloaded.</exception>
public GuildPermissions GuildPermissions
@@ -92,6 +105,8 @@ namespace Discord.Rest
UpdateRoles(model.Roles.Value);
if (model.PremiumSince.IsSpecified)
_premiumSinceTicks = model.PremiumSince.Value?.UtcTicks;
if (model.TimedOutUntil.IsSpecified)
_timedOutTicks = model.TimedOutUntil.Value?.UtcTicks;
if (model.Pending.IsSpecified)
IsPending = model.Pending.Value;
}
@@ -152,6 +167,12 @@ namespace Discord.Rest
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> RemoveRolesAsync(roles.Select(x => x.Id));
/// <inheritdoc />
public Task SetTimeOutAsync(TimeSpan span, RequestOptions options = null)
=> UserHelper.SetTimeoutAsync(this, Discord, span, options);
/// <inheritdoc />
public Task RemoveTimeOutAsync(RequestOptions options = null)
=> UserHelper.RemoveTimeOutAsync(this, Discord, options);

/// <inheritdoc />
/// <exception cref="InvalidOperationException">Resolving permissions requires the parent guild to be downloaded.</exception>


+ 8
- 0
src/Discord.Net.Rest/Entities/Users/RestWebhookUser.cs View File

@@ -62,6 +62,8 @@ namespace Discord.Rest
/// <inheritdoc />
int IGuildUser.Hierarchy => 0;
/// <inheritdoc />
DateTimeOffset? IGuildUser.TimedOutUntil => null;
/// <inheritdoc />
GuildPermissions IGuildUser.GuildPermissions => GuildPermissions.Webhook;

/// <inheritdoc />
@@ -97,6 +99,12 @@ namespace Discord.Rest
/// <inheritdoc />
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users.");
/// <inheritdoc />
Task IGuildUser.SetTimeOutAsync(TimeSpan span, RequestOptions options) =>
throw new NotSupportedException("Timeouts are not supported on webhook users.");
/// <inheritdoc />
Task IGuildUser.RemoveTimeOutAsync(RequestOptions options) =>
throw new NotSupportedException("Timeouts are not supported on webhook users.");
#endregion

#region IVoiceState


+ 28
- 1
src/Discord.Net.Rest/Entities/Users/UserHelper.cs View File

@@ -31,11 +31,16 @@ namespace Discord.Rest
{
var args = new GuildUserProperties();
func(args);

if (args.TimedOutUntil.IsSpecified && args.TimedOutUntil.Value.Value.Offset > (new TimeSpan(28, 0, 0, 0)))
throw new ArgumentOutOfRangeException(nameof(args.TimedOutUntil), "Offset cannot be more than 28 days from the current date.");

var apiArgs = new API.Rest.ModifyGuildMemberParams
{
Deaf = args.Deaf,
Mute = args.Mute,
Nickname = args.Nickname
Nickname = args.Nickname,
TimedOutUntil = args.TimedOutUntil
};

if (args.Channel.IsSpecified)
@@ -84,5 +89,27 @@ namespace Discord.Rest
foreach (var roleId in roleIds)
await client.ApiClient.RemoveRoleAsync(user.Guild.Id, user.Id, roleId, options).ConfigureAwait(false);
}

public static async Task SetTimeoutAsync(IGuildUser user, BaseDiscordClient client, TimeSpan span, RequestOptions options)
{
if (span.TotalDays > 28) // As its double, an exact value of 28 can be accepted.
throw new ArgumentOutOfRangeException(nameof(span), "Offset cannot be more than 28 days from the current date.");
if (span.Ticks <= 0)
throw new ArgumentOutOfRangeException(nameof(span), "Offset cannot hold no value or have a negative value.");
var apiArgs = new API.Rest.ModifyGuildMemberParams()
{
TimedOutUntil = DateTimeOffset.UtcNow.Add(span)
};
await client.ApiClient.ModifyGuildMemberAsync(user.Guild.Id, user.Id, apiArgs, options).ConfigureAwait(false);
}

public static async Task RemoveTimeOutAsync(IGuildUser user, BaseDiscordClient client, RequestOptions options)
{
var apiArgs = new API.Rest.ModifyGuildMemberParams()
{
TimedOutUntil = null
};
await client.ApiClient.ModifyGuildMemberAsync(user.Guild.Id, user.Id, apiArgs, options).ConfigureAwait(false);
}
}
}

+ 20
- 1
src/Discord.Net.WebSocket/Entities/Users/SocketGuildUser.cs View File

@@ -20,6 +20,7 @@ namespace Discord.WebSocket
{
#region SocketGuildUser
private long? _premiumSinceTicks;
private long? _timedOutTicks;
private long? _joinedAtTicks;
private ImmutableArray<ulong> _roleIds;

@@ -89,6 +90,17 @@ namespace Discord.WebSocket
public AudioInStream AudioStream => Guild.GetAudioStream(Id);
/// <inheritdoc />
public DateTimeOffset? PremiumSince => DateTimeUtils.FromTicks(_premiumSinceTicks);
/// <inheritdoc />
public DateTimeOffset? TimedOutUntil
{
get
{
if (!_timedOutTicks.HasValue || _timedOutTicks.Value < 0)
return null;
else
return DateTimeUtils.FromTicks(_timedOutTicks);
}
}

/// <summary>
/// Returns the position of the user within the role hierarchy.
@@ -157,6 +169,8 @@ namespace Discord.WebSocket
UpdateRoles(model.Roles.Value);
if (model.PremiumSince.IsSpecified)
_premiumSinceTicks = model.PremiumSince.Value?.UtcTicks;
if (model.TimedOutUntil.IsSpecified)
_timedOutTicks = model.TimedOutUntil.Value?.UtcTicks;
if (model.Pending.IsSpecified)
IsPending = model.Pending.Value;
}
@@ -221,7 +235,12 @@ namespace Discord.WebSocket
/// <inheritdoc />
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null)
=> RemoveRolesAsync(roles.Select(x => x.Id));

/// <inheritdoc />
public Task SetTimeOutAsync(TimeSpan span, RequestOptions options = null)
=> UserHelper.SetTimeoutAsync(this, Discord, span, options);
/// <inheritdoc />
public Task RemoveTimeOutAsync(RequestOptions options = null)
=> UserHelper.RemoveTimeOutAsync(this, Discord, options);
/// <inheritdoc />
public ChannelPermissions GetPermissions(IGuildChannel channel)
=> new ChannelPermissions(Permissions.ResolveChannel(Guild, this, channel, GuildPermissions.RawValue));


+ 8
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketThreadUser.cs View File

@@ -39,6 +39,10 @@ namespace Discord.WebSocket
public DateTimeOffset? PremiumSince
=> GuildUser.PremiumSince;

/// <inheritdoc/>
public DateTimeOffset? TimedOutUntil
=> GuildUser.TimedOutUntil;

/// <inheritdoc/>
public bool? IsPending
=> GuildUser.IsPending;
@@ -171,7 +175,11 @@ namespace Discord.WebSocket

/// <inheritdoc/>
public Task RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options = null) => GuildUser.RemoveRolesAsync(roles, options);
/// <inheritdoc/>
public Task SetTimeOutAsync(TimeSpan span, RequestOptions options = null) => GuildUser.SetTimeOutAsync(span, options);

/// <inheritdoc/>
public Task RemoveTimeOutAsync(RequestOptions options = null) => GuildUser.RemoveTimeOutAsync(options);
/// <inheritdoc/>
GuildPermissions IGuildUser.GuildPermissions => GuildUser.GuildPermissions;



+ 10
- 0
src/Discord.Net.WebSocket/Entities/Users/SocketWebhookUser.cs View File

@@ -72,6 +72,8 @@ namespace Discord.WebSocket
/// <inheritdoc />
DateTimeOffset? IGuildUser.PremiumSince => null;
/// <inheritdoc />
DateTimeOffset? IGuildUser.TimedOutUntil => null;
/// <inheritdoc />
bool? IGuildUser.IsPending => null;
/// <inheritdoc />
int IGuildUser.Hierarchy => 0;
@@ -129,6 +131,14 @@ namespace Discord.WebSocket
/// <exception cref="NotSupportedException">Roles are not supported on webhook users.</exception>
Task IGuildUser.RemoveRolesAsync(IEnumerable<IRole> roles, RequestOptions options) =>
throw new NotSupportedException("Roles are not supported on webhook users.");
/// <inheritdoc />
/// <exception cref="NotSupportedException">Timeouts are not supported on webhook users.</exception>
Task IGuildUser.SetTimeOutAsync(TimeSpan span, RequestOptions options) =>
throw new NotSupportedException("Timeouts are not supported on webhook users.");
/// <inheritdoc />
/// <exception cref="NotSupportedException">Timeouts are not supported on webhook users.</exception>
Task IGuildUser.RemoveTimeOutAsync(RequestOptions options) =>
throw new NotSupportedException("Timeouts are not supported on webhook users.");
#endregion

#region IVoiceState


+ 2
- 0
test/Discord.Net.Tests.Unit/GuildPermissionsTests.cs View File

@@ -99,6 +99,7 @@ namespace Discord
AssertFlag(() => new GuildPermissions(createPublicThreads: true), GuildPermission.CreatePublicThreads);
AssertFlag(() => new GuildPermissions(createPrivateThreads: true), GuildPermission.CreatePrivateThreads);
AssertFlag(() => new GuildPermissions(useExternalStickers: true), GuildPermission.UseExternalStickers);
AssertFlag(() => new GuildPermissions(moderateMembers: true), GuildPermission.ModerateMembers);
}

/// <summary>
@@ -176,6 +177,7 @@ namespace Discord
AssertUtil(GuildPermission.CreatePublicThreads, x => x.CreatePublicThreads, (p, enable) => p.Modify(createPublicThreads: enable));
AssertUtil(GuildPermission.CreatePrivateThreads, x => x.CreatePrivateThreads, (p, enable) => p.Modify(createPrivateThreads: enable));
AssertUtil(GuildPermission.UseExternalStickers, x => x.UseExternalStickers, (p, enable) => p.Modify(useExternalStickers: enable));
AssertUtil(GuildPermission.ModerateMembers, x => x.ModerateMembers, (p, enable) => p.Modify(moderateMembers: enable));
}
}
}

Loading…
Cancel
Save