Co-Authored-By: Armano den Boef <68127614+Rozen4334@users.noreply.github.com>tags/3.1.0
@@ -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 | |||
} | |||
} |
@@ -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 | |||
@@ -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; } | |||
} | |||
} |
@@ -86,6 +86,17 @@ namespace Discord | |||
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> | |||
/// <example> | |||
@@ -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); | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -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; } | |||
} | |||
} |
@@ -16,6 +16,7 @@ namespace Discord.Rest | |||
{ | |||
#region RestGuildUser | |||
private long? _premiumSinceTicks; | |||
private long? _timedOutTicks; | |||
private long? _joinedAtTicks; | |||
private ImmutableArray<ulong> _roleIds; | |||
@@ -48,6 +49,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> | |||
@@ -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 | |||
@@ -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,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)); | |||
@@ -40,6 +40,10 @@ namespace Discord.WebSocket | |||
=> GuildUser.PremiumSince; | |||
/// <inheritdoc/> | |||
public DateTimeOffset? TimedOutUntil | |||
=> GuildUser.TimedOutUntil; | |||
/// <inheritdoc/> | |||
public bool? IsPending | |||
=> GuildUser.IsPending; | |||
/// <inheritdoc /> | |||
@@ -171,8 +175,12 @@ 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; | |||
/// <inheritdoc/> | |||
@@ -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 | |||
@@ -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)); | |||
} | |||
} | |||
} |